Repository: espressif/idf-extra-components Branch: master Commit: 84363b033d7a Files: 1222 Total size: 5.3 MB Directory structure: gitextract_q3q76cb9/ ├── .build-test-rules.yml ├── .codespellrc ├── .editorconfig ├── .git-blame-ignore-revs ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ ├── feature-request.yml │ │ └── other-issue.yml │ ├── PULL_REQUEST_TEMPLATE/ │ │ ├── new_component.md │ │ └── update_component.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── build_docs.py │ ├── clang-tidy/ │ │ └── test_app/ │ │ └── CMakeLists.txt │ ├── consistency_check.py │ ├── filter_sarif.py │ ├── get_idf_build_apps_args.py │ ├── get_pytest_args.py │ ├── readme_workflows.md │ ├── setup_qemu.sh │ └── workflows/ │ ├── build_and_run_apps.yml │ ├── clang-tidy.yml │ ├── deploy_gh_pages.yml │ ├── issue_comment.yml │ ├── new_issues.yml │ ├── new_prs.yml │ ├── pre-commit.yml │ ├── test_sbom.yml │ ├── upload_component.yml │ └── vulnerability_scan.yml ├── .gitignore ├── .gitmodules ├── .idf_build_apps.toml ├── .ignore_build_warnings.txt ├── .pre-commit-config.yaml ├── README.md ├── argtable3/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── idf_component.yml │ ├── sbom_argtable3.yml │ └── test_apps/ │ ├── argtable_unit_tests/ │ │ ├── CMakeLists.txt │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── test_argtable3.c │ │ │ └── test_main.c │ │ ├── pytest_argtable3.py │ │ └── sdkconfig.defaults │ └── console_compat/ │ ├── CMakeLists.txt │ ├── README.md │ ├── check_argtable_path.py │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── test_main.c ├── bdc_motor/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── bdc_motor.h │ ├── interface/ │ │ └── bdc_motor_interface.h │ ├── src/ │ │ ├── bdc_motor.c │ │ └── bdc_motor_mcpwm_impl.c │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── bdc_motor_test.c │ └── idf_component.yml ├── catch2/ │ ├── CMakeLists.txt │ ├── LICENSE.txt │ ├── README.md │ ├── cmd_catch2.cpp │ ├── examples/ │ │ ├── catch2-console/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ ├── test_cases.cpp │ │ │ │ └── test_main.cpp │ │ │ ├── pytest_catch2_console.py │ │ │ └── sdkconfig.defaults │ │ └── catch2-test/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── test_cases.cpp │ │ │ └── test_main.cpp │ │ ├── pytest_catch2.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ ├── include/ │ │ └── cmd_catch2.h │ └── sbom_catch2.yml ├── cbor/ │ ├── CMakeLists.txt │ ├── examples/ │ │ └── cbor/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── cbor_example_main.c │ │ │ └── idf_component.yml │ │ ├── pytest_cbor.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ └── port/ │ └── include/ │ └── unreachable_fix.h ├── ccomp_timer/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── ccomp_timer.c │ ├── ccomp_timer_impl_riscv.c │ ├── ccomp_timer_impl_xtensa.c │ ├── idf_component.yml │ ├── include/ │ │ └── ccomp_timer.h │ ├── private_include/ │ │ └── ccomp_timer_impl.h │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── ccomp_timer_test.c │ │ ├── ccomp_timer_test_api.c │ │ ├── ccomp_timer_test_data.c │ │ ├── ccomp_timer_test_inst.c │ │ └── idf_component.yml │ ├── pytest_ccomp_timer.py │ └── sdkconfig.defaults ├── cjson/ │ ├── CMakeLists.txt │ ├── Kconfig │ ├── idf_component.yml │ └── sbom_cJSON.yml ├── coap/ │ ├── CMakeLists.txt │ ├── Kconfig │ ├── examples/ │ │ ├── coap_client/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── certs/ │ │ │ │ │ ├── coap_ca.pem │ │ │ │ │ ├── coap_client.crt │ │ │ │ │ └── coap_client.key │ │ │ │ ├── coap_client_example_main.c │ │ │ │ ├── idf_component.yml │ │ │ │ └── oscore/ │ │ │ │ └── coap_oscore.conf │ │ │ ├── partitions.csv │ │ │ ├── pytest_coap_client_example.py │ │ │ ├── sdkconfig.ci │ │ │ ├── sdkconfig.defaults │ │ │ └── sdkconfig.defaults.esp32h2 │ │ └── coap_server/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── certs/ │ │ │ │ ├── coap_ca.pem │ │ │ │ ├── coap_server.crt │ │ │ │ └── coap_server.key │ │ │ ├── coap_server_example_main.c │ │ │ ├── idf_component.yml │ │ │ └── oscore/ │ │ │ └── coap_oscore.conf │ │ ├── partitions.csv │ │ ├── sdkconfig.ci │ │ ├── sdkconfig.defaults │ │ └── sdkconfig.defaults.esp32h2 │ ├── idf_component.yml │ ├── port/ │ │ └── include/ │ │ ├── coap_config.h │ │ └── coap_config_posix.h │ ├── sbom_libcoap.yml │ └── sdkconfig.rename ├── conftest.py ├── coremark/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── coremark_example/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── coremark_example_main.c │ │ │ └── idf_component.yml │ │ ├── pytest_coremark.py │ │ ├── sdkconfig.defaults │ │ ├── sdkconfig.defaults.esp32 │ │ ├── sdkconfig.defaults.esp32s2 │ │ └── sdkconfig.defaults.esp32s3 │ ├── idf_component.yml │ ├── linker.lf.in │ └── port/ │ ├── core_portme.c │ └── core_portme.h.in ├── dhara/ │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ └── sbom_dhara.yml ├── eigen/ │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── dummy.c │ ├── examples/ │ │ └── svd/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ └── main.cpp │ │ └── sdkconfig.defaults │ └── idf_component.yml ├── esp_cli/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── host_test/ │ │ ├── CMakeLists.txt │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── test_esp_cli.c │ │ │ └── test_main.c │ │ ├── pytest_host_esp_cli.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ ├── include/ │ │ └── esp_cli.h │ ├── sbom_esp_cli.yml │ ├── src/ │ │ └── esp_cli.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_esp_cli.c │ │ └── test_main.c │ ├── pytest_esp_cli.py │ └── sdkconfig.defaults ├── esp_cli_commands/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ ├── command_set/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── command_set_main.c │ │ │ │ └── idf_component.yml │ │ │ └── pytest_command_set.py │ │ ├── command_with_arg/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── command_with_arg_main.c │ │ │ │ └── idf_component.yml │ │ │ └── pytest_command_with_arg.py │ │ ├── conftest.py │ │ ├── dynamic_registration/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── dynamic_registration_main.c │ │ │ │ └── idf_component.yml │ │ │ └── pytest_dynamic_registration.py │ │ ├── integration_with_argtable/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── integration_with_argtable_main.c │ │ │ └── pytest_integration_with_argtable.py │ │ ├── static_registration/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── static_registration_main.c │ │ │ └── pytest_static_registration.py │ │ └── utils/ │ │ └── command_utils.h │ ├── idf_component.yml │ ├── include/ │ │ ├── esp_cli_commands.h │ │ └── esp_cli_commands_utils.h │ ├── linker.lf │ ├── linux/ │ │ └── esp_cli_commands.ld │ ├── private_include/ │ │ ├── esp_cli_commands_internal.h │ │ └── esp_cli_dynamic_commands.h │ ├── sbom_esp_cli_commands.yml │ ├── src/ │ │ ├── esp_cli_commands.c │ │ ├── esp_cli_commands_helpers.c │ │ └── esp_cli_dynamic_commands.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── include/ │ │ │ └── test_esp_cli_commands_utils.h │ │ ├── test_esp_cli_commands.c │ │ └── test_main.c │ ├── pytest_esp_cli_commands.py │ └── sdkconfig.defaults ├── esp_daylight/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── get_started/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── esp_daylight_example_main.c │ │ │ └── idf_component.yml │ │ └── pytest_esp_daylight_example.py │ ├── idf_component.yml │ ├── include/ │ │ └── esp_daylight.h │ ├── src/ │ │ └── esp_daylight.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_app_main.c │ │ └── test_esp_daylight.c │ ├── pytest_esp_daylight.py │ └── sdkconfig.defaults ├── esp_delta_ota/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── https_delta_ota/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── ca_cert.pem │ │ │ ├── idf_component.yml │ │ │ ├── main.c │ │ │ └── tests/ │ │ │ ├── certs/ │ │ │ │ ├── prvtkey.pem │ │ │ │ └── servercert.pem │ │ │ ├── test_local_server_ota.c │ │ │ └── test_local_server_ota.h │ │ ├── partitions.csv │ │ ├── pytest_https_delta_ota.py │ │ ├── sdkconfig.ci │ │ ├── sdkconfig.defaults │ │ └── tools/ │ │ ├── esp_delta_ota_patch_gen.py │ │ └── requirements.txt │ ├── idf_component.yml │ ├── include/ │ │ └── esp_delta_ota.h │ ├── src/ │ │ └── esp_delta_ota.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── esp_delta_ota_test_main.c │ │ ├── idf_component.yml │ │ └── test_esp_delta_ota.c │ ├── pytest_esp_delta_ota.py │ └── sdkconfig.defaults ├── esp_encrypted_img/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── pre_encrypted_ota/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── ecc_key/ │ │ │ └── public.pem │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── idf_component.yml │ │ │ ├── pre_encrypted_ota.c │ │ │ └── tests/ │ │ │ ├── certs/ │ │ │ │ ├── prvtkey.pem │ │ │ │ └── servercert.pem │ │ │ ├── test_local_server_ota.c │ │ │ └── test_local_server_ota.h │ │ ├── partitions.csv │ │ ├── pytest_pre_encrypted_ota.py │ │ ├── rsa_key/ │ │ │ └── private.pem │ │ ├── sdkconfig.ci │ │ ├── sdkconfig.ci.partial_download │ │ ├── sdkconfig.defaults │ │ └── server_certs/ │ │ └── ca_cert.pem │ ├── idf_component.yml │ ├── include/ │ │ └── esp_encrypted_img.h │ ├── private_include/ │ │ ├── esp_encrypted_img_priv.h │ │ └── esp_encrypted_img_utilities.h │ ├── project_include.cmake │ ├── src/ │ │ ├── esp_encrypted_img.c │ │ └── esp_encrypted_img_utilities.c │ ├── test_apps/ │ │ ├── CMakeLists.txt │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── certs/ │ │ │ │ └── test_rsa_private_key.pem │ │ │ ├── esp_encrypted_img_test.c │ │ │ ├── idf_component.yml │ │ │ ├── test.c │ │ │ ├── test_mocks.c │ │ │ └── test_mocks.h │ │ ├── partitions.csv │ │ ├── pytest_esp_encrypted_img.py │ │ ├── sdkconfig.ci.ds_peripheral │ │ ├── sdkconfig.defaults │ │ ├── sdkconfig.defaults.esp32 │ │ └── sdkconfig.defaults.esp32s3 │ └── tools/ │ └── esp_enc_img_gen.py ├── esp_ext_part_tables/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── basic/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── example_utils.c │ │ │ ├── example_utils.h │ │ │ ├── idf_component.yml │ │ │ └── main.c │ │ ├── pytest_esp_ext_part_tables_example_basic.py │ │ └── sdkconfig.defaults.ci │ ├── idf_component.yml │ ├── include/ │ │ ├── esp_ext_part_tables.h │ │ ├── esp_mbr.h │ │ └── esp_mbr_utils.h │ ├── src/ │ │ ├── esp_ext_part_tables.c │ │ ├── esp_mbr.c │ │ └── esp_mbr_utils.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ └── test_esp_ext_part.c │ ├── pytest_esp_ext_part_tables.py │ └── sdkconfig.defaults ├── esp_flash_dispatcher/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── esp_flash_dispatcher.c │ ├── idf_component.yml │ ├── include/ │ │ └── esp_flash_dispatcher.h │ └── test_apps/ │ ├── CMakeLists.txt │ ├── README.md │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_app_main.c │ │ └── test_esp_flash_dispatcher.c │ ├── partitions.csv │ ├── pytest_flash_dispatcher.py │ └── sdkconfig.defaults ├── esp_gcov/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── esp_gcov.h │ ├── gcov_rtio.c │ ├── idf_component.yml │ ├── project_include.cmake │ └── sdkconfig.rename ├── esp_isotp/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── inc/ │ │ └── esp_isotp.h │ └── src/ │ ├── esp_isotp.c │ └── isotp_config.h ├── esp_jpeg/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── Kconfig │ ├── README.md │ ├── examples/ │ │ └── get_started/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main/ │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── decode_image.c │ │ ├── decode_image.h │ │ ├── idf_component.yml │ │ ├── lcd_tjpgd_example_main.c │ │ ├── pretty_effect.c │ │ └── pretty_effect.h │ ├── idf_component.yml │ ├── include/ │ │ └── jpeg_decoder.h │ ├── jpeg_decoder.c │ ├── jpeg_default_huffman_table.c │ ├── license.txt │ ├── test_apps/ │ │ ├── CMakeLists.txt │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── jpg_to_rgb888_hex.py │ │ │ ├── test_logo_jpg.h │ │ │ ├── test_logo_rgb888.h │ │ │ ├── test_tjpgd_main.c │ │ │ ├── test_usb_camera_2_jpg.h │ │ │ ├── test_usb_camera_2_rgb888.h │ │ │ ├── test_usb_camera_jpg.h │ │ │ ├── test_usb_camera_rgb888.h │ │ │ └── tjpgd_test.c │ │ ├── pytest_esp_jpeg.py │ │ ├── sdkconfig.ci │ │ └── sdkconfig.defaults │ └── tjpgd/ │ ├── tjpgd.c │ ├── tjpgd.h │ └── tjpgdcnf.h ├── esp_lcd_qemu_rgb/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── lcd_qemu_rgb_panel/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main/ │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── idf_component.yml │ │ ├── lcd_qemu_rgb_panel_main.c │ │ └── lvgl_demo_ui.c │ ├── idf_component.yml │ ├── interface/ │ │ └── esp_lcd_qemu_rgb.h │ └── src/ │ ├── esp_lcd_qemu_rgb.c │ └── esp_lcd_qemu_rgb_struct.h ├── esp_linenoise/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ ├── basic_line_reading/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── basic_line_reading.c │ │ │ │ └── idf_component.yml │ │ │ └── pytest_basic_line_reading.py │ │ ├── completion/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── completion.c │ │ │ │ └── idf_component.yml │ │ │ └── pytest_completion.py │ │ ├── history_usage/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── history_usage.c │ │ │ │ └── idf_component.yml │ │ │ ├── partitions.csv │ │ │ ├── pytest_history_usage.py │ │ │ ├── sdkconfig.defaults │ │ │ └── sdkconfig.defaults.esp32 │ │ ├── multi_instance/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── multi_instance.c │ │ │ └── pytest_multi_instance.py │ │ └── utils/ │ │ ├── README.md │ │ ├── common_io.c │ │ └── common_io.h │ ├── idf_component.yml │ ├── include/ │ │ └── esp_linenoise.h │ ├── linenoise/ │ │ ├── linenoise.c │ │ └── linenoise.h │ ├── private_include/ │ │ └── esp_linenoise_private.h │ ├── sbom_esp_linenoise.yml │ ├── src/ │ │ ├── esp_linenoise.c │ │ └── esp_linenoise_internals.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── include/ │ │ │ └── test_utils.h │ │ ├── test_esp_linenoise_behavioral.c │ │ ├── test_esp_linenoise_get_set.c │ │ ├── test_linenoise_legacy.c │ │ ├── test_main.c │ │ └── test_utils.c │ ├── pytest_linenoise.py │ └── sdkconfig.defaults ├── esp_schedule/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── get_started/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── esp_schedule_example_main.c │ │ │ ├── esp_schedule_example_stub.c │ │ │ ├── idf_component.yml │ │ │ └── network/ │ │ │ ├── app_network.c │ │ │ └── app_network.h │ │ ├── sdkconfig.defaults │ │ └── sdkconfig.defaults.esp32c2 │ ├── idf_component.yml │ ├── include/ │ │ └── esp_schedule.h │ └── src/ │ ├── esp_schedule.c │ ├── esp_schedule_internal.h │ └── esp_schedule_nvs.c ├── esp_serial_slave_link/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── docs/ │ │ ├── Doxyfile │ │ ├── book.toml │ │ └── src/ │ │ ├── SUMMARY.md │ │ ├── api.md │ │ ├── index.md │ │ ├── sdio_slave_protocol.md │ │ └── spi_slave_hd_protocol.md │ ├── essl.c │ ├── essl_internal.h │ ├── essl_sdio.c │ ├── essl_sdio_defs.c │ ├── essl_spi.c │ ├── idf_component.yml │ ├── include/ │ │ ├── esp_serial_slave_link/ │ │ │ ├── essl.h │ │ │ ├── essl_sdio.h │ │ │ ├── essl_sdio_defs.h │ │ │ └── essl_spi.h │ │ └── essl_spi/ │ │ ├── esp32c2_defs.h │ │ ├── esp32c3_defs.h │ │ ├── esp32s2_defs.h │ │ └── esp32s3_defs.h │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── essl_test.c │ └── idf_component.yml ├── esp_sysview/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── linker.lf.in │ ├── sbom_segger.yml │ ├── sdkconfig.rename │ └── src/ │ ├── Config/ │ │ ├── Global.h │ │ ├── SEGGER_RTT_Conf.h │ │ └── SEGGER_SYSVIEW_Conf.h │ ├── SEGGER/ │ │ ├── SEGGER.h │ │ ├── SEGGER_RTT.h │ │ ├── SEGGER_SYSVIEW.c │ │ ├── SEGGER_SYSVIEW.h │ │ ├── SEGGER_SYSVIEW_ConfDefaults.h │ │ └── SEGGER_SYSVIEW_Int.h │ ├── Sample/ │ │ └── FreeRTOSV10.4/ │ │ ├── Config/ │ │ │ └── esp/ │ │ │ └── SEGGER_SYSVIEW_Config_FreeRTOS.c │ │ ├── SEGGER_SYSVIEW_FreeRTOS.c │ │ └── SEGGER_SYSVIEW_FreeRTOS.h │ ├── esp/ │ │ ├── SEGGER_RTT_esp.c │ │ ├── SEGGER_SYSVIEW_esp.c │ │ ├── adapter_encoder_sysview.c │ │ └── adapter_encoder_sysview.h │ ├── ext/ │ │ ├── heap_trace_module.c │ │ ├── heap_trace_tohost.c │ │ └── logging.c │ └── include/ │ ├── esp_sysview_heap_trace_module.h │ └── esp_trace_freertos_impl.h ├── expat/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── idf_component.yml │ ├── port/ │ │ └── include/ │ │ └── expat_config.h │ ├── sbom_libexpat.yml │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_expat.c │ │ └── test_main.c │ ├── pytest_expat.py │ └── sdkconfig.defaults ├── fmt/ │ ├── CMakeLists.txt │ ├── README.md │ ├── examples/ │ │ └── hello_fmt/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── hello_fmt.cpp │ │ │ └── idf_component.yml │ │ └── pytest_fmt.py │ ├── idf_component.yml │ └── sbom_fmt.yml ├── freetype/ │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── freetype-example/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── freetype-example.c │ │ │ └── idf_component.yml │ │ ├── partitions.csv │ │ ├── pytest_freetype.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ └── sbom_freetype.yml ├── iqmath/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── _IQNfunctions/ │ │ ├── _IQNasin_acos.c │ │ ├── _IQNatan2.c │ │ ├── _IQNdiv.c │ │ ├── _IQNdiv.h │ │ ├── _IQNexp.c │ │ ├── _IQNfrac.c │ │ ├── _IQNlog.c │ │ ├── _IQNmpy.c │ │ ├── _IQNmpy.h │ │ ├── _IQNmpyIQX.c │ │ ├── _IQNrepeat.c │ │ ├── _IQNrmpy.c │ │ ├── _IQNrsmpy.c │ │ ├── _IQNsin_cos.c │ │ ├── _IQNsqrt.c │ │ ├── _IQNtables.c │ │ ├── _IQNtables.h │ │ ├── _IQNtoF.c │ │ ├── _IQNtoa.c │ │ ├── _IQNversion.c │ │ └── _atoIQN.c │ ├── examples/ │ │ └── get_started/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ └── iqmath_example_main.c │ │ └── pytest_iqmath_example.py │ ├── idf_component.yml │ ├── include/ │ │ └── IQmathLib.h │ ├── support/ │ │ ├── RTS_support.h │ │ └── support.h │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_app_main.c │ │ └── test_iqmath.c │ ├── pytest_iqmath.py │ └── sdkconfig.defaults ├── jsmn/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── jsmn.h │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── jsmn_test.c ├── json_generator/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── json_generator.h │ ├── src/ │ │ └── json_generator.c │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── json_generator_test.c ├── json_parser/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── json_parser.h │ ├── src/ │ │ └── json_parser.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_json_parser.c │ │ └── test_main.c │ ├── pytest_json_parser.py │ └── sdkconfig.defaults ├── led_strip/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── docs/ │ │ ├── Doxyfile │ │ ├── book.toml │ │ └── src/ │ │ ├── SUMMARY.md │ │ ├── api.md │ │ └── index.md │ ├── examples/ │ │ ├── led_strip_rmt_ws2812/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ └── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ └── led_strip_rmt_ws2812_main.c │ │ └── led_strip_spi_ws2812/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ └── led_strip_spi_ws2812_main.c │ ├── idf_component.yml │ ├── include/ │ │ ├── led_strip.h │ │ ├── led_strip_rmt.h │ │ ├── led_strip_spi.h │ │ └── led_strip_types.h │ ├── interface/ │ │ └── led_strip_interface.h │ └── src/ │ ├── led_strip_api.c │ ├── led_strip_rmt_dev.c │ ├── led_strip_rmt_encoder.c │ ├── led_strip_rmt_encoder.h │ └── led_strip_spi_dev.c ├── libjpeg-turbo/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── dummy.c │ ├── examples/ │ │ └── hello_jpeg/ │ │ ├── CMakeLists.txt │ │ └── main/ │ │ ├── CMakeLists.txt │ │ ├── decode_image.c │ │ ├── decode_image.h │ │ ├── idf_component.yml │ │ └── main.c │ ├── idf_component.yml │ └── sbom_libjpeg.yml ├── libpng/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── pnglibconf.h │ ├── sbom_libpng.yml │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── out.pgm │ │ ├── test_libpng.c │ │ └── test_main.c │ ├── pytest_libpng.py │ └── sdkconfig.defaults ├── libsodium/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── idf_component.yml │ ├── port/ │ │ ├── crypto_hash_mbedtls/ │ │ │ ├── crypto_hash_sha256_mbedtls.c │ │ │ └── crypto_hash_sha512_mbedtls.c │ │ ├── randombytes_esp32.c │ │ └── randombytes_internal.h │ ├── port_include/ │ │ ├── sodium/ │ │ │ ├── crypto_hash_sha256.h │ │ │ ├── crypto_hash_sha512.h │ │ │ └── version.h │ │ └── sodium.h │ ├── sbom_libsodium.yml │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_main.c │ │ └── test_sodium.c │ ├── partitions.csv │ ├── pytest_libsodium.py │ └── sdkconfig.defaults ├── network_provisioning/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ ├── README.md │ │ ├── thread_prov/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── app_main.c │ │ │ │ ├── esp_ot_config.h │ │ │ │ └── idf_component.yml │ │ │ ├── partitions.csv │ │ │ ├── sdkconfig.defaults │ │ │ └── sdkconfig.defaults.esp32 │ │ └── wifi_prov/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── app_main.c │ │ │ └── idf_component.yml │ │ ├── partitions.csv │ │ ├── sdkconfig.defaults │ │ └── sdkconfig.defaults.esp32 │ ├── idf_component.yml │ ├── include/ │ │ └── network_provisioning/ │ │ ├── manager.h │ │ ├── network_config.h │ │ ├── network_scan.h │ │ ├── scheme_ble.h │ │ ├── scheme_console.h │ │ └── scheme_softap.h │ ├── proto/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── makefile │ │ ├── network_config.proto │ │ ├── network_constants.proto │ │ ├── network_ctrl.proto │ │ └── network_scan.proto │ ├── proto-c/ │ │ ├── network_config.pb-c.c │ │ ├── network_config.pb-c.h │ │ ├── network_constants.pb-c.c │ │ ├── network_constants.pb-c.h │ │ ├── network_ctrl.pb-c.c │ │ ├── network_ctrl.pb-c.h │ │ ├── network_scan.pb-c.c │ │ └── network_scan.pb-c.h │ ├── python/ │ │ ├── network_config_pb2.py │ │ ├── network_constants_pb2.py │ │ ├── network_ctrl_pb2.py │ │ └── network_scan_pb2.py │ ├── src/ │ │ ├── handlers.c │ │ ├── manager.c │ │ ├── network_config.c │ │ ├── network_ctrl.c │ │ ├── network_ctrl.h │ │ ├── network_provisioning_priv.h │ │ ├── network_scan.c │ │ ├── scheme_ble.c │ │ ├── scheme_console.c │ │ └── scheme_softap.c │ └── tool/ │ └── esp_prov/ │ ├── README.md │ ├── __init__.py │ ├── esp_prov.py │ ├── proto/ │ │ └── __init__.py │ ├── prov/ │ │ ├── __init__.py │ │ ├── custom_prov.py │ │ ├── network_ctrl.py │ │ ├── network_prov.py │ │ └── network_scan.py │ ├── security/ │ │ ├── __init__.py │ │ ├── security.py │ │ ├── security0.py │ │ ├── security1.py │ │ ├── security2.py │ │ └── srp6a.py │ ├── transport/ │ │ ├── __init__.py │ │ ├── ble_cli.py │ │ ├── transport.py │ │ ├── transport_ble.py │ │ ├── transport_console.py │ │ └── transport_http.py │ └── utils/ │ ├── __init__.py │ └── convenience.py ├── nghttp/ │ ├── CMakeLists.txt │ ├── Kconfig │ ├── idf_component.yml │ ├── port/ │ │ ├── include/ │ │ │ └── nghttp2/ │ │ │ └── nghttp2ver.h │ │ └── private_include/ │ │ ├── config.h │ │ └── esp_nghttp2_session.h │ └── sbom_nghttp2.yml ├── onewire_bus/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── docs/ │ │ ├── Doxyfile │ │ ├── book.toml │ │ └── src/ │ │ ├── SUMMARY.md │ │ ├── api.md │ │ └── index.md │ ├── idf_component.yml │ ├── include/ │ │ ├── onewire_bus.h │ │ ├── onewire_bus_impl_rmt.h │ │ ├── onewire_bus_impl_uart.h │ │ ├── onewire_cmd.h │ │ ├── onewire_crc.h │ │ ├── onewire_device.h │ │ └── onewire_types.h │ ├── interface/ │ │ └── onewire_bus_interface.h │ ├── src/ │ │ ├── onewire_bus_api.c │ │ ├── onewire_bus_impl_rmt.c │ │ ├── onewire_bus_impl_uart.c │ │ ├── onewire_crc.c │ │ └── onewire_device.c │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── idf_component.yml │ │ └── onewire_bus_test.c │ ├── pytest_onewire_bus.py │ ├── sdkconfig.ci.rmt │ └── sdkconfig.ci.uart ├── pcap/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── pcap.h │ ├── src/ │ │ └── pcap.c │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── pcap_test.c ├── pid_ctrl/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ ├── include/ │ │ └── pid_ctrl.h │ ├── src/ │ │ └── pid_ctrl.c │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── pid_ctrl_test.c ├── pytest.ini ├── qrcode/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── esp_qrcode_main.c │ ├── esp_qrcode_wrapper.c │ ├── idf_component.yml │ ├── include/ │ │ └── qrcode.h │ ├── qrcodegen.c │ ├── qrcodegen.h │ └── test_apps/ │ ├── CMakeLists.txt │ └── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── qrcode_test.c ├── quirc/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── idf_component.yml │ └── test_apps/ │ ├── CMakeLists.txt │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_main.c │ │ ├── test_qrcode.pgm │ │ └── test_quirc.c │ ├── pytest_quirc.py │ └── sdkconfig.defaults ├── sh2lib/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── http2_request/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── http2_request_example_main.c │ │ │ └── idf_component.yml │ │ ├── pytest_http2_request.py │ │ ├── sdkconfig.ci │ │ ├── sdkconfig.ci.esp32 │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ ├── sh2lib.c │ └── sh2lib.h ├── spi_nand_flash/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── VERSIONING.md │ ├── host_test/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── test_app_main.cpp │ │ │ ├── test_nand_flash.cpp │ │ │ ├── test_nand_flash_bdl.cpp │ │ │ └── test_nand_flash_ftl.cpp │ │ ├── pytest_nand_flash_linux.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ ├── include/ │ │ ├── esp_nand_blockdev.h │ │ ├── nand_device_types.h │ │ ├── nand_diag_api.h │ │ ├── nand_linux_mmap_emul.h │ │ ├── nand_private/ │ │ │ └── nand_impl_wrap.h │ │ ├── spi_nand_flash.h │ │ └── spi_nand_flash_test_helpers.h │ ├── layered_architecture.md │ ├── priv_include/ │ │ ├── nand.h │ │ ├── nand_flash_devices.h │ │ ├── nand_impl.h │ │ └── spi_nand_oper.h │ ├── src/ │ │ ├── devices/ │ │ │ ├── nand_alliance.c │ │ │ ├── nand_gigadevice.c │ │ │ ├── nand_micron.c │ │ │ ├── nand_winbond.c │ │ │ ├── nand_xtx.c │ │ │ └── nand_zetta.c │ │ ├── dhara_glue.c │ │ ├── nand.c │ │ ├── nand_diag_api.c │ │ ├── nand_flash_blockdev.c │ │ ├── nand_impl.c │ │ ├── nand_impl_linux.c │ │ ├── nand_impl_wrap.c │ │ ├── nand_linux_mmap_emul.c │ │ ├── nand_wl_blockdev.c │ │ ├── spi_nand_flash_test_helpers.c │ │ └── spi_nand_oper.c │ └── test_app/ │ ├── CMakeLists.txt │ ├── README.md │ ├── main/ │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ ├── test_app_main.c │ │ ├── test_spi_nand_flash.c │ │ └── test_spi_nand_flash_bdl.c │ ├── pytest_spi_nand_flash.py │ ├── sdkconfig.ci.bdl │ ├── sdkconfig.ci.default │ └── sdkconfig.defaults ├── spi_nand_flash_fatfs/ │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ ├── nand_flash/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── idf_component.yml │ │ │ │ └── spi_nand_flash_example_main.c │ │ │ ├── pytest_nand_flash_example.py │ │ │ └── sdkconfig.ci │ │ └── nand_flash_debug_app/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ └── spi_nand_flash_debug_app_main.c │ │ ├── pytest_nand_flash_debug_example.py │ │ └── sdkconfig.defaults │ ├── idf_component.yml │ ├── include/ │ │ ├── diskio_nand.h │ │ └── esp_vfs_fat_nand.h │ └── src/ │ ├── diskio_nand.c │ └── vfs_fat_spinandflash.c ├── supertinycron/ │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE.txt │ ├── README.md │ ├── examples/ │ │ └── cron_example/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main/ │ │ ├── CMakeLists.txt │ │ ├── cron_example_main.c │ │ └── idf_component.yml │ ├── idf_component.yml │ └── sbom_supertinycron.yml ├── thorvg/ │ ├── .build-test-rules.yml │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── cross_file.txt.in │ ├── dummy.c │ ├── examples/ │ │ └── thorvg_lottie/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── lottie_files/ │ │ │ └── emoji-animation.json │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ └── thorvg_example_main.c │ │ ├── partitions.csv │ │ ├── sdkconfig.defaults │ │ ├── sdkconfig.defaults.esp32p4 │ │ └── sdkconfig.defaults.esp32s3 │ ├── idf_component.yml │ ├── project_include.cmake │ └── sbom_thorvg.yml ├── touch_element/ │ ├── .build-test-rules.yml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── docs/ │ │ ├── Doxyfile │ │ ├── book.toml │ │ └── src/ │ │ ├── SUMMARY.md │ │ ├── api.md │ │ ├── img/ │ │ │ └── source/ │ │ │ ├── te_architecture.drawio │ │ │ ├── te_button.odg │ │ │ ├── te_component.odg │ │ │ ├── te_matrix.odg │ │ │ ├── te_slider.odg │ │ │ └── te_threshold.odg │ │ └── index.md │ ├── examples/ │ │ ├── touch_button/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── idf_component.yml │ │ │ │ └── touch_button_example_main.c │ │ │ └── pytest_touch_button.py │ │ ├── touch_element_waterproof/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── idf_component.yml │ │ │ │ └── waterproof_example_main.c │ │ │ └── pytest_touch_element_waterproof.py │ │ ├── touch_elements_combination/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── touch_elements_example_main.c │ │ │ └── pytest_touch_elements_combination.py │ │ ├── touch_matrix/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Kconfig.projbuild │ │ │ │ ├── idf_component.yml │ │ │ │ └── touch_matrix_example_main.c │ │ │ └── pytest_touch_matrix.py │ │ └── touch_slider/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ ├── idf_component.yml │ │ │ └── touch_slider_example_main.c │ │ └── pytest_touch_slider.py │ ├── idf_component.yml │ ├── include/ │ │ ├── esp_private/ │ │ │ ├── touch_element_private.h │ │ │ ├── touch_sensor_legacy_hal.h │ │ │ └── touch_sensor_legacy_ll.h │ │ └── touch_element/ │ │ ├── touch_button.h │ │ ├── touch_element.h │ │ ├── touch_matrix.h │ │ ├── touch_sensor_legacy_types.h │ │ └── touch_slider.h │ ├── test_apps/ │ │ ├── CMakeLists.txt │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ ├── idf_component.yml │ │ │ ├── test_app_main.c │ │ │ ├── test_touch_button.c │ │ │ ├── test_touch_element.c │ │ │ ├── test_touch_matrix.c │ │ │ └── test_touch_slider.c │ │ ├── pytest_touch_element.py │ │ └── sdkconfig.defaults │ ├── touch_button.c │ ├── touch_element.c │ ├── touch_matrix.c │ ├── touch_sensor_legacy_hal.c │ └── touch_slider.c ├── unit-test-app/ │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── examples/ │ │ └── unit-test-app/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── configs/ │ │ │ └── .gitkeep │ │ ├── idf_ext.py │ │ ├── main/ │ │ │ ├── CMakeLists.txt │ │ │ └── app_main.c │ │ ├── partition_table_unit_test_app.csv │ │ ├── partition_table_unit_test_app_2m.csv │ │ ├── partition_table_unit_test_two_ota.csv │ │ ├── partition_table_unit_test_two_ota_2m.csv │ │ ├── sdkconfig.defaults │ │ ├── sdkconfig.defaults.esp32 │ │ ├── sdkconfig.defaults.esp32c2 │ │ ├── sdkconfig.defaults.esp32c3 │ │ ├── sdkconfig.defaults.esp32s2 │ │ ├── sdkconfig.defaults.esp32s3 │ │ └── tools/ │ │ ├── CreateSectionTable.py │ │ └── ElfUnitTestParser.py │ └── idf_component.yml └── zlib/ ├── .build-test-rules.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── idf_component.yml ├── sbom_zlib.yml └── test_apps/ ├── CMakeLists.txt └── main/ ├── CMakeLists.txt ├── idf_component.yml └── zlib_test.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .build-test-rules.yml ================================================ led_strip/examples/led_strip_rmt_ws2812: disable: - if: CONFIG_SOC_RMT_SUPPORTED != 1 reason: Relevant only for RMT enabled targets led_strip/examples/led_strip_spi_ws2812: enable: - if: (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR > 5) reason: Example uses some SPI driver features which was introduced in IDF v5.1 disable: - if: CONFIG_SOC_GPSPI_SUPPORTED != 1 reason: Relevant only for SPI enabled targets esp_delta_ota: enable: - if: IDF_VERSION_MAJOR > 4 reason: Example uses esp_app_format component which was introduced in IDF v5.0 disable: - if: SOC_WIFI_SUPPORTED != 1 reason: Relevant only for WiFi enabled targets esp_encrypted_img/examples/pre_encrypted_ota: enable: - if: IDF_TARGET in ["esp32", "esp32s3"] reason: ESP32 runs RSA OTA encryption test, ESP32-S3 runs ECC OTA encryption test esp_ext_part_tables/test_apps: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR >= 6)) and IDF_TARGET == "linux" reason: Host test is enough sh2lib/examples/http2_request: enable: - if: IDF_VERSION_MAJOR > 4 and INCLUDE_DEFAULT == 1 reason: Example uses sh2lib component which was introduced in IDF v5.0 esp_jpeg/examples/get_started: enable: - if: IDF_VERSION_MAJOR > 4 and IDF_TARGET in ["esp32", "esp32s2", "esp32s3"] reason: Example depends on BSP, which is supported only for IDF >= 5.0 and limited targets esp_schedule/examples/get_started: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR > 5)) and SOC_WIFI_SUPPORTED == 1 reason: Network provisioning component has dependencies IDF >= 5.1; example only supports Wi-Fi enabled targets catch2/examples/catch2-test: enable: - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR < 5 reason: Example relies on WHOLE_ARCHIVE component property which was introduced in IDF v5.0 - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR < 1) and IDF_TARGET == "linux") reason: Docker container for release/v5.0 doesn't contain a C++ compiler catch2/examples/catch2-console: disable: - if: IDF_VERSION_MAJOR < 5 reason: Example relies on WHOLE_ARCHIVE component property which was introduced in IDF v5.0 network_provisioning/examples/thread_prov: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR > 5)) reason: Network provisioning component has dependencies IDF >= 5.1 disable: - if: SOC_IEEE802154_SUPPORTED != 1 reason: Relevant only for IEEE802.15.4 enabled targets network_provisioning/examples/wifi_prov: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR > 5)) reason: Network provisioning component has dependencies IDF >= 5.1 disable: - if: SOC_WIFI_SUPPORTED != 1 reason: Relevant only for WiFi enabled targets esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) or (IDF_VERSION_MAJOR > 5)) and (IDF_TARGET in ["esp32", "esp32c3"]) reason: Example is meant to be run under QEMU, which currently only supports ESP32 and ESP32-C3 spi_nand_flash/test_app: disable: - if: IDF_VERSION_MAJOR < 5 reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions. - if: IDF_VERSION_MAJOR < 6 and CONFIG_NAME == "bdl" reason: BDL support requires the esp_blockdev interface, available starting from IDF version v6.0. spi_nand_flash/host_test: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR < 5 reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions. - if: IDF_VERSION_MAJOR == 5 and (IDF_VERSION_MINOR < 3) reason: Fails to build on older versions of IDF spi_nand_flash_fatfs/examples/nand_flash: disable: - if: IDF_VERSION_MAJOR < 5 reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions. ================================================ FILE: .codespellrc ================================================ [codespell] skip = build,COPYING.*,LICENSE,*.svg ignore-words-list = ser,DOUT,dout,ans,nd,Dettached,IST write-changes = true ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # http://editorconfig.org root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{*.md,*.rst}] trim_trailing_whitespace = false [{*.cmake,CMakeLists.txt}] indent_style = space indent_size = 4 max_line_length = 120 [{*.sh,*.yml,*.yaml}] indent_style = space indent_size = 2 ================================================ FILE: .git-blame-ignore-revs ================================================ # Fix configuration files 9e76da9fa7ddbeef8cb0d47fd8f2e7668f58b3ec ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug report description: Report build and runtime bugs labels: ["Type: Bug"] body: - type: checkboxes id: checklist attributes: label: Answers checklist. description: Before submitting a new issue, please follow the checklist and try to find the answer. options: - label: I have read the component documentation [ESP-IDF Components](https://components.espressif.com) and the issue is not addressed there. required: true - label: I am using target and esp-idf version as defined in component's idf_component.yml required: true - label: I have searched the [issue tracker](https://github.com/espressif/idf-extra-components/issues?q=is%3Aissue) for a similar issue and not found any related issue. required: true - type: dropdown id: component attributes: label: Which component are you using? If you choose Other, provide details in More Information. multiple: false options: - argtable3 - bdc_motor - catch2 - cbor - ccomp_timer - cjson - coap - coremark - dhara - eigen - esp_daylight - esp_delta_ota - esp_cli_commands - esp_encrypted_img - esp_flash_dispatcher - esp_ext_part_tables - esp_gcov - esp_isotp - esp_jpeg - esp_lcd_qemu_rgb - esp_schedule - esp_cli - esp_serial_slave_link - esp_sysview - expat - fmt - freetype - iqmath - jsmn - json_generator - json_parser - led_strip - libpng - libsodium - esp_linenoise - network_provisioning - nghttp - onewire_bus - pcap - pid_ctrl - qrcode - quirc - sh2lib - spi_nand_flash - spi_nand_flash_fatfs - supertinycron - thorvg - touch_element - unit-test-app - zlib - libjpeg-turbo - Other validations: required: true - type: input id: idf_version attributes: label: ESP-IDF version. description: On which ESP-IDF version does this issue occur on? Run `git describe --tags` in your esp-idf folder to find it. placeholder: ex. v5.0-rc1 validations: required: true - type: input id: devkit attributes: label: Development Kit. description: On which Development Kit does this issue occur on? placeholder: ex. ESP32-Wrover-Kit v2 | Custom Board validations: required: true - type: input id: component_version attributes: label: Used Component version. description: On which Component version does this issue occur on? Check `dependencies.lock` file in your project root to find it. placeholder: ex. v1.2.0-rc0 validations: required: true - type: textarea id: more-info attributes: label: More Information. description: Do you have any other information from investigating this? placeholder: ex. I tried on my friend's Windows 10 PC and the command works there. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Component Registry url: https://components.espressif.com about: Registry with all available ESP-IDF components. - name: ESP-IDF Programming Guide url: https://docs.espressif.com/projects/esp-idf/en/latest/ about: Documentation for configuring and using ESP-IDF. - name: Espressif documentation page url: https://www.espressif.com/en/support/download/documents about: Hardware documentation (datasheets, Technical Reference Manual, etc). - name: Forum url: https://esp32.com about: For questions about using ESP-IDF and/or ESP32 series chips. Please submit all questions starting "How do I..." here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: Feature request description: Suggest an idea or new component for this project. labels: ["Type: Feature Request"] body: - type: markdown attributes: value: | * We welcome any ideas or feature requests! It’s helpful if you can explain exactly why the feature would be useful. * There are usually some outstanding feature requests in the [existing issues list](https://github.com/espressif/idf-extra-components/labels/Type%3A%20Feature%20Request), feel free to add comments to them. - type: textarea id: problem-related attributes: label: Is your feature request related to a problem? description: Please provide a clear and concise description of what the problem is. placeholder: ex. I'm always frustrated when ... - type: textarea id: solution attributes: label: Describe the solution you'd like. description: Please provide a clear and concise description of what you want to happen. placeholder: ex. When building my application ... - type: textarea id: alternatives attributes: label: Describe alternatives you've considered. description: Please provide a clear and concise description of any alternative solutions or features you've considered. placeholder: ex. Choosing other approach wouldn't work, because ... - type: textarea id: context attributes: label: Additional context. description: Please add any other context or screenshots about the feature request here. placeholder: ex. This would work only when ... ================================================ FILE: .github/ISSUE_TEMPLATE/other-issue.yml ================================================ name: General issue report description: File an issue report body: - type: checkboxes id: checklist attributes: label: Answers checklist. description: Before submitting a new issue, please follow the checklist and try to find the answer. options: - label: I have read the documentation of the component in question and the issue is not addressed there. required: true - label: I have searched the issue tracker for a similar issue and not found a similar issue. required: true - type: textarea id: issue attributes: label: General issue report description: Your issue report goes here. placeholder: ex. How do I... validations: required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/new_component.md ================================================ # Checklist - [ ] Component contains License - [ ] Component contains README.md - [ ] Component contains idf_component.yml file with `url` field defined - [ ] Component was added to [upload job](https://github.com/espressif/idf-extra-components/blob/master/.github/workflows/upload_component.yml#L18) - [ ] Component was added to [bug report](https://github.com/espressif/idf-extra-components/blob/master/.github/ISSUE_TEMPLATE/bug-report.yml?plain=1#L22) - [ ] Component has an example project - [ ] _Optional:_ Component has a test_app - [ ] CI passing # Change description _Please describe your change here_ ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/update_component.md ================================================ # Checklist - [ ] Version in idf_component.yml file bumped - [ ] _Optional:_ README.md updated - [ ] CI passing # Change description _Please describe your change here_ ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ # Checklist - [ ] Component contains License - [ ] Component contains README.md - [ ] Component contains idf_component.yml file with `url` field defined - [ ] Component was added to [upload job](https://github.com/espressif/idf-extra-components/blob/master/.github/workflows/upload_component.yml#L18) - [ ] Component was added to [build job](https://github.com/espressif/idf-extra-components/blob/master/test_app/CMakeLists.txt#L8) - [ ] _Optional:_ Component contains unit tests - [ ] CI passing # Change description _Please describe your change here_ ================================================ FILE: .github/build_docs.py ================================================ #!/usr/bin/env python3 """ Documentation build script for ESP-IDF Extra Components. This script searches for components with documentation, builds them using mdbook, and copies the built documentation to an output directory. """ import argparse import logging import os import pathlib import shutil import subprocess import sys from contextlib import contextmanager from dataclasses import dataclass from typing import List, Union # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) logger = logging.getLogger("build_docs") @dataclass class BuildConfig: """Configuration for the documentation build process.""" repo_root: pathlib.Path output_dir: pathlib.Path version: str = "latest" fail_fast: bool = True @contextmanager def change_directory(path: Union[str, pathlib.Path]): """Context manager for temporarily changing the working directory.""" original_dir = pathlib.Path.cwd() try: os.chdir(path) yield finally: os.chdir(original_dir) def find_components_with_docs(repo_root: pathlib.Path) -> List[str]: """ Search the repo for component folders that have docs/book.toml. Args: repo_root: Repository root directory Returns: List of component names with documentation """ components = [] for item in repo_root.iterdir(): if not item.is_dir(): continue docs_path = item / "docs" book_toml_path = docs_path / "book.toml" if docs_path.is_dir() and book_toml_path.is_file(): components.append(item.name) logger.info(f"Found component with docs: {item.name}") return components def generate_api_docs(component_docs_path: pathlib.Path) -> bool: """ Generate API documentation using esp-doxybook. Args: component_docs_path: Path to component docs directory Returns: True if successful, False otherwise """ api_md_path = component_docs_path / "src" / "api.md" # Create src directory if it doesn't exist (component_docs_path / "src").mkdir(exist_ok=True) logger.info(f"Generating API documentation in {api_md_path}") try: with change_directory(component_docs_path): # Note: Using relative paths since we're in the docs directory subprocess.run( ["esp-doxybook", "-i", "doxygen_output/xml", "-o", "src/api.md"], check=True, capture_output=True, text=True ) logger.info("API documentation generated successfully") return True except subprocess.CalledProcessError as e: logger.warning(f"Failed to generate API documentation: {e}") logger.warning(f"stdout: {e.stdout if hasattr(e, 'stdout') else 'N/A'}") logger.warning(f"stderr: {e.stderr if hasattr(e, 'stderr') else 'N/A'}") logger.warning("Continuing with mdbook build...") return False def build_component_docs(component_name: str, config: BuildConfig) -> bool: """ Build documentation for a component using mdbook. Args: component_name: Component name config: Build configuration Returns: True if build was successful, False otherwise """ component_docs_path = config.repo_root / component_name / "docs" logger.info(f"Building docs for {component_name}...") try: # Generate API documentation generate_api_docs(component_docs_path) # Build mdbook env = os.environ.copy() # Set site-url based on version site_url = f"/idf-extra-components/{config.version}/{component_name}/" logger.info(f"Setting site-url to: {site_url}") env["MDBOOK_OUTPUT__HTML__SITE_URL"] = site_url result = subprocess.run( ["mdbook", "build", str(component_docs_path)], check=True, env=env, capture_output=True, text=True ) # Log mdbook output at debug level logger.debug(f"mdbook stdout: {result.stdout}") logger.debug(f"mdbook stderr: {result.stderr}") # Verify the book directory was created book_path = component_docs_path / "book" if not book_path.exists(): logger.error(f"Book path {book_path} does not exist after build") return False return True except subprocess.CalledProcessError as e: logger.error(f"Error building docs for {component_name}: {e}") logger.error(f"stdout: {e.stdout if hasattr(e, 'stdout') else 'N/A'}") logger.error(f"stderr: {e.stderr if hasattr(e, 'stderr') else 'N/A'}") return False def copy_docs_to_output(component_name: str, config: BuildConfig) -> bool: """ Copy the built documentation to the docs output directory. Args: component_name: Component name config: Build configuration Returns: True if copy was successful, False otherwise """ source_path = config.repo_root / component_name / "docs" / "book" dest_path = config.output_dir / component_name if not source_path.exists(): logger.warning(f"Source path {source_path} does not exist, skipping copy") return False # Remove destination if it exists to avoid issues with copytree if dest_path.exists(): shutil.rmtree(dest_path) # Create parent directory if needed dest_path.parent.mkdir(exist_ok=True) # Copy the documentation shutil.copytree(source_path, dest_path) logger.info(f"Copied docs from {source_path} to {dest_path}") return True def build_all_docs(config: BuildConfig) -> bool: """ Build documentation for all components. Args: config: Build configuration Returns: True if all builds were successful, False otherwise """ # Find components with docs components = find_components_with_docs(config.repo_root) logger.info(f"Found {len(components)} components with documentation: {', '.join(components)}") if not components: logger.warning("No components with documentation found") return True # Clean output directory if config.output_dir.exists(): shutil.rmtree(config.output_dir) config.output_dir.mkdir(exist_ok=True) # Build docs for each component build_success = True for component in components: if not build_component_docs(component, config): logger.error(f"Documentation build failed for component {component}") build_success = False if config.fail_fast: logger.info("Fail-fast enabled, stopping build") break else: # Only copy docs if build was successful copy_docs_to_output(component, config) return build_success def parse_args() -> argparse.Namespace: """Parse command line arguments.""" parser = argparse.ArgumentParser( description="Build component documentation", formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument( "--version", type=str, default="latest", help="Version identifier for the docs (e.g., 'latest', 'v1.0', 'pr-preview-123')" ) parser.add_argument( "--output-dir", type=str, default="docs_build_output", help="Directory where built documentation will be stored" ) parser.add_argument( "--no-fail-fast", action="store_true", help="Continue building even if one component fails" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose output" ) return parser.parse_args() def main(): """Main entry point.""" args = parse_args() # Set logging level based on verbosity if args.verbose: logger.setLevel(logging.DEBUG) # Create build configuration config = BuildConfig( repo_root=pathlib.Path.cwd(), output_dir=pathlib.Path.cwd() / args.output_dir, version=args.version, fail_fast=not args.no_fail_fast ) logger.info("Building documentation with config:") logger.info(f"- Output directory: {config.output_dir}") logger.info(f"- Version: {config.version}") logger.info(f"- Fail fast: {config.fail_fast}") # Build all documentation success = build_all_docs(config) if success: logger.info("All documentation built successfully") return 0 else: logger.error("Documentation build failed") return 1 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: .github/clang-tidy/test_app/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) # add all the components from the root directory of the repo set(root_dir ${CMAKE_CURRENT_LIST_DIR}/../../..) file(GLOB component_cmakelists_files ${root_dir}/*/CMakeLists.txt) # convert to absolute paths set(component_dirs) set(component_names) foreach(component_cmakelists_file ${component_cmakelists_files}) get_filename_component(component_dir ${component_cmakelists_file} DIRECTORY) get_filename_component(component_name ${component_dir} NAME) # skip touch_element because it does not support esp32 if(${component_name} STREQUAL "touch_element") continue() endif() if(${component_name} STREQUAL "test_app") continue() endif() list(APPEND component_names ${component_name}) list(APPEND component_dirs ${component_dir}) endforeach() set(EXTRA_COMPONENT_DIRS ${component_dirs}) # generate an idf.py argument file with `--exclude-paths ` for each submodule set(clang_check_args "--include-paths ${root_dir}") list(APPEND clang_check_args "--exclude-paths ${root_dir}/.github") list(APPEND clang_check_args "--exclude-paths $ENV{IDF_PATH}") # Exclude third-party code from clang-tidy checks list(APPEND clang_check_args "--exclude-paths ${root_dir}/esp_sysview/src/SEGGER") list(APPEND clang_check_args "--exclude-paths ${root_dir}/esp_sysview/src/Sample") # get the list of submodules execute_process( COMMAND git config --file ${root_dir}/.gitmodules --get-regexp path OUTPUT_VARIABLE submodules_output ) # each line will be in the format "submodule..path ", extract the part after the last space STRING(REGEX REPLACE "\r?\n" ";" submodules_output "${submodules_output}") foreach(line ${submodules_output}) # extract the path STRING(REGEX REPLACE ".* " "" submodule_path "${line}") # add the exclude-paths argument list(APPEND clang_check_args "--exclude-paths ${root_dir}/${submodule_path}") endforeach() set(clang_check_args_file ${CMAKE_CURRENT_BINARY_DIR}/clang_check_args) list(JOIN clang_check_args " " clang_check_args_str) file(WRITE ${clang_check_args_file} ${clang_check_args_str}) # actual project definition... include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(test_app) ================================================ FILE: .github/consistency_check.py ================================================ #!/usr/bin/env python # This script performs various consistency checks on the repository. import argparse import logging import glob import os from pathlib import Path import yaml LOG = logging.getLogger("consistency_check") failures = 0 def main(): parser = argparse.ArgumentParser() parser.add_argument("--root", default=".", help="Root directory of the repository") args = parser.parse_args() logging.basicConfig(level=logging.INFO) check_build_manifests_added_to_config(args) check_components_added_to_upload_job(args) check_components_added_to_issue_template(args) if failures: LOG.error(f"Found {failures} issues") raise SystemExit(1) #### Checks #### def check_build_manifests_added_to_config(args): LOG.info("Checking that all .build-test-rules.yml files are added to .idf_build_apps.toml") build_manifests_from_repo = set(glob.glob(f"{args.root}/**/.build-test-rules.yml", recursive=True)) # exclude the ones under 'managed_components' build_manifests_from_repo = set([Path(f) for f in build_manifests_from_repo if "managed_components" not in f]) idf_build_apps_toml = load_toml(os.path.join(args.root, ".idf_build_apps.toml")) build_manifests_from_config = set([Path(f) for f in idf_build_apps_toml.get("manifest_file", [])]) missing = build_manifests_from_repo - build_manifests_from_config if missing: LOG.error(f"Missing build manifests in .idf_build_apps.toml: {missing}") add_failure() def check_components_added_to_upload_job(args): LOG.info("Checking that all components are added to the upload job") components_from_repo = set([Path(f).name for f in get_component_dirs(args)]) upload_job = load_yaml(os.path.join(args.root, ".github/workflows/upload_component.yml")) upload_job_steps = upload_job.get("jobs", {}).get("upload_components", {}).get("steps", []) upload_job_step = next((step for step in upload_job_steps if step.get("name") == "Upload components to component service"), None) components_from_upload_job = set([name.strip() for name in upload_job_step.get("with", {}).get("components", "").split("\n") if name.strip()]) missing = components_from_repo - components_from_upload_job if missing: LOG.error(f"Missing components in upload job: {missing}") add_failure() def check_components_added_to_issue_template(args): LOG.info("Checking that all components are added to the issue template") issue_template = load_yaml(os.path.join(args.root, ".github/ISSUE_TEMPLATE/bug-report.yml")) issue_template_component = next((element for element in issue_template.get("body", []) if element.get("id") == "component"), None) components_from_issue_template = set(issue_template_component.get("attributes", {}).get("options", [])) components_from_repo = set([Path(component).name for component in get_component_dirs(args)]) missing = components_from_repo - components_from_issue_template if missing: LOG.error(f"Missing components in issue template: {missing}") add_failure() extra = components_from_issue_template - components_from_repo - set(["Other"]) if extra: LOG.error(f"Extra components in issue template: {extra}") add_failure() #### Utility functions #### def load_toml(filepath) -> dict: try: import tomllib # type: ignore # python 3.11 try: with open(str(filepath), 'rb') as fr: return tomllib.load(fr) except Exception as e: raise ValueError(f"Failed to load {filepath}: {e}") except ImportError: import toml try: return toml.load(str(filepath)) except Exception as e: raise ValueError(f"Failed to load {filepath}: {e}") def load_yaml(filepath) -> dict: with open(filepath, "r") as f: return yaml.safe_load(f) def get_component_dirs(args): """ Returns a list of component paths in this repository. """ components_from_repo = set(glob.glob(f"{args.root}/*/idf_component.yml")) components_from_repo = [Path(f).parent.name for f in components_from_repo] return components_from_repo def add_failure(): global failures failures += 1 if __name__ == "__main__": main() ================================================ FILE: .github/filter_sarif.py ================================================ #!/usr/bin/env python3 import argparse import copy import json import typing as t def main(): parser = argparse.ArgumentParser() parser.add_argument('-o', '--output', type=argparse.FileType('w'), help='Output filtered SARIF file') parser.add_argument('--include-prefix', required=True, action='append', help='File prefix for source code to include in analysis') parser.add_argument('--exclude-text-contains', action='append', default=[], help='Exclude results whose message.text contains this substring (may be repeated)') parser.add_argument('input_file', type=argparse.FileType('r'), help='Input SARIF file') args = parser.parse_args() process(args.input_file, args.output, args.include_prefix, args.exclude_text_contains) def process(in_file: t.TextIO, out_file: t.TextIO, include_prefix_list: t.List[str], exclude_text_contains_list: t.List[str]) -> None: in_json = json.load(in_file) if len(in_json['runs']) != 1: raise NotImplementedError('Only 1 run is supported') in_results = in_json['runs'][0]['results'] out_results = [] for result in in_results: transformed = transform_result(result, include_prefix_list, exclude_text_contains_list) if transformed is not None: out_results.append(transformed) out_json = copy.deepcopy(in_json) out_json['runs'][0]['results'] = out_results json.dump(out_json, out_file, indent=True) def normalize_uri_optional(uri: t.Optional[str], include_prefix_list: t.List[str], strict: bool) -> t.Optional[str]: if uri is None: return None for include_prefix in include_prefix_list: if uri.startswith(include_prefix): return uri.replace(include_prefix, '') return None if strict else uri def message_contains_any(text: str, substrings: t.List[str]) -> bool: return any(substr in text for substr in substrings) def dedupe_related_locations(related_locations: t.Any, include_prefix_list: t.List[str]) -> t.List[t.Dict[str, t.Any]]: if not isinstance(related_locations, list) or not related_locations: return [] seen_keys: t.Set[t.Tuple[t.Any, ...]] = set() deduped: t.List[t.Dict[str, t.Any]] = [] for rel in related_locations: if not isinstance(rel, dict): continue rel_msg_text = rel['message']['text'] rel_uri = rel['physicalLocation']['artifactLocation']['uri'] rel_uri_norm = normalize_uri_optional(rel_uri, include_prefix_list, strict=False) rel['physicalLocation']['artifactLocation']['uri'] = rel_uri_norm key = (rel_msg_text, rel_uri_norm, rel['physicalLocation']['region']['startLine'], rel['physicalLocation']['region']['startColumn']) if key in seen_keys: continue seen_keys.add(key) deduped.append(rel) return deduped def transform_result(result: t.Dict[str, t.Any], include_prefix_list: t.List[str], exclude_text_contains_list: t.List[str]) -> t.Optional[t.Dict[str, t.Any]]: locations = result['locations'] if len(locations) != 1: raise NotImplementedError('Only 1 location is supported') artifact_location = locations[0]['physicalLocation']['artifactLocation'] uri = artifact_location['uri'] normalized_uri = normalize_uri_optional(uri, include_prefix_list, strict=True) if not normalized_uri: return None message_text = result['message']['text'] if message_contains_any(message_text, exclude_text_contains_list): return None new_result = copy.deepcopy(result) new_result['locations'][0]['physicalLocation']['artifactLocation']['uri'] = normalized_uri deduped_related = dedupe_related_locations(new_result.get('relatedLocations'), include_prefix_list) if deduped_related: new_result['relatedLocations'] = deduped_related elif 'relatedLocations' in new_result: # Ensure we have a list per schema even if empty new_result['relatedLocations'] = [] return new_result if __name__ == '__main__': main() ================================================ FILE: .github/get_idf_build_apps_args.py ================================================ #!/usr/bin/env python3 import argparse import os def main(): parser = argparse.ArgumentParser() parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') parser.add_argument('modified_files_list', type=argparse.FileType('r'), help='Input file containing list of modified files') parser.add_argument('idf_build_apps_args', type=argparse.FileType('w'), help='Output file containing idf-build-apps arguments') args = parser.parse_args() modified_files = args.modified_files_list.read().splitlines() idf_build_apps_args = [] if modified_files: idf_build_apps_args += [ '--modified-files', '"' + ';'.join(modified_files) + '"' ] if args.verbose: print('Modified files:') for file in sorted(modified_files): print(f' - {file}') modified_components = set() excluded_dirs = ['.github'] for file in modified_files: toplevel = file.split('/')[0] if toplevel in excluded_dirs: continue if not os.path.isdir(toplevel): continue modified_components.add(toplevel) if modified_components: idf_build_apps_args += [ '--modified-components', '"' + ';'.join(modified_components) + '"' ] else: idf_build_apps_args += [ '--modified-components', 'dummy_component' ] if args.verbose: print('Modified components:') for component in sorted(modified_components): print(f' - {component}') args.idf_build_apps_args.write(' '.join(idf_build_apps_args)) if __name__ == '__main__': main() ================================================ FILE: .github/get_pytest_args.py ================================================ #!/usr/bin/env python3 import argparse import json import glob def main(): parser = argparse.ArgumentParser() parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') parser.add_argument('--target', type=str, required=True, help='Target to run tests for') parser.add_argument('build_info_json', type=str, help='Input file(s) containing build info generated by idf-build-apps. Accepts globs.') parser.add_argument('pytest_args', type=argparse.FileType('w'), help='Output file containing pytest arguments') args = parser.parse_args() pytest_args = [] app_ignore_status = {} app_json_files = glob.glob(args.build_info_json) if args.verbose: print(f'Found {len(app_json_files)} app_json files') for app_json_file in app_json_files: print(f' - {app_json_file}') for app_json_file in app_json_files: with open(app_json_file, 'r') as build_info_json: if args.verbose: print(f'Processing {app_json_file}') for app_json_line in build_info_json.readlines(): app_json = json.loads(app_json_line) app_dir = app_json['app_dir'] if app_dir not in app_ignore_status.keys(): app_ignore_status[app_dir] = True if app_json['target'] == args.target and app_json['build_status'] != 'skipped': app_ignore_status[app_dir] = False for app_dir, ignore in app_ignore_status.items(): if ignore: if args.verbose: print(f'Skipping {app_dir}') pytest_args += [ '--ignore', app_dir ] else: if args.verbose: print(f'Not skipping {app_dir}') args.pytest_args.write(' '.join(pytest_args)) if __name__ == '__main__': main() ================================================ FILE: .github/readme_workflows.md ================================================ # CI in idf-extra-components ## Build and test apps The workflow defined in [build_and_run_apps.yml](workflows/build_and_run_apps.yml) builds the apps (examples, test apps) and runs the tests on self-hosted runners. ```mermaid flowchart TD PR((Pull Request)) PR -->labels schedule((Schedule
Push to master)) schedule -->idf-build-apps-build subgraph "Generate pipeline" labels[Get labels] --> get-changes get-changes[Get changed files] get-changes --> build-all build-all{Build all apps
label set?} build-all --> |yes| changed-components changed-components --> idf-build-apps-args build-all --> |no| idf-build-apps-args changed-components[Get changed components] idf-build-apps-args[Prepare idf-build-apps arguments] end subgraph "Build apps" idf-build-apps-args --> idf-build-apps-build idf-build-apps-build[idf-build-apps build] --> build-only build-only{Build only
label set?} build-only --> |no| upload-artifacts upload-artifacts[Upload artifacts] end subgraph "Test apps" upload-artifacts -->download-artifacts download-artifacts[Download artifacts] -->pytest pytest[Pytest] -->upload-results upload-results[Upload results] end subgraph "Generate report" upload-results -->download-results download-results[Download results] -->generate-report generate-report[Generate report] end build-only --> |yes| fin generate-report -->fin fin([Finish]) ``` ================================================ FILE: .github/setup_qemu.sh ================================================ #!/bin/sh set -e # QEMU version and date string for easy maintenance QEMU_VERSION="9.2.2" QEMU_DATE="20250817" QEMU_RELEASE="esp-develop-${QEMU_VERSION}-${QEMU_DATE}" # 1. Detect host architecture ARCH=$(uname -m) case "$ARCH" in x86_64) QEMU_ARCH="x86_64" QEMU_RISCV32_FILE="qemu-riscv32-softmmu-esp_develop_${QEMU_VERSION}_${QEMU_DATE}-x86_64-linux-gnu.tar.xz" QEMU_XTENSA_FILE="qemu-xtensa-softmmu-esp_develop_${QEMU_VERSION}_${QEMU_DATE}-x86_64-linux-gnu.tar.xz" ;; aarch64 | arm64) QEMU_ARCH="aarch64" QEMU_RISCV32_FILE="qemu-riscv32-softmmu-esp_develop_${QEMU_VERSION}_${QEMU_DATE}-aarch64-linux-gnu.tar.xz" QEMU_XTENSA_FILE="qemu-xtensa-softmmu-esp_develop_${QEMU_VERSION}_${QEMU_DATE}-aarch64-linux-gnu.tar.xz" ;; *) echo "Unsupported architecture: $ARCH" exit 1 ;; esac # Install some dependencies apt-get update apt-get install -y libgcrypt20 libglib2.0-0 libpixman-1-0 libsdl2-2.0-0 libslirp0 QEMU_DIR="qemu" # 2. Download the correct binary QEMU_RISCV32_URL="https://github.com/espressif/qemu/releases/download/${QEMU_RELEASE}/$QEMU_RISCV32_FILE" curl -LO "$QEMU_RISCV32_URL" # 3. Extract the compressed file tar -xJf "$QEMU_RISCV32_FILE" rm "$QEMU_RISCV32_FILE" if [ -f "$QEMU_DIR/bin/qemu-system-riscv32" ]; then echo "QEMU RISCV32 installation successful." else echo "QEMU RISCV32 installation failed." exit 1 fi # Rename qemu directory to avoid conflicts mv "$QEMU_DIR" "${QEMU_DIR}_riscv32" QEMU_XTENSA_URL="https://github.com/espressif/qemu/releases/download/${QEMU_RELEASE}/$QEMU_XTENSA_FILE" curl -LO "$QEMU_XTENSA_URL" # 3. Extract the compressed file tar -xJf "$QEMU_XTENSA_FILE" rm "$QEMU_XTENSA_FILE" if [ -f "$QEMU_DIR/bin/qemu-system-xtensa" ]; then echo "QEMU XTENSA installation successful." else echo "QEMU XTENSA installation failed." exit 1 fi # Rename qemu directory to avoid conflicts mv "$QEMU_DIR" "${QEMU_DIR}_xtensa" QEMU_DIR=$(pwd)/qemu # 4. Add both QEMU directories to PATH export PATH="$PATH:${QEMU_DIR}_riscv32/bin:${QEMU_DIR}_xtensa/bin" # 5. Verify QEMU installation if command -v qemu-system-xtensa &> /dev/null; then echo "QEMU XTENSA is installed and available in PATH." else echo "QEMU XTENSA is not installed or not available in PATH." exit 1 fi if command -v qemu-system-riscv32 &> /dev/null; then echo "QEMU RISCV32 is installed and available in PATH." else echo "QEMU RISCV32 is not installed or not available in PATH." exit 1 fi ================================================ FILE: .github/workflows/build_and_run_apps.yml ================================================ name: Build and Run Apps on: schedule: - cron: '0 0 * * *' # Once per day at midnight pull_request: types: [opened, reopened, synchronize] push: branches: - master jobs: prepare: name: Prepare pipeline runs-on: ubuntu-22.04 permissions: contents: read pull-requests: read outputs: test_all_apps: ${{ steps.get_labels.outputs.test_all_apps }} build_only: ${{ steps.get_labels.outputs.build_only }} idf_build_apps_args: ${{ steps.find_changes.outputs.idf_build_apps_args }} steps: - uses: actions/checkout@v4 with: submodules: 'true' - name: Fix git repo permissions # Needed by the next git diff step. # See https://github.com/actions/runner/issues/2033 if: github.event_name == 'pull_request' run: | build_dir=$PWD cd / git config --global --add safe.directory $build_dir cd - - name: Install dependencies run: pip install 'idf-build-apps~=2.12' - name: Get labels id: get_labels if: github.event_name == 'pull_request' env: GH_TOKEN: ${{ github.token }} # Check for labels # "PR: test all apps" # "PR: build only" run: | gh api --jq '.labels.[].name' /repos/{owner}/{repo}/pulls/${{ github.event.number }} > labels.txt test_all_apps=$(grep -c 'PR: test all apps' labels.txt || true) build_only=$(grep -c 'PR: build only' labels.txt || true) echo "test_all_apps=$test_all_apps" >> $GITHUB_OUTPUT echo "build_only=$build_only" >> $GITHUB_OUTPUT echo "test_all_apps=$test_all_apps" echo "build_only=$build_only" - name: Find changed files and components id: find_changes if: github.event_name == 'pull_request' && steps.get_labels.outputs.test_all_apps == '0' # - based on the files list, determine which components have changed # - output both lists as a file of idf-build-apps arguments run: | git fetch --recurse-submodules=no origin ${{ github.base_ref }}:base_ref git fetch --recurse-submodules=no origin pull/${{ github.event.pull_request.number }}/head:pr_ref git diff --name-only -r base_ref pr_ref > changed_files.txt python3 .github/get_idf_build_apps_args.py -v changed_files.txt idf_build_apps_args.txt echo "idf_build_apps_args=$(cat idf_build_apps_args.txt)" >> $GITHUB_OUTPUT echo "idf_build_apps_args=$(cat idf_build_apps_args.txt)" build: name: Build Apps needs: prepare strategy: fail-fast: false matrix: idf_ver: - "release-v5.1" - "release-v5.2" - "release-v5.3" - "release-v5.4" - "release-v5.5" - "latest" parallel_index: [1,2,3,4,5] # Update --parallel-count below when changing this runs-on: ubuntu-22.04 container: espressif/idf:${{ matrix.idf_ver }} steps: - uses: actions/checkout@v4 with: submodules: 'true' - name: Install dependencies shell: bash run: | export IDF_PYTHON_CHECK_CONSTRAINTS=yes ${IDF_PATH}/install.sh --enable-ci . ${IDF_PATH}/export.sh pip install --upgrade 'idf-build-apps~=2.12' - name: Build apps shell: bash run: | . ${IDF_PATH}/export.sh export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" idf-build-apps build --parallel-index ${{ matrix.parallel_index }} --parallel-count 5 --collect-app-info build_info_${{ matrix.idf_ver }}_${{ matrix.parallel_index }}.json ${{ needs.prepare.outputs.idf_build_apps_args }} - uses: actions/upload-artifact@v4 if: github.repository_owner == 'espressif' && needs.prepare.outputs.build_only == '0' with: name: app_binaries_${{ matrix.idf_ver }}_${{ matrix.parallel_index }} path: | */examples/*/build_esp*/bootloader/bootloader.bin */examples/*/build_esp*/partition_table/partition-table.bin */examples/*/build_esp*/*.bin */examples/*/build_esp*/flasher_args.json */examples/*/build_esp*/config/sdkconfig.json */test_app*/**/build_esp*/bootloader/bootloader.bin */test_app*/**/build_esp*/partition_table/partition-table.bin */test_app*/**/build_esp*/*.bin */test_app*/**/build_esp*/flasher_args.json */test_app*/**/build_esp*/config/sdkconfig.json build_info*.json build-linux: name: Build Apps for Linux needs: prepare strategy: fail-fast: false matrix: idf_ver: # Not building for 5.1 with linux target due to limited support - "release-v5.2" - "release-v5.3" - "release-v5.4" - "release-v5.5" - "latest" runs-on: ubuntu-22.04 container: image: espressif/idf:${{ matrix.idf_ver }} steps: - uses: actions/checkout@v4 with: submodules: 'true' - name: Install dependencies shell: bash run: | export IDF_PYTHON_CHECK_CONSTRAINTS=yes ${IDF_PATH}/install.sh --enable-ci . ${IDF_PATH}/export.sh pip install --upgrade 'idf-build-apps~=2.12' - name: Build apps for Linux shell: bash run: | . ${IDF_PATH}/export.sh export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" idf-build-apps build --target linux --collect-app-info build_info_linux_${{ matrix.idf_ver }}_${{ matrix.parallel_index }}.json ${{ needs.prepare.outputs.idf_build_apps_args }} - uses: actions/upload-artifact@v4 if: github.repository_owner == 'espressif' && needs.prepare.outputs.build_only == '0' with: name: app_binaries_${{ matrix.idf_ver }}_linux path: | */examples/*/build_linux*/*.elf */examples/*/build_linux*/config/sdkconfig.json */test_app*/**/build_linux*/*.elf */test_app*/**/build_linux*/config/sdkconfig.json */host_test/**/build_linux*/*.elf */host_test/**/build_linux*/config/sdkconfig.json build_info*.json run-target: name: Run apps on target if: github.repository_owner == 'espressif' && needs.prepare.outputs.build_only != '1' needs: [build] strategy: fail-fast: false matrix: idf_ver: - "release-v5.1" - "release-v5.2" - "release-v5.3" - "release-v5.4" - "release-v5.5" - "latest" runner: - runs-on: "esp32" marker: "generic" target: "esp32" runner-labels: [self-hosted, linux, docker, "esp32"] pytest_args: "" - runs-on: "esp32s2" marker: "generic" target: "esp32s2" runner-labels: [self-hosted, linux, docker, "esp32s2"] pytest_args: "" - runs-on: "esp32s3" marker: "generic" target: "esp32s3" runner-labels: [self-hosted, linux, docker, "esp32s3"] pytest_args: "" - runs-on: "ESP32-ETHERNET-KIT" marker: "ethernet" target: "esp32" runner-labels: [self-hosted, linux, docker, "ESP32-ETHERNET-KIT"] pytest_args: "" - runs-on: "spi_nand_flash" marker: "spi_nand_flash" target: "esp32" runner-labels: [self-hosted, linux, docker, "spi_nand_flash"] pytest_args: "" - runs-on: "qemu" marker: "qemu" target: ["esp32s3", "esp32c3"] runner-labels: [self-hosted, linux, docker] pytest_args: "--embedded-services idf,qemu" env: TEST_RESULT_NAME: test_results_${{ matrix.runner.target }}_${{ matrix.runner.marker }}_${{ matrix.idf_ver }} TEST_RESULT_FILE: test_results_${{ matrix.runner.target }}_${{ matrix.runner.marker }}_${{ matrix.idf_ver }}.xml runs-on: ${{ matrix.runner.runner-labels }} container: image: python:3.11-bookworm options: --privileged # Privileged mode has access to serial ports steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: pattern: app_binaries_${{ matrix.idf_ver }}_* merge-multiple: true - name: Install Python packages env: PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" run: | pip install --prefer-binary cryptography pytest-embedded pytest-embedded-qemu pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code idf-ci - name: Setup QEMU if: matrix.runner.marker == 'qemu' run: | . .github/setup_qemu.sh echo "PATH=$PATH" >> $GITHUB_ENV - name: Run apps run: | python3 .github/get_pytest_args.py --target=${{ matrix.runner.target }} -v 'build_info*.json' pytest-args.txt cat pytest-args.txt pytest --suppress-no-test-exit-code $(cat pytest-args.txt) --ignore-glob '*/managed_components/*' --ignore=.github --junit-xml=${{ env.TEST_RESULT_FILE }} --target=${{ matrix.runner.target }} -m ${{ matrix.runner.marker }} --build-dir=build_${{ matrix.runner.target }} ${{ matrix.runner.pytest_args }} - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: ${{ env.TEST_RESULT_NAME }} path: ${{ env.TEST_RESULT_FILE }} run-target-linux: name: Run apps on Linux target if: github.repository_owner == 'espressif' && needs.prepare.outputs.build_only != '1' needs: [build-linux] strategy: fail-fast: false matrix: idf_ver: # - "release-v5.1" # Not testing for 5.1 with linux target due to limited support - "release-v5.2" - "release-v5.3" - "release-v5.4" - "release-v5.5" - "latest" runner: - runs-on: "linux" marker: "host_test" target: "linux" pytest_args: "--embedded-services idf" exclude: - idf_ver: "release-v5.2" # Bug with Unity IDF test apps runner: target: "linux" env: TEST_RESULT_NAME: test_results_${{ matrix.runner.target }}_${{ matrix.runner.marker }}_${{ matrix.idf_ver }} TEST_RESULT_FILE: test_results_${{ matrix.runner.target }}_${{ matrix.runner.marker }}_${{ matrix.idf_ver }}.xml runs-on: ubuntu-latest container: image: python:3.14-slim-trixie # Newer Debian base to support newer glibc for Linux apps steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: pattern: app_binaries_${{ matrix.idf_ver }}_* merge-multiple: true - name: Install Python packages env: PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" run: | pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code idf-ci - name: Make .elf files executable if: matrix.runner.target == 'linux' continue-on-error: true shell: bash run: | chmod +x */examples/*/build_linux*/*.elf || echo "No example .elf files found" shopt -s globstar chmod +x */test_app*/**/build_linux*/*.elf || echo "No test_app .elf files found" chmod +x */host_test/**/build_linux*/*.elf || echo "No host_test .elf files found" - name: Run apps shell: bash run: | python3 .github/get_pytest_args.py --target=${{ matrix.runner.target }} -v 'build_info*.json' pytest-args.txt cat pytest-args.txt pytest --suppress-no-test-exit-code $(cat pytest-args.txt) --ignore-glob '*/managed_components/*' --ignore=.github --junit-xml=${{ env.TEST_RESULT_FILE }} --target=${{ matrix.runner.target }} -m ${{ matrix.runner.marker }} --build-dir=build_${{ matrix.runner.target }} ${{ matrix.runner.pytest_args }} - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: ${{ env.TEST_RESULT_NAME }} path: ${{ env.TEST_RESULT_FILE }} publish-results: name: Publish Test results needs: - run-target - run-target-linux if: github.repository_owner == 'espressif' && always() && github.event_name == 'pull_request' && needs.prepare.outputs.build_only == '0' runs-on: ubuntu-22.04 permissions: checks: write pull-requests: write steps: - name: Download Test results uses: actions/download-artifact@v4 with: pattern: test_results_* path: test_results - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@v2 with: files: test_results/**/*.xml ================================================ FILE: .github/workflows/clang-tidy.yml ================================================ name: Run clang-tidy on: pull_request: types: [opened, reopened, synchronize] push: branches: - master permissions: contents: read security-events: write jobs: build: name: Run clang-tidy runs-on: ubuntu-24.04 container: espressif/idf:latest steps: - uses: actions/checkout@v4 with: submodules: 'true' - name: Install esp-clang run: | ${IDF_PATH}/tools/idf_tools.py --non-interactive install esp-clang - name: Install clang-tidy-sarif run: | curl -sSL https://github.com/psastras/sarif-rs/releases/download/clang-tidy-sarif-v0.8.0/clang-tidy-sarif-x86_64-unknown-linux-gnu -o clang-tidy-sarif chmod +x clang-tidy-sarif - name: Install pyclang shell: bash run: | . ${IDF_PATH}/export.sh pip install pyclang~=0.2.0 - name: Run code analysis shell: bash env: IDF_TOOLCHAIN: clang IDF_TARGET: esp32 working-directory: .github/clang-tidy/test_app run: | . ${IDF_PATH}/export.sh idf.py reconfigure idf.py clang-check @build/clang_check_args --run-clang-tidy-py run-clang-tidy cp warnings.txt ${GITHUB_WORKSPACE}/warnings.txt - name: Convert clang-tidy results into SARIF output run: | export PATH=$PWD:$PATH ./clang-tidy-sarif -o results.sarif.raw -i warnings.txt # Remove warnings which recommend using functions not supported in newlib. python3 $GITHUB_WORKSPACE/.github/filter_sarif.py -o results.sarif \ --include-prefix ${GITHUB_WORKSPACE}/ \ --exclude-text-contains memset_s \ --exclude-text-contains memmove_s \ --exclude-text-contains memcpy_s \ --exclude-text-contains fprintf_s \ --exclude-text-contains snprintf_s \ --exclude-text-contains strncpy_s \ --exclude-text-contains sscanf_s \ results.sarif.raw - uses: actions/upload-artifact@v4 with: path: | warnings.txt results.sarif results.sarif.raw - name: Upload SARIF file uses: github/codeql-action/upload-sarif@v4 with: sarif_file: results.sarif category: clang-tidy ================================================ FILE: .github/workflows/deploy_gh_pages.yml ================================================ name: Build and Deploy Programming Guides on: push: branches: [master] pull_request: branches: [master] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup mdBook 📥 uses: peaceiris/actions-mdbook@v2 with: mdbook-version: "latest" - name: Install doxygen tools 🧱 run: |- sudo apt-get install -y doxygen pip install esp-doxybook - name: Build docs 🔧 run: | version="latest" if [[ "${{ github.event_name }}" == "pull_request" ]]; then version="pr-preview-${{ github.event.pull_request.number }}" fi python3 ./.github/build_docs.py --version "$version" --output-dir "docs_build_output" - name: Check Links 🔍 uses: lycheeverse/lychee-action@v2 with: # Check links in docs_build_output args: >- --no-progress --include-fragments --root-dir docs_build_output --exclude-path '.*/404\.html$' docs_build_output/**/*.html # Fail the action if broken links are found fail: true # Create directory structure for GitHub Pages with "version prefix" - name: Prepare files for deployment 📁 if: github.event_name == 'push' && github.ref == 'refs/heads/master' run: | version="latest" mkdir -p "gh-pages/$version" cp -r docs_build_output/* "gh-pages/$version/" - name: Upload Pages artifact 📤 if: github.event_name == 'push' && github.ref == 'refs/heads/master' uses: actions/upload-pages-artifact@v3 with: path: gh-pages deploy: if: github.event_name == 'push' && github.ref == 'refs/heads/master' needs: build environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }}latest/ runs-on: ubuntu-latest steps: - name: Setup Pages 📥 uses: actions/configure-pages@v5 - name: Deploy to GitHub Pages 🚀 id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/issue_comment.yml ================================================ name: Sync issue comments to JIRA permissions: issues: write pull-requests: write # This workflow will be triggered when new issue comment is created (including PR comments) on: issue_comment # Limit to single concurrent run for workflows which can create Jira issues. # Same concurrency group is used in new_issues.yml concurrency: jira_issues jobs: sync_issue_comments_to_jira: name: Sync Issue Comments to Jira runs-on: ubuntu-latest if: ${{ github.repository_owner == 'espressif' }} steps: - uses: actions/checkout@v4 - name: Sync issue comments to JIRA uses: espressif/github-actions/sync_issues_to_jira@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JIRA_PASS: ${{ secrets.JIRA_PASS }} JIRA_PROJECT: IEC JIRA_URL: ${{ secrets.JIRA_URL }} JIRA_USER: ${{ secrets.JIRA_USER }} ================================================ FILE: .github/workflows/new_issues.yml ================================================ name: Sync issues to Jira permissions: issues: write # This workflow will be triggered when a new issue is opened on: issues # Limit to single concurrent run for workflows which can create Jira issues. # Same concurrency group is used in issue_comment.yml concurrency: jira_issues jobs: sync_issues_to_jira: name: Sync issues to Jira runs-on: ubuntu-latest if: ${{ github.repository_owner == 'espressif' }} steps: - uses: actions/checkout@v4 - name: Sync GitHub issues to Jira project uses: espressif/github-actions/sync_issues_to_jira@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JIRA_PASS: ${{ secrets.JIRA_PASS }} JIRA_PROJECT: IEC JIRA_URL: ${{ secrets.JIRA_URL }} JIRA_USER: ${{ secrets.JIRA_USER }} ================================================ FILE: .github/workflows/new_prs.yml ================================================ name: Sync remain PRs to Jira permissions: pull-requests: write # This workflow will be triggered every hour, to sync remaining PRs (i.e. PRs with zero comment) to Jira project # Note that, PRs can also get synced when new PR comment is created on: schedule: - cron: "0 * * * *" # Limit to single concurrent run for workflows which can create Jira issues. # Same concurrency group is used in issue_comment.yml concurrency: jira_issues jobs: sync_prs_to_jira: name: Sync PRs to Jira runs-on: ubuntu-latest if: ${{ github.repository_owner == 'espressif' }} steps: - uses: actions/checkout@v4 - name: Sync PRs to Jira project uses: espressif/github-actions/sync_issues_to_jira@master with: cron_job: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JIRA_PASS: ${{ secrets.JIRA_PASS }} JIRA_PROJECT: IEC JIRA_URL: ${{ secrets.JIRA_URL }} JIRA_USER: ${{ secrets.JIRA_USER }} ================================================ FILE: .github/workflows/pre-commit.yml ================================================ name: pre-commit on: pull_request: types: [opened, reopened, synchronize] jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.1 ================================================ FILE: .github/workflows/test_sbom.yml ================================================ name: Run SBOM manifests validation test on: pull_request: types: [opened, reopened, synchronize] jobs: test_sbom: name: Run SBOM manifests validation test runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Validate SBOM manifests run: | git config --global safe.directory $(pwd) pip install esp-idf-sbom python3 -m esp_idf_sbom manifest validate ================================================ FILE: .github/workflows/upload_component.yml ================================================ name: Push components to Espressif Component Service on: # For pull requests: perform upload with "--dry-run" argument, # i.e. validate that the component passes all checks for being uploaded. pull_request: # For pushes to master: actually upload the components to the registry. push: branches: - master jobs: upload_components: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - run: | echo "${{ ( github.ref_name != 'master' || github.repository_owner != 'espressif' ) && 'Checking' || 'Uploading' }} components" - name: Upload components to component service uses: espressif/upload-components-ci-action@v2 with: components: | argtable3 bdc_motor catch2 cbor ccomp_timer cjson coap coremark dhara eigen esp_daylight esp_cli_commands esp_delta_ota esp_encrypted_img esp_flash_dispatcher esp_ext_part_tables esp_gcov esp_isotp esp_lcd_qemu_rgb esp_linenoise esp_jpeg esp_schedule esp_cli esp_serial_slave_link esp_sysview expat fmt freetype iqmath jsmn json_generator json_parser led_strip libsodium network_provisioning nghttp onewire_bus pcap pid_ctrl qrcode quirc sh2lib spi_nand_flash spi_nand_flash_fatfs supertinycron thorvg touch_element unit-test-app zlib libpng libjpeg-turbo namespace: "espressif" # API token will only be available in the master branch in the main repository. # However, dry-run doesn't require a valid token. api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} dry_run: ${{ github.ref_name != 'master' || github.repository_owner != 'espressif' }} ================================================ FILE: .github/workflows/vulnerability_scan.yml ================================================ name: Vulnerability scan on: schedule: - cron: '0 0 * * *' workflow_dispatch: jobs: vulnerability-scan: name: Vulnerability scan runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: submodules: recursive - name: Vulnerability scan env: SBOM_CHECK_LOCAL_DB: ${{ vars.SBOM_CHECK_LOCAL_DB }} SBOM_MATTERMOST_WEBHOOK: ${{ secrets.SBOM_MATTERMOST_WEBHOOK }} NVDAPIKEY: ${{ secrets.NVDAPIKEY }} uses: espressif/esp-idf-sbom-action@master ================================================ FILE: .gitignore ================================================ **/dist/** **/build*/** **/__pycache__/** sdkconfig sdkconfig.old dependencies.lock **/managed_components/** .vscode/ .cache .DS_Store warnings.txt # Ignore artifacts for documentation build doxygen_output/ **/docs/book/ docs_build_output/ pytest-args.txt build_info*.json ================================================ FILE: .gitmodules ================================================ [submodule "libsodium/libsodium"] path = libsodium/libsodium url = https://github.com/jedisct1/libsodium.git [submodule "cbor/tinycbor"] path = cbor/tinycbor url = https://github.com/intel/tinycbor.git [submodule "nghttp/nghttp2"] path = nghttp/nghttp2 url = https://github.com/nghttp2/nghttp2.git [submodule "expat/expat"] path = expat/expat url = https://github.com/libexpat/libexpat.git [submodule "coap/libcoap"] path = coap/libcoap url = https://github.com/obgm/libcoap.git [submodule "eigen/eigen"] path = eigen/eigen url = https://gitlab.com/libeigen/eigen.git [submodule "fmt/fmt"] path = fmt/fmt url = https://github.com/fmtlib/fmt.git [submodule "esp_delta_ota/detools"] path = esp_delta_ota/detools url = https://github.com/eerimoq/detools.git [submodule "quirc/quirc"] path = quirc/quirc url = https://github.com/dlbeer/quirc.git [submodule "zlib/zlib"] path = zlib/zlib url = https://github.com/madler/zlib.git [submodule "libpng/libpng"] path = libpng/libpng url = https://github.com/glennrp/libpng.git [submodule "coremark/coremark"] path = coremark/coremark url = https://github.com/eembc/coremark.git [submodule "freetype/freetype"] path = freetype/freetype url = https://github.com/freetype/freetype.git [submodule "catch2/Catch2"] path = catch2/Catch2 url = https://github.com/catchorg/Catch2.git [submodule "dhara/dhara"] path = dhara/dhara url = https://github.com/dlbeer/dhara.git [submodule "supertinycron/supertinycron"] path = supertinycron/supertinycron url = https://github.com/exander77/supertinycron.git [submodule "thorvg/thorvg"] path = thorvg/thorvg url = https://github.com/thorvg/thorvg [submodule "libjpeg-turbo/libjpeg-turbo"] path = libjpeg-turbo/libjpeg-turbo url = https://github.com/libjpeg-turbo/libjpeg-turbo.git [submodule "argtable3/argtable3"] path = argtable3/argtable3 url = https://github.com/argtable/argtable3.git [submodule "esp_isotp/isotp-c"] path = esp_isotp/isotp-c url = https://github.com/SimonCahill/isotp-c.git [submodule "cjson/cJSON"] path = cjson/cJSON url = https://github.com/DaveGamble/cJSON.git ================================================ FILE: .idf_build_apps.toml ================================================ recursive = true exclude = [ ".github", ] manifest_file = [ "argtable3/.build-test-rules.yml", "bdc_motor/.build-test-rules.yml", "ccomp_timer/.build-test-rules.yml", "coremark/.build-test-rules.yml", "esp_daylight/.build-test-rules.yml", "esp_delta_ota/.build-test-rules.yml", "esp_cli_commands/.build-test-rules.yml", "esp_encrypted_img/.build-test-rules.yml", "esp_flash_dispatcher/.build-test-rules.yml", "esp_gcov/.build-test-rules.yml", "esp_jpeg/.build-test-rules.yml", "esp_linenoise/.build-test-rules.yml", "esp_schedule/.build-test-rules.yml", "esp_cli/.build-test-rules.yml", "esp_ext_part_tables/.build-test-rules.yml", "esp_serial_slave_link/.build-test-rules.yml", "esp_sysview/.build-test-rules.yml", "expat/.build-test-rules.yml", "iqmath/.build-test-rules.yml", "esp_isotp/.build-test-rules.yml", "jsmn/.build-test-rules.yml", "json_generator/.build-test-rules.yml", "json_parser/.build-test-rules.yml", "libpng/.build-test-rules.yml", "libsodium/.build-test-rules.yml", "onewire_bus/.build-test-rules.yml", "pcap/.build-test-rules.yml", "pid_ctrl/.build-test-rules.yml", "qrcode/.build-test-rules.yml", "quirc/.build-test-rules.yml", "thorvg/.build-test-rules.yml", "touch_element/.build-test-rules.yml", "zlib/.build-test-rules.yml", "libjpeg-turbo/.build-test-rules.yml", ".build-test-rules.yml", ] check_warnings = true # build related options build_dir = "build_@t_@w" config_rules = [ 'sdkconfig.ci=default', 'sdkconfig.ci.*=', ] ignore_warning_file = ".ignore_build_warnings.txt" ================================================ FILE: .ignore_build_warnings.txt ================================================ DeprecationWarning: pkg_resources is deprecated as an API WARNING: The following Kconfig variables were used in "if" clauses Warning: Deprecated: Option '--flash_mode' is deprecated. Use '--flash-mode' instead. Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' instead. Warning: Deprecated: Option '--flash_size' is deprecated. Use '--flash-size' instead. ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/igrr/astyle_py.git rev: v1.1.0 hooks: - id: astyle_py args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper'] exclude: 'network_provisioning/proto-c' - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell - repo: local hooks: - id: consistency_check name: Repo consistency checks language: python entry: python .github/consistency_check.py pass_filenames: false additional_dependencies: - "toml; python_version < '3.11'" - pyyaml - repo: https://github.com/espressif/esp-idf-kconfig.git rev: v2.5.0 hooks: - id: check-kconfig-files - id: check-deprecated-kconfig-options ================================================ FILE: README.md ================================================ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) [![Build and Run Apps](https://github.com/espressif/idf-extra-components/actions/workflows/build_and_run_apps.yml/badge.svg?branch=master)](https://github.com/espressif/idf-extra-components/actions/workflows/build_and_run_apps.yml) [![Clang-Tidy](https://github.com/espressif/idf-extra-components/actions/workflows/clang-tidy.yml/badge.svg?branch=master)](https://github.com/espressif/idf-extra-components/security/code-scanning?query=is%3Aopen+branch%3Amaster) # ESP-IDF Extra Components This repository is used to maintain various extra components for [ESP-IDF](https://github.com/espressif/esp-idf). These components can be installed from [ESP Component Registry](https://components.espressif.com/). Many of the components in this repository are wrappers around third-party libraries, such as zlib, libpng, etc. There are also various components developed by Espressif which don't currently fit into another repository (like [esp-protocols](https://github.com/espressif/esp-protocols), [esp-usb](https://github.com/espressif/esp-usb), [esp-iot-solution](https://github.com/espressif/esp-iot-solution), etc). ## Related Projects - [ESP-IDF](https://github.com/espressif/esp-idf) - [ESP Component Registry](https://components.espressif.com/) - [IDF Component Manager](https://github.com/espressif/idf-component-manager) - [ESP IOT Solution](https://github.com/espressif/esp-iot-solution) - [ESP Protocols](https://github.com/espressif/esp-protocols) - [ESP USB](https://github.com/espressif/esp-usb) ## Contribution We welcome contributions to idf-extra-components repository! You can contribute by fixing bugs, adding features, adding documentation or reporting an [issue](https://github.com/espressif/idf-extra-components/issues). We accept contributions via [Github Pull Requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). Before reporting an issue, make sure you've searched for a similar one that was already created. ### Adding New Components Please note that this repository is intended for components maintained by Espressif developers. If you don't work at Espressif and you'd like to publish a component to the ESP Component Registry, please set up a separate repository for your component. You can find more information about this in the [IDF Component Manager documentation](https://docs.espressif.com/projects/idf-component-manager/en/latest/). You can also check out the [talk about developing and publishing components](https://youtu.be/D86gQ4knUnc) from Espressif DevCon 2023. Feel free to [open an issue](https://github.com/espressif/idf-component-manager/issues) if you encounter any problem. ================================================ FILE: argtable3/.build-test-rules.yml ================================================ argtable3/test_apps/argtable_unit_tests: disable: - if: IDF_TARGET not in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" argtable3/test_apps/console_compat: disable: - if: IDF_TARGET not in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: argtable3/CMakeLists.txt ================================================ # This creates a virtual include file with an "argtable3" subdirectory where # "/argtable3/src/argtable3.h" is copied. the virtual directory is then used as # the include folder. This is so we can keep including argtable3 header as follow: # #include "argtable3/argtable3.h" set(VIRTUAL_INCLUDE_DIR "${CMAKE_BINARY_DIR}/virtual_include") file(MAKE_DIRECTORY "${VIRTUAL_INCLUDE_DIR}/argtable3") file(COPY "${COMPONENT_PATH}/argtable3/src/argtable3.h" DESTINATION "${VIRTUAL_INCLUDE_DIR}/argtable3") # list of C files under argtable3 source directory file(GLOB iec_argtable3_srcs "${COMPONENT_PATH}/argtable3/src/*.c" ) idf_component_register( SRCS ${iec_argtable3_srcs} INCLUDE_DIRS "${VIRTUAL_INCLUDE_DIR}" ) # Check if console component is in the build and has argtable3 sources. # If so, exclude console's argtable3 sources from compilation and make console # link against this managed argtable3 component instead. idf_build_get_property(build_components BUILD_COMPONENTS) if("console" IN_LIST build_components) # Get console component directory idf_component_get_property(console_dir console COMPONENT_DIR) # Check if console still has argtable3 sources (future-proofing) if(EXISTS "${console_dir}/argtable3") # List of argtable3 source files in console file(GLOB console_argtable3_srcs "${console_dir}/argtable3/*.c" ) # Exclude these files from compilation by marking them as header-only set_source_files_properties(${console_argtable3_srcs} DIRECTORY ${console_dir} PROPERTIES HEADER_FILE_ONLY ON) # Make console link against this managed argtable3 component idf_component_add_link_dependency(FROM console) endif() endif() target_compile_definitions(${COMPONENT_LIB} PRIVATE ARG_ENABLE_LOG=0 ARG_REPLACE_GETOPT=0 ) ================================================ FILE: argtable3/idf_component.yml ================================================ version: "3.3.1~1" description: "Argtable3 - GNU-style command-line option parsing C library" url: https://github.com/espressif/idf-extra-components/tree/master/argtable3 documentation: https://www.argtable.org/docs/ dependencies: idf: ">=5.1" sbom: manifests: - path: sbom_argtable3.yml dest: argtable3 ================================================ FILE: argtable3/sbom_argtable3.yml ================================================ name: argtable3 version: 3.3.1 supplier: 'Organization: argtable' description: GNU-style command-line option parsing C library url: https://github.com/argtable/argtable3/ hash: b50c6c81f25eef8af51141678b333c55b661414d ================================================ FILE: argtable3/test_apps/argtable_unit_tests/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(argtable3_test) ================================================ FILE: argtable3/test_apps/argtable_unit_tests/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_argtable3.c" "test_main.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: argtable3/test_apps/argtable_unit_tests/main/idf_component.yml ================================================ dependencies: espressif/argtable3: version: "*" override_path: "../../.." ================================================ FILE: argtable3/test_apps/argtable_unit_tests/main/test_argtable3.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "unity.h" #include "argtable3/argtable3.h" /* helper macros */ #define ARG_TABLE_FREE(tbl) arg_freetable((tbl), sizeof(tbl)/sizeof(tbl[0])) #define PARSE_ARGS(tbl, argc, argv) arg_parse((int)(argc), (char**)(argv), (void**)tbl) /* ===================== ARG TYPES ===================== */ TEST_CASE("argument constructors create valid structs", "[argtable3]") { arg_rem_t *argRem = arg_rem(NULL, "comment"); TEST_ASSERT_NOT_NULL(argRem); free(argRem); arg_lit_t *argLit0 = arg_lit0("v", "verbose", "Enable verbose"); TEST_ASSERT_NOT_NULL(argLit0); free(argLit0); arg_lit_t *argLit1 = arg_lit1("f", "force", "Force operation"); TEST_ASSERT_NOT_NULL(argLit1); free(argLit1); arg_int_t *argInt0 = arg_int0("i", "int", "", "Optional int"); TEST_ASSERT_NOT_NULL(argInt0); free(argInt0); arg_int_t *argInt1 = arg_int1("i", "int", "", "Required int"); TEST_ASSERT_NOT_NULL(argInt1); free(argInt1); arg_int_t *argIntn = arg_intn("i", "int", "", 0, 3, "Multiple ints"); TEST_ASSERT_NOT_NULL(argIntn); free(argIntn); arg_str_t *argStr0 = arg_str0("s", "str", "", "Optional string"); TEST_ASSERT_NOT_NULL(argStr0); free(argStr0); arg_str_t *argStr1 = arg_str1("s", "str", "", "Required string"); TEST_ASSERT_NOT_NULL(argStr1); free(argStr1); arg_str_t *argStrn = arg_strn("s", "str", "", 1, 3, "Multi string"); TEST_ASSERT_NOT_NULL(argStrn); free(argStrn); arg_file_t *argFile0 = arg_file0("f", "file", "", "Optional file"); TEST_ASSERT_NOT_NULL(argFile0); free(argFile0); arg_file_t *argFile1 = arg_file1("f", "file", "", "Required file"); TEST_ASSERT_NOT_NULL(argFile1); free(argFile1); arg_file_t *argFilen = arg_filen("f", "file", "", 1, 3, "Multi file"); TEST_ASSERT_NOT_NULL(argFilen); free(argFilen); arg_dbl_t *argDbl0 = arg_dbl0("d", "double", "", "Optional double"); TEST_ASSERT_NOT_NULL(argDbl0); free(argDbl0); arg_dbl_t *argDbl1 = arg_dbl1("d", "double", "", "Required double"); TEST_ASSERT_NOT_NULL(argDbl1); free(argDbl1); arg_dbl_t *argDbln = arg_dbln("d", "double", "", 1, 2, "Multi double"); TEST_ASSERT_NOT_NULL(argDbln); free(argDbln); arg_date_t *argDate0 = arg_date0("t", "time", "", "%Y-%m-%d", "Optional date"); TEST_ASSERT_NOT_NULL(argDate0); free(argDate0); arg_date_t *argDate1 = arg_date1("t", "time", "", "%Y-%m-%d", "Required date"); TEST_ASSERT_NOT_NULL(argDate1); free(argDate1); arg_rex_t *argRex0 = arg_rex0("r", "regex", "", "^[a-z]+$", 0, "Regex"); TEST_ASSERT_NOT_NULL(argRex0); free(argRex0); arg_rex_t *argRex1 = arg_rex1("r", "regex", "", "^[a-z]+$", 0, "Regex"); TEST_ASSERT_NOT_NULL(argRex1); free(argRex1); arg_end_t *argEnd = arg_end(5); TEST_ASSERT_NOT_NULL(argEnd); free(argEnd); } TEST_CASE("arg_int: parses optional integer", "[argtable3]") { char *argv[] = {"prog", "-n", "100"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_int *n = arg_int0("n", "number", "", "An integer value"); struct arg_end *end = arg_end(10); void *argtable[] = {n, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_INT(100, n->ival[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_dbl: parses optional double", "[argtable3]") { char *argv[] = {"prog", "-d", "3.1415"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_dbl *d = arg_dbl0("d", "double", "", "A double value"); struct arg_end *end = arg_end(10); void *argtable[] = {d, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_FLOAT_WITHIN(0.0001, 3.1415, d->dval[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_lit: parses literal flags", "[argtable3]") { char *argv[] = {"prog", "-v"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_lit *v = arg_lit0("v", "verbose", "Enable verbose output"); struct arg_end *end = arg_end(10); void *argtable[] = {v, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_INT(1, v->count); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_str: parses string argument", "[argtable3]") { char *argv[] = {"prog", "-s", "hello"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_str *s = arg_str0("s", "string", "", "A string"); struct arg_end *end = arg_end(10); void *argtable[] = {s, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_STRING("hello", s->sval[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_file: parses file paths", "[argtable3]") { char *argv[] = {"prog", "-f", "/tmp/test.txt"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_file *f = arg_file0("f", "file", "", "A file path"); struct arg_end *end = arg_end(10); void *argtable[] = {f, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_STRING("/tmp/test.txt", f->filename[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_rex: validates regex input", "[argtable3]") { char *argv[] = {"prog", "-r", "abc123"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_rex *r = arg_rex0("r", "regex", "[a-z]+[0-9]+", "", 0, "Regex"); struct arg_end *end = arg_end(10); void *argtable[] = {r, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_STRING("abc123", r->sval[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_date: parses date-time string", "[argtable3]") { char *argv[] = {"prog", "-t", "2025-06-27 12:00:00"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_date *dt = arg_date0("t", "time", "%Y-%m-%d %H:%M:%S", "", "DateTime"); struct arg_end *end = arg_end(10); void *argtable[] = {dt, end}; TEST_ASSERT_EQUAL_INT(0, PARSE_ARGS(argtable, argc, argv)); TEST_ASSERT_EQUAL_INT(2025 - 1900, dt->tmval[0].tm_year); TEST_ASSERT_EQUAL_INT(5, dt->tmval[0].tm_mon); TEST_ASSERT_EQUAL_INT(27, dt->tmval[0].tm_mday); TEST_ASSERT_EQUAL_INT(12, dt->tmval[0].tm_hour); ARG_TABLE_FREE(argtable); } /* ===================== API Tests ===================== */ TEST_CASE("arg_print_syntax, glossary, and GNU glossary", "[argtable3]") { struct arg_lit *verbose = arg_lit0("v", "verbose", "Enable verbose output"); struct arg_str *name = arg_str1("n", "name", "", "Name is required"); struct arg_end *end = arg_end(20); void *argtable[] = {verbose, name, end}; char buf[256] = {0}; FILE *f = fmemopen(buf, sizeof(buf), "w"); TEST_ASSERT_NOT_NULL(f); arg_print_syntaxv(f, argtable, "\n"); fflush(f); TEST_ASSERT_NOT_NULL(strstr(buf, "[-v|--verbose]")); TEST_ASSERT_NOT_NULL(strstr(buf, "-n|--name=")); arg_print_glossary(f, argtable, "%s %s\n"); fflush(f); TEST_ASSERT_NOT_NULL(strstr(buf, "-v, --verbose Enable verbose output")); TEST_ASSERT_NOT_NULL(strstr(buf, "-n, --name= Name is required")); fclose(f); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_print_errors prints expected message", "[argtable3]") { char *argv[] = {"prog"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_int *num = arg_int1("n", NULL, "", "Required number"); struct arg_end *end = arg_end(10); void *argtable[] = {num, end}; int errors = arg_parse(argc, (char **)argv, argtable); TEST_ASSERT_GREATER_THAN(0, errors); char buf[256] = {0}; FILE *f = fmemopen(buf, sizeof(buf), "w"); TEST_ASSERT_NOT_NULL(f); arg_print_errors(f, end, argv[0]); fclose(f); printf("%s\n", buf); TEST_ASSERT_NOT_NULL(strstr(buf, "missing option")); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_print_option and arg_print_option_ds output", "[argtable3]") { char buf[128] = {0}; FILE *f = fmemopen(buf, sizeof(buf), "w"); TEST_ASSERT_NOT_NULL(f); arg_print_option(f, "f", "file", "", "\n"); fclose(f); TEST_ASSERT_NOT_NULL(strstr(buf, "-f|--file=")); } TEST_CASE("returns errors for invalid input", "[argtable3]") { char *argv[] = {"prog", "-i", "NaN"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_int *i = arg_int1("i", "int", "", "An integer"); struct arg_end *end = arg_end(10); void *argtable[] = {i, end}; int nerrors = PARSE_ARGS(argtable, argc, argv); TEST_ASSERT_GREATER_THAN(0, nerrors); char buf[128] = {0}; FILE *f = fmemopen(buf, sizeof(buf), "w"); TEST_ASSERT_NOT_NULL(f); arg_print_errors(f, end, argv[0]); fclose(f); TEST_ASSERT_NOT_NULL(strstr(buf, "invalid argument")); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_parse, arg_nullcheck, arg_freetable basic flow", "[argtable3]") { char *argv[] = {"prog", "-n", "123"}; int argc = sizeof(argv) / sizeof(argv[0]); struct arg_int *num = arg_int1("n", NULL, "", "Required number"); struct arg_end *end = arg_end(10); void *argtable[] = {num, end}; TEST_ASSERT(0 == arg_nullcheck(argtable)); TEST_ASSERT_EQUAL_INT(0, arg_parse(argc, (char **)argv, argtable)); TEST_ASSERT_EQUAL_INT(123, num->ival[0]); ARG_TABLE_FREE(argtable); } TEST_CASE("arg_parse: success and error cases", "[argtable3]") { // Success case char *argv_success[] = {"prog", "-n", "42", "--name", "ESP32"}; int argc_success = sizeof(argv_success) / sizeof(argv_success[0]); struct arg_int *num = arg_int1("n", "number", "", "A required number"); struct arg_str *name = arg_str0(NULL, "name", "", "An optional name"); struct arg_end *end = arg_end(10); void *argtable_success[] = {num, name, end}; int rc_success = arg_parse(argc_success, (char **)argv_success, argtable_success); TEST_ASSERT_EQUAL_INT(0, rc_success); TEST_ASSERT_EQUAL_INT(42, num->ival[0]); TEST_ASSERT_EQUAL_STRING("ESP32", name->sval[0]); ARG_TABLE_FREE(argtable_success); // Error case: missing required argument char *argv_fail[] = {"prog", "--name", "ESP32"}; int argc_fail = sizeof(argv_fail) / sizeof(argv_fail[0]); num = arg_int1("n", "number", "", "A required number"); name = arg_str0(NULL, "name", "", "An optional name"); end = arg_end(10); void *argtable_fail[] = {num, name, end}; int rc_fail = arg_parse(argc_fail, argv_fail, argtable_fail); TEST_ASSERT_GREATER_THAN(0, rc_fail); char buf[256] = {0}; FILE *f = fmemopen(buf, sizeof(buf), "w"); TEST_ASSERT_NOT_NULL(f); arg_print_errors(f, end, argv_fail[0]); fclose(f); TEST_ASSERT_NOT_NULL(strstr(buf, "missing option")); ARG_TABLE_FREE(argtable_fail); } ================================================ FILE: argtable3/test_apps/argtable_unit_tests/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running argtable3 component tests\n"); unity_run_menu(); } ================================================ FILE: argtable3/test_apps/argtable_unit_tests/pytest_argtable3.py ================================================ import pytest from pytest_embedded import Dut @pytest.mark.generic def test_argtable3(dut: Dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: argtable3/test_apps/argtable_unit_tests/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_EN=n ================================================ FILE: argtable3/test_apps/console_compat/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(console_compat) idf_build_get_property(python PYTHON) add_custom_target(check_argtable_path ALL COMMAND ${python} ${CMAKE_CURRENT_SOURCE_DIR}/check_argtable_path.py ${CMAKE_CURRENT_BINARY_DIR}/console_compat.elf ${CMAKE_CURRENT_SOURCE_DIR}/../../.. $ENV{IDF_PATH} "${_CMAKE_TOOLCHAIN_PREFIX}strings" DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/console_compat.elf ) ================================================ FILE: argtable3/test_apps/console_compat/README.md ================================================ This test app verifies that if IDF's console component (which also contains argtable3 source) is built alongside argtable3 component, then the argtable3 code from this component is used. ================================================ FILE: argtable3/test_apps/console_compat/check_argtable_path.py ================================================ import argparse import subprocess import sys import os import re def main(): parser = argparse.ArgumentParser() parser.add_argument("elf_file", type=str, help="The ELF file to check") parser.add_argument("iec_path", type=str, help="The path to the idf-extra-components directory") parser.add_argument("idf_path", type=str, help="The path to the esp-idf directory") parser.add_argument("strings_program", type=str, help="The program to use to extract strings from the ELF file (e.g. 'strings')") args = parser.parse_args() # look for any files from argtable3 directory in 'strings app.elf' # This is more generic and works regardless of which argtable3 functions are used # Matches both IEC path (/argtable3/src/arg_*.c) and IDF path (/argtable3/arg_*.c) strings_output = subprocess.check_output([args.strings_program, args.elf_file], encoding="utf-8") lines_with_argtable = [line for line in strings_output.splitlines() if re.search(r'/argtable3/(src/)?arg_\w+\.c', line)] found_iec_path = False found_idf_path = False iec_files = [] idf_files = [] for line in lines_with_argtable: if os.path.abspath(args.iec_path) in line: found_iec_path = True iec_files.append(line.strip()) if os.path.abspath(args.idf_path) in line: found_idf_path = True idf_files.append(line.strip()) print("Found argtable3 files from IEC:", file=sys.stderr) for f in iec_files: print(f" {f}", file=sys.stderr) print("Found argtable3 files from IDF:", file=sys.stderr) for f in idf_files: print(f" {f}", file=sys.stderr) print(f"\nSummary - IDF files: {len(idf_files)}, IEC files: {len(iec_files)}", file=sys.stderr) if found_idf_path or not found_iec_path: print("Error: argtable3 files were found in IDF or not found in IEC", file=sys.stderr) raise SystemExit(1) if __name__ == "__main__": main() ================================================ FILE: argtable3/test_apps/console_compat/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_main.c" PRIV_REQUIRES console) ================================================ FILE: argtable3/test_apps/console_compat/main/idf_component.yml ================================================ dependencies: espressif/argtable3: version: "*" override_path: "../../.." ================================================ FILE: argtable3/test_apps/console_compat/main/test_main.c ================================================ #include "esp_console.h" void app_main(void) { // call a function of console component which uses argtable3 esp_console_register_help_command(); // in CMakeLists.txt, we'll check the map file to make sure // that the correct version of argtable3 functions was linked. } ================================================ FILE: bdc_motor/.build-test-rules.yml ================================================ bdc_motor/test_apps: disable: - if: SOC_MCPWM_SUPPORTED != 1 reason: Only MCPWM backend is implemented ================================================ FILE: bdc_motor/CHANGELOG.md ================================================ # Changelog ## 0.2.0 - Clean up the component dependency, don't depend on the `driver` component directly ## 0.1.0 - Initial version ================================================ FILE: bdc_motor/CMakeLists.txt ================================================ set(srcs "src/bdc_motor.c") if(CONFIG_SOC_MCPWM_SUPPORTED) list(APPEND srcs "src/bdc_motor_mcpwm_impl.c") endif() if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3") set(priv_requires "esp_driver_mcpwm") else() set(priv_requires "driver") endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" "interface" PRIV_REQUIRES ${priv_requires}) ================================================ FILE: bdc_motor/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: bdc_motor/README.md ================================================ # Brushed DC Motor Control [![Component Registry](https://components.espressif.com/components/espressif/bdc_motor/badge.svg)](https://components.espressif.com/components/espressif/bdc_motor) This directory contains an implementation for Brushed DC Motor by different peripherals. Currently only MCPWM is supported as the BDC motor backend. To learn more about how to use this component, please check API Documentation from header file [bdc_motor.h](./include/bdc_motor.h). ================================================ FILE: bdc_motor/idf_component.yml ================================================ version: "0.2.1" description: Brushed DC Motor Control Driver url: https://github.com/espressif/idf-extra-components/tree/master/bdc_motor repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.0" ================================================ FILE: bdc_motor/include/bdc_motor.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Brushed DC Motor handle */ typedef struct bdc_motor_t *bdc_motor_handle_t; /** * @brief Enable BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Enable motor successfully * - ESP_ERR_INVALID_ARG: Enable motor failed because of invalid parameters * - ESP_FAIL: Enable motor failed because other error occurred */ esp_err_t bdc_motor_enable(bdc_motor_handle_t motor); /** * @brief Disable BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Disable motor successfully * - ESP_ERR_INVALID_ARG: Disable motor failed because of invalid parameters * - ESP_FAIL: Disable motor failed because other error occurred */ esp_err_t bdc_motor_disable(bdc_motor_handle_t motor); /** * @brief Set speed for bdc motor * * @param motor: BDC Motor handle * @param speed: BDC speed * * @return * - ESP_OK: Set motor speed successfully * - ESP_ERR_INVALID_ARG: Set motor speed failed because of invalid parameters * - ESP_FAIL: Set motor speed failed because other error occurred */ esp_err_t bdc_motor_set_speed(bdc_motor_handle_t motor, uint32_t speed); /** * @brief Forward BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Forward motor successfully * - ESP_FAIL: Forward motor failed because some other error occurred */ esp_err_t bdc_motor_forward(bdc_motor_handle_t motor); /** * @brief Reverse BDC Motor * * @param strip: BDC Motor handle * * @return * - ESP_OK: Reverse motor successfully * - ESP_FAIL: Reverse motor failed because some other error occurred */ esp_err_t bdc_motor_reverse(bdc_motor_handle_t motor); /** * @brief Stop motor in a coast way (a.k.a Fast Decay) * * @param motor: BDC Motor handle * * @return * - ESP_OK: Stop motor successfully * - ESP_FAIL: Stop motor failed because some other error occurred */ esp_err_t bdc_motor_coast(bdc_motor_handle_t motor); /** * @brief Stop motor in a brake way (a.k.a Slow Decay) * * @param motor: BDC Motor handle * * @return * - ESP_OK: Stop motor successfully * - ESP_FAIL: Stop motor failed because some other error occurred */ esp_err_t bdc_motor_brake(bdc_motor_handle_t motor); /** * @brief Free BDC Motor resources * * @param strip: BDC Motor handle * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t bdc_motor_del(bdc_motor_handle_t motor); /** * @brief BDC Motor Configuration */ typedef struct { uint32_t pwma_gpio_num; /*!< BDC Motor PWM A gpio number */ uint32_t pwmb_gpio_num; /*!< BDC Motor PWM B gpio number */ uint32_t pwm_freq_hz; /*!< PWM frequency, in Hz */ } bdc_motor_config_t; /** * @brief BDC Motor MCPWM specific configuration */ typedef struct { int group_id; /*!< MCPWM group number */ uint32_t resolution_hz; /*!< MCPWM timer resolution */ } bdc_motor_mcpwm_config_t; /** * @brief Create BDC Motor based on MCPWM peripheral * * @param motor_config: BDC Motor configuration * @param mcpwm_config: MCPWM specific configuration * @param ret_motor Returned BDC Motor handle * @return * - ESP_OK: Create BDC Motor handle successfully * - ESP_ERR_INVALID_ARG: Create BDC Motor handle failed because of invalid argument * - ESP_ERR_NO_MEM: Create BDC Motor handle failed because of out of memory * - ESP_FAIL: Create BDC Motor handle failed because some other error */ esp_err_t bdc_motor_new_mcpwm_device(const bdc_motor_config_t *motor_config, const bdc_motor_mcpwm_config_t *mcpwm_config, bdc_motor_handle_t *ret_motor); #ifdef __cplusplus } #endif ================================================ FILE: bdc_motor/interface/bdc_motor_interface.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif typedef struct bdc_motor_t bdc_motor_t; /*!< Type of BDC motor */ /** * @brief BDC motor interface definition */ struct bdc_motor_t { /** * @brief Enable BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Enable motor successfully * - ESP_ERR_INVALID_ARG: Enable motor failed because of invalid parameters * - ESP_FAIL: Enable motor failed because other error occurred */ esp_err_t (*enable)(bdc_motor_t *motor); /** * @brief Disable BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Disable motor successfully * - ESP_ERR_INVALID_ARG: Disable motor failed because of invalid parameters * - ESP_FAIL: Disable motor failed because other error occurred */ esp_err_t (*disable)(bdc_motor_t *motor); /** * @brief Set speed for bdc motor * * @param motor: BDC Motor handle * @param speed: BDC speed * * @return * - ESP_OK: Set motor speed successfully * - ESP_ERR_INVALID_ARG: Set motor speed failed because of invalid parameters * - ESP_FAIL: Set motor speed failed because other error occurred */ esp_err_t (*set_speed)(bdc_motor_t *motor, uint32_t speed); /** * @brief Forward BDC motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Forward motor successfully * - ESP_FAIL: Forward motor failed because some other error occurred */ esp_err_t (*forward)(bdc_motor_t *motor); /** * @brief Reverse BDC Motor * * @param motor: BDC Motor handle * * @return * - ESP_OK: Reverse motor successfully * - ESP_FAIL: Reverse motor failed because some other error occurred */ esp_err_t (*reverse)(bdc_motor_t *motor); /** * @brief Stop motor in a coast way (a.k.a Fast Decay) * * @param motor: BDC Motor handle * * @return * - ESP_OK: Stop motor successfully * - ESP_FAIL: Stop motor failed because some other error occurred */ esp_err_t (*coast)(bdc_motor_t *motor); /** * @brief Stop motor in a brake way (a.k.a Slow Decay) * * @param motor: BDC Motor handle * * @return * - ESP_OK: Stop motor successfully * - ESP_FAIL: Stop motor failed because some other error occurred */ esp_err_t (*brake)(bdc_motor_t *motor); /** * @brief Free BDC Motor handle resources * * @param motor: BDC Motor handle * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t (*del)(bdc_motor_t *motor); }; #ifdef __cplusplus } #endif ================================================ FILE: bdc_motor/src/bdc_motor.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "esp_check.h" #include "bdc_motor.h" #include "bdc_motor_interface.h" static const char *TAG = "bdc_motor"; esp_err_t bdc_motor_enable(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->enable(motor); } esp_err_t bdc_motor_disable(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->disable(motor); } esp_err_t bdc_motor_set_speed(bdc_motor_handle_t motor, uint32_t speed) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->set_speed(motor, speed); } esp_err_t bdc_motor_forward(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->forward(motor); } esp_err_t bdc_motor_reverse(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->reverse(motor); } esp_err_t bdc_motor_coast(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->coast(motor); } esp_err_t bdc_motor_brake(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->brake(motor); } esp_err_t bdc_motor_del(bdc_motor_handle_t motor) { ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return motor->del(motor); } ================================================ FILE: bdc_motor/src/bdc_motor_mcpwm_impl.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "esp_check.h" #include "driver/mcpwm_prelude.h" #include "bdc_motor.h" #include "bdc_motor_interface.h" static const char *TAG = "bdc_motor_mcpwm"; typedef struct { bdc_motor_t base; mcpwm_timer_handle_t timer; mcpwm_oper_handle_t operator; mcpwm_cmpr_handle_t cmpa; mcpwm_cmpr_handle_t cmpb; mcpwm_gen_handle_t gena; mcpwm_gen_handle_t genb; } bdc_motor_mcpwm_obj; static esp_err_t bdc_motor_mcpwm_set_speed(bdc_motor_t *motor, uint32_t speed) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(mcpwm_motor->cmpa, speed), TAG, "set compare value failed"); ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(mcpwm_motor->cmpb, speed), TAG, "set compare value failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_enable(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_timer_enable(mcpwm_motor->timer), TAG, "enable timer failed"); ESP_RETURN_ON_ERROR(mcpwm_timer_start_stop(mcpwm_motor->timer, MCPWM_TIMER_START_NO_STOP), TAG, "start timer failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_disable(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_timer_start_stop(mcpwm_motor->timer, MCPWM_TIMER_STOP_EMPTY), TAG, "stop timer failed"); ESP_RETURN_ON_ERROR(mcpwm_timer_disable(mcpwm_motor->timer), TAG, "disable timer failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_forward(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, -1, true), TAG, "disable force level for gena failed"); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 0, true), TAG, "set force level for genb failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_reverse(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, -1, true), TAG, "disable force level for genb failed"); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 0, true), TAG, "set force level for gena failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_coast(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 0, true), TAG, "set force level for gena failed"); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 0, true), TAG, "set force level for genb failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_brake(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 1, true), TAG, "set force level for gena failed"); ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 1, true), TAG, "set force level for genb failed"); return ESP_OK; } static esp_err_t bdc_motor_mcpwm_del(bdc_motor_t *motor) { bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base); mcpwm_del_generator(mcpwm_motor->gena); mcpwm_del_generator(mcpwm_motor->genb); mcpwm_del_comparator(mcpwm_motor->cmpa); mcpwm_del_comparator(mcpwm_motor->cmpb); mcpwm_del_operator(mcpwm_motor->operator); mcpwm_del_timer(mcpwm_motor->timer); free(mcpwm_motor); return ESP_OK; } esp_err_t bdc_motor_new_mcpwm_device(const bdc_motor_config_t *motor_config, const bdc_motor_mcpwm_config_t *mcpwm_config, bdc_motor_handle_t *ret_motor) { bdc_motor_mcpwm_obj *mcpwm_motor = NULL; esp_err_t ret = ESP_OK; ESP_GOTO_ON_FALSE(motor_config && mcpwm_config && ret_motor, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); mcpwm_motor = calloc(1, sizeof(bdc_motor_mcpwm_obj)); ESP_GOTO_ON_FALSE(mcpwm_motor, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt motor"); // mcpwm timer mcpwm_timer_config_t timer_config = { .group_id = mcpwm_config->group_id, .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, .resolution_hz = mcpwm_config->resolution_hz, .period_ticks = mcpwm_config->resolution_hz / motor_config->pwm_freq_hz, .count_mode = MCPWM_TIMER_COUNT_MODE_UP, }; ESP_GOTO_ON_ERROR(mcpwm_new_timer(&timer_config, &mcpwm_motor->timer), err, TAG, "create MCPWM timer failed"); mcpwm_operator_config_t operator_config = { .group_id = mcpwm_config->group_id, }; ESP_GOTO_ON_ERROR(mcpwm_new_operator(&operator_config, &mcpwm_motor->operator), err, TAG, "create MCPWM operator failed"); ESP_GOTO_ON_ERROR(mcpwm_operator_connect_timer(mcpwm_motor->operator, mcpwm_motor->timer), err, TAG, "connect timer and operator failed"); mcpwm_comparator_config_t comparator_config = { .flags.update_cmp_on_tez = true, }; ESP_GOTO_ON_ERROR(mcpwm_new_comparator(mcpwm_motor->operator, &comparator_config, &mcpwm_motor->cmpa), err, TAG, "create comparator failed"); ESP_GOTO_ON_ERROR(mcpwm_new_comparator(mcpwm_motor->operator, &comparator_config, &mcpwm_motor->cmpb), err, TAG, "create comparator failed"); // set the initial compare value for both comparators mcpwm_comparator_set_compare_value(mcpwm_motor->cmpa, 0); mcpwm_comparator_set_compare_value(mcpwm_motor->cmpb, 0); mcpwm_generator_config_t generator_config = { .gen_gpio_num = motor_config->pwma_gpio_num, }; ESP_GOTO_ON_ERROR(mcpwm_new_generator(mcpwm_motor->operator, &generator_config, &mcpwm_motor->gena), err, TAG, "create generator failed"); generator_config.gen_gpio_num = motor_config->pwmb_gpio_num; ESP_GOTO_ON_ERROR(mcpwm_new_generator(mcpwm_motor->operator, &generator_config, &mcpwm_motor->genb), err, TAG, "create generator failed"); mcpwm_generator_set_action_on_timer_event(mcpwm_motor->gena, MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)); mcpwm_generator_set_action_on_compare_event(mcpwm_motor->gena, MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, mcpwm_motor->cmpa, MCPWM_GEN_ACTION_LOW)); mcpwm_generator_set_action_on_timer_event(mcpwm_motor->genb, MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)); mcpwm_generator_set_action_on_compare_event(mcpwm_motor->genb, MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, mcpwm_motor->cmpb, MCPWM_GEN_ACTION_LOW)); mcpwm_motor->base.enable = bdc_motor_mcpwm_enable; mcpwm_motor->base.disable = bdc_motor_mcpwm_disable; mcpwm_motor->base.forward = bdc_motor_mcpwm_forward; mcpwm_motor->base.reverse = bdc_motor_mcpwm_reverse; mcpwm_motor->base.coast = bdc_motor_mcpwm_coast; mcpwm_motor->base.brake = bdc_motor_mcpwm_brake; mcpwm_motor->base.set_speed = bdc_motor_mcpwm_set_speed; mcpwm_motor->base.del = bdc_motor_mcpwm_del; *ret_motor = &mcpwm_motor->base; return ESP_OK; err: if (mcpwm_motor) { if (mcpwm_motor->gena) { mcpwm_del_generator(mcpwm_motor->gena); } if (mcpwm_motor->genb) { mcpwm_del_generator(mcpwm_motor->genb); } if (mcpwm_motor->cmpa) { mcpwm_del_comparator(mcpwm_motor->cmpa); } if (mcpwm_motor->cmpb) { mcpwm_del_comparator(mcpwm_motor->cmpb); } if (mcpwm_motor->operator) { mcpwm_del_operator(mcpwm_motor->operator); } if (mcpwm_motor->timer) { mcpwm_del_timer(mcpwm_motor->timer); } free(mcpwm_motor); } return ret; } ================================================ FILE: bdc_motor/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(bdc_motor_test) ================================================ FILE: bdc_motor/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "bdc_motor_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: bdc_motor/test_apps/main/bdc_motor_test.c ================================================ #include void app_main(void) { } ================================================ FILE: bdc_motor/test_apps/main/idf_component.yml ================================================ dependencies: espressif/bdc_motor: version: "*" override_path: "../../" ================================================ FILE: catch2/CMakeLists.txt ================================================ idf_component_register(SRCS cmd_catch2.cpp INCLUDE_DIRS include) set(CATCH_CONFIG_NO_POSIX_SIGNALS 1 CACHE BOOL OFF FORCE) add_subdirectory(Catch2) target_link_libraries(${COMPONENT_LIB} PUBLIC Catch2::Catch2) get_target_property(catch_target Catch2::Catch2 ALIASED_TARGET) # Silence a warning in catch_exception_translator_registry.cpp target_compile_options(${catch_target} PRIVATE -Wno-unused-function) # Link to pthreads to avoid issues with STL headers idf_build_get_property(target IDF_TARGET) if(NOT target STREQUAL "linux") target_link_libraries(${catch_target} PUBLIC idf::pthread) # Work around a linking dependency issue in ESP-IDF target_link_libraries(${COMPONENT_LIB} PUBLIC "-Wl,-u getentropy") endif() # If console component is present in the build, include the console # command feature. idf_build_get_property(build_components BUILD_COMPONENTS) if("console" IN_LIST build_components) target_compile_definitions(${COMPONENT_LIB} PRIVATE WITH_CONSOLE) target_link_libraries(${COMPONENT_LIB} PUBLIC idf::console) endif() ================================================ FILE: catch2/LICENSE.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: catch2/README.md ================================================ # Catch2 unit testing library This component is an ESP-IDF wrapper around [Catch2 unit testing library](https://github.com/catchorg/Catch2). Tests written using this component can be executed both on real hardware and on the host. ## Using Catch2 in ESP-IDF ### Example To get started with Catch2 quickly, use the example provided along with this component: ```bash idf.py create-project-from-example "espressif/catch:catch2-test" cd catch2-test idf.py set-target esp32 idf.py build flash monitor ``` The example can also be used on host (Linux): ```bash idf.py --preview set-target linux idf.py build monitor ``` ### Writing tests Tests and assertions are written as usual for Catch2, refer to the [official documentation](https://github.com/catchorg/Catch2/tree/devel/docs) for more details. Here is a simple example: ```c++ #include TEST_CASE("Test case 1") { REQUIRE(1 == 1); } ``` To ensure the test cases are linked into the application, use `WHOLE_ARCHIVE` argument when calling `idf_component_register()` in CMake. For example: ```cmake idf_component_register(SRCS "test_main.cpp" "test_cases.cpp" INCLUDE_DIRS "." WHOLE_ARCHIVE) ``` ### Enabling C++ exceptions Catch2 relies on C++ exceptions for assertion handling. To get most benefits out of Catch2, it is recommended to have exceptions enabled in the project (`CONFIG_COMPILER_CXX_EXCEPTIONS=y`). ### Stack size Catch2 uses significant amount of stack space — around 8kB, plus the stack space used by the test cases themselves. Therefore it is necessary to increase the stack size of the `main` task using the `CONFIG_ESP_MAIN_TASK_STACK_SIZE` option, or to invoke Catch from a new task with sufficient stack size. It is also recommended to keep `CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` or `CONFIG_ESP_SYSTEM_HW_STACK_GUARD` options enabled to detect stack overflows. ### Invoking the test runner Catch2 test framework can implement the application entry point (`main(int, char**)`) which calls the test runner. This functionality is typically used via the `CATCH_CONFIG_MAIN` macro. However ESP-IDF applications use `app_main(void)` function as an entry point, so the approach with `CATCH_CONFIG_MAIN` doesn't work. Instead, invoke Catch2 in your `app_main` as follows: ```c++ #include extern "C" void app_main(void) { // prepare command line arguments to Catch2 const int argc = 1; const char* argv[2] = {"target_test_main", NULL}; // run the tests int result = Catch::Session().run(argc, argv); // ... handle the result ``` ### Integration with ESP-IDF `console` component This component provides a function to register an ESP-IDF console command to invoke Catch2 test cases: ```c++ esp_err_t register_catch2(const char* cmd_name); ``` This function registers a command with the specified name (for example, "test") with ESP-IDF `console` component. The command passes all the arguments to Catch2 test runner. This makes it possible to invoke tests from an interactive console running on an ESP chip. To try this functionality, use `catch2-console` example: ```bash idf.py create-project-from-example "espressif/catch:catch2-console" cd catch2-console idf.py set-target esp32 idf.py build flash monitor ``` ================================================ FILE: catch2/cmd_catch2.cpp ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: BSL-1.0 * Note: same license as Catch2 */ #include "esp_err.h" #if WITH_CONSOLE #include "Catch2/src/catch2/catch_config.hpp" #include "esp_console.h" #include "catch2/catch_session.hpp" #include "cmd_catch2.h" static int cmd_catch2(int argc, char **argv) { static auto session = Catch::Session(); Catch::ConfigData configData; session.useConfigData(configData); return session.run(argc, argv); } extern "C" esp_err_t register_catch2(const char *cmd_name) { esp_console_cmd_t cmd = {}; cmd.command = cmd_name, cmd.help = "Run tests"; cmd.func = &cmd_catch2; return esp_console_cmd_register(&cmd); } #else // WITH_CONSOLE // Defined to avoid ranlib warning on macOS // (the table of contents is empty (no object file members in the library define global symbols)) extern "C" esp_err_t register_catch2(const char *cmd_name) { return ESP_OK; } #endif // WITH_CONSOLE ================================================ FILE: catch2/examples/catch2-console/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(catch2-console) ================================================ FILE: catch2/examples/catch2-console/README.md ================================================ # Catch2 console example This example shows how to combine Catch2 test framework with ESP-IDF `console` component. In this example, you can execute test cases from an interactive console on an ESP chip. ## Using the example To run the example, build and flash the project as usual. For example, with an ESP32 chip: ```bash idf.py set-target esp32 idf.py build flash monitor ``` In the console, use `test` command to invoke Catch2. `test` accepts the same command line arguments as Catch2 tests on the host: - `test -h` — prints command line argument reference - `test --list-tests` — lists all the registered tests - `test` — runs all the registered tests - `test ` — runs specific tests [See Catch2 documentation](https://github.com/catchorg/Catch2/blob/devel/docs/command-line.md) for the complete command line argument reference. ================================================ FILE: catch2/examples/catch2-console/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_main.cpp" "test_cases.cpp" INCLUDE_DIRS "." WHOLE_ARCHIVE PRIV_REQUIRES console) ================================================ FILE: catch2/examples/catch2-console/main/idf_component.yml ================================================ dependencies: espressif/catch2: version: "*" override_path: "../../../" ================================================ FILE: catch2/examples/catch2-console/main/test_cases.cpp ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include TEST_CASE("Test case 1") { REQUIRE(1 == 1); } ================================================ FILE: catch2/examples/catch2-console/main/test_main.cpp ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include "esp_console.h" #include "cmd_catch2.h" #if SOC_USB_SERIAL_JTAG_SUPPORTED #if !CONFIG_ESP_CONSOLE_SECONDARY_NONE #warning "A secondary serial console is not useful when using the console component. Please disable it in menuconfig." #endif #endif extern "C" void app_main(void) { esp_console_repl_t *repl = NULL; esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); repl_config.prompt = "catch2>"; repl_config.task_stack_size = 10000; /* Register commands */ esp_console_register_help_command(); register_catch2("test"); #if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM) esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); #elif defined(CONFIG_ESP_CONSOLE_USB_CDC) esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl)); #elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl)); #else #error Unsupported console type #endif ESP_ERROR_CHECK(esp_console_start_repl(repl)); } ================================================ FILE: catch2/examples/catch2-console/pytest_catch2_console.py ================================================ # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut @pytest.mark.generic def test_catch2_console_example(dut: Dut) -> None: dut.expect_exact('Type \'help\' to get the list of commands.') dut.write('test -?\n') dut.expect_exact('For more detailed usage please see the project docs') dut.write('test\n') dut.expect_exact('All tests passed') dut.expect_exact('1 assertion in 1 test case') ================================================ FILE: catch2/examples/catch2-console/sdkconfig.defaults ================================================ CONFIG_COMPILER_CXX_EXCEPTIONS=y CONFIG_ESP_CONSOLE_SECONDARY_NONE=y ================================================ FILE: catch2/examples/catch2-test/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(catch2-test) ================================================ FILE: catch2/examples/catch2-test/README.md ================================================ # Catch2 example This example should help you get started with Catch2 test framework. ## Using the example To run the example on an ESP32, build and flash the project as usual: ```bash idf.py set-target esp32 idf.py build flash monitor ``` The example can also be used on Linux host: ```bash idf.py --preview set-target linux idf.py build monitor ``` ## Example structure - [main/idf_component.yml](main/idf_component.yml) adds a dependency on `espressif/catch2` component. - [main/CMakeLists.txt](main/CMakeLists.txt) specifies the source files and registers the `main` component with `WHOLE_ARCHIVE` option enabled. - [main/test_main.cpp](main/test_main.cpp) implements the application entry point which calls the test runner. - [main/test_cases.cpp](main/test_cases.cpp) implements one trivial test case. - [sdkconfig.defaults](sdkconfig.defaults) sets the options required to run the example: enables C++ exceptions and increases the size of the `main` task stack. ## Expected output ``` Randomness seeded to: 3499211612 =============================================================================== All tests passed (1 assertion in 1 test case) Test passed. ``` ================================================ FILE: catch2/examples/catch2-test/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_main.cpp" "test_cases.cpp" INCLUDE_DIRS "." WHOLE_ARCHIVE) ================================================ FILE: catch2/examples/catch2-test/main/idf_component.yml ================================================ dependencies: espressif/catch2: version: "*" override_path: "../../../" ================================================ FILE: catch2/examples/catch2-test/main/test_cases.cpp ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include TEST_CASE("Test case 1") { REQUIRE(1 == 1); } ================================================ FILE: catch2/examples/catch2-test/main/test_main.cpp ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include extern "C" void app_main(void) { int argc = 1; const char *argv[2] = { "target_test_main", NULL }; auto result = Catch::Session().run(argc, argv); if (result != 0) { printf("Test failed with result %d\n", result); } else { printf("Test passed.\n"); } } ================================================ FILE: catch2/examples/catch2-test/pytest_catch2.py ================================================ # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut @pytest.mark.generic def test_catch2_example(dut: Dut) -> None: dut.expect_exact('All tests passed') dut.expect_exact('1 assertion in 1 test case') ================================================ FILE: catch2/examples/catch2-test/sdkconfig.defaults ================================================ CONFIG_COMPILER_CXX_EXCEPTIONS=y CONFIG_ESP_MAIN_TASK_STACK_SIZE=10000 ================================================ FILE: catch2/idf_component.yml ================================================ version: "3.7.0" description: A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later url: https://github.com/espressif/idf-extra-components/tree/master/catch2 repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues documentation: https://github.com/catchorg/Catch2/tree/devel/docs dependencies: # Mostly because in older IDF versions there was no WHOLE_ARCHIVE component property, # so linking all the test cases in a component was a bit hard. idf: ">=5.0.0" sbom: manifests: - path: sbom_catch2.yml dest: Catch2 ================================================ FILE: catch2/include/cmd_catch2.h ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: BSL-1.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include "esp_err.h" /** * @brief Register a command to run Catch2 tests. * * @param cmd_name Name of the command to use. For example, "test". * @return esp_err_t ESP_OK on success, otherwise an error code. */ esp_err_t register_catch2(const char *cmd_name); #ifdef __cplusplus } #endif ================================================ FILE: catch2/sbom_catch2.yml ================================================ name: catch2 version: 3.7.0 cpe: cpe:2.3:a:catchorg:catch2:{}:*:*:*:*:*:*:* supplier: 'Organization: catchorg ' description: A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later url: https://github.com/catchorg/Catch2 hash: 31588bb4f56b638dd5afc28d3ebff9b9dcefb88d ================================================ FILE: cbor/CMakeLists.txt ================================================ idf_component_register(SRCS "tinycbor/src/cborencoder_close_container_checked.c" "tinycbor/src/cborencoder.c" "tinycbor/src/cborencoder_float.c" "tinycbor/src/cborerrorstrings.c" "tinycbor/src/cborparser_dup_string.c" "tinycbor/src/cborparser.c" "tinycbor/src/cborparser_float.c" "tinycbor/src/cborpretty_stdio.c" "tinycbor/src/cborpretty.c" "tinycbor/src/cbortojson.c" "tinycbor/src/cborvalidation.c" "tinycbor/src/open_memstream.c" INCLUDE_DIRS "tinycbor/src") # for open_memstream.c set_source_files_properties(tinycbor/src/open_memstream.c PROPERTIES COMPILE_DEFINITIONS "__linux__") # Fix unreachable macro redefinition issue between ESP-IDF toolchain and tinycbor # by force-including a header that resolves the conflict target_compile_options(${COMPONENT_LIB} PRIVATE "SHELL:-include \"${CMAKE_CURRENT_SOURCE_DIR}/port/include/unreachable_fix.h\"") ================================================ FILE: cbor/examples/cbor/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(cbor) ================================================ FILE: cbor/examples/cbor/README.md ================================================ # CBOR Example ## Overview The [CBOR](https://en.wikipedia.org/wiki/CBOR)(Concise Binary Object Representation) is a binary data serialization format which is similar to JSON but with smaller footprint. This example will illustrate how to encode and decode CBOR data using the APIs provided by [tinycbor](https://github.com/intel/tinycbor). For detailed information about how CBOR encoding and decoding works, please refer to [REF7049](https://tools.ietf.org/html/rfc7049) or [cbor.io](http://cbor.io/); ## How to use example ### Hardware Required This example should be able to run on any commonly available ESP32 development board. ### Build and Flash Run `idf.py -p PORT flash monitor` to build and flash the project. (To exit the serial monitor, type ``Ctrl-]``.) See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. ## Example Output ```bash I (320) example: encoded buffer size 67 I (320) example: convert CBOR to JSON [{"chip":"esp32","unicore":false,"ip":[192,168,1,100]},3.1400001049041748,"simple(99)","2019-07-10 09:00:00+0000","undefined"] I (340) example: decode CBOR manually Array[ Map{ chip esp32 unicore false ip Array[ 192 168 1 100 ] } 3.14 simple(99) 2019-07-10 09:00:00+0000 undefined ] ``` ## Troubleshooting For more API usage, please refer to [tinycbor API](https://intel.github.io/tinycbor/current/). (For any technical queries, please open an [issue](https://github.com/espressif/idf-extra-components/issues) on GitHub. We will get back to you as soon as possible.) ================================================ FILE: cbor/examples/cbor/main/CMakeLists.txt ================================================ idf_component_register(SRCS "cbor_example_main.c" INCLUDE_DIRS "") ================================================ FILE: cbor/examples/cbor/main/cbor_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ /* CBOR Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include "esp_log.h" #include "cbor.h" #include "cborjson.h" static const char *TAG = "example"; #define CBOR_CHECK(a, str, goto_tag, ret_value, ...) \ do \ { \ if ((a) != CborNoError) \ { \ ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ ret = ret_value; \ goto goto_tag; \ } \ } while (0) static void indent(int nestingLevel) { while (nestingLevel--) { printf(" "); } } static void dumpbytes(const uint8_t *buf, size_t len) { while (len--) { printf("%02X ", *buf++); } } /** * Decode CBOR data manually */ static CborError example_dump_cbor_buffer(CborValue *it, int nestingLevel) { CborError ret = CborNoError; while (!cbor_value_at_end(it)) { CborType type = cbor_value_get_type(it); indent(nestingLevel); switch (type) { case CborArrayType: { CborValue recursed; assert(cbor_value_is_container(it)); puts("Array["); ret = cbor_value_enter_container(it, &recursed); CBOR_CHECK(ret, "enter container failed", err, ret); ret = example_dump_cbor_buffer(&recursed, nestingLevel + 1); CBOR_CHECK(ret, "recursive dump failed", err, ret); ret = cbor_value_leave_container(it, &recursed); CBOR_CHECK(ret, "leave container failed", err, ret); indent(nestingLevel); puts("]"); continue; } case CborMapType: { CborValue recursed; assert(cbor_value_is_container(it)); puts("Map{"); ret = cbor_value_enter_container(it, &recursed); CBOR_CHECK(ret, "enter container failed", err, ret); ret = example_dump_cbor_buffer(&recursed, nestingLevel + 1); CBOR_CHECK(ret, "recursive dump failed", err, ret); ret = cbor_value_leave_container(it, &recursed); CBOR_CHECK(ret, "leave container failed", err, ret); indent(nestingLevel); puts("}"); continue; } case CborIntegerType: { int64_t val; ret = cbor_value_get_int64(it, &val); CBOR_CHECK(ret, "parse int64 failed", err, ret); printf("%lld\n", (long long)val); break; } case CborByteStringType: { uint8_t *buf; size_t n; ret = cbor_value_dup_byte_string(it, &buf, &n, it); CBOR_CHECK(ret, "parse byte string failed", err, ret); dumpbytes(buf, n); puts(""); free(buf); continue; } case CborTextStringType: { char *buf; size_t n; ret = cbor_value_dup_text_string(it, &buf, &n, it); CBOR_CHECK(ret, "parse text string failed", err, ret); puts(buf); free(buf); continue; } case CborTagType: { CborTag tag; ret = cbor_value_get_tag(it, &tag); CBOR_CHECK(ret, "parse tag failed", err, ret); printf("Tag(%lld)\n", (long long)tag); break; } case CborSimpleType: { uint8_t type; ret = cbor_value_get_simple_type(it, &type); CBOR_CHECK(ret, "parse simple type failed", err, ret); printf("simple(%u)\n", type); break; } case CborNullType: puts("null"); break; case CborUndefinedType: puts("undefined"); break; case CborBooleanType: { bool val; ret = cbor_value_get_boolean(it, &val); CBOR_CHECK(ret, "parse boolean type failed", err, ret); puts(val ? "true" : "false"); break; } case CborHalfFloatType: { uint16_t val; ret = cbor_value_get_half_float(it, &val); CBOR_CHECK(ret, "parse half float type failed", err, ret); printf("__f16(%04x)\n", val); break; } case CborFloatType: { float val; ret = cbor_value_get_float(it, &val); CBOR_CHECK(ret, "parse float type failed", err, ret); printf("%g\n", val); break; } case CborDoubleType: { double val; ret = cbor_value_get_double(it, &val); CBOR_CHECK(ret, "parse double float type failed", err, ret); printf("%g\n", val); break; } case CborInvalidType: { ret = CborErrorUnknownType; CBOR_CHECK(ret, "unknown cbor type", err, ret); break; } } ret = cbor_value_advance_fixed(it); CBOR_CHECK(ret, "fix value failed", err, ret); } return CborNoError; err: return ret; } void app_main(void) { CborEncoder root_encoder; CborParser root_parser; CborValue it; uint8_t buf[100]; // Initialize the outermost cbor encoder cbor_encoder_init(&root_encoder, buf, sizeof(buf), 0); // Create an array containing several items CborEncoder array_encoder; CborEncoder map_encoder; cbor_encoder_create_array(&root_encoder, &array_encoder, 5); // [ // 1. Create a map containing several pairs cbor_encoder_create_map(&array_encoder, &map_encoder, 3); // { // chip:esp32 cbor_encode_text_stringz(&map_encoder, "chip"); cbor_encode_text_stringz(&map_encoder, "esp32"); // unicore:false cbor_encode_text_stringz(&map_encoder, "unicore"); cbor_encode_boolean(&map_encoder, false); // ip:[192,168,1,100] cbor_encode_text_stringz(&map_encoder, "ip"); CborEncoder array2; cbor_encoder_create_array(&map_encoder, &array2, 4); // [ // Encode several numbers cbor_encode_uint(&array2, 192); cbor_encode_uint(&array2, 168); cbor_encode_uint(&array2, 1); cbor_encode_uint(&array2, 100); cbor_encoder_close_container(&map_encoder, &array2); // ] cbor_encoder_close_container(&array_encoder, &map_encoder); // } // 2. Encode float number cbor_encode_float(&array_encoder, 3.14); // 3. Encode simple value cbor_encode_simple_value(&array_encoder, 99); // 4. Encode a string cbor_encode_text_stringz(&array_encoder, "2019-07-10 09:00:00+0000"); // 5. Encode a undefined value cbor_encode_undefined(&array_encoder); cbor_encoder_close_container(&root_encoder, &array_encoder); // ] // If error happened when encoding, then this value should be meaningless ESP_LOGI(TAG, "encoded buffer size %d", cbor_encoder_get_buffer_size(&root_encoder, buf)); // Initialize the cbor parser and the value iterator cbor_parser_init(buf, sizeof(buf), 0, &root_parser, &it); ESP_LOGI(TAG, "convert CBOR to JSON"); // Dump the values in JSON format cbor_value_to_json(stdout, &it, 0); puts(""); ESP_LOGI(TAG, "decode CBOR manually"); // Decode CBOR data manually example_dump_cbor_buffer(&it, 0); } ================================================ FILE: cbor/examples/cbor/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File version: "1.0.0" description: CBOR Example dependencies: espressif/cbor: version: '0.*' override_path: '../../../' ================================================ FILE: cbor/examples/cbor/pytest_cbor.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 from __future__ import unicode_literals import textwrap import pytest from pytest_embedded import Dut @pytest.mark.generic def test_examples_cbor(dut: Dut) -> None: dut.expect(r'example: encoded buffer size \d+') dut.expect('example: convert CBOR to JSON') parsed_info = dut.expect(r'\[\{"chip":"(\w+)","unicore":(\w+),"ip":\[(\d+),(\d+),(\d+),(\d+)\]\},' r'3.1400001049041748' r',"simple\(99\)","2019-07-10 09:00:00\+0000","undefined"\]') dut.expect('example: decode CBOR manually') dut.expect(textwrap.dedent(r''' Array\[\s* Map{{\s* chip\s* {}\s* unicore\s* {}\s* ip\s* Array\[\s* {}\s* {}\s* {}\s* {}\s* \]\s* }}\s* 3.14\s* simple\(99\)\s* 2019-07-10 09:00:00\+0000\s* undefined\s* \]'''.format(parsed_info[1].decode(), parsed_info[2].decode(), parsed_info[3].decode(), parsed_info[4].decode(), parsed_info[5].decode(),parsed_info[6].decode())).replace('{', r'\{').replace('}', r'\}')) ================================================ FILE: cbor/examples/cbor/sdkconfig.defaults ================================================ # This example is not supported with newlib nano: # - First 64-bit integers are not supported in newlib nano # - There is also issue with `PRIu8` format specifier with newlib nano (observed on ESP32-C2, see IDFCI-1375) CONFIG_NEWLIB_NANO_FORMAT=n ================================================ FILE: cbor/idf_component.yml ================================================ version: "0.6.1~4" description: "CBOR: Concise Binary Object Representation Library" url: https://github.com/espressif/idf-extra-components/tree/master/cbor dependencies: idf: ">=5.0" ================================================ FILE: cbor/port/include/unreachable_fix.h ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #ifndef CBOR_UNREACHABLE_FIX_H #define CBOR_UNREACHABLE_FIX_H /* This header fixes the unreachable macro redefinition issue between ESP-IDF toolchain and tinycbor library */ /* Force include stddef.h first to get the ESP-IDF definition */ #include /* Now undefine it so tinycbor can define its own version */ #ifdef unreachable #undef unreachable #endif #endif /* CBOR_UNREACHABLE_FIX_H */ ================================================ FILE: ccomp_timer/.build-test-rules.yml ================================================ ccomp_timer/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32s2", "esp32c3"] reason: "Testing on these targets is sufficient (xtensa, risc-v, single/dual core)." ================================================ FILE: ccomp_timer/CHANGELOG.md ================================================ ## 1.0.0 - Move the cache compensated timer from `esp-idf/tools/unit-test-app/components` to component registry. ================================================ FILE: ccomp_timer/CMakeLists.txt ================================================ idf_build_get_property(arch IDF_TARGET_ARCH) set(srcs "ccomp_timer.c") if(CONFIG_IDF_TARGET_ARCH_RISCV) list(APPEND srcs "ccomp_timer_impl_riscv.c") endif() if(CONFIG_IDF_TARGET_ARCH_XTENSA) list(APPEND srcs "ccomp_timer_impl_xtensa.c") endif() if("${arch}" STREQUAL "xtensa") set(priv_requires perfmon driver) else() set(priv_requires driver) endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS include PRIV_INCLUDE_DIRS private_include PRIV_REQUIRES "${priv_requires}") ================================================ FILE: ccomp_timer/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: ccomp_timer/README.md ================================================ # Cache Compensated Timer [![Component Registry](https://components.espressif.com/components/espressif/ccomp_timer/badge.svg)](https://components.espressif.com/components/espressif/ccomp_timer) The **Cache Compensated Timer** is a timer that tries to account for instruction and data stall cycles caused by cache misses. It is useful for measuring the time spent in a function or a block of code. On Xtensa targets (e.g. ESP32), the timer is built on top of the debug module's performance monitor counter. Due to hardware limitations, on RISC-V targets this driver falls back to using the CPU's cycle counter, which actually **doesn't** account for the cache misses. To achieve a measurement that is independent of cache misses you could place the code is to be measured into IRAM. ================================================ FILE: ccomp_timer/ccomp_timer.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "ccomp_timer.h" #include "ccomp_timer_impl.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "esp_log.h" #include "esp_intr_alloc.h" esp_err_t ccomp_timer_start(void) { esp_err_t err = ESP_OK; ccomp_timer_impl_lock(); if (ccomp_timer_impl_is_init()) { if (ccomp_timer_impl_is_active()) { err = ESP_ERR_INVALID_STATE; } } else { err = ccomp_timer_impl_init(); } ccomp_timer_impl_unlock(); if (err != ESP_OK) { goto fail; } err = ccomp_timer_impl_reset(); if (err != ESP_OK) { goto fail; } err = ccomp_timer_impl_start(); if (err == ESP_OK) { return ESP_OK; } fail: return err; } int64_t IRAM_ATTR ccomp_timer_stop(void) { esp_err_t err = ESP_OK; ccomp_timer_impl_lock(); if (!ccomp_timer_impl_is_active()) { err = ESP_ERR_INVALID_STATE; } ccomp_timer_impl_unlock(); if (err != ESP_OK) { goto fail; } err = ccomp_timer_impl_stop(); if (err != ESP_OK) { goto fail; } int64_t t = ccomp_timer_get_time(); err = ccomp_timer_impl_deinit(); if (err == ESP_OK && t != -1) { return t; } fail: return -1; } int64_t IRAM_ATTR ccomp_timer_get_time(void) { return ccomp_timer_impl_get_time(); } ================================================ FILE: ccomp_timer/ccomp_timer_impl_riscv.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "freertos/portmacro.h" #include "esp_freertos_hooks.h" #include "soc/soc_caps.h" #include "esp_rom_sys.h" #include "esp_cpu.h" #include "esp_private/esp_clk.h" typedef enum { PERF_TIMER_UNINIT = 0, // timer has not been initialized yet PERF_TIMER_IDLE, // timer has been initialized but is not tracking elapsed time PERF_TIMER_ACTIVE // timer is tracking elapsed time } ccomp_timer_state_t; typedef struct { uint32_t last_ccount; // last CCOUNT value, updated every os tick ccomp_timer_state_t state; // state of the timer int64_t ccount; // accumulated processors cycles during the time when timer is active } ccomp_timer_status_t; // Each core has its independent timer ccomp_timer_status_t s_status[SOC_CPU_CORES_NUM]; static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED; static void IRAM_ATTR update_ccount(void) { if (s_status[esp_cpu_get_core_id()].state == PERF_TIMER_ACTIVE) { int64_t new_ccount = esp_cpu_get_cycle_count(); if (new_ccount > s_status[esp_cpu_get_core_id()].last_ccount) { s_status[esp_cpu_get_core_id()].ccount += new_ccount - s_status[esp_cpu_get_core_id()].last_ccount; } else { // CCOUNT has wrapped around s_status[esp_cpu_get_core_id()].ccount += new_ccount + (UINT32_MAX - s_status[esp_cpu_get_core_id()].last_ccount); } s_status[esp_cpu_get_core_id()].last_ccount = new_ccount; } } esp_err_t ccomp_timer_impl_init(void) { s_status[esp_cpu_get_core_id()].state = PERF_TIMER_IDLE; return ESP_OK; } esp_err_t ccomp_timer_impl_deinit(void) { s_status[esp_cpu_get_core_id()].state = PERF_TIMER_UNINIT; return ESP_OK; } esp_err_t ccomp_timer_impl_start(void) { s_status[esp_cpu_get_core_id()].state = PERF_TIMER_ACTIVE; s_status[esp_cpu_get_core_id()].last_ccount = esp_cpu_get_cycle_count(); // Update elapsed cycles every OS tick esp_register_freertos_tick_hook_for_cpu(update_ccount, esp_cpu_get_core_id()); return ESP_OK; } esp_err_t IRAM_ATTR ccomp_timer_impl_stop(void) { esp_deregister_freertos_tick_hook_for_cpu(update_ccount, esp_cpu_get_core_id()); update_ccount(); s_status[esp_cpu_get_core_id()].state = PERF_TIMER_IDLE; return ESP_OK; } int64_t IRAM_ATTR ccomp_timer_impl_get_time(void) { update_ccount(); int64_t cycles = s_status[esp_cpu_get_core_id()].ccount; return (cycles * 1000000) / esp_clk_cpu_freq(); } esp_err_t ccomp_timer_impl_reset(void) { s_status[esp_cpu_get_core_id()].ccount = 0; s_status[esp_cpu_get_core_id()].last_ccount = 0; return ESP_OK; } bool ccomp_timer_impl_is_init(void) { return s_status[esp_cpu_get_core_id()].state != PERF_TIMER_UNINIT; } bool IRAM_ATTR ccomp_timer_impl_is_active(void) { return s_status[esp_cpu_get_core_id()].state == PERF_TIMER_ACTIVE; } void IRAM_ATTR ccomp_timer_impl_lock(void) { portENTER_CRITICAL(&s_lock); } void IRAM_ATTR ccomp_timer_impl_unlock(void) { portEXIT_CRITICAL(&s_lock); } ================================================ FILE: ccomp_timer/ccomp_timer_impl_xtensa.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "ccomp_timer_impl.h" #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_attr.h" #include "eri.h" #include "freertos/FreeRTOS.h" #include "esp_freertos_hooks.h" #include "perfmon.h" #include "xtensa/core-macros.h" #include "xtensa/xt_perf_consts.h" #include "xtensa-debug-module.h" #include "esp_private/esp_clk.h" #define D_STALL_COUNTER_ID 0 #define I_STALL_COUNTER_ID 1 typedef enum { PERF_TIMER_UNINIT = 0, // timer has not been initialized yet PERF_TIMER_IDLE, // timer has been initialized but is not tracking elapsed time PERF_TIMER_ACTIVE // timer is tracking elapsed time } ccomp_timer_state_t; typedef struct { int i_ovfl; // number of times instruction stall counter has overflowed int d_ovfl; // number of times data stall counter has overflowed uint32_t last_ccount; // last CCOUNT value, updated every os tick ccomp_timer_state_t state; // state of the timer intr_handle_t intr_handle; // handle to allocated handler for perfmon counter overflows, so that it can be freed during deinit int64_t ccount; // accumulated processors cycles during the time when timer is active } ccomp_timer_status_t; // Each core has its independent timer ccomp_timer_status_t s_status[] = { (ccomp_timer_status_t) { .i_ovfl = 0, .d_ovfl = 0, .ccount = 0, .last_ccount = 0, .state = PERF_TIMER_UNINIT, .intr_handle = NULL, }, (ccomp_timer_status_t) { .i_ovfl = 0, .d_ovfl = 0, .ccount = 0, .last_ccount = 0, .state = PERF_TIMER_UNINIT, .intr_handle = NULL } }; static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED; static void IRAM_ATTR update_ccount(void) { if (s_status[xPortGetCoreID()].state == PERF_TIMER_ACTIVE) { int64_t new_ccount = xthal_get_ccount(); if (new_ccount > s_status[xPortGetCoreID()].last_ccount) { s_status[xPortGetCoreID()].ccount += new_ccount - s_status[xPortGetCoreID()].last_ccount; } else { // CCOUNT has wrapped around s_status[xPortGetCoreID()].ccount += new_ccount + (UINT32_MAX - s_status[xPortGetCoreID()].last_ccount); } s_status[xPortGetCoreID()].last_ccount = new_ccount; } } static void inline update_overflow(int id, int *cnt) { uint32_t pmstat = eri_read(ERI_PERFMON_PMSTAT0 + id * sizeof(int32_t)); if (pmstat & PMSTAT_OVFL) { *cnt += 1; // Clear overflow and PerfMonInt asserted bits. The only valid bits in PMSTAT is the ones we're trying to clear. So it should be // ok to just modify the whole register. eri_write(ERI_PERFMON_PMSTAT0 + id, ~0x0); } } static void IRAM_ATTR perf_counter_overflow_handler(void *args) { update_overflow(D_STALL_COUNTER_ID, &s_status[xPortGetCoreID()].d_ovfl); update_overflow(I_STALL_COUNTER_ID, &s_status[xPortGetCoreID()].i_ovfl); } static void set_perfmon_interrupt(bool enable) { uint32_t d_pmctrl = eri_read(ERI_PERFMON_PMCTRL0 + D_STALL_COUNTER_ID * sizeof(int32_t)); uint32_t i_pmctrl = eri_read(ERI_PERFMON_PMCTRL0 + I_STALL_COUNTER_ID * sizeof(int32_t)); if (enable) { d_pmctrl |= PMCTRL_INTEN; i_pmctrl |= PMCTRL_INTEN; } else { d_pmctrl &= ~PMCTRL_INTEN; i_pmctrl &= ~PMCTRL_INTEN; } eri_write(ERI_PERFMON_PMCTRL0 + D_STALL_COUNTER_ID * sizeof(int32_t), d_pmctrl); eri_write(ERI_PERFMON_PMCTRL0 + I_STALL_COUNTER_ID * sizeof(int32_t), i_pmctrl); } esp_err_t ccomp_timer_impl_init(void) { // Keep track of how many times each counter has overflowed. esp_err_t err = esp_intr_alloc(ETS_INTERNAL_PROFILING_INTR_SOURCE, 0, perf_counter_overflow_handler, NULL, &s_status[xPortGetCoreID()].intr_handle); if (err != ESP_OK) { return err; } xtensa_perfmon_init(D_STALL_COUNTER_ID, XTPERF_CNT_D_STALL, XTPERF_MASK_D_STALL_BUSY, 0, -1); xtensa_perfmon_init(I_STALL_COUNTER_ID, XTPERF_CNT_I_STALL, XTPERF_MASK_I_STALL_BUSY, 0, -1); set_perfmon_interrupt(true); s_status[xPortGetCoreID()].state = PERF_TIMER_IDLE; return ESP_OK; } esp_err_t ccomp_timer_impl_deinit(void) { set_perfmon_interrupt(false); esp_err_t err = esp_intr_free(s_status[xPortGetCoreID()].intr_handle); if (err != ESP_OK) { return err; } s_status[xPortGetCoreID()].intr_handle = NULL; s_status[xPortGetCoreID()].state = PERF_TIMER_UNINIT; return ESP_OK; } esp_err_t ccomp_timer_impl_start(void) { s_status[xPortGetCoreID()].state = PERF_TIMER_ACTIVE; s_status[xPortGetCoreID()].last_ccount = xthal_get_ccount(); // Update elapsed cycles every OS tick esp_register_freertos_tick_hook_for_cpu(update_ccount, xPortGetCoreID()); xtensa_perfmon_start(); return ESP_OK; } esp_err_t IRAM_ATTR ccomp_timer_impl_stop(void) { xtensa_perfmon_stop(); esp_deregister_freertos_tick_hook_for_cpu(update_ccount, xPortGetCoreID()); update_ccount(); s_status[xPortGetCoreID()].state = PERF_TIMER_IDLE; return ESP_OK; } int64_t IRAM_ATTR ccomp_timer_impl_get_time(void) { update_ccount(); int64_t d_stalls = xtensa_perfmon_value(D_STALL_COUNTER_ID) + s_status[xPortGetCoreID()].d_ovfl * (1 << sizeof(int32_t)); int64_t i_stalls = xtensa_perfmon_value(I_STALL_COUNTER_ID) + s_status[xPortGetCoreID()].i_ovfl * (1 << sizeof(int32_t)); int64_t stalls = d_stalls + i_stalls; int64_t cycles = s_status[xPortGetCoreID()].ccount; return ((cycles - stalls) * 1000000) / esp_clk_cpu_freq(); } esp_err_t ccomp_timer_impl_reset(void) { xtensa_perfmon_reset(D_STALL_COUNTER_ID); xtensa_perfmon_reset(I_STALL_COUNTER_ID); s_status[xPortGetCoreID()].d_ovfl = 0; s_status[xPortGetCoreID()].i_ovfl = 0; s_status[xPortGetCoreID()].ccount = 0; s_status[xPortGetCoreID()].last_ccount = 0; return ESP_OK; } bool ccomp_timer_impl_is_init(void) { return s_status[xPortGetCoreID()].state != PERF_TIMER_UNINIT; } bool IRAM_ATTR ccomp_timer_impl_is_active(void) { return s_status[xPortGetCoreID()].state == PERF_TIMER_ACTIVE; } void IRAM_ATTR ccomp_timer_impl_lock(void) { portENTER_CRITICAL(&s_lock); } void IRAM_ATTR ccomp_timer_impl_unlock(void) { portEXIT_CRITICAL(&s_lock); } ================================================ FILE: ccomp_timer/idf_component.yml ================================================ version: "1.0.0~1" description: Cache Compensated Timer url: https://github.com/espressif/idf-extra-components/tree/master/ccomp_timer issues: "https://github.com/espressif/idf-extra-components/issues" dependencies: idf: ">=5.0" ================================================ FILE: ccomp_timer/include/ccomp_timer.h ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Start the timer on the current core. * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_STATE: The timer has already been started previously. * - Others: Fail */ esp_err_t ccomp_timer_start(void); /** * @brief Stop the timer on the current core. * * @note Returns -1 if an error has occurred and stopping the timer failed. * * @return The time elapsed from the last ccomp_timer_start call on the current * core. */ int64_t ccomp_timer_stop(void); /** * Return the current timer value on the current core without stopping the timer. * * @note Returns -1 if an error has occurred and stopping the timer failed. * * @note If called while timer is active i.e. between ccomp_timer_start and ccomp_timer_stop, * this function returns the elapsed time from ccomp_timer_start. Once ccomp_timer_stop * has been called, the timer becomes inactive and stops keeping time. As a result, if this function gets * called after esp_cccomp_timer_stop, this function will return the same value as when the timer was stopped. * * @return The elapsed time from the last ccomp_timer_start call on the current * core. */ int64_t ccomp_timer_get_time(void); #ifdef __cplusplus } #endif ================================================ FILE: ccomp_timer/private_include/ccomp_timer_impl.h ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Initialize the underlying implementation for cache compensated timer. This might involve * setting up architecture-specific event counters and or allocating interrupts that handle events for those counters. * @return * - ESP_OK: Success * - ESP_ERR_INVALID_STATE: The timer has already been started previously. * - Others: Fail */ esp_err_t ccomp_timer_impl_init(void); /** * @brief Deinitialize the underlying implementation for cache compensated timer. This should restore * the state of the program to before ccomp_timer_impl_init. * @return * - ESP_OK: Success * - ESP_ERR_INVALID_STATE: The timer has already been started previously. * - Others: Fail */ esp_err_t ccomp_timer_impl_deinit(void); /** * @brief Make the underlying implementation start keeping time. * * @return * - ESP_OK: Success * - Others: Fail */ esp_err_t ccomp_timer_impl_start(void); /** * @brief Make the underlying implementation stop keeping time. * * @return * - ESP_OK: Success * - Others: Fail */ esp_err_t ccomp_timer_impl_stop(void); /** * @brief Reset the timer to its initial state. * * @return * - ESP_OK: Success * - Others: Fail */ esp_err_t ccomp_timer_impl_reset(void); /** * @brief Get the elapsed time kept track of by the underlying implementation in microseconds. * * @return The elapsed time in microseconds. Set to -1 if the operation is unsuccessful. */ int64_t ccomp_timer_impl_get_time(void); /** * @brief Obtain an internal critical section used in the implementation. Should be treated * as a spinlock. */ void ccomp_timer_impl_lock(void); /** * @brief Start the performance timer on the current core. */ void ccomp_timer_impl_unlock(void); /** * @brief Check if timer has been initialized. * * @return * - true: the timer has been initialized using ccomp_timer_impl_init * - false: the timer has not been initialized, or ccomp_timer_impl_deinit has been called recently */ bool ccomp_timer_impl_is_init(void); /** * @brief Check if timer is keeping time. * * @return * - true: the timer is keeping track of elapsed time from ccomp_timer_impl_start * - false: the timer is not keeping track of elapsed time since ccomp_timer_impl_start has not yet been called or ccomp_timer_impl_stop has been called recently */ bool ccomp_timer_impl_is_active(void); #ifdef __cplusplus } #endif ================================================ FILE: ccomp_timer/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(ccomp_timer_test) ================================================ FILE: ccomp_timer/test_apps/main/CMakeLists.txt ================================================ idf_build_get_property(arch IDF_TARGET_ARCH) set(priv_requires esp_timer unity) if("${arch}" STREQUAL "xtensa") list(APPEND priv_requires perfmon) endif() idf_component_register(SRCS "ccomp_timer_test.c" "ccomp_timer_test_api.c" "ccomp_timer_test_data.c" "ccomp_timer_test_inst.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES ${priv_requires} WHOLE_ARCHIVE) ================================================ FILE: ccomp_timer/test_apps/main/ccomp_timer_test.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(50); } void app_main(void) { printf("Running ccomp_timer component tests\n"); unity_run_menu(); } ================================================ FILE: ccomp_timer/test_apps/main/ccomp_timer_test_api.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include "esp_timer.h" #include "esp_log.h" #include "esp_attr.h" #include "ccomp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #ifndef CONFIG_FREERTOS_UNICORE #include "esp_ipc.h" #endif #include "unity.h" #ifndef CONFIG_FREERTOS_UNICORE static void start_timer(void *param) { esp_err_t *err = (esp_err_t *)param; *err = ccomp_timer_start(); } static void stop_timer(void *param) { int64_t *t = (int64_t *)param; *t = ccomp_timer_stop(); } #endif static void computation(void *param) { int *l = (int *)param; for (volatile int i = 0, a = 0; i < *l; i++) { a += i; } } TEST_CASE("starting and stopping works", "[ccomp_timer]") { esp_err_t err; int64_t t; /* * Test on the same task */ err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_OK, err); // Start an already started timer err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, err); t = ccomp_timer_stop(); TEST_ASSERT_GREATER_OR_EQUAL(0, t); // Stopping a non started timer t = ccomp_timer_stop(); TEST_ASSERT_EQUAL(-1, t); #ifndef CONFIG_FREERTOS_UNICORE /* * Test on different task on same core */ err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_OK, err); esp_ipc_call_blocking(xPortGetCoreID(), start_timer, &err); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, err); t = ccomp_timer_stop(); TEST_ASSERT_GREATER_OR_EQUAL(0, t); esp_ipc_call_blocking(xPortGetCoreID(), stop_timer, &t); TEST_ASSERT_EQUAL(-1, t); /* * Timer being stopped from another task on the same core */ err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_OK, err); esp_ipc_call_blocking(xPortGetCoreID(), stop_timer, &t); TEST_ASSERT_GREATER_OR_EQUAL(0, t); /* * Test on different task on same core */ err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_OK, err); esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, start_timer, &err); TEST_ASSERT_EQUAL(ESP_OK, err); t = ccomp_timer_stop(); TEST_ASSERT_GREATER_OR_EQUAL(0, t); esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, stop_timer, &t); TEST_ASSERT_GREATER_OR_EQUAL(0, t); #endif } TEST_CASE("getting the time works", "[ccomp_timer]") { // Get wall time and start ccomp timer int64_t start = esp_timer_get_time(); ccomp_timer_start(); int64_t t_a = ccomp_timer_get_time(); int temp = 10000; computation(&temp); int64_t t_b = ccomp_timer_get_time(); // Check that ccomp time after computation is more than // ccomp time before computation. TEST_ASSERT_LESS_THAN(t_b, t_a); // Get time diff between wall time and ccomp time int64_t t_1 = ccomp_timer_stop(); int64_t t_2 = esp_timer_get_time() - start; // The times should at least be in the same ballpark (at least within 10%) float diff = (llabs(t_1 - t_2)) / ((float)t_2); TEST_ASSERT(diff <= 10.0f); // Since the timer was already stopped, test that ccomp_timer_get_time // returns the same time as ccomp_timer_stop int64_t t_c = ccomp_timer_get_time(); TEST_ASSERT_EQUAL(t_1, t_c); } #ifndef CONFIG_FREERTOS_UNICORE TEST_CASE("timers for each core counts independently", "[ccomp_timer]") { esp_err_t err; // Start a timer on this core err = ccomp_timer_start(); TEST_ASSERT_EQUAL(ESP_OK, err); // Do some work on this core int temp = 10000; computation(&temp); // Start a timer on the other core esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, start_timer, &err); TEST_ASSERT_EQUAL(ESP_OK, err); // Do some work on other core (less work than this core did) temp = 5000; esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, computation, &temp); // Stop timers from both cores int64_t t_1 = ccomp_timer_stop(); TEST_ASSERT_GREATER_OR_EQUAL(0, t_1); int64_t t_2; esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, stop_timer, &t_2); TEST_ASSERT_GREATER_OR_EQUAL(0, t_2); // Since this core did more work, it probably has longer measured time TEST_ASSERT_GREATER_THAN(t_2, t_1); } #endif ================================================ FILE: ccomp_timer/test_apps/main/ccomp_timer_test_data.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include "esp_timer.h" #include "esp_log.h" #include "esp_attr.h" #include "ccomp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_private/esp_clk.h" #include "unity.h" #include "sdkconfig.h" #if CONFIG_IDF_TARGET_ESP32 #define CACHE_WAYS 2 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 15) #define TEST_SIZE (CACHE_SIZE / 8) #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 // Default cache configuration - no override specified on #define CACHE_WAYS 8 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 13) #define TEST_SIZE (CACHE_SIZE) #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 #define CACHE_WAYS 8 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 14) #define TEST_SIZE (CACHE_SIZE) #endif typedef struct { uint8_t **accesses; size_t len; } ccomp_test_access_t; typedef struct { int64_t wall; int64_t ccomp; } ccomp_test_time_t; /* No performance monitor in RISCV for now */ #if !__riscv //IDF-5052 static const char *TAG = "test_ccomp_timer"; #if CONFIG_SPIRAM static uint8_t *flash_mem; #else static const uint8_t flash_mem[2 * CACHE_SIZE] = {0}; #endif static IRAM_ATTR void perform_accesses(ccomp_test_access_t *access) { volatile int a = 0; for (int i = 0; i < access->len; i++) { a += (int)(*(access->accesses[i])); } } static void prepare_cache(const uint8_t *to_cache) { volatile int a = 0; for (int i = 0; i < CACHE_SIZE; i++) { a += to_cache[i]; } } static void prepare_access_pattern(int hit_rate, const uint8_t *cached, ccomp_test_access_t *out) { assert(hit_rate <= 100); assert(hit_rate >= 0); int misses = (100 - hit_rate) * CACHE_LINE_SIZE; int hits = hit_rate * CACHE_LINE_SIZE; uint8_t **accesses = calloc(TEST_SIZE, sizeof(uint8_t *)); TEST_ASSERT_NOT_NULL(accesses); for (int i = 0, h = 0, i_h = 1, m = -1, i_m = 0; i < TEST_SIZE; i++, h += i_h, m += i_m) { if (i_m) { accesses[i] = (uint8_t *) (cached + CACHE_SIZE + i); } else { accesses[i] = (uint8_t *) (cached + i); } if (h >= hits) { h = -1; i_h = 0; m = 0; i_m = 1; } if (m >= misses) { m = -1; i_m = 0; h = 0; i_h = 1; } } out->accesses = accesses; out->len = TEST_SIZE; } static ccomp_test_time_t perform_test_at_hit_rate(int hit_rate, const uint8_t *mem) { ccomp_test_access_t access; prepare_access_pattern(hit_rate, mem, &access); prepare_cache(mem); int64_t start = esp_timer_get_time(); ccomp_timer_start(); perform_accesses(&access); ccomp_test_time_t t = { .ccomp = ccomp_timer_stop(), .wall = esp_timer_get_time() - start }; free(access.accesses); return t; } static ccomp_test_time_t ccomp_test_ref_time(void) { #if CONFIG_SPIRAM uint8_t *mem = heap_caps_malloc(2 * CACHE_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT); #else uint8_t *mem = heap_caps_malloc(sizeof(flash_mem), MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT); #endif TEST_ASSERT_NOT_NULL(mem); ccomp_test_time_t t = perform_test_at_hit_rate(0, mem); free(mem); return t; } TEST_CASE("data cache hit rate sweep", "[ccomp_timer]") { ccomp_test_time_t t_ref; ccomp_test_time_t t_hr; #if CONFIG_SPIRAM flash_mem = heap_caps_malloc(2 * CACHE_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); #endif // Perform accesses on RAM. The time recorded here serves as // reference. t_ref = ccomp_test_ref_time(); ESP_LOGI(TAG, "Reference Time(us): %lld", (long long)t_ref.ccomp); // Measure time at particular hit rates for (int i = 0; i <= 100; i += 5) { t_hr = perform_test_at_hit_rate(i, flash_mem); float error = (llabs(t_ref.ccomp - t_hr.ccomp) / (float)t_ref.ccomp) * 100.0f; ESP_LOGI(TAG, "Hit Rate(%%): %d Wall Time(us): %lld Compensated Time(us): %lld Error(%%): %f", i, (long long)t_hr.wall, (long long)t_hr.ccomp, error); // Check if the measured time is at least within some percent of the // reference. TEST_ASSERT(error <= 10.0f); } #if CONFIG_SPIRAM free(flash_mem); #endif } #endif // __riscv ================================================ FILE: ccomp_timer/test_apps/main/ccomp_timer_test_inst.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include "esp_timer.h" #include "esp_log.h" #include "esp_attr.h" #include "ccomp_timer.h" #include "freertos/FreeRTOS.h" #include "unity.h" #include "sdkconfig.h" #if CONFIG_IDF_TARGET_ESP32 #define CACHE_WAYS 2 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 15) // Only test half due to lack of memory #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 // Default cache configuration - no override specified on #define CACHE_WAYS 8 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 13) #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 #define CACHE_WAYS 8 #define CACHE_LINE_SIZE 32 #define CACHE_SIZE (1 << 14) #endif typedef void (*ccomp_test_func_t)(void); static const char *TAG = "test_ccomp_timer"; typedef struct { int64_t wall; int64_t ccomp; } ccomp_test_time_t; typedef struct { ccomp_test_func_t *funcs; size_t len; } ccomp_test_call_t; #define FUNC() \ do \ { \ volatile int a = 0; \ a++; \ } while (0); __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func1(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func2(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func3(void) { FUNC(); } #if TEMPORARY_DISABLED_FOR_TARGETS(ESP32) __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func4(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func5(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func6(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func7(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func8(void) { FUNC(); } __aligned(CACHE_SIZE / CACHE_WAYS) static void test_func9(void) { FUNC(); } #endif static void IRAM_ATTR iram_func(void) { FUNC(); } static void IRAM_ATTR perform_calls(ccomp_test_call_t *call) { for (int i = 0; i < call->len; i++) { call->funcs[i](); } } static void IRAM_ATTR prepare_cache(ccomp_test_call_t *call) { perform_calls(call); } static void IRAM_ATTR prepare_calls(int hit_rate, ccomp_test_func_t *alts, size_t alts_len, size_t len, ccomp_test_call_t *out) { assert(hit_rate <= 100); assert(hit_rate >= 0); int misses = (100 - hit_rate); int hits = hit_rate; ccomp_test_func_t *funcs = calloc(len, sizeof(ccomp_test_func_t)); for (int i = 0, h = 0, i_h = 1, m = -1, i_m = 0, l = 0; i < len; i++, h += i_h, m += i_m) { funcs[i] = alts[l % alts_len]; if (i_m) { l++; } if (h >= hits) { h = -1; i_h = 0; m = 0; i_m = 1; } if (m >= misses) { m = -1; i_m = 0; h = 0; i_h = 1; } } out->funcs = funcs; out->len = len; } static ccomp_test_time_t IRAM_ATTR perform_test_at_hit_rate(int hit_rate) { static portMUX_TYPE m = portMUX_INITIALIZER_UNLOCKED; ccomp_test_call_t calls; ccomp_test_func_t alts[] = {test_func1, test_func2, test_func3, #if TEMPORARY_DISABLED_FOR_TARGETS(ESP32) test_func4, test_func5, test_func6, test_func7, test_func8, test_func9, #endif }; prepare_calls(hit_rate, alts, sizeof(alts) / sizeof(alts[0]), 10000, &calls); ccomp_test_func_t f[] = {test_func1, test_func2}; ccomp_test_call_t cache = { .funcs = f, .len = sizeof(f) / sizeof(f[0]) }; portENTER_CRITICAL(&m); prepare_cache(&cache); int64_t start = esp_timer_get_time(); ccomp_timer_start(); perform_calls(&calls); ccomp_test_time_t t = { .ccomp = ccomp_timer_stop(), .wall = esp_timer_get_time() - start }; portEXIT_CRITICAL(&m); free(calls.funcs); return t; } static ccomp_test_time_t ccomp_test_ref_time(void) { ccomp_test_call_t calls; ccomp_test_func_t alts[] = {iram_func}; prepare_calls(0, alts, 1, 10000, &calls); int64_t start = esp_timer_get_time(); ccomp_timer_start(); perform_calls(&calls); ccomp_test_time_t t = { .ccomp = ccomp_timer_stop(), .wall = esp_timer_get_time() - start }; free(calls.funcs); return t; } TEST_CASE("instruction cache hit rate sweep test", "[ccomp_timer]") { ccomp_test_time_t t_ref; ccomp_test_time_t t_hr; // Perform accesses on RAM. The time recorded here serves as // reference. t_ref = ccomp_test_ref_time(); ESP_LOGI(TAG, "Reference Time(us): %lld", (long long)t_ref.ccomp); // Measure time at particular hit rates for (int i = 0; i <= 100; i += 5) { t_hr = perform_test_at_hit_rate(i); float error = (llabs(t_ref.ccomp - t_hr.ccomp) / (float)t_ref.wall) * 100.0f; ESP_LOGI(TAG, "Hit Rate(%%): %d Wall Time(us): %lld Compensated Time(us): %lld Error(%%): %f", i, (long long)t_hr.wall, (long long)t_hr.ccomp, error); // Check if the measured time is at least within some percent of the // reference. TEST_ASSERT(error <= 5.0f); } } ================================================ FILE: ccomp_timer/test_apps/main/idf_component.yml ================================================ dependencies: espressif/ccomp_timer: version: "*" override_path: ../../ ================================================ FILE: ccomp_timer/test_apps/pytest_ccomp_timer.py ================================================ import pytest @pytest.mark.generic def test_ccomp_timer(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: ccomp_timer/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: cjson/CMakeLists.txt ================================================ idf_component_register(SRCS "cJSON/cJSON.c" "cJSON/cJSON_Utils.c" INCLUDE_DIRS "cJSON") target_compile_definitions(${COMPONENT_LIB} PUBLIC CJSON_NESTING_LIMIT=${CONFIG_CJSON_NESTING_LIMIT} CJSON_CIRCULAR_LIMIT=${CONFIG_CJSON_CIRCULAR_LIMIT}) ================================================ FILE: cjson/Kconfig ================================================ menu "cJSON" config CJSON_NESTING_LIMIT int "Maximum nesting depth for JSON parsing" default 1000 help Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. This is to prevent stack overflows. Lower values use less stack, higher values allow deeper nesting. config CJSON_CIRCULAR_LIMIT int "Maximum depth for circular reference detection" default 10000 help Limits the depth for circular reference detection during printing/comparison operations. This prevents infinite loops and stack overflows when processing JSON structures with circular references. Lower values use less stack. endmenu ================================================ FILE: cjson/idf_component.yml ================================================ version: "1.7.19~2" description: "cJSON: Ultralightweight JSON parser in ANSI C" url: https://github.com/espressif/idf-extra-components/tree/master/cjson dependencies: idf: ">=5.0" sbom: manifests: - path: sbom_cJSON.yml dest: cJSON ================================================ FILE: cjson/sbom_cJSON.yml ================================================ name: cJSON version: "1.7.19" cpe: - cpe:2.3:a:cjson_project:cjson:{}:*:*:*:*:*:*:* - cpe:2.3:a:davegamble:cjson:{}:*:*:*:*:*:*:* supplier: 'Person: Dave Gamble' description: "cJSON: Ultralightweight JSON parser in ANSI C" url: https://github.com/DaveGamble/cJSON hash: b2890c8d76bbb64e710585ebc0a917196b9c67e7 ================================================ FILE: coap/CMakeLists.txt ================================================ set(include_dirs port/include libcoap/include) set(srcs "libcoap/src/coap_address.c" "libcoap/src/coap_asn1.c" "libcoap/src/coap_async.c" "libcoap/src/coap_block.c" "libcoap/src/coap_cache.c" "libcoap/src/coap_debug.c" "libcoap/src/coap_dtls.c" "libcoap/src/coap_encode.c" "libcoap/src/coap_event.c" "libcoap/src/coap_hashkey.c" "libcoap/src/coap_io.c" "libcoap/src/coap_layers.c" "libcoap/src/coap_mbedtls.c" "libcoap/src/coap_mem.c" "libcoap/src/coap_net.c" "libcoap/src/coap_netif.c" "libcoap/src/coap_notls.c" "libcoap/src/coap_option.c" "libcoap/src/coap_oscore.c" "libcoap/src/coap_pdu.c" "libcoap/src/coap_prng.c" "libcoap/src/coap_proxy.c" "libcoap/src/coap_resource.c" "libcoap/src/coap_session.c" "libcoap/src/coap_str.c" "libcoap/src/coap_subscribe.c" "libcoap/src/coap_tcp.c" "libcoap/src/coap_time.c" "libcoap/src/coap_threadsafe.c" "libcoap/src/coap_uri.c" "libcoap/src/coap_ws.c") if(CONFIG_COAP_OSCORE_SUPPORT) list(APPEND srcs "libcoap/src/oscore/oscore.c" "libcoap/src/oscore/oscore_cbor.c" "libcoap/src/oscore/oscore_context.c" "libcoap/src/oscore/oscore_cose.c" "libcoap/src/oscore/oscore_crypto.c") endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" REQUIRES lwip mbedtls) target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") ================================================ FILE: coap/Kconfig ================================================ menu "CoAP Configuration" config COAP_MBEDTLS_PSK bool "Pre-Shared Keys" default y help If the CoAP information is to be encrypted, the encryption environment can be set up using Pre-Shared keys (PSK) mode. - Encrypt using defined Pre-Shared Keys (PSK if uri includes coaps://) - Note: If PKI is set as well, a server will decide which method to use based on the incoming encryption request. It is up to the client logic to decide which one to use. config COAP_MBEDTLS_PKI bool "PKI Certificates" default n help If the CoAP information is to be encrypted, the encryption environment can be set up using Public Key Infrastructure (PKI) mode. - Encrypt using defined Public Key Infrastructure (PKI if uri includes coaps://) - Note: If PSK is set as well, a server will decide which method to use based on the incoming encryption request. It is up to the client logic to decide which one to use. config COAP_DEBUGGING bool "Enable CoAP debugging" default n help Enable CoAP debugging functions at compile time for the example code. If this option is enabled, call coap_set_log_level() at runtime in order to enable CoAP debug output via the ESP log mechanism. Note: The Mbed TLS library logging is controlled by the mbedTLS configuration, but logging level mbedTLS must be set for CoAP to log it. choice COAP_DEBUGGING_LEVEL bool "Set CoAP debugging level" depends on COAP_DEBUGGING default COAP_LOG_WARNING help Set CoAP debugging level config COAP_LOG_EMERG bool "Emergency" config COAP_LOG_ALERT bool "Alert" config COAP_LOG_CRIT bool "Critical" config COAP_LOG_ERROR bool "Error" config COAP_LOG_WARNING bool "Warning" config COAP_LOG_NOTICE bool "Notice" config COAP_LOG_INFO bool "Info" config COAP_LOG_DEBUG bool "Debug" config COAP_LOG_OSCORE bool "OSCORE" endchoice config COAP_LOG_DEFAULT_LEVEL int default 0 if !COAP_DEBUGGING default 0 if COAP_LOG_EMERG default 1 if COAP_LOG_ALERT default 2 if COAP_LOG_CRIT default 3 if COAP_LOG_ERROR default 4 if COAP_LOG_WARNING default 5 if COAP_LOG_NOTICE default 6 if COAP_LOG_INFO default 7 if COAP_LOG_DEBUG default 8 if COAP_LOG_OSCORE config COAP_TCP_SUPPORT bool "Enable TCP within CoAP" default y help Enable TCP functionality for CoAP. This is required if TLS sessions are to be used. If this option is disabled, redundant CoAP TCP code is removed. config COAP_OSCORE_SUPPORT bool "Enable OSCORE support within CoAP" default n help Enable OSCORE (Object Security for Constrained RESTful Environments) functionality for CoAP. If this option is disabled, redundant CoAP OSCORE code is removed. config COAP_OBSERVE_PERSIST bool "Enable Server Observe Persist support within CoAP" default n help Enable Server Observe Persist support for CoAP. If this option is disabled, redundant CoAP Observe Persist code is removed. config COAP_Q_BLOCK bool "Enable Q-Block (RFC9177) support in CoAP" default n help Enable Q-Block (RFC9177) fast block transfers using NON (instead of CON) support for CoAP config COAP_ASYNC_SUPPORT bool "Enable asynchronous separate response support in CoAP" default y help Enable support for asynchronous separate responses in CoAP config COAP_THREAD_SAFE bool "Enable thread-safe support within CoAP" default n help Enable thread-safe support for CoAP. If this option is disabled, CoAP is not multi-threaded safe config COAP_THREAD_RECURSIVE_CHECK bool "Enable thread-safe recursion checking support within CoAP" depends on COAP_THREAD_SAFE default n help Enable thread-safe recursion checking support for CoAP. If this option is disabled, recursive locking occur in CoAP config COAP_WEBSOCKETS bool "Enable WebSockets support within CoAP" default n help Enable WebSockets support for CoAP. If this option is disabled, redundant CoAP WebSocket code is removed. config COAP_CLIENT_SUPPORT bool "Enable Client functionality within CoAP" default n help Enable client functionality (ability to make requests and receive responses) for CoAP. If the server is going to act as a proxy, then this needs to be enabled to support the ongoing session going to the next hop. If this option is disabled, redundant CoAP client only code is removed. If both this option and COAP_SERVER_SUPPORT are disabled, then both are automatically enabled for backwards compatibility. config COAP_SERVER_SUPPORT bool "Enable Server functionality within CoAP" default n help Enable server functionality (ability to receive requests and send responses) for CoAP. If this option is disabled, redundant CoAP server only code is removed. If both this option and COAP_CLIENT_SUPPORT are disabled, then both are automatically enabled for backwards compatibility. config COAP_PROXY_SUPPORT bool "Enable Proxy functionality within CoAP" depends on COAP_CLIENT_SUPPORT && COAP_SERVER_SUPPORT default n help Enable Proxy functionality (ability to receive requests, pass them to an upstream server and send back responses to client) for CoAP. If this option is disabled, redundant CoAP proxy only code is removed. endmenu ================================================ FILE: coap/examples/coap_client/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main esp_eth) project(coap_client) ================================================ FILE: coap/examples/coap_client/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | # CoAP client example (See the README.md file in the upper level esp-idf 'examples' directory for more information about examples.) This CoAP client example is very simplified adaptation of one of the [libcoap](https://github.com/obgm/libcoap) examples. CoAP client example will connect your ESP32 device to a CoAP server, send off a GET request and fetch the response data from CoAP server. The client can be extended to PUT / POST / DELETE requests, as well as supporting the Observer extensions [RFC7641](https://tools.ietf.org/html/rfc7641). If the URI is prefixed with coaps:// instead of coap://, then the CoAP client will attempt to use the DTLS protocol using the defined Pre-Shared Keys(PSK) or Public Key Infrastructure (PKI) which the CoAP server needs to know about. If both PSK and PKI are defined, then it is the responsibility of the client code to decide (PSK or PKI) which to use. If the URI is prefixed with coap+tcp://, then the CoAP will try to use TCP for the communication. If the URI is prefixed with coaps+tcp://, then the CoAP will try to use TLS for the communication. If both PSK and PKI are defined, then it is the responsibility of the client code to decide (PSK or PKI) which to use. If the URI is prefixed with coap+ws://, then the CoAP will try to use WebSockets (over TCP) for the communication, which assumes that CoAP WebSockets is enabled in the build. If the URI is prefixed with coaps+ws://, then the CoAP will try to use WebSockets (over TLS) for the communication, which assumes that CoAP WebSockets is enabled in the build. If both PSK and PKI are defined, then it is the responsibility of the client code to decide (PSK or PKI) which to use. The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with constrained nodes and constrained networks in the Internet of Things. The protocol is designed for machine-to-machine (M2M) applications such as smart energy and building automation. Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details. ## How to use example ### Configure the project ``` idf.py menuconfig ``` Example Connection Configuration ---> * Set WiFi SSID * Set WiFi Password Component config ---> CoAP Configuration ---> * Set encryption method definition, PSK (default) and/or PKI * Enable CoAP debugging if required * Disable CoAP using TCP if this is not required (TCP needed for TLS or WebSockets) * Disable CoAP server functionality to reduce code size * Enable OSCORE (RFC8613) support if required * Enable WebSockets (RFC8323) support if required Example CoAP Client Configuration ---> * Set CoAP Target Uri * If PSK, Set CoAP Preshared Key to use in connection to the server * If PSK, Set CoAP PSK Client identity (username) Note: * For enabled PKI, the certificates are stored in main/certs. * For enabled OSCORE, the OSCORE configuration is stored in main/oscore and will be used on every request. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py build idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output Prerequisite: we startup a CoAP server on coap server example, or use the default of coap://californium.eclipseprojects.io. and you could receive data from CoAP server if succeed, such as the following log: ``` ... I (332) wifi: mode : sta (30:ae:a4:04:1b:7c) I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1 I (1672) wifi: state: init -> auth (b0) I (1682) wifi: state: auth -> assoc (0) I (1692) wifi: state: assoc -> run (10) I (1692) wifi: connected with huawei_cw, channel 11 I (1692) wifi: pm start, type: 1 I (2582) event: sta ip: 192.168.3.89, mask: 255.255.255.0, gw: 192.168.3.1 I (2582) CoAP_client: Connected to AP I (2582) CoAP_client: DNS lookup succeeded. IP=35.185.40.182 Received: **************************************************************** CoAP RFC 7252 Cf 3.0.0-SNAPSHOT **************************************************************** This server is using the Eclipse Californium (Cf) CoAP framework published under EPL+EDL: http://www.eclipse.org/californium/ (c) 2014-2020 Institute for Pervasive Computing, ETH Zurich and others **************************************************************** ... ``` ## libcoap Documentation This can be found at [libcoap Documentation](https://libcoap.net/documentation.html). The current API is 4.3.4. ## libcoap Specific Issues These can be raised at [libcoap Issues](https://github.com/obgm/libcoap/issues). ## Troubleshooting * Please make sure Target Url includes valid `host`, optional `port`, optional `path`, and begins with `coap://`, `coaps://`, `coap+tcp://`, `coaps+tcp://`, `coap+ws://` or `coaps+ws://`. * Not all hosts support TCP/TLS including coap+tcp://californium.eclipseprojects.io * Not all hosts support WebSockets, which needs to be enabled as an option * CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration -> Enable CoAP debugging' and setting appropriate log level. If Mbed TLS logging is required, this needs to be configured separately under mbedTLS Component Configuration. ================================================ FILE: coap/examples/coap_client/main/CMakeLists.txt ================================================ # Embed CA, certificate & key directly into binary idf_component_register(SRCS "coap_client_example_main.c" INCLUDE_DIRS "." EMBED_TXTFILES certs/coap_ca.pem certs/coap_client.crt certs/coap_client.key oscore/coap_oscore.conf) ================================================ FILE: coap/examples/coap_client/main/Kconfig.projbuild ================================================ menu "Example CoAP Client Configuration" config EXAMPLE_TARGET_DOMAIN_URI string "Target Uri" default "coaps://californium.eclipseprojects.io" help Target uri for the example to use. Use coaps:// prefix for encrypted traffic using Pre-Shared Key (PSK) or Public Key Infrastructure (PKI). config EXAMPLE_COAP_PSK_KEY string "Preshared Key (PSK) to used in the connection to the CoAP server" depends on COAP_MBEDTLS_PSK default "sesame" help The Preshared Key to use to encrypt the communicatons. The same key must be used at both ends of the CoAP connection, and the CoaP client must request an URI prefixed with coaps:// instead of coap:// for DTLS to be used. config EXAMPLE_COAP_PSK_IDENTITY string "PSK Client identity (username)" depends on COAP_MBEDTLS_PSK default "password" help The identity (or username) to use to identify to the CoAP server which PSK key to use. endmenu ================================================ FILE: coap/examples/coap_client/main/certs/coap_ca.pem ================================================ -----BEGIN CERTIFICATE----- MIICDDCCAbKgAwIBAgIIPKO8L7vZoqAwCgYIKoZIzj0EAwIwXDEQMA4GA1UEAxMH Y2Ytcm9vdDEUMBIGA1UECxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2Ug SW9UMQ8wDQYDVQQHEwZPdHRhd2ExCzAJBgNVBAYTAkNBMB4XDTIzMTAyNjA4MDgx NVoXDTI1MTAyNTA4MDgxNVowWjEOMAwGA1UEAxMFY2YtY2ExFDASBgNVBAsTC0Nh bGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElvVDEPMA0GA1UEBxMGT3R0YXdh MQswCQYDVQQGEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLCbJjxIS4hI AnRFTlx23gkd4zyFd50zdpTnoUPz19oQ1o1youavC5Go9vrYoWxyx+zpph8T4brB C/mZGIgPVMOjYDBeMB0GA1UdDgQWBBSxVzoI1TL87++hsUb9vQwqODzgUTALBgNV HQ8EBAMCAQYwDwYDVR0TBAgwBgEB/wIBATAfBgNVHSMEGDAWgBTqNhC1fqOTsHRn IVZ9OabfWsxpcTAKBggqhkjOPQQDAgNIADBFAiBSEn3egc31JhhHTVYi5uhl0t4d ewujkEmwzBuruzf/xAIhAK/fXy2tsNoyLitFQ97x6LYV25jKmLKUlhL2mC/PwQdO -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICDDCCAbKgAwIBAgIIPKO8L7vZoqAwCgYIKoZIzj0EAwIwXDEQMA4GA1UEAxMH Y2Ytcm9vdDEUMBIGA1UECxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2Ug SW9UMQ8wDQYDVQQHEwZPdHRhd2ExCzAJBgNVBAYTAkNBMB4XDTIzMTAyNjA4MDgx NVoXDTI1MTAyNTA4MDgxNVowWjEOMAwGA1UEAxMFY2YtY2ExFDASBgNVBAsTC0Nh bGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElvVDEPMA0GA1UEBxMGT3R0YXdh MQswCQYDVQQGEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLCbJjxIS4hI AnRFTlx23gkd4zyFd50zdpTnoUPz19oQ1o1youavC5Go9vrYoWxyx+zpph8T4brB C/mZGIgPVMOjYDBeMB0GA1UdDgQWBBSxVzoI1TL87++hsUb9vQwqODzgUTALBgNV HQ8EBAMCAQYwDwYDVR0TBAgwBgEB/wIBATAfBgNVHSMEGDAWgBTqNhC1fqOTsHRn IVZ9OabfWsxpcTAKBggqhkjOPQQDAgNIADBFAiBSEn3egc31JhhHTVYi5uhl0t4d ewujkEmwzBuruzf/xAIhAK/fXy2tsNoyLitFQ97x6LYV25jKmLKUlhL2mC/PwQdO -----END CERTIFICATE----- ================================================ FILE: coap/examples/coap_client/main/certs/coap_client.crt ================================================ -----BEGIN CERTIFICATE----- MIIB/TCCAaOgAwIBAgIIVNrVgKT9OE8wCgYIKoZIzj0EAwIwWjEOMAwGA1UEAxMF Y2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElv VDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMzEwMjYwODA4MjJa Fw0yNTEwMjUwODA4MjJaMF4xEjAQBgNVBAMTCWNmLWNsaWVudDEUMBIGA1UECxML Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQxYO5/M5 ie6+3QPOaAy5MD6CkFILZwIb2rOBCX/EWPaocX1H+eynUnaEEbmqxeN6rnI/pH19 j4PtsegfHLrzzaNPME0wHQYDVR0OBBYEFKwEDLTJ+5cQoZfbjWN1vJ2ssgK+MAsG A1UdDwQEAwIHgDAfBgNVHSMEGDAWgBSxVzoI1TL87++hsUb9vQwqODzgUTAKBggq hkjOPQQDAgNIADBFAiA2KCOw3n2AK9Vm8u2u1bQREIEs3tKAU7eFjpNFn929NwIh AInhBGoEwS2Xlu5bdZSfWnujoRrEQiIiQpStmLxVcIsH -----END CERTIFICATE----- ================================================ FILE: coap/examples/coap_client/main/certs/coap_client.key ================================================ -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg59PuAri3l+8cAbNL bFFBAwd/h0cESnSD3iZSGyrr7xGhRANCAARDFg7n8zmJ7r7dA85oDLkwPoKQUgtn Ahvas4EJf8RY9qhxfUf57KdSdoQRuarF43qucj+kfX2Pg+2x6B8cuvPN -----END PRIVATE KEY----- ================================================ FILE: coap/examples/coap_client/main/coap_client_example_main.c ================================================ /* CoAP client Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ /* * WARNING * libcoap is not multi-thread safe, so only this thread must make any coap_*() * calls. Any external (to this thread) data transmitted in/out via libcoap * therefore has to be passed in/out by xQueue*() via this thread. */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_log.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "protocol_examples_common.h" #include "coap_config.h" #include "coap3/coap.h" #ifndef CONFIG_COAP_CLIENT_SUPPORT #error COAP_CLIENT_SUPPORT needs to be enabled #endif /* COAP_CLIENT_SUPPORT */ #define COAP_DEFAULT_TIME_SEC 60 /* The examples use simple Pre-Shared-Key configuration that you can set via 'idf.py menuconfig'. If you'd rather not, just change the below entries to strings with the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key" Note: PSK will only be used if the URI is prefixed with coaps:// instead of coap:// and the PSK must be one that the server supports (potentially associated with the IDENTITY) */ #define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY #define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY /* The examples use uri Logging Level that you can set via 'idf.py menuconfig'. If you'd rather not, just change the below entry to a value that is between 0 and 7 with the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7 Caution: Logging is enabled in libcoap only up to level as defined by 'idf.py menuconfig' to reduce code size. */ #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL /* The examples use uri "coap://californium.eclipseprojects.io" that you can set via the project configuration (idf.py menuconfig) If you'd rather not, just change the below entries to strings with the config you want - ie #define COAP_DEFAULT_DEMO_URI "coaps://californium.eclipseprojects.io" */ #define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI const static char *TAG = "CoAP_client"; static int resp_wait = 1; static coap_optlist_t *optlist = NULL; static int wait_ms; #ifdef CONFIG_COAP_OSCORE_SUPPORT coap_oscore_conf_t *oscore_conf = NULL; #endif /* CONFIG_COAP_OSCORE_SUPPORT */ #ifdef CONFIG_COAP_MBEDTLS_PKI /* CA cert, taken from coap_ca.pem Client cert, taken from coap_client.crt Client key, taken from coap_client.key The PEM, CRT and KEY file are examples taken from https://github.com/eclipse/californium/tree/master/demo-certs/src/main/resources as the Certificate test (by default) is against the californium server. To embed it in the app binary, the PEM, CRT and KEY file is named in the CMakeLists.txt EMBED_TXTFILES definition. */ extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start"); extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end"); extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start"); extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end"); extern uint8_t client_key_start[] asm("_binary_coap_client_key_start"); extern uint8_t client_key_end[] asm("_binary_coap_client_key_end"); #endif /* CONFIG_COAP_MBEDTLS_PKI */ #ifdef CONFIG_COAP_OSCORE_SUPPORT extern uint8_t oscore_conf_start[] asm("_binary_coap_oscore_conf_start"); extern uint8_t oscore_conf_end[] asm("_binary_coap_oscore_conf_end"); #endif /* CONFIG_COAP_OSCORE_SUPPORT */ static coap_response_t message_handler(coap_session_t *session, const coap_pdu_t *sent, const coap_pdu_t *received, const coap_mid_t mid) { const unsigned char *data = NULL; size_t data_len; size_t offset; size_t total; coap_pdu_code_t rcvd_code = coap_pdu_get_code(received); if (COAP_RESPONSE_CLASS(rcvd_code) == 2) { if (coap_get_data_large(received, &data_len, &data, &offset, &total)) { if (data_len != total) { printf("Unexpected partial data received offset %u, length %u\n", offset, data_len); } printf("Received:\n%.*s\n", (int)data_len, data); resp_wait = 0; } return COAP_RESPONSE_OK; } printf("%d.%02d", (rcvd_code >> 5), rcvd_code & 0x1F); if (coap_get_data_large(received, &data_len, &data, &offset, &total)) { printf(": "); while (data_len--) { printf("%c", isprint(*data) ? *data : '.'); data++; } } printf("\n"); resp_wait = 0; return COAP_RESPONSE_OK; } #ifdef CONFIG_COAP_MBEDTLS_PKI static int verify_cn_callback(const char *cn, const uint8_t *asn1_public_cert, size_t asn1_length, coap_session_t *session, unsigned depth, int validated, void *arg ) { coap_log_info("CN '%s' presented by server (%s)\n", cn, depth ? "CA" : "Certificate"); return 1; } #endif /* CONFIG_COAP_MBEDTLS_PKI */ static void coap_log_handler (coap_log_t level, const char *message) { uint32_t esp_level = ESP_LOG_INFO; const char *cp = strchr(message, '\n'); while (cp) { ESP_LOG_LEVEL(esp_level, TAG, "%.*s", (int)(cp - message), message); message = cp + 1; cp = strchr(message, '\n'); } if (message[0] != '\000') { ESP_LOG_LEVEL(esp_level, TAG, "%s", message); } } #ifdef CONFIG_COAP_MBEDTLS_PSK static coap_session_t * coap_start_psk_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri, coap_proto_t proto) { static coap_dtls_cpsk_t dtls_psk; static char client_sni[256]; memset(client_sni, 0, sizeof(client_sni)); memset (&dtls_psk, 0, sizeof(dtls_psk)); dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION; dtls_psk.validate_ih_call_back = NULL; dtls_psk.ih_call_back_arg = NULL; if (uri->host.length) { memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni) - 1)); } else { memcpy(client_sni, "localhost", 9); } dtls_psk.client_sni = client_sni; dtls_psk.psk_info.identity.s = (const uint8_t *)EXAMPLE_COAP_PSK_IDENTITY; dtls_psk.psk_info.identity.length = sizeof(EXAMPLE_COAP_PSK_IDENTITY) - 1; dtls_psk.psk_info.key.s = (const uint8_t *)EXAMPLE_COAP_PSK_KEY; dtls_psk.psk_info.key.length = sizeof(EXAMPLE_COAP_PSK_KEY) - 1; #ifdef CONFIG_COAP_OSCORE_SUPPORT return coap_new_client_session_oscore_psk(ctx, NULL, dst_addr, proto, &dtls_psk, oscore_conf); #else /* ! CONFIG_COAP_OSCORE_SUPPORT */ return coap_new_client_session_psk2(ctx, NULL, dst_addr, proto, &dtls_psk); #endif /* ! CONFIG_COAP_OSCORE_SUPPORT */ } #endif /* CONFIG_COAP_MBEDTLS_PSK */ #ifdef CONFIG_COAP_MBEDTLS_PKI static coap_session_t * coap_start_pki_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri, coap_proto_t proto) { unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start; unsigned int client_crt_bytes = client_crt_end - client_crt_start; unsigned int client_key_bytes = client_key_end - client_key_start; static coap_dtls_pki_t dtls_pki; static char client_sni[256]; memset (&dtls_pki, 0, sizeof(dtls_pki)); dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; if (ca_pem_bytes) { /* * Add in additional certificate checking. * This list of enabled can be tuned for the specific * requirements - see 'man coap_encryption'. * * Note: A list of root cas file can be setup separately using * coap_context_set_pki_root_cas(), but the below is used to * define what checking actually takes place. */ dtls_pki.verify_peer_cert = 1; dtls_pki.check_common_ca = 1; dtls_pki.allow_self_signed = 1; dtls_pki.allow_expired_certs = 1; dtls_pki.cert_chain_validation = 1; dtls_pki.cert_chain_verify_depth = 2; dtls_pki.check_cert_revocation = 1; dtls_pki.allow_no_crl = 1; dtls_pki.allow_expired_crl = 1; dtls_pki.allow_bad_md_hash = 1; dtls_pki.allow_short_rsa_length = 1; dtls_pki.validate_cn_call_back = verify_cn_callback; dtls_pki.cn_call_back_arg = NULL; dtls_pki.validate_sni_call_back = NULL; dtls_pki.sni_call_back_arg = NULL; memset(client_sni, 0, sizeof(client_sni)); if (uri->host.length) { memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni))); } else { memcpy(client_sni, "localhost", 9); } dtls_pki.client_sni = client_sni; } dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start; dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes; dtls_pki.pki_key.key.pem_buf.private_key = client_key_start; dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes; dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start; dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes; #ifdef CONFIG_COAP_OSCORE_SUPPORT return coap_new_client_session_oscore_pki(ctx, NULL, dst_addr, proto, &dtls_pki, oscore_conf); #else /* ! CONFIG_COAP_OSCORE_SUPPORT */ return coap_new_client_session_pki(ctx, NULL, dst_addr, proto, &dtls_pki); #endif /* ! CONFIG_COAP_OSCORE_SUPPORT */ } #endif /* CONFIG_COAP_MBEDTLS_PKI */ static coap_session_t * coap_start_anon_pki_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri, coap_proto_t proto) { static coap_dtls_pki_t dtls_pki; static char client_sni[256]; memset (&dtls_pki, 0, sizeof(dtls_pki)); dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; memset(client_sni, 0, sizeof(client_sni)); if (uri->host.length) { memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni))); } else { memcpy(client_sni, "localhost", 9); } dtls_pki.client_sni = client_sni; dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM; dtls_pki.pki_key.key.pem.public_cert = NULL; dtls_pki.pki_key.key.pem.private_key = NULL; dtls_pki.pki_key.key.pem.ca_file = NULL; #ifdef CONFIG_COAP_OSCORE_SUPPORT return coap_new_client_session_oscore_pki(ctx, NULL, dst_addr, proto, &dtls_pki, oscore_conf); #else /* ! CONFIG_COAP_OSCORE_SUPPORT */ return coap_new_client_session_pki(ctx, NULL, dst_addr, proto, &dtls_pki); #endif /* ! CONFIG_COAP_OSCORE_SUPPORT */ } static void coap_example_client(void *p) { coap_address_t dst_addr; static coap_uri_t uri; const char *server_uri = COAP_DEFAULT_DEMO_URI; coap_context_t *ctx = NULL; coap_session_t *session = NULL; coap_pdu_t *request = NULL; unsigned char token[8]; size_t tokenlength; coap_addr_info_t *info_list = NULL; coap_proto_t proto; char tmpbuf[INET6_ADDRSTRLEN]; #define BUFSIZE 40 unsigned char uri_path[BUFSIZE]; #ifdef CONFIG_COAP_OSCORE_SUPPORT coap_str_const_t osc_conf = { 0, 0}; #endif /* CONFIG_COAP_OSCORE_SUPPORT */ /* Initialize libcoap library */ coap_startup(); /* Set up the CoAP logging */ coap_set_log_handler(coap_log_handler); coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL); /* Set up the CoAP context */ ctx = coap_new_context(NULL); if (!ctx) { ESP_LOGE(TAG, "coap_new_context() failed"); goto clean_up; } coap_context_set_block_mode(ctx, COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); coap_register_response_handler(ctx, message_handler); if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) { ESP_LOGE(TAG, "CoAP server uri %s error", server_uri); goto clean_up; } info_list = coap_resolve_address_info(&uri.host, uri.port, uri.port, uri.port, uri.port, 0, 1 << uri.scheme, COAP_RESOLVE_TYPE_REMOTE); if (info_list == NULL) { ESP_LOGE(TAG, "failed to resolve address"); goto clean_up; } proto = info_list->proto; memcpy(&dst_addr, &info_list->addr, sizeof(dst_addr)); coap_free_address_info(info_list); /* Convert provided uri into CoAP options */ if (coap_uri_into_options(&uri, &dst_addr, &optlist, 1, uri_path, sizeof(uri_path)) < 0) { ESP_LOGE(TAG, "Failed to create options for URI %s", server_uri); goto clean_up; } /* This is to keep the test suites happy */ coap_print_ip_addr(&dst_addr, tmpbuf, sizeof(tmpbuf)); ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf); #ifdef CONFIG_COAP_OSCORE_SUPPORT osc_conf.s = oscore_conf_start; osc_conf.length = oscore_conf_end - oscore_conf_start; oscore_conf = coap_new_oscore_conf(osc_conf, NULL, NULL, 0); #endif /* CONFIG_COAP_OSCORE_SUPPORT */ /* * Note that if the URI starts with just coap:// (not coaps://) the * session will still be plain text. */ if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP || uri.scheme == COAP_URI_SCHEME_COAPS_WS) { #ifndef CONFIG_MBEDTLS_TLS_CLIENT ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured"); goto clean_up; #endif /* CONFIG_MBEDTLS_TLS_CLIENT */ /* * Encrypted request. Client needs to choose whether using PSK (if configured) * or PKI (if configured) or anonymous PKI (where certificates are not defined locally). * This code chooses PSK (if configured), then PKI (if configured) then anonymous * PKI where certificates are not defined locally. */ #ifdef CONFIG_COAP_MBEDTLS_PSK session = coap_start_psk_session(ctx, &dst_addr, &uri, proto); #endif /* CONFIG_COAP_MBEDTLS_PSK */ #ifdef CONFIG_COAP_MBEDTLS_PKI if (!session) { session = coap_start_pki_session(ctx, &dst_addr, &uri, proto); } #endif /* CONFIG_COAP_MBEDTLS_PKI */ if (!session) { session = coap_start_anon_pki_session(ctx, &dst_addr, &uri, proto); } } else { #ifdef CONFIG_COAP_OSCORE_SUPPORT session = coap_new_client_session_oscore(ctx, NULL, &dst_addr, proto, oscore_conf); #else /* ! CONFIG_COAP_OSCORE_SUPPORT */ session = coap_new_client_session(ctx, NULL, &dst_addr, proto); #endif /* ! CONFIG_COAP_OSCORE_SUPPORT */ } if (!session) { ESP_LOGE(TAG, "coap_new_client_session() failed"); goto clean_up; } #ifdef CONFIG_COAP_WEBSOCKETS if (proto == COAP_PROTO_WS || proto == COAP_PROTO_WSS) { coap_ws_set_host_request(session, &uri.host); } #endif /* CONFIG_COAP_WEBSOCKETS */ while (1) { request = coap_new_pdu(coap_is_mcast(&dst_addr) ? COAP_MESSAGE_NON : COAP_MESSAGE_CON, COAP_REQUEST_CODE_GET, session); if (!request) { ESP_LOGE(TAG, "coap_new_pdu() failed"); goto clean_up; } /* Add in an unique token */ coap_session_new_token(session, &tokenlength, token); coap_add_token(request, tokenlength, token); /* * To make this a POST, you will need to do the following * Change COAP_REQUEST_CODE_GET to COAP_REQUEST_CODE_POST for coap_new_pdu() * Add in here a Content-Type Option based on the format of the POST text. E.G. for JSON * u_char buf[4]; * coap_insert_optlist(&optlist, * coap_new_optlist(COAP_OPTION_CONTENT_FORMAT, * coap_encode_var_safe (buf, sizeof (buf), * COAP_MEDIATYPE_APPLICATION_JSON), * buf)); * Add in here the POST data of length length. E.G. * coap_add_data_large_request(session, request length, data, NULL, NULL); */ coap_add_optlist_pdu(request, &optlist); resp_wait = 1; coap_send(session, request); wait_ms = COAP_DEFAULT_TIME_SEC * 1000; while (resp_wait) { int result = coap_io_process(ctx, wait_ms > 1000 ? 1000 : wait_ms); if (result >= 0) { if (result >= wait_ms) { ESP_LOGE(TAG, "No response from server"); break; } else { wait_ms -= result; } } } for (int countdown = 10; countdown >= 0; countdown--) { ESP_LOGI(TAG, "%d... ", countdown); vTaskDelay(1000 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "Starting again!"); } clean_up: if (optlist) { coap_delete_optlist(optlist); optlist = NULL; } if (session) { coap_session_release(session); } if (ctx) { coap_free_context(ctx); } coap_cleanup(); ESP_LOGI(TAG, "Finished"); vTaskDelete(NULL); } void app_main(void) { ESP_ERROR_CHECK( nvs_flash_init() ); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); xTaskCreate(coap_example_client, "coap", 8 * 1024, NULL, 5, NULL); } ================================================ FILE: coap/examples/coap_client/main/idf_component.yml ================================================ version: 1.0.0 description: CoAP Client Example dependencies: espressif/coap: version: ^4.3.0 override_path: ../../../ protocol_examples_common: path: ${IDF_PATH}/examples/common_components/protocol_examples_common ================================================ FILE: coap/examples/coap_client/main/oscore/coap_oscore.conf ================================================ # https://libcoap.net/doc/reference/develop/man_coap-oscore-conf.html master_secret,hex,"0102030405060708090a0b0c0d0e0f10" master_salt,hex,"9e7ca92223786340" sender_id,hex,"01" recipient_id,hex,"02" id_context,hex,"37cbf3210017a2d3" replay_window,integer,30 aead_alg,integer,10 hkdf_alg,integer,-10 ssn_freq,integer,4 rfc8613_b_2,bool,true ================================================ FILE: coap/examples/coap_client/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1300K, ================================================ FILE: coap/examples/coap_client/pytest_coap_client_example.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pexpect import pytest from pytest_embedded import Dut @pytest.mark.ethernet def test_coap_example(dut: Dut) -> None: dut.expect('Loaded app from partition at offset', timeout=30) try: dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30) except pexpect.exceptions.TIMEOUT: raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') dut.expect('DNS lookup succeeded', timeout=30) dut.expect('Received', timeout=30) dut.expect(r'This server is using the Eclipse Californium \(Cf\) CoAP framework', timeout=30) dut.expect(r'published under EPL\+EDL: http://www\.eclipse\.org/californium/', timeout=30) dut.expect('Starting again!', timeout=30) ================================================ FILE: coap/examples/coap_client/sdkconfig.ci ================================================ CONFIG_EXAMPLE_CONNECT_ETHERNET=y CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 CONFIG_EXAMPLE_ETH_PHY_ADDR=1 CONFIG_COAP_OSCORE_SUPPORT=y CONFIG_COAP_OBSERVE_PERSIST=y CONFIG_COAP_WEBSOCKETS=y ================================================ FILE: coap/examples/coap_client/sdkconfig.defaults ================================================ CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_PSK_MODES=y CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y CONFIG_LWIP_NETBUF_RECVINFO=y CONFIG_COAP_CLIENT_SUPPORT=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" ================================================ FILE: coap/examples/coap_client/sdkconfig.defaults.esp32h2 ================================================ CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_CONNECT_ETHERNET=y ================================================ FILE: coap/examples/coap_server/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main esp_eth) project(coap_server) ================================================ FILE: coap/examples/coap_server/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | # CoAP server example (See the README.md file in the upper level esp-idf 'examples' directory for more information about examples.) This CoAP server example is very simplified adaptation of one of the [libcoap](https://github.com/obgm/libcoap) examples. CoAP server example will startup a daemon task, receive requests / data from CoAP client and transmit data to CoAP client. If the incoming request requests the use of DTLS (connecting to port 5684), then the CoAP server will try to establish a DTLS session using the previously defined Pre-Shared Key (PSK) - which must be the same as the one that the CoAP client is using, or Public Key Infrastructure (PKI) where the PKI information must match as requested. The Constrained Application Protocol (CoAP) is a specialized web transfer protocol for use with constrained nodes and constrained networks in the Internet of Things. The protocol is designed for machine-to-machine (M2M) applications such as smart energy and building automation. Please refer to [RFC7252](https://www.rfc-editor.org/rfc/pdfrfc/rfc7252.txt.pdf) for more details. ## How to use example ### Configure the project ``` idf.py menuconfig ``` Example Connection Configuration ---> * Set WiFi SSID * Set WiFi Password Component config ---> CoAP Configuration ---> * Set encryption method definition, PSK (default) and/or PKI * Enable CoAP debugging if required * Disable CoAP using TCP if this is not required (TCP needed for TLS or WebSockets) * Disable CoAP client functionality to reduce code size unless this server is a proxy * Enable OSCORE (RFC8613) support if required * Enable WebSockets (RFC8323) support if required Example CoAP Server Configuration ---> * If PSK, Set CoAP Preshared Key to use for connections to the server Note: * For enabled PKI, the certificates are stored in main/certs. * For enabled OSCORE, the OSCORE configuration is stored in main/oscore. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py build idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output current CoAP server would startup a daemon task, and the log is such as the following: ``` ... I (332) wifi: mode : sta (30:ae:a4:04:1b:7c) I (1672) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1 I (1672) wifi: state: init -> auth (b0) I (1682) wifi: state: auth -> assoc (0) I (1692) wifi: state: assoc -> run (10) I (1692) wifi: connected with huawei_cw, channel 11 I (1692) wifi: pm start, type: 1 I (2622) event: sta ip: 192.168.3.84, mask: 255.255.255.0, gw: 192.168.3.1 I (2622) CoAP_server: Connected to AP ... ``` If a CoAP client queries the `/Espressif` resource, CoAP server will return `"Hello World!"` until a CoAP client does a PUT with different data. If a clent queries the `/oscore` resource, CoAP server will return `OSCORE Success!` if OSCORE is enable AND the client is using OSCORE. ## libcoap Documentation This can be found at [libcoap Documentation](https://libcoap.net/documentation.html). The current API is 4.3.4. ## libcoap Specific Issues These can be raised at [libcoap Issues](https://github.com/obgm/libcoap/issues). ## Troubleshooting * Please make sure CoAP client gets or puts data under path: `/Espressif` or gets `/.well-known/core`. * CoAP logging can be enabled by running 'idf.py menuconfig -> Component config -> CoAP Configuration -> Enable CoAP debugging' and setting appropriate log level. If Mbed TLS logging is required, this needs to be configured separately under mbedTLS Component Configuration. ================================================ FILE: coap/examples/coap_server/main/CMakeLists.txt ================================================ idf_component_register(SRCS "coap_server_example_main.c" INCLUDE_DIRS "." EMBED_TXTFILES certs/coap_ca.pem certs/coap_server.crt certs/coap_server.key oscore/coap_oscore.conf) ================================================ FILE: coap/examples/coap_server/main/Kconfig.projbuild ================================================ menu "Example CoAP Server Configuration" config EXAMPLE_COAP_PSK_KEY string "Preshared Key (PSK) to used in the connection from the CoAP client" depends on COAP_MBEDTLS_PSK default "secret-key" help The Preshared Key to use to encrypt the communicatons. The same key must be used at both ends of the CoAP connection, and the CoaP client must request an URI prefixed with coaps:// instead of coap:// for DTLS to be used. config EXAMPLE_COAP_LISTEN_PORT string "CoAP Listen port" default "5683" help Port number to listen for CoAP traffic. config EXAMPLE_COAPS_LISTEN_PORT string "CoAP Secure Listen port" default "5684" depends on COAP_MBEDTLS_PSK || COAP_MBEDTLS_PKI help Port number to listen for CoAP secure ((D)TLS) traffic. config EXAMPLE_COAP_WEBSOCKET_PORT string "CoAP Websocket port" default "80" depends on COAP_WEBSOCKETS help Port number to listen for WebSocket traffic on. The default is 80. config EXAMPLE_COAP_WEBSOCKET_SECURE_PORT string "CoAP Websocket Secure port" default "443" depends on COAP_WEBSOCKETS && (COAP_MBEDTLS_PSK || COAP_MBEDTLS_PKI) help Port number to listen for WebSocket Secure (TLS) traffic on. The default is 443. choice EXAMPLE_COAP_MCAST_IP_MODE prompt "Receive Multicast IP type" help Example can receive multicast IPV4, IPV6, both or none. config EXAMPLE_COAP_MCAST_NONE bool "None" config EXAMPLE_COAP_MCAST_IPV4_V6 bool "IPV4 & IPV6" depends on LWIP_IPV4 && LWIP_IPV6 select EXAMPLE_COAP_MCAST_IPV4 select EXAMPLE_COAP_MCAST_IPV6 config EXAMPLE_COAP_MCAST_IPV4_ONLY bool "IPV4" depends on LWIP_IPV4 select EXAMPLE_COAP_MCAST_IPV4 config EXAMPLE_COAP_MCAST_IPV6_ONLY bool "IPV6" depends on LWIP_IPV6 select EXAMPLE_COAP_MCAST_IPV6 endchoice config EXAMPLE_COAP_MCAST_IPV4 bool depends on LWIP_IPV4 config EXAMPLE_COAP_MCAST_IPV6 bool depends on LWIP_IPV6 config EXAMPLE_COAP_MULTICAST_IPV4_ADDR string "CoAP Multicast IPV4 Address (receive)" default "224.0.1.187" depends on EXAMPLE_COAP_MCAST_IPV4 help IPV4 multicast address to receive multicast traffic on. The default CoAP IPV4 address is 224.0.1.187. config EXAMPLE_COAP_MULTICAST_IPV6_ADDR string "CoAP Multicast IPV6 Address (receive)" default "FF02::FD" depends on EXAMPLE_COAP_MCAST_IPV6 help IPV6 multicast address to receive multicast traffic on. The default CoAP FF02::FD address is a link-local multicast address. Consult IPV6 specifications or documentation for information about meaning of different IPV6 multicast ranges. endmenu ================================================ FILE: coap/examples/coap_server/main/certs/coap_ca.pem ================================================ -----BEGIN CERTIFICATE----- MIICDDCCAbKgAwIBAgIIPKO8L7vZoqAwCgYIKoZIzj0EAwIwXDEQMA4GA1UEAxMH Y2Ytcm9vdDEUMBIGA1UECxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2Ug SW9UMQ8wDQYDVQQHEwZPdHRhd2ExCzAJBgNVBAYTAkNBMB4XDTIzMTAyNjA4MDgx NVoXDTI1MTAyNTA4MDgxNVowWjEOMAwGA1UEAxMFY2YtY2ExFDASBgNVBAsTC0Nh bGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElvVDEPMA0GA1UEBxMGT3R0YXdh MQswCQYDVQQGEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLCbJjxIS4hI AnRFTlx23gkd4zyFd50zdpTnoUPz19oQ1o1youavC5Go9vrYoWxyx+zpph8T4brB C/mZGIgPVMOjYDBeMB0GA1UdDgQWBBSxVzoI1TL87++hsUb9vQwqODzgUTALBgNV HQ8EBAMCAQYwDwYDVR0TBAgwBgEB/wIBATAfBgNVHSMEGDAWgBTqNhC1fqOTsHRn IVZ9OabfWsxpcTAKBggqhkjOPQQDAgNIADBFAiBSEn3egc31JhhHTVYi5uhl0t4d ewujkEmwzBuruzf/xAIhAK/fXy2tsNoyLitFQ97x6LYV25jKmLKUlhL2mC/PwQdO -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICDDCCAbKgAwIBAgIIPKO8L7vZoqAwCgYIKoZIzj0EAwIwXDEQMA4GA1UEAxMH Y2Ytcm9vdDEUMBIGA1UECxMLQ2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2Ug SW9UMQ8wDQYDVQQHEwZPdHRhd2ExCzAJBgNVBAYTAkNBMB4XDTIzMTAyNjA4MDgx NVoXDTI1MTAyNTA4MDgxNVowWjEOMAwGA1UEAxMFY2YtY2ExFDASBgNVBAsTC0Nh bGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElvVDEPMA0GA1UEBxMGT3R0YXdh MQswCQYDVQQGEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLCbJjxIS4hI AnRFTlx23gkd4zyFd50zdpTnoUPz19oQ1o1youavC5Go9vrYoWxyx+zpph8T4brB C/mZGIgPVMOjYDBeMB0GA1UdDgQWBBSxVzoI1TL87++hsUb9vQwqODzgUTALBgNV HQ8EBAMCAQYwDwYDVR0TBAgwBgEB/wIBATAfBgNVHSMEGDAWgBTqNhC1fqOTsHRn IVZ9OabfWsxpcTAKBggqhkjOPQQDAgNIADBFAiBSEn3egc31JhhHTVYi5uhl0t4d ewujkEmwzBuruzf/xAIhAK/fXy2tsNoyLitFQ97x6LYV25jKmLKUlhL2mC/PwQdO -----END CERTIFICATE----- ================================================ FILE: coap/examples/coap_server/main/certs/coap_server.crt ================================================ -----BEGIN CERTIFICATE----- MIICZDCCAgmgAwIBAgIIDY1x9glyw2UwCgYIKoZIzj0EAwIwWjEOMAwGA1UEAxMF Y2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElv VDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMzEwMjYwODA4MTZa Fw0yNTEwMjUwODA4MTZaMF4xEjAQBgNVBAMTCWNmLXNlcnZlcjEUMBIGA1UECxML Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECXC+y/fC +rvwHZHizj7zVmLbhM6cjCK654Y8qFt+56G78+1VgfZzpIDf6k7XsSJIRpX+TrCY UDUmkknL9mnbB6OBtDCBsTAdBgNVHQ4EFgQUCp3HwJN1vjsydK1G5jjHjcGefqQw CwYDVR0PBAQDAgeAMGIGA1UdEQRbMFmCDm15LnRlc3Quc2VydmVygh5jYWxpZm9y bml1bS5lY2xpcHNlcHJvamVjdHMuaW+HBCO5KLaCCWxvY2FsaG9zdIcEfwAAAYcQ AAAAAAAAAAAAAAAAAAAAATAfBgNVHSMEGDAWgBSxVzoI1TL87++hsUb9vQwqODzg UTAKBggqhkjOPQQDAgNJADBGAiEArsVlRAvaNzmmKDfKHBtaG2ogusSec7+PAebk FIQSqEMCIQCbYJCnft3yhKbvBedO+zH/T0RT5V4eE/Yx4JKxbz1Q7A== -----END CERTIFICATE----- ================================================ FILE: coap/examples/coap_server/main/certs/coap_server.key ================================================ -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxL7AZvC26M+snaJ2 +L8XSPUvKJN9Tk4im2CaBMzDezahRANCAAQJcL7L98L6u/AdkeLOPvNWYtuEzpyM IrrnhjyoW37nobvz7VWB9nOkgN/qTtexIkhGlf5OsJhQNSaSScv2adsH -----END PRIVATE KEY----- ================================================ FILE: coap/examples/coap_server/main/coap_server_example_main.c ================================================ /* CoAP server Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ /* * WARNING * libcoap is not multi-thread safe, so only this thread must make any coap_*() * calls. Any external (to this thread) data transmitted in/out via libcoap * therefore has to be passed in/out by xQueue*() via this thread. */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_log.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "protocol_examples_common.h" #include "coap_config.h" #include "coap3/coap.h" #ifndef CONFIG_COAP_SERVER_SUPPORT #error COAP_SERVER_SUPPORT needs to be enabled #endif /* COAP_SERVER_SUPPORT */ /* The examples use simple Pre-Shared-Key configuration that you can set via 'idf.py menuconfig'. If you'd rather not, just change the below entries to strings with the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key" Note: PSK will only be used if the URI is prefixed with coaps:// instead of coap:// and the PSK must be one that the server supports (potentially associated with the IDENTITY) */ #define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY /* The examples use CoAP Logging Level that you can set via 'idf.py menuconfig'. If you'd rather not, just change the below entry to a value that is between 0 and 7 with the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7 Caution: Logging is enabled in libcoap only up to level as defined by 'idf.py menuconfig' to reduce code size. */ #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL const static char *TAG = "CoAP_server"; static char espressif_data[100]; static int espressif_data_len = 0; #ifdef CONFIG_COAP_MBEDTLS_PKI /* CA cert, taken from coap_ca.pem Server cert, taken from coap_server.crt Server key, taken from coap_server.key The PEM, CRT and KEY file are examples taken from https://github.com/eclipse/californium/tree/master/demo-certs/src/main/resources as the Certificate test (by default) for the coap_client is against the californium server. To embed it in the app binary, the PEM, CRT and KEY file is named in the CMakeLists.txt EMBED_TXTFILES definition. */ extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start"); extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end"); extern uint8_t server_crt_start[] asm("_binary_coap_server_crt_start"); extern uint8_t server_crt_end[] asm("_binary_coap_server_crt_end"); extern uint8_t server_key_start[] asm("_binary_coap_server_key_start"); extern uint8_t server_key_end[] asm("_binary_coap_server_key_end"); #endif /* CONFIG_COAP_MBEDTLS_PKI */ #ifdef CONFIG_COAP_OSCORE_SUPPORT extern uint8_t oscore_conf_start[] asm("_binary_coap_oscore_conf_start"); extern uint8_t oscore_conf_end[] asm("_binary_coap_oscore_conf_end"); #endif /* CONFIG_COAP_OSCORE_SUPPORT */ #define INITIAL_DATA "Hello World!" /* * The resource handler */ static void hnd_espressif_get(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); coap_add_data_large_response(resource, session, request, response, query, COAP_MEDIATYPE_TEXT_PLAIN, 60, 0, (size_t)espressif_data_len, (const u_char *)espressif_data, NULL, NULL); } static void hnd_espressif_put(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { size_t size; size_t offset; size_t total; const unsigned char *data; coap_resource_notify_observers(resource, NULL); if (strcmp (espressif_data, INITIAL_DATA) == 0) { coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED); } else { coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED); } /* coap_get_data_large() sets size to 0 on error */ (void)coap_get_data_large(request, &size, &data, &offset, &total); if (size == 0) { /* re-init */ snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); espressif_data_len = strlen(espressif_data); } else { espressif_data_len = size > sizeof (espressif_data) ? sizeof (espressif_data) : size; memcpy (espressif_data, data, espressif_data_len); } } static void hnd_espressif_delete(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { coap_resource_notify_observers(resource, NULL); snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); espressif_data_len = strlen(espressif_data); coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED); } #ifdef CONFIG_COAP_OSCORE_SUPPORT static void hnd_oscore_get(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); coap_add_data_large_response(resource, session, request, response, query, COAP_MEDIATYPE_TEXT_PLAIN, 60, 0, sizeof("OSCORE Success!"), (const u_char *)"OSCORE Success!", NULL, NULL); } #endif /* CONFIG_COAP_OSCORE_SUPPORT */ #ifdef CONFIG_COAP_MBEDTLS_PKI static int verify_cn_callback(const char *cn, const uint8_t *asn1_public_cert, size_t asn1_length, coap_session_t *session, unsigned depth, int validated, void *arg ) { coap_log_info("CN '%s' presented by server (%s)\n", cn, depth ? "CA" : "Certificate"); return 1; } #endif /* CONFIG_COAP_MBEDTLS_PKI */ static void coap_log_handler (coap_log_t level, const char *message) { uint32_t esp_level = ESP_LOG_INFO; const char *cp = strchr(message, '\n'); while (cp) { ESP_LOG_LEVEL(esp_level, TAG, "%.*s", (int)(cp - message), message); message = cp + 1; cp = strchr(message, '\n'); } if (message[0] != '\000') { ESP_LOG_LEVEL(esp_level, TAG, "%s", message); } } static void coap_example_server(void *p) { coap_context_t *ctx = NULL; coap_resource_t *resource = NULL; int have_ep = 0; uint16_t u_s_port = atoi(CONFIG_EXAMPLE_COAP_LISTEN_PORT); #ifdef CONFIG_EXAMPLE_COAPS_LISTEN_PORT uint16_t s_port = atoi(CONFIG_EXAMPLE_COAPS_LISTEN_PORT); #else /* ! CONFIG_EXAMPLE_COAPS_LISTEN_PORT */ uint16_t s_port = 0; #endif /* ! CONFIG_EXAMPLE_COAPS_LISTEN_PORT */ #ifdef CONFIG_EXAMPLE_COAP_WEBSOCKET_PORT uint16_t ws_port = atoi(CONFIG_EXAMPLE_COAP_WEBSOCKET_PORT); #else /* ! CONFIG_EXAMPLE_COAP_WEBSOCKET_PORT */ uint16_t ws_port = 0; #endif /* ! CONFIG_EXAMPLE_COAP_WEBSOCKET_PORT */ #ifdef CONFIG_EXAMPLE_COAP_WEBSOCKET_SECURE_PORT uint16_t ws_s_port = atoi(CONFIG_EXAMPLE_COAP_WEBSOCKET_SECURE_PORT); #else /* ! CONFIG_EXAMPLE_COAP_WEBSOCKET_SECURE_PORT */ uint16_t ws_s_port = 0; #endif /* ! CONFIG_EXAMPLE_COAP_WEBSOCKET_SECURE_PORT */ uint32_t scheme_hint_bits; #ifdef CONFIG_COAP_OSCORE_SUPPORT coap_str_const_t osc_conf = { 0, 0}; coap_oscore_conf_t *oscore_conf; #endif /* CONFIG_COAP_OSCORE_SUPPORT */ /* Initialize libcoap library */ coap_startup(); snprintf(espressif_data, sizeof(espressif_data), INITIAL_DATA); espressif_data_len = strlen(espressif_data); coap_set_log_handler(coap_log_handler); coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL); while (1) { unsigned wait_ms; coap_addr_info_t *info = NULL; coap_addr_info_t *info_list = NULL; ctx = coap_new_context(NULL); if (!ctx) { ESP_LOGE(TAG, "coap_new_context() failed"); goto clean_up; } coap_context_set_block_mode(ctx, COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); coap_context_set_max_idle_sessions(ctx, 20); coap_context_set_keepalive(ctx, 30); #ifdef CONFIG_COAP_MBEDTLS_PSK /* Need PSK setup before we set up endpoints */ coap_context_set_psk(ctx, "CoAP", (const uint8_t *)EXAMPLE_COAP_PSK_KEY, sizeof(EXAMPLE_COAP_PSK_KEY) - 1); #endif /* CONFIG_COAP_MBEDTLS_PSK */ #ifdef CONFIG_COAP_MBEDTLS_PKI /* Need PKI setup before we set up endpoints */ unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start; unsigned int server_crt_bytes = server_crt_end - server_crt_start; unsigned int server_key_bytes = server_key_end - server_key_start; coap_dtls_pki_t dtls_pki; memset (&dtls_pki, 0, sizeof(dtls_pki)); dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION; if (ca_pem_bytes) { /* * Add in additional certificate checking. * This list of enabled can be tuned for the specific * requirements - see 'man coap_encryption'. * * Note: A list of root ca file can be setup separately using * coap_context_set_pki_root_cas(), but the below is used to * define what checking actually takes place. */ dtls_pki.verify_peer_cert = 1; dtls_pki.check_common_ca = 1; dtls_pki.allow_self_signed = 1; dtls_pki.allow_expired_certs = 1; dtls_pki.cert_chain_validation = 1; dtls_pki.cert_chain_verify_depth = 2; dtls_pki.check_cert_revocation = 1; dtls_pki.allow_no_crl = 1; dtls_pki.allow_expired_crl = 1; dtls_pki.allow_bad_md_hash = 1; dtls_pki.allow_short_rsa_length = 1; dtls_pki.validate_cn_call_back = verify_cn_callback; dtls_pki.cn_call_back_arg = NULL; dtls_pki.validate_sni_call_back = NULL; dtls_pki.sni_call_back_arg = NULL; } dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF; dtls_pki.pki_key.key.pem_buf.public_cert = server_crt_start; dtls_pki.pki_key.key.pem_buf.public_cert_len = server_crt_bytes; dtls_pki.pki_key.key.pem_buf.private_key = server_key_start; dtls_pki.pki_key.key.pem_buf.private_key_len = server_key_bytes; dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start; dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes; coap_context_set_pki(ctx, &dtls_pki); #endif /* CONFIG_COAP_MBEDTLS_PKI */ #ifdef CONFIG_COAP_OSCORE_SUPPORT osc_conf.s = oscore_conf_start; osc_conf.length = oscore_conf_end - oscore_conf_start; oscore_conf = coap_new_oscore_conf(osc_conf, NULL, NULL, 0); coap_context_oscore_server(ctx, oscore_conf); #endif /* CONFIG_COAP_OSCORE_SUPPORT */ /* set up the CoAP server socket(s) */ scheme_hint_bits = coap_get_available_scheme_hint_bits( #if defined(CONFIG_COAP_MBEDTLS_PSK) || defined(CONFIG_COAP_MBEDTLS_PKI) 1, #else /* ! CONFIG_COAP_MBEDTLS_PSK) && ! CONFIG_COAP_MBEDTLS_PKI */ 0, #endif /* ! CONFIG_COAP_MBEDTLS_PSK) && ! CONFIG_COAP_MBEDTLS_PKI */ #ifdef CONFIG_COAP_WEBSOCKETS 1, #else /* ! CONFIG_COAP_WEBSOCKETS */ 0, #endif /* ! CONFIG_COAP_WEBSOCKETS */ 0); #if LWIP_IPV6 info_list = coap_resolve_address_info(coap_make_str_const("::"), u_s_port, s_port, ws_port, ws_s_port, 0, scheme_hint_bits, COAP_RESOLVE_TYPE_LOCAL); #else /* LWIP_IPV6 */ info_list = coap_resolve_address_info(coap_make_str_const("0.0.0.0"), u_s_port, s_port, ws_port, ws_s_port, 0, scheme_hint_bits, COAP_RESOLVE_TYPE_LOCAL); #endif /* LWIP_IPV6 */ if (info_list == NULL) { ESP_LOGE(TAG, "coap_resolve_address_info() failed"); goto clean_up; } for (info = info_list; info != NULL; info = info->next) { coap_endpoint_t *ep; ep = coap_new_endpoint(ctx, &info->addr, info->proto); if (!ep) { ESP_LOGW(TAG, "cannot create endpoint for proto %u", info->proto); } else { have_ep = 1; } } coap_free_address_info(info_list); if (!have_ep) { ESP_LOGE(TAG, "No endpoints available"); goto clean_up; } resource = coap_resource_init(coap_make_str_const("Espressif"), 0); if (!resource) { ESP_LOGE(TAG, "coap_resource_init() failed"); goto clean_up; } coap_register_handler(resource, COAP_REQUEST_GET, hnd_espressif_get); coap_register_handler(resource, COAP_REQUEST_PUT, hnd_espressif_put); coap_register_handler(resource, COAP_REQUEST_DELETE, hnd_espressif_delete); /* We possibly want to Observe the GETs */ coap_resource_set_get_observable(resource, 1); coap_add_resource(ctx, resource); #ifdef CONFIG_COAP_OSCORE_SUPPORT resource = coap_resource_init(coap_make_str_const("oscore"), COAP_RESOURCE_FLAGS_OSCORE_ONLY); if (!resource) { ESP_LOGE(TAG, "coap_resource_init() failed"); goto clean_up; } coap_register_handler(resource, COAP_REQUEST_GET, hnd_oscore_get); coap_add_resource(ctx, resource); #endif /* CONFIG_COAP_OSCORE_SUPPORT */ #if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV4) || defined(CONFIG_EXAMPLE_COAP_MCAST_IPV6) esp_netif_t *netif = NULL; for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) { char buf[8]; netif = esp_netif_next(netif); esp_netif_get_netif_impl_name(netif, buf); #if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV4) coap_join_mcast_group_intf(ctx, CONFIG_EXAMPLE_COAP_MULTICAST_IPV4_ADDR, buf); #endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV4 */ #if defined(CONFIG_EXAMPLE_COAP_MCAST_IPV6) /* When adding IPV6 esp-idf requires ifname param to be filled in */ coap_join_mcast_group_intf(ctx, CONFIG_EXAMPLE_COAP_MULTICAST_IPV6_ADDR, buf); #endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV6 */ } #endif /* CONFIG_EXAMPLE_COAP_MCAST_IPV4 || CONFIG_EXAMPLE_COAP_MCAST_IPV6 */ wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; while (1) { int result = coap_io_process(ctx, wait_ms); if (result < 0) { break; } else if (result && (unsigned)result < wait_ms) { /* decrement if there is a result wait time returned */ wait_ms -= result; } if (result) { /* result must have been >= wait_ms, so reset wait_ms */ wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; } } } clean_up: coap_free_context(ctx); coap_cleanup(); vTaskDelete(NULL); } void app_main(void) { ESP_ERROR_CHECK( nvs_flash_init() ); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); xTaskCreate(coap_example_server, "coap", 8 * 1024, NULL, 5, NULL); } ================================================ FILE: coap/examples/coap_server/main/idf_component.yml ================================================ version: 1.0.0 description: CoAP Server Example dependencies: espressif/coap: version: ^4.3.0 override_path: ../../../ protocol_examples_common: path: ${IDF_PATH}/examples/common_components/protocol_examples_common ================================================ FILE: coap/examples/coap_server/main/oscore/coap_oscore.conf ================================================ # https://libcoap.net/doc/reference/develop/man_coap-oscore-conf.html master_secret,hex,"0102030405060708090a0b0c0d0e0f10" master_salt,hex,"9e7ca92223786340" sender_id,hex,"02" recipient_id,hex,"01" id_context,hex,"37cbf3210017a2d3" replay_window,integer,30 aead_alg,integer,10 hkdf_alg,integer,-10 ssn_freq,integer,4 rfc8613_b_2,bool,true ================================================ FILE: coap/examples/coap_server/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1300K, ================================================ FILE: coap/examples/coap_server/sdkconfig.ci ================================================ CONFIG_EXAMPLE_CONNECT_ETHERNET=y CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 CONFIG_EXAMPLE_ETH_PHY_ADDR=1 CONFIG_COAP_OSCORE_SUPPORT=y CONFIG_COAP_OBSERVE_PERSIST=y CONFIG_COAP_WEBSOCKETS=y ================================================ FILE: coap/examples/coap_server/sdkconfig.defaults ================================================ CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_PSK_MODES=y CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y CONFIG_LWIP_NETBUF_RECVINFO=y CONFIG_COAP_SERVER_SUPPORT=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" ================================================ FILE: coap/examples/coap_server/sdkconfig.defaults.esp32h2 ================================================ CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_CONNECT_ETHERNET=y ================================================ FILE: coap/idf_component.yml ================================================ version: "4.3.5~6" description: Constrained Application Protocol (CoAP) C Library url: https://github.com/espressif/idf-extra-components/tree/master/coap dependencies: idf: ">=4.4" sbom: manifests: - path: sbom_libcoap.yml dest: libcoap ================================================ FILE: coap/port/include/coap_config.h ================================================ /* * libcoap configure implementation for ESP32 platform. * * coap.h -- main header file for CoAP stack of libcoap * * Copyright (C) 2010-2012,2015-2025 Olaf Bergmann * 2015 Carsten Schoenert * * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD * * This file is part of the CoAP library libcoap. Please see README for terms * of use. */ #ifndef COAP_CONFIG_H_ #define COAP_CONFIG_H_ /* Always enabled in ESP-IDF */ #ifndef WITH_POSIX #define WITH_POSIX #endif #include "coap_config_posix.h" #define HAVE_STDIO_H #define HAVE_ASSERT_H #define HAVE_INTTYPES_H #define PACKAGE_STRING PACKAGE_NAME PACKAGE_VERSION /* it's just provided by libc. i hope we don't get too many of those, as * actually we'd need autotools again to find out what environment we're * building in */ #define HAVE_STRNLEN 1 #define HAVE_LIMITS_H #define COAP_RESOURCES_NOHASH /* Note: If neither of COAP_CLIENT_SUPPORT or COAP_SERVER_SUPPORT is set, then libcoap sets both for backward compatibility */ #ifdef CONFIG_COAP_CLIENT_SUPPORT #define COAP_CLIENT_SUPPORT 1 #endif /* CONFIG_COAP_CLIENT_SUPPORT */ #ifdef CONFIG_COAP_SERVER_SUPPORT #define COAP_SERVER_SUPPORT 1 #endif /* CONFIG_COAP_SERVER_SUPPORT */ #ifdef CONFIG_COAP_PROXY_SUPPORT #define COAP_PROXY_SUPPORT 1 #endif /* CONFIG_COAP_PROXY_SUPPORT */ #ifdef CONFIG_LWIP_IPV4 #define COAP_IPV4_SUPPORT 1 #else /* ! CONFIG_LWIP_IPV4 */ struct sockaddr_in { u8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; }; #endif /* ! CONFIG_LWIP_IPV4 */ #ifdef CONFIG_LWIP_IPV6 #define COAP_IPV6_SUPPORT 1 #else /* ! CONFIG_LWIP_IPV6 */ struct sockaddr_in6 { u8_t sin6_len; sa_family_t sin6_family; in_port_t sin6_port; u32_t sin6_flowinfo; struct in_addr sin6_addr; u32_t sin6_scope_id; }; #ifndef INET6_ADDRSTRLEN #define INET6_ADDRSTRLEN 40 #endif /* INET6_ADDRSTRLEN */ #endif /* ! CONFIG_LWIP_IPV6 */ #ifdef CONFIG_COAP_ASYNC_SUPPORT #define COAP_ASYNC_SUPPORT 1 #endif /* CONFIG_COAP_ASYNC_SUPPORT */ #ifdef CONFIG_COAP_TCP_SUPPORT #define COAP_DISABLE_TCP 0 #else /* ! CONFIG_COAP_TCP_SUPPORT */ #define COAP_DISABLE_TCP 1 #endif /* ! CONFIG_COAP_TCP_SUPPORT */ #ifdef CONFIG_COAP_OSCORE_SUPPORT #define COAP_OSCORE_SUPPORT 1 #else /* ! CONFIG_COAP_OSCORE_SUPPORT */ #define COAP_OSCORE_SUPPORT 0 #endif /* ! CONFIG_COAP_OSCORE_SUPPORT */ #ifdef CONFIG_COAP_WEBSOCKETS #define COAP_WS_SUPPORT 1 #else /* ! CONFIG_COAP_WEBSOCKETS */ #define COAP_WS_SUPPORT 0 #endif /* ! CONFIG_COAP_WEBSOCKETS */ #ifdef CONFIG_COAP_OBSERVE_PERSIST #define COAP_WITH_OBSERVE_PERSIST 1 #else /* ! CONFIG_COAP_OBSERVE_PERSIST */ #define COAP_WITH_OBSERVE_PERSIST 0 #endif /* ! CONFIG_COAP_OBSERVE_PERSIST */ #ifdef CONFIG_COAP_Q_BLOCK #define COAP_Q_BLOCK_SUPPORT 1 #else /* ! CONFIG_COAP_Q_BLOCK */ #define COAP_Q_BLOCK_SUPPORT 0 #endif /* ! CONFIG_COAP_Q_BLOCK */ #ifdef CONFIG_COAP_THREAD_RECURSIVE_CHECK #define COAP_THREAD_RECURSIVE_CHECK 1 #else /* ! CONFIG_COAP_THREAD_RECURSIVE_CHECK */ #define COAP_THREAD_RECURSIVE_CHECK 0 #endif /* ! CONFIG_COAP_THREAD_RECURSIVE_CHECK */ #ifdef CONFIG_COAP_THREAD_SAFE #define COAP_THREAD_SAFE 1 #else /* ! CONFIG_COAP_THREAD_SAFE */ #define COAP_THREAD_SAFE 0 #endif /* ! CONFIG_COAP_THREAD_SAFE */ #ifdef CONFIG_COAP_DEBUGGING #define COAP_MAX_LOGGING_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL #else /* ! CONFIG_COAP_DEBUGGING */ #define COAP_MAX_LOGGING_LEVEL 0 #endif /* ! CONFIG_COAP_DEBUGGING */ #endif /* COAP_CONFIG_H_ */ ================================================ FILE: coap/port/include/coap_config_posix.h ================================================ /* * libcoap configure implementation for ESP32 platform. * * Uses libcoap software implementation for failover when concurrent * configure operations are in use. * * coap.h -- main header file for CoAP stack of libcoap * * Copyright (C) 2010-2012,2015-2025 Olaf Bergmann * 2015 Carsten Schoenert * * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD * * This file is part of the CoAP library libcoap. Please see README for terms * of use. */ #ifndef COAP_CONFIG_POSIX_H_ #define COAP_CONFIG_POSIX_H_ #ifdef WITH_POSIX #include #include #include #include "lwip/init.h" #define HAVE_SYS_SOCKET_H #define HAVE_MALLOC #define HAVE_ARPA_INET_H #define HAVE_TIME_H #define HAVE_NETDB_H #define HAVE_NETINET_IN_H #define HAVE_STRUCT_CMSGHDR #define HAVE_PTHREAD_H #define HAVE_PTHREAD_MUTEX_LOCK #define HAVE_GETRANDOM #define ipi_spec_dst ipi_addr struct in6_pktinfo { struct in6_addr ipi6_addr; /* src/dst IPv6 address */ unsigned int ipi6_ifindex; /* send/recv interface index */ }; #if LWIP_VERSION < 0x02020000 #define IN6_IS_ADDR_V4MAPPED(a) \ ((((__const uint32_t *) (a))[0] == 0) \ && (((__const uint32_t *) (a))[1] == 0) \ && (((__const uint32_t *) (a))[2] == htonl (0xffff))) #endif // LWIP_VERSION < 0x02020000 /* As not defined, just need to define is as something innocuous */ #define IPV6_PKTINFO IPV6_CHECKSUM #define PACKAGE_NAME "libcoap-posix" #define PACKAGE_VERSION "4.3.5" #ifdef CONFIG_MBEDTLS_TLS_ENABLED #define COAP_WITH_LIBMBEDTLS 1 #endif /* CONFIG_MBEDTLS_TLS_ENABLED */ #define COAP_DEFAULT_MAX_PDU_RX_SIZE CONFIG_LWIP_TCP_MSL #define COAP_CONSTRAINED_STACK 1 #define ESPIDF_VERSION #define gai_strerror(x) "gai_strerror() not supported" #endif /* WITH_POSIX */ #endif /* COAP_CONFIG_POSIX_H_ */ ================================================ FILE: coap/sbom_libcoap.yml ================================================ name: libcoap version: 4.3.5 cpe: cpe:2.3:a:libcoap:libcoap:{}:*:*:*:*:*:*:* supplier: 'Organization: libcoap ' description: A CoAP (RFC 7252) implementation in C url: https://github.com/obgm/libcoap hash: d9b4031ee61df1a40ecec46fe3817a1f985b3919 cve-exclude-list: - cve: CVE-2024-31031 reason: Resolved in version 4.3.5-rc1 - cve: CVE-2023-51847 reason: Resolved in version 4.3.5-rc1 - cve: CVE-2024-46304 reason: Resolved in version 4.3.5-rc3 - cve: CVE-2025-50518 reason: Not applicable as per comment https://github.com/obgm/libcoap/issues/1724#issuecomment-3296780541 - cve: CVE-2025-59391 reason: Resolved in version 4.3.5a - cve: CVE-2025-65493 reason: Resolved in version 4.3.5a - cve: CVE-2025-65494 reason: Resolved in version 4.3.5a - cve: CVE-2025-65495 reason: Resolved in version 4.3.5a - cve: CVE-2025-65496 reason: Resolved in version 4.3.5a - cve: CVE-2025-65497 reason: Resolved in version 4.3.5a - cve: CVE-2025-65498 reason: Resolved in version 4.3.5a - cve: CVE-2025-65499 reason: Resolved in version 4.3.5a - cve: CVE-2025-65500 reason: Resolved in version 4.3.5a - cve: CVE-2025-65501 reason: Resolved in version 4.3.5a - cve: CVE-2025-34468 reason: Resolved in version 4.3.5a - cve: CVE-2026-29013 reason: Resolved in version 4.3.5b ================================================ FILE: coap/sdkconfig.rename ================================================ # sdkconfig replacement configurations for deprecated options formatted as # CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION # Compiler options CONFIG_COAP_MBEDTLS_DEBUG CONFIG_COAP_DEBUGGING ================================================ FILE: conftest.py ================================================ from _pytest.fixtures import FixtureRequest from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture import pytest import os import logging import typing as t @pytest.hookimpl(tryfirst=True) # run early def pytest_ignore_collect(collection_path, config): ignoring = config.getoption("ignore") or [] for pattern in ignoring: if collection_path.match(pattern) or str(collection_path).startswith(pattern): print(f"pytest would ignore: {collection_path}") return True for glob_pattern in config.getoption("ignore_glob") or []: if collection_path.match(glob_pattern): print(f"pytest would ignore by glob: {collection_path}") return True return False @pytest.fixture @multi_dut_argument def config(request: FixtureRequest) -> str: """Fixture that provides the configuration for tests. :param request: Pytest fixture request :returns: Configuration string, defaults to 'default' if not specified """ return getattr(request, 'param', None) or 'default' @pytest.fixture @multi_dut_fixture def build_dir( request: FixtureRequest, app_path: str, target: t.Optional[str], config: t.Optional[str], ) -> str: """Find a valid build directory based on priority rules. Checks local build directories in the following order: 1. build__ 2. build_ 3. build_ 4. build :param request: Pytest fixture request :param app_path: Path to the application :param target: Target being used :param config: Configuration being used :returns: Valid build directory name, or skips the test if no build directory is found """ check_dirs = [] build_dir_arg = request.config.getoption('build_dir') if build_dir_arg: check_dirs.append(build_dir_arg) if target is not None and config is not None: check_dirs.append(f'build_{target}_{config}') if target is not None: check_dirs.append(f'build_{target}') if config is not None: check_dirs.append(f'build_{config}') check_dirs.append('build') for check_dir in check_dirs: binary_path = os.path.join(app_path, check_dir) if os.path.isdir(binary_path): logging.info(f'Found valid binary path: {binary_path}') return check_dir logging.warning('Checking binary path: %s... missing... trying another location', binary_path) pytest.skip( f'No valid build directory found (checked: {", ".join(check_dirs)}). ' f'Build the binary via "idf.py -B {check_dirs[0]} build" to enable this test.' ) ================================================ FILE: coremark/.build-test-rules.yml ================================================ coremark/examples: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target." ================================================ FILE: coremark/CMakeLists.txt ================================================ set(srcs coremark/core_list_join.c coremark/core_main.c coremark/core_matrix.c coremark/core_state.c coremark/core_util.c port/core_portme.c ) if(NOT CMAKE_BUILD_EARLY_EXPANSION) configure_file(linker.lf.in ${CMAKE_CURRENT_BINARY_DIR}/linker.lf) endif() idf_component_register(SRCS ${srcs} PRIV_INCLUDE_DIRS port coremark LDFRAGMENTS ${CMAKE_CURRENT_BINARY_DIR}/linker.lf PRIV_REQUIRES esp_timer) # compile coremark component with -O3 flag (will override the optimization level which is set globally) # set "-fjump-tables" and "-ftree-switch-conversion" explicitly, since IDF build system disables them by default set(component_compile_options "-O3" "-fjump-tables" "-ftree-switch-conversion") target_compile_options(${COMPONENT_LIB} PRIVATE ${component_compile_options}) # Get the compilation options and store them as a target property set(compile_options_list "$;$;${component_compile_options}") set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILER_OPT "${compile_options_list}") # Generate core_portme.h file, expanding "COMPILER_OPT" and "COMPILER_VER" generator expressions target_include_directories(${COMPONENT_LIB} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/core_portme.h INPUT ${CMAKE_CURRENT_SOURCE_DIR}/port/core_portme.h.in TARGET ${COMPONENT_LIB}) ================================================ FILE: coremark/LICENSE ================================================ # COREMARK® ACCEPTABLE USE AGREEMENT This ACCEPTABLE USE AGREEMENT (this “Agreement”) is offered by Embedded Microprocessor Benchmark Consortium, a California nonprofit corporation (“Licensor”), to users of its CoreMark® software (“Licensee”) exclusively on the following terms. Licensor offers benchmarking software (“Software”) pursuant to an open source license, but carefully controls use of its benchmarks and their associated goodwill. Licensor has registered its trademark in one of the benchmarks available through the Software, COREMARK, Ser. No. 85/487,290; Reg. No. 4,179,307 (the “Trademark”), and promotes the use of a standard metric as a benchmark for assessing the performance of embedded systems. Solely on the terms described herein, Licensee may use and display the Trademark in connection with the generation of data regarding measurement and analysis of computer and embedded system benchmarking via the Software (the “Licensed Use”). ## Article 1 – License Grant. 1.1. License. Subject to the terms and conditions of this Agreement, Licensor hereby grants to Licensee, and Licensee hereby accepts from Licensor, a personal, non-exclusive, royalty-free, revocable right and license to use and display the Trademark during the term of this Agreement (the “Term”), solely and exclusively in connection with the Licensed Use. During the Term, Licensee (i) shall not modify or otherwise create derivative works of the Trademark, and (ii) may use the Trademark only to the extent permitted under this License. Neither Licensee nor any affiliate or agent thereof shall otherwise use the Trademark without the prior express written consent of Licensor, which may be withheld in its sole and absolute discretion. All rights not expressly granted to Licensee hereunder shall remain the exclusive property of Licensor. 1.2. Modifications to the Software. Licensee shall not use the Trademark in connection with any use of a modified, derivative, or otherwise altered copy of the Software. 1.3. Licensor’s Use. Nothing in this Agreement shall preclude Licensor or any of its successors or assigns from using or permitting other entities to use the Trademark, whether or not such entity directly or indirectly competes or conflicts with Licensee’s Licensed Use in any manner. 1.4. Term and Termination. This Agreement is perpetual unless terminated by either of the parties. Licensee may terminate this Agreement for convenience, without cause or liability, for any reason or for no reason whatsoever, upon ten (10) business days written notice. Licensor may terminate this Agreement effective immediately upon notice of breach. Upon termination, Licensee shall immediately remove all implementations of the Trademark from the Licensed Use, and delete all digitals files and records of all materials related to the Trademark. ## Article 2 – Ownership. 2.1. Ownership. Licensee acknowledges and agrees that Licensor is the owner of all right, title, and interest in and to the Trademark, and all such right, title, and interest shall remain with Licensor. Licensee shall not contest, dispute, challenge, oppose, or seek to cancel Licensor’s right, title, and interest in and to the Trademark. Licensee shall not prosecute any application for registration of the Trademark. Licensee shall display appropriate notices regarding ownership of the Trademark in connection with the Licensed Use. 2.2. Goodwill. Licensee acknowledges that Licensee shall not acquire any right, title, or interest in the Trademark by virtue of this Agreement other than the license granted hereunder, and disclaims any such right, title, interest, or ownership. All goodwill and reputation generated by Licensee’s use of the Trademark shall inure to the exclusive benefit of Licensor. Licensee shall not by any act or omission use the Trademark in any manner that disparages or reflects adversely on Licensor or its Licensed Use or reputation. Licensee shall not take any action that would interfere with or prejudice Licensor’s ownership or registration of the Trademark, the validity of the Trademark or the validity of the license granted by this Agreement. If Licensor determines and notifies Licensee that any act taken in connection with the Licensed Use (i) is inaccurate, unlawful or offensive to good taste; (ii) fails to provide for proper trademark notices, or (iii) otherwise violates Licensee’s obligations under this Agreement, the license granted under this Agreement shall terminate. ## Article 3 – Indemnification. 3.1. Indemnification Generally. Licensee agrees to indemnify, defend, and hold harmless (collectively “indemnify” or “indemnification”) Licensor, including Licensor’s members, managers, officers, and employees (collectively “Related Persons”), from and against, and pay or reimburse Licensor and such Related Persons for, any and all third-party actions, claims, demands, proceedings, investigations, inquiries (collectively, “Claims”), and any and all liabilities, obligations, fines, deficiencies, costs, expenses, royalties, losses, and damages (including reasonable outside counsel fees and expenses) associated with such Claims, to the extent that such Claim arises out of (i) Licensee’s material breach of this Agreement, or (ii) any allegation(s) that Licensee’s actions infringe or violate any third-party intellectual property right, including without limitation, any U.S. copyright, patent, or trademark, or are otherwise found to be tortious or criminal (whether or not such indemnified person is a named party in a legal proceeding). 3.2. Notice and Defense of Claims. Licensor shall promptly notify Licensee of any Claim for which indemnification is sought, following actual knowledge of such Claim, provided however that the failure to give such notice shall not relieve Licensee of its obligations hereunder except to the extent that Licensee is materially prejudiced by such failure. In the event that any third-party Claim is brought, Licensee shall have the right and option to undertake and control the defense of such action with counsel of its choice, provided however that (i) Licensor at its own expense may participate and appear on an equal footing with Licensee in the defense of any such Claim, (ii) Licensor may undertake and control such defense in the event of the material failure of Licensee to undertake and control the same; and (iii) the defense of any Claim relating to the intellectual property rights of Licensor or its licensors and any related counterclaims shall be solely controlled by Licensor with counsel of its choice. Licensee shall not consent to judgment or concede or settle or compromise any Claim without the prior written approval of Licensor (whose approval shall not be unreasonably withheld), unless such concession or settlement or compromise includes a full and unconditional release of Licensor and any applicable Related Persons from all liabilities in respect of such Claim. ## Article 4 – Miscellaneous. 4.1. Relationship of the Parties. This Agreement does not create a partnership, franchise, joint venture, agency, fiduciary, or employment relationship between the parties. 4.2. No Third-Party Beneficiaries. Except for the rights of Related Persons under Article 3 (Indemnification), there are no third-party beneficiaries to this Agreement. 4.3. Assignment. Licensee’s rights hereunder are non-assignable, and may not be sublicensed. 4.4. Equitable Relief. Licensee acknowledges that the remedies available at law for any breach of this Agreement will, by their nature, be inadequate. Accordingly, Licensor may obtain injunctive relief or other equitable relief to restrain a breach or threatened breach of this Agreement or to specifically enforce this Agreement, without proving that any monetary damages have been sustained, and without the requirement of posting of a bond prior to obtaining such equitable relief. 4.5. Governing Law. This Agreement will be interpreted, construed, and enforced in all respects in accordance with the laws of the State of California, without reference to its conflict of law principles. 4.6. Attorneys’ Fees. If any legal action, arbitration or other proceeding is brought for the enforcement of this Agreement, or because of an alleged dispute, breach, default, or misrepresentation in connection with any of the provisions of this Agreement, the successful or prevailing party shall be entitled to recover its reasonable attorneys’ fees and other reasonable costs incurred in that action or proceeding, in addition to any other relief to which it may be entitled. 4.7. Amendment; Waiver. This Agreement may not be amended, nor may any rights under it be waived, except in writing by Licensor. 4.8. Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be contrary to law, the provision shall be modified by the court and interpreted so as best to accomplish the objectives of the original provision to the fullest extent permitted by law, and the remaining provisions of this Agreement shall remain in effect. 4.9. Entire Agreement. This Agreement constitutes the entire agreement between the parties and supersedes all prior and contemporaneous agreements, proposals or representations, written or oral, concerning its subject matter. # Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ ## TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: coremark/README.md ================================================ [![Component Registry](https://components.espressif.com/components/espressif/coremark/badge.svg)](https://components.espressif.com/components/espressif/coremark) # Coremark for ESP-IDF This component is a port of [CoreMark® benchmark](https://github.com/eembc/coremark) to ESP-IDF. It handles compiling CoreMark source files, providing necessary functions to measure timestamps, and enables various compiler to get higher performance. # Using the benchmark If you want to run the benchmark and see the results, create a demo project from the provided example: ```bash idf.py create-project-from-example "espressif/coremark:coremark_example" ``` You can then build the project in `coremark_example` directory as usual. For example, to build the project for ESP32-C3: ```bash cd coremark_example idf.py set-target esp32c3 idf.py build idf.py -p PORT flash monitor ``` (where `PORT` is the name of the serial port) Refer to ESP-IDF Getting Started Guide for more information about compiling and running a project. # Using as a component You can also integrate CoreMark code into you project by adding dependency on `espressif/coremark` component: ```bash idf.py add-dependency espressif/coremark ``` CoreMark benchmark entry point is an `int main(void)` function, which you can call from your application. # Performance tweaks This example does the following things to improve the benchmark result: 1. Enables `-O3` compiler flag for CoreMark source files. 2. Adds `-fjump-tables -ftree-switch-conversion` compiler flags for CoreMark source files. This overrides `-fno-jump-tables -fno-tree-switch-conversion` flags which get set in ESP-IDF build system by default. 3. Places CoreMark code into internal instruction RAM using [linker.lf](linker.lf) file. For general information about optimizing performance of ESP-IDF applications, see the ["Performance" chapter of the Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/performance/index.html). # Example output Running on ESP32-C3, we can obtain the following output: ``` Running coremark... 2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 14661 Total time (secs): 14.661000 Iterations/Sec : 409.249028 Iterations : 6000 Compiler version : GCC12.2.0 Compiler flags : -ffunction-sections -fdata-sections -gdwarf-4 -ggdb -nostartfiles -nostartfiles -Og -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -std=gnu17 -O3 -fjump-tables -ftree-switch-conversion Memory location : IRAM seedcrc : 0xe9f5 [0]crclist : 0xe714 [0]crcmatrix : 0x1fd7 [0]crcstate : 0x8e3a [0]crcfinal : 0xa14c Correct operation validated. See README.md for run and reporting rules. CoreMark 1.0 : 409.249028 / GCC12.2.0 -ffunction-sections -fdata-sections -gdwarf-4 -ggdb -nostartfiles -nostartfiles -Og -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -std=gnu17 -O3 -fjump-tables -ftree-switch-conversion / IRAM CPU frequency: 160 MHz ``` # Legal CoreMark is a trademark of EEMBC and EEMBC is a registered trademark of the Embedded Microprocessor Benchmark Consortium. CoreMark source code is Copyright (c) 2009 EEMBC. The source code is distributed under Apache 2.0 license with additional restrictions with regards to the use of the benchmark. See [LICENSE.md](LICENSE.md) for more details. Any additional code in this component ("port layer") is Copyright (c) 2022-2023 Espressif Systems (Shanghai) Co. Ltd. and is licensed under Apache 2.0 license. ================================================ FILE: coremark/examples/coremark_example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(coremark_example) ================================================ FILE: coremark/examples/coremark_example/README.md ================================================ # CoreMark example This example can be used to run CoreMark benchmark on an Espressif chip. The example doesn't require any special hardware and can run on any development board. ## Building and running Run the application as usual for an ESP-IDF project. For example, for ESP32-C3: ``` idf.py set-target esp32c3 idf.py -p PORT flash monitor ``` After launching, the benchmark takes a few seconds to run, please be patient. ## Example output Running on ESP32-C3, we can obtain the following output: ``` Running coremark... 2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 14661 Total time (secs): 14.661000 Iterations/Sec : 409.249028 Iterations : 6000 Compiler version : GCC12.2.0 Compiler flags : -ffunction-sections -fdata-sections -gdwarf-4 -ggdb -nostartfiles -nostartfiles -Og -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -std=gnu17 -O3 -fjump-tables -ftree-switch-conversion Memory location : IRAM seedcrc : 0xe9f5 [0]crclist : 0xe714 [0]crcmatrix : 0x1fd7 [0]crcstate : 0x8e3a [0]crcfinal : 0xa14c Correct operation validated. See README.md for run and reporting rules. CoreMark 1.0 : 409.249028 / GCC12.2.0 -ffunction-sections -fdata-sections -gdwarf-4 -ggdb -nostartfiles -nostartfiles -Og -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -std=gnu17 -O3 -fjump-tables -ftree-switch-conversion / IRAM CPU frequency: 160 MHz ``` ================================================ FILE: coremark/examples/coremark_example/main/CMakeLists.txt ================================================ idf_component_register(SRCS coremark_example_main.c PRIV_REQUIRES coremark) ================================================ FILE: coremark/examples/coremark_example/main/coremark_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include "sdkconfig.h" // In IDF v5.x, there is a common CPU frequency option for all targets #if defined(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ) #define CPU_FREQ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ // In IDF v4.x, CPU frequency options were target-specific #elif defined(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ) #define CPU_FREQ CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ #elif defined(CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ) #define CPU_FREQ CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ #elif defined(CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ) #define CPU_FREQ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ #elif defined(CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ) #define CPU_FREQ CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ #endif // Entry point of coremark benchmark extern int main(void); void app_main(void) { printf("Running coremark...\n"); main(); printf("CPU frequency: %d MHz\n", CPU_FREQ); } ================================================ FILE: coremark/examples/coremark_example/main/idf_component.yml ================================================ description: Coremark benchmark application dependencies: espressif/coremark: version: "*" override_path: '../../../' ================================================ FILE: coremark/examples/coremark_example/pytest_coremark.py ================================================ import pytest @pytest.mark.generic def test_coremark(dut): dut.expect_exact("Running coremark...") dut.expect_exact("Correct operation validated", timeout=30) ================================================ FILE: coremark/examples/coremark_example/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: coremark/examples/coremark_example/sdkconfig.defaults.esp32 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y ================================================ FILE: coremark/examples/coremark_example/sdkconfig.defaults.esp32s2 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y ================================================ FILE: coremark/examples/coremark_example/sdkconfig.defaults.esp32s3 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y ================================================ FILE: coremark/idf_component.yml ================================================ version: "1.1.0~2" description: CoreMark Benchmark url: https://github.com/espressif/idf-extra-components/tree/master/coremark issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git documentation: https://www.eembc.org/coremark/ dependencies: idf: ">=5.0" ================================================ FILE: coremark/linker.lf.in ================================================ [mapping:coremark] archive: lib${COMPONENT_NAME}.a entries: * (noflash) ================================================ FILE: coremark/port/core_portme.c ================================================ /* * SPDX-FileCopyrightText: 2018 EEMBC * SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "coremark.h" #include "core_portme.h" #include "sdkconfig.h" #include #include #include "esp_timer.h" #if VALIDATION_RUN volatile ee_s32 seed1_volatile = 0x3415; volatile ee_s32 seed2_volatile = 0x3415; volatile ee_s32 seed3_volatile = 0x66; #endif #if PERFORMANCE_RUN volatile ee_s32 seed1_volatile = 0x0; volatile ee_s32 seed2_volatile = 0x0; volatile ee_s32 seed3_volatile = 0x66; #endif #if PROFILE_RUN volatile ee_s32 seed1_volatile = 0x8; volatile ee_s32 seed2_volatile = 0x8; volatile ee_s32 seed3_volatile = 0x8; #endif volatile ee_s32 seed4_volatile = 0; volatile ee_s32 seed5_volatile = 0; /* Porting : Timing functions How to capture time and convert to seconds must be ported to whatever is supported by the platform. e.g. Read value from on board RTC, read value from cpu clock cycles performance counter etc. Sample implementation for standard time.h and windows.h definitions included. */ /* Define : TIMER_RES_DIVIDER Divider to trade off timer resolution and total time that can be measured. Use lower values to increase resolution, but make sure that overflow does not occur. If there are issues with the return value overflowing, increase this value. */ #define NSECS_PER_SEC CLOCKS_PER_SEC #define CORETIMETYPE ee_u32 #define GETMYTIME(_t) (*_t=(ee_u32)(esp_timer_get_time()/1000)) #define MYTIMEDIFF(fin,ini) ((fin)-(ini) ) /* 32-bit Timer overflow */ #define TIMER_RES_DIVIDER 1 #define SAMPLE_TIME_IMPLEMENTATION 1 #define EE_TICKS_PER_SEC (1000) /** Define Host specific (POSIX), or target specific global time variables. */ static CORETIMETYPE start_time_val, stop_time_val; /* Function : start_time This function will be called right before starting the timed portion of the benchmark. Implementation may be capturing a system timer (as implemented in the example code) or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. */ void start_time(void) { GETMYTIME(&start_time_val ); } /* Function : stop_time This function will be called right after ending the timed portion of the benchmark. Implementation may be capturing a system timer (as implemented in the example code) or other system parameters - e.g. reading the current value of cpu cycles counter. */ void stop_time(void) { GETMYTIME(&stop_time_val ); } /* Function : get_time Return an abstract "ticks" number that signifies time on the system. Actual value returned may be cpu cycles, milliseconds or any other value, as long as it can be converted to seconds by . This methodology is taken to accommodate any hardware or simulated platform. The sample implementation returns millisecs by default, and the resolution is controlled by */ CORE_TICKS get_time(void) { CORE_TICKS elapsed = (CORE_TICKS)(MYTIMEDIFF(stop_time_val, start_time_val)); return elapsed; } /* Function : time_in_secs Convert the value returned by get_time to seconds. The type is used to accommodate systems with no support for floating point. Default implementation implemented by the EE_TICKS_PER_SEC macro above. */ secs_ret time_in_secs(CORE_TICKS ticks) { secs_ret retval = ((secs_ret)ticks) / EE_TICKS_PER_SEC; return retval; } ee_u32 default_num_contexts = 1; /* Function : portable_init Target specific initialization code Test for some common mistakes. */ void portable_init(core_portable *p, int *argc, char *argv[]) { if (sizeof(ee_ptr_int) != sizeof(ee_u8 *)) { ee_printf("ERROR! Please define ee_ptr_int to a type that holds a pointer!\n"); } if (sizeof(ee_u32) != 4) { ee_printf("ERROR! Please define ee_u32 to a 32b unsigned type!\n"); } p->portable_id = 1; } /* Function : portable_fini Target specific final code */ void portable_fini(core_portable *p) { p->portable_id = 0; } ================================================ FILE: coremark/port/core_portme.h.in ================================================ /* * SPDX-FileCopyrightText: 2018 EEMBC * SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ /* Topic : Description This file contains configuration constants required to execute on different platforms */ #ifndef CORE_PORTME_H #define CORE_PORTME_H /************************/ /* Data types and settings */ /************************/ #include #include "esp_idf_version.h" /* Configuration : HAS_FLOAT Define to 1 if the platform supports floating point. */ #ifndef HAS_FLOAT #define HAS_FLOAT 1 #endif /* Configuration : HAS_TIME_H Define to 1 if platform has the time.h header file, and implementation of functions thereof. */ #ifndef HAS_TIME_H #define HAS_TIME_H 0 #endif /* Configuration : USE_CLOCK Define to 1 if platform has the time.h header file, and implementation of functions thereof. */ #ifndef USE_CLOCK #define USE_CLOCK 0 #endif /* Configuration : HAS_STDIO Define to 1 if the platform has stdio.h. */ #ifndef HAS_STDIO #define HAS_STDIO 1 #endif /* Configuration : HAS_PRINTF Define to 1 if the platform has stdio.h and implements the printf function. */ #ifndef HAS_PRINTF #define HAS_PRINTF 1 #endif /* Definitions : COMPILER_VERSION, COMPILER_FLAGS, MEM_LOCATION Initialize these strings per platform */ #ifndef COMPILER_VERSION #ifdef __GNUC__ #define COMPILER_VERSION "GCC"__VERSION__ #else #define COMPILER_VERSION "Please put compiler version here (e.g. gcc 4.1)" #endif #endif #ifndef COMPILER_FLAGS #define COMPILER_FLAGS "$>,EXCLUDE,^-(([DWI])|(fmacro)).*>, >" #endif #ifndef MEM_LOCATION #define MEM_LOCATION "IRAM" #endif /* Data Types : To avoid compiler issues, define the data types that need to be used for 8b, 16b and 32b in . *Important* : ee_ptr_int needs to be the data type used to hold pointers, otherwise coremark may fail!!! */ typedef int16_t ee_s16; typedef uint16_t ee_u16; typedef int ee_s32; typedef double ee_f32; typedef uint8_t ee_u8; typedef unsigned long ee_u32; typedef ee_u32 ee_ptr_int; #define ee_size_t size_t /* Configuration : CORE_TICKS Define type of return from the timing functions. */ typedef ee_u32 CORE_TICKS; /* align_mem : This macro is used to align an offset to point to a 32b value. It is used in the Matrix algorithm to initialize the input memory blocks. */ #define align_mem(x) (void *)(4 + (((ee_ptr_int)(x) - 1) & ~3)) /* Configuration : SEED_METHOD Defines method to get seed values that cannot be computed at compile time. Valid values : SEED_ARG - from command line. SEED_FUNC - from a system function. SEED_VOLATILE - from volatile variables. */ #ifndef SEED_METHOD #define SEED_METHOD SEED_VOLATILE #endif /* Configuration : MEM_METHOD Defines method to get a block of memry. Valid values : MEM_MALLOC - for platforms that implement malloc and have malloc.h. MEM_STATIC - to use a static memory array. MEM_STACK - to allocate the data block on the stack (NYI). */ #ifndef MEM_METHOD #define MEM_METHOD MEM_STATIC #endif /* Configuration : MULTITHREAD Define for parallel execution Valid values : 1 - only one context (default). N>1 - will execute N copies in parallel. Note : If this flag is defined to more then 1, an implementation for launching parallel contexts must be defined. Two sample implementations are provided. Use or to enable them. It is valid to have a different implementation of and in , to fit a particular architecture. */ #ifndef MULTITHREAD #define MULTITHREAD 1 #define USE_PTHREAD 0 #define USE_FORK 0 #define USE_SOCKET 0 #endif /* Configuration : MAIN_HAS_NOARGC Needed if platform does not support getting arguments to main. Valid values : 0 - argc/argv to main is supported 1 - argc/argv to main is not supported Note : This flag only matters if MULTITHREAD has been defined to a value greater then 1. */ #ifndef MAIN_HAS_NOARGC #define MAIN_HAS_NOARGC 1 #endif /* Configuration : MAIN_HAS_NORETURN Needed if platform does not support returning a value from main. Valid values : 0 - main returns an int, and return value will be 0. 1 - platform does not support returning a value from main */ #ifndef MAIN_HAS_NORETURN #define MAIN_HAS_NORETURN 0 #endif /* Variable : default_num_contexts Not used for this simple port, must cintain the value 1. */ extern ee_u32 default_num_contexts; typedef struct CORE_PORTABLE_S { ee_u8 portable_id; } core_portable; /* target specific init/fini */ void portable_init(core_portable *p, int *argc, char *argv[]); void portable_fini(core_portable *p); #if !defined(PROFILE_RUN) && !defined(PERFORMANCE_RUN) && !defined(VALIDATION_RUN) #if (TOTAL_DATA_SIZE==1200) #define PROFILE_RUN 1 #elif (TOTAL_DATA_SIZE==2000) #define PERFORMANCE_RUN 1 #else #define VALIDATION_RUN 1 #endif #endif #endif /* CORE_PORTME_H */ ================================================ FILE: dhara/CMakeLists.txt ================================================ idf_component_register(INCLUDE_DIRS dhara SRC_DIRS "dhara/dhara") ================================================ FILE: dhara/LICENSE ================================================ Dhara - NAND flash management layer Copyright (C) 2013 Daniel Beer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: dhara/README.md ================================================ # NAND Flash translation layer for small MCUs This component is an ESP-IDF wrapper around [dhara library](https://github.com/dlbeer/dhara) For the details, please refer to the official documentation: https://github.com/dlbeer/dhara/blob/master/README ================================================ FILE: dhara/idf_component.yml ================================================ version: "0.1.0" description: NAND Flash translation layer url: https://github.com/espressif/idf-extra-components/tree/master/dhara issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git sbom: manifests: - path: sbom_dhara.yml dest: dhara ================================================ FILE: dhara/sbom_dhara.yml ================================================ name: dhara version: 1b166e41b74b4a62ee6001ba5fab7a8805e80ea2 cpe: cpe:2.3:a:dhara:dhara:{}:*:*:*:*:*:*:* supplier: 'Organization: dhara' description: NAND Flash translation layer for small MCUs url: https://github.com/dlbeer/dhara hash: 1b166e41b74b4a62ee6001ba5fab7a8805e80ea2 ================================================ FILE: eigen/CMakeLists.txt ================================================ idf_component_register( # We need the dummy source file so that the component # library is not an interface library. This allows to # get the list of include directories from other components # via INCLUDE_DIRECTORIES property later on. SRCS dummy.c) # Determine compilation flags used for building Eigen # Flags inherited from IDF build system and other IDF components: set(idf_include_directories $) set(includes "-I$") if(CONFIG_COMPILER_OPTIMIZATION_DEFAULT) set(opt_args -DCMAKE_BUILD_TYPE=Debug) elseif(CONFIG_COMPILER_OPTIMIZATION_SIZE) set(opt_args -DCMAKE_BUILD_TYPE=MinSizeRel) elseif(CONFIG_COMPILER_OPTIMIZATION_PERF) set(opt_args -DCMAKE_BUILD_TYPE=Release) elseif(CONFIG_COMPILER_OPTIMIZATION_NONE) set(opt_args -DCMAKE_BUILD_TYPE=Debug) else() message(FATAL_ERROR "Unsupported optimization level") endif() include(ExternalProject) # Build Eigen in this directory: set(BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/eigen-build) # Add Eigen as a subproject. ExternalProject_Add(eigen_proj SOURCE_DIR ${COMPONENT_DIR}/eigen BINARY_DIR ${BINARY_DIR} # These two options are set so that Ninja immediately outputs # the subproject build to the terminal. Otherwise it looks like the # build process "hangs" for too long until Eigen build is complete. USES_TERMINAL_CONFIGURE TRUE USES_TERMINAL_BUILD TRUE # Arguments to pass to Eigen CMake invocation: CMAKE_ARGS ${opt_args} -DCMAKE_INSTALL_PREFIX=${BINARY_DIR}/install -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} ) add_dependencies(${COMPONENT_LIB} eigen_proj) # Attach generated libraries and header files to an interface library: add_library(eigen_interface_lib INTERFACE) set_target_properties(eigen_interface_lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${BINARY_DIR}/install/include) add_dependencies(eigen_interface_lib eigen_proj) # Finally, link the interface library to the component library: target_link_libraries(${COMPONENT_LIB} INTERFACE eigen_interface_lib) ================================================ FILE: eigen/LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: eigen/README.md ================================================ # Eigen for ESP-IDF [![Component Registry](https://components.espressif.com/components/espressif/eigen/badge.svg)](https://components.espressif.com/components/espressif/eigen) This component ports Eigen library into the esp-idf. **Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.** For more information go to http://eigen.tuxfamily.org/. For ***pull request***, ***bug reports***, and ***feature requests***, go to https://gitlab.com/libeigen/eigen. ================================================ FILE: eigen/dummy.c ================================================ ================================================ FILE: eigen/examples/svd/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(Eigen_SVD) ================================================ FILE: eigen/examples/svd/README.md ================================================ # Eigen example for IDF Eigen component This example shows how to use Eigen library in ESP-IDF projects. The example does matrix multiplication and single value decomposition (SVD) transformation. This example does not require any special hardware, and can be run on any common development board. To run the example on target please run: ``` idf.py -p PORT flash monitor ``` ================================================ FILE: eigen/examples/svd/main/CMakeLists.txt ================================================ idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "." REQUIRES eigen) ================================================ FILE: eigen/examples/svd/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/eigen: version: "^3.4.0" # This line define the local path of the eigen component because this # example is part of the eigen component. This line is optional. override_path: "../../.." ## Required IDF version idf: version: ">=4.3.0" ================================================ FILE: eigen/examples/svd/main/main.cpp ================================================ /* * SPDX-FileCopyrightText: 2010-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include extern "C" void app_main(void); static void multiply2Matrices() { Eigen::MatrixXf M(2, 2); Eigen::MatrixXf V(2, 2); for (int i = 0; i <= 1; i++) { for (int j = 0; j <= 1; j++) { M(i, j) = 1; V(i, j) = 2; } } Eigen::MatrixXf Result = M * V; std::cout << "MatrixXf Result = " << std::endl << Result << std::endl; } static void runSVD() { Eigen::MatrixXf C; C.setRandom(27, 18); Eigen::JacobiSVD svd(C, Eigen::ComputeThinU | Eigen::ComputeThinV); Eigen::MatrixXf Cp = svd.matrixU() * svd.singularValues().asDiagonal() * svd.matrixV().transpose(); Eigen::MatrixXf diff = Cp - C; std::cout << "SDV matrix U: " << std::endl << svd.matrixU() << std::endl; std::cout << "SDV singularValues: " << std::endl << svd.singularValues().transpose() << std::endl; std::cout << "SDV matrix V: " << std::endl << svd.matrixV() << std::endl; std::cout << "diff:\n" << diff.array().abs().sum() << "\n"; } void app_main(void) { std::cout << "Eigen example." << std::endl; multiply2Matrices(); runSVD(); std::cout << "Example finished!" << std::endl; } ================================================ FILE: eigen/examples/svd/sdkconfig.defaults ================================================ # # Common ESP-related # # CONFIG_ESP_TIMER_PROFILING is not set CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384 ================================================ FILE: eigen/idf_component.yml ================================================ version: "3.4.0~2" description: Eigen port to ESP url: https://github.com/espressif/idf-extra-components/tree/master/eigen dependencies: idf: ">=4.3.0" ================================================ FILE: esp_cli/.build-test-rules.yml ================================================ esp_cli/host_test: enable: - if: IDF_TARGET == "linux" reason: "Sufficient to test on Linux target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 4 reason: "those versions of esp-idf do not support eventfd for linux target" esp_cli/test_apps: enable: - if: IDF_TARGET == "esp32s3" reason: "Need support for USB Serial JTAG" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 3 reason: "esp_vfs_fs_ops_t not available" ================================================ FILE: esp_cli/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) set(srcs "src/esp_cli.c") idf_component_register( SRCS ${srcs} INCLUDE_DIRS include REQUIRES esp_linenoise esp_cli_commands WHOLE_ARCHIVE) ================================================ FILE: esp_cli/Kconfig ================================================ menu "esp_cli configuration" config ESP_CLI_HAS_QUIT_CMD bool "Register quit command" default n help Register a static command "quit" that allows the user to return from the esp_cli main loop. The command is registered through the ESP_CLI_COMMAND_REGISTER macro provided by esp_cli_commands component and is placed in the dedicated flash section. endmenu ================================================ FILE: esp_cli/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright Espressif Systems Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_cli/README.md ================================================ # esp_cli Component The `esp_cli` component provides a **Runtime Evaluation Loop (REPL)** mechanism for ESP-IDF-based applications. It allows developers to build interactive command-line interfaces (CLI) that support user-defined commands, history management, and customizable callbacks for command execution. This component integrates with [`esp_linenoise`](../esp_linenoise) for line editing and input handling, and with [`esp_cli_commands`](../esp_cli_commands) for command parsing and execution. --- ## Features - Modular REPL management with explicit `start` and `stop` control - Integration with [`esp_linenoise`](../esp_linenoise) for input and history - Support for command sets through [`esp_cli_commands`](../esp_cli_commands) - Configurable callbacks for: - Pre-execution processing - Post-execution handling - On-stop and on-exit events - Thread-safe operation using FreeRTOS semaphores - Optional command history persistence to filesystem --- ## Usage A typical use case involves: 1. Initializing `esp_linenoise` and `esp_cli_commands` 2. Creating the esp_cli instance with `esp_cli_create()` 3. Running `esp_cli()` in a task 4. Starting and stopping the esp_cli using `esp_cli_start()` and `esp_cli_stop()` 5. Destroying the instance with `esp_cli_destroy()` when done ### Example ```c #include #include #include "driver/uart_vfs.h" #include "driver/uart.h" #include "esp_log.h" #include "esp_cli.h" #include "esp_cli_commands.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define EXAMPLE_COMMAND_MAX_LENGTH 128 static const char *TAG = "repl_example"; static int example_cmd_func(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { (void)context; /* this is NULL and useless for the help command */ (void)argc; (void)argv; const char example_cmd_msg[] = "example command output\n"; cmd_args->write_func(cmd_args->out_fd, example_cmd_msg, strlen(example_cmd_msg)); return 0; } static const char *example_cmd_hint(void *context) { (void)context; return "example cmd hint"; } static const char *example_cmd_glossary(void *context) { (void)context; return "example command glossary"; } static const char example_cmd_help_str[] = "example command help"; ESP_CLI_COMMAND_REGISTER(cmd, cmd, example_cmd_help_str, example_cmd_func, NULL, example_cmd_hint, example_cmd_glossary); static void example_completion_cb(const char *str, void *cb_ctx, esp_linenoise_completion_cb_t cb) { esp_cli_commands_get_completion(NULL, str, cb_ctx, cb); } static char *example_hints_cb(const char *str, int *color, int *bold) { /* return the hint of a given command */ return NULL; } static void example_free_hints_cb(void *ptr) { /* free the hint pointed at by the pointer in parameter */ } static void example_cli_task(void *arg) { esp_cli_handle_t repl_hdl = (esp_cli_handle_t)arg; // Run REPL loop (blocking until esp_cli_stop() is called) // The loop won't be reached until esp_cli_start() is called esp_cli(repl_hdl); ESP_LOGI(TAG, "esp_cli instance task exiting"); vTaskDelete(NULL); } static void example_init_io(void) { /* Drain stdout before reconfiguring it */ fflush(stdout); fsync(fileno(stdout)); #if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM) /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); /* Configure UART. Note that REF_TICK is used so that the baud rate remains * correct while APB frequency is changing in light sleep mode. */ const uart_config_t uart_config = { .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, #if SOC_UART_SUPPORT_REF_TICK .source_clk = UART_SCLK_REF_TICK, #elif SOC_UART_SUPPORT_XTAL_CLK .source_clk = UART_SCLK_XTAL, #endif }; /* Install UART driver for interrupt-driven reads and writes */ ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0) ); ESP_ERROR_CHECK( uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config) ); /* Tell VFS to use UART driver */ uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); #elif defined(CONFIG_ESP_CONSOLE_USB_CDC) /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); /* Enable blocking mode on stdin and stdout */ fcntl(fileno(stdout), F_SETFL, 0); fcntl(fileno(stdin), F_SETFL, 0); #elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ usb_serial_jtag_vfs_set_rx_line_endings(ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ usb_serial_jtag_vfs_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); /* Enable blocking mode on stdin and stdout */ fcntl(fileno(stdout), F_SETFL, 0); fcntl(fileno(stdin), F_SETFL, 0); usb_serial_jtag_driver_config_t jtag_config = { .tx_buffer_size = 256, .rx_buffer_size = 256, }; /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */ ESP_ERROR_CHECK( usb_serial_jtag_driver_install(&jtag_config)); /* Tell vfs to use usb-serial-jtag driver */ usb_serial_jtag_vfs_use_driver(); #else #error Unsupported console type #endif /* Disable buffering on stdin */ setvbuf(stdin, NULL, _IONBF, 0); } void app_main(void) { esp_err_t ret; esp_cli_handle_t cli = NULL; /* configure the IO used by the esp_cli */ example_init_io(); // Initialize esp_linenoise (mandatory) esp_linenoise_handle_t esp_linenoise_hdl = NULL; esp_linenoise_config_t esp_linenoise_config; esp_linenoise_config.prompt = ">"; esp_linenoise_config.max_cmd_line_length = EXAMPLE_COMMAND_MAX_LENGTH; esp_linenoise_config.history_max_length = 16; esp_linenoise_config.in_fd = STDIN_FILENO; esp_linenoise_config.out_fd = STDOUT_FILENO; esp_linenoise_config.allow_multi_line = true; esp_linenoise_config.allow_empty_line = true; esp_linenoise_config.allow_dumb_mode = false; esp_linenoise_config.completion_cb = example_completion_cb; esp_linenoise_config.hints_cb = example_hints_cb; esp_linenoise_config.free_hints_cb = example_free_hints_cb; esp_linenoise_config.read_bytes_cb = NULL; // use default read function esp_linenoise_config.write_bytes_cb = NULL; // use default write function esp_linenoise_config.history = NULL; ESP_ERROR_CHECK(esp_linenoise_create_instance(&esp_linenoise_config, &esp_linenoise_hdl)); // Initialize command set (optional) const char* cmd_set[1] = { "cmd" }; esp_cli_command_set_handle_t esp_cli_commands_cmd_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_set, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); esp_cli_config_t cli_cfg = { .linenoise_handle = esp_linenoise_hdl, .command_set_handle = esp_cli_commands_cmd_set, /* optional */ .max_cmd_line_size = EXAMPLE_COMMAND_MAX_LENGTH, .history_save_path = "/spiffs/cli_history.txt", /* optional */ }; ret = esp_cli_create(&cli_cfg, &cli); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create esp_cli instance (%s)", esp_err_to_name(ret)); return; } // Create esp_cli instance task if (xTaskCreate(example_cli_task, "example_cli_task", 4096, cli, 5, NULL) != pdPASS) { ESP_LOGE(TAG, "Failed to create esp_cli instance task"); esp_cli_destroy(cli); return; } ESP_LOGI(TAG, "Starting esp_cli..."); ret = esp_cli_start(cli); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start esp_cli (%s)", esp_err_to_name(ret)); esp_cli_destroy(cli); return; } // Application logic can run in parallel while esp_cli instance runs in its own task // [...] vTaskDelay(pdMS_TO_TICKS(10000)); // Example delay // Stop esp_cli instance ret = esp_cli_stop(cli); if (ret != ESP_OK) { ESP_LOGW(TAG, "Failed to stop esp_cli (%s)", esp_err_to_name(ret)); } ESP_LOGI(TAG, "esp_cli exited"); // Destroy esp_cli instance and clean up ret = esp_cli_destroy(cli); if (ret != ESP_OK) { ESP_LOGW(TAG, "Failed to destroy esp_cli instance cleanly (%s)", esp_err_to_name(ret)); } ESP_LOGI(TAG, "esp_cli example finished"); } ``` ================================================ FILE: esp_cli/host_test/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_cli_host_test) ================================================ FILE: esp_cli/host_test/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_esp_cli.c" "test_main.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: esp_cli/host_test/main/idf_component.yml ================================================ dependencies: espressif/esp_cli: version: "*" override_path: "../.." ================================================ FILE: esp_cli/host_test/main/test_esp_cli.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "unity.h" #include "esp_cli.h" #include "esp_linenoise.h" #include "esp_cli_commands.h" #include inline __attribute__((always_inline)) uint32_t get_millis(void) { struct timeval tv = { 0 }; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } inline __attribute__((always_inline)) void wait_ms(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); } static size_t s_on_enter_nb_of_calls = 0; static size_t s_pre_executor_nb_of_calls = 0; static size_t s_post_executor_nb_of_calls = 0; static size_t s_on_stop_nb_of_calls = 0; static size_t s_on_exit_nb_of_calls = 0; void test_on_enter(void *ctx, esp_cli_handle_t handle) { s_on_enter_nb_of_calls++; return; } esp_err_t test_pre_executor(void *ctx, const char *buf, esp_err_t reader_ret_val) { s_pre_executor_nb_of_calls++; return ESP_OK; } esp_err_t test_post_executor(void *ctx, const char *buf, esp_err_t executor_ret_val, int cmd_ret_val) { s_post_executor_nb_of_calls++; return ESP_OK; } void test_on_stop(void *ctx, esp_cli_handle_t handle) { s_on_stop_nb_of_calls++; return; } void test_on_exit(void *ctx, esp_cli_handle_t handle) { s_on_exit_nb_of_calls++; return; } /* Pass two semaphores: * - start_sem: child gives it when it reached esp_cli() (so main knows child started) * - done_sem: child gives it just before deleting itself (so main can "join") */ typedef struct task_args { SemaphoreHandle_t start_sem; SemaphoreHandle_t done_sem; esp_cli_handle_t hdl; } task_args_t; static void esp_cli_task(void *args) { task_args_t *task_args = (task_args_t *)args; /* signal to main that task started and esp_cli() will run */ xSemaphoreGive(task_args->start_sem); /* run the esp_cli REPL loop (will return when stopped) */ esp_cli(task_args->hdl); /* signal completion (emulates pthread_join notification) */ xSemaphoreGive(task_args->done_sem); /* self-delete */ vTaskDelete(NULL); } static int s_socket_fd[2]; static void test_socket_setup(int socket_fd[2]) { TEST_ASSERT_EQUAL(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd)); /* ensure reads are blocking */ int flags = fcntl(socket_fd[0], F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(socket_fd[0], F_SETFL, flags); flags = fcntl(socket_fd[1], F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(socket_fd[1], F_SETFL, flags); } static void test_socket_teardown(int socket_fd[2]) { close(socket_fd[0]); close(socket_fd[1]); } static void test_send_characters(int socket_fd, const char *msg) { wait_ms(100); const size_t msg_len = strlen(msg); const int nwrite = write(socket_fd, msg, msg_len); TEST_ASSERT_EQUAL(msg_len, nwrite); } static void esp_cli_teardown(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, int socket_fd[2], esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl) { /* destroy the instance of esp_cli */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_destroy(*cli_hdl)); /* delete the linenoise instance */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(*linenoise_hdl)); /* cleanup semaphores */ vSemaphoreDelete(*start_sem); vSemaphoreDelete(*done_sem); /* close the socketpair */ test_socket_teardown(socket_fd); s_on_stop_nb_of_calls = 0; s_on_exit_nb_of_calls = 0; s_on_enter_nb_of_calls = 0; s_pre_executor_nb_of_calls = 0; s_post_executor_nb_of_calls = 0; } static void esp_cli_setup(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, int socket_fd[2], esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl) { /* create semaphores */ *start_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(start_sem); *done_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(done_sem); /* create the socket_pair */ test_socket_setup(socket_fd); /* ensure both semaphores are in the "taken/empty" state: taking with 0 timeout guarantees they are empty afterwards regardless of the create semantics on this FreeRTOS build. */ xSemaphoreTake(*start_sem, 0); xSemaphoreTake(*done_sem, 0); esp_linenoise_config_t linenoise_config; esp_linenoise_get_instance_config_default(&linenoise_config); linenoise_config.in_fd = socket_fd[0]; linenoise_config.out_fd = socket_fd[0]; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, linenoise_hdl)); TEST_ASSERT_NOT_NULL(*linenoise_hdl); esp_cli_config_t cli_config = { .linenoise_handle = *linenoise_hdl, .command_set_handle = NULL, .max_cmd_line_size = 256, .history_save_path = NULL, .on_enter = { .func = test_on_enter, .ctx = NULL }, .pre_executor = { .func = test_pre_executor, .ctx = NULL }, .post_executor = { .func = test_post_executor, .ctx = NULL }, .on_stop = { .func = test_on_stop, .ctx = NULL }, .on_exit = { .func = test_on_exit, .ctx = NULL } }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_create(&cli_config, cli_hdl)); TEST_ASSERT_NOT_NULL(*cli_hdl); s_on_stop_nb_of_calls = 0; s_on_exit_nb_of_calls = 0; s_on_enter_nb_of_calls = 0; s_pre_executor_nb_of_calls = 0; s_post_executor_nb_of_calls = 0; } TEST_CASE("esp_cli() loop calls all callbacks and exit on call to esp_cli_stop", "[esp_cli]") { SemaphoreHandle_t start_sem, done_sem; esp_linenoise_handle_t linenoise_hdl; esp_cli_handle_t cli_hdl; esp_cli_setup(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl); /* create the esp_cli instance task */ task_args_t args = {.start_sem = start_sem, .done_sem = done_sem, .hdl = cli_hdl}; BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task", 4096, &args, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* should fail before esp_cli instance is started */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* start esp_cli instance */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_start(NULL)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl)); wait_ms(100); /* wait for the esp_cli task to signal it started */ TEST_ASSERT_TRUE(xSemaphoreTake(start_sem, pdMS_TO_TICKS(2000))); /* send a dummy string new line terminated to trigger linenoise to return */ const char *input_line = "dummy_message\n"; test_send_characters(s_socket_fd[1], input_line); /* wait for a bit so esp_cli() has time to loop back into esp_linenoise_get_line */ wait_ms(500); /* check that pre-executor, post-executor callbacks are called */ TEST_ASSERT_EQUAL(1, s_pre_executor_nb_of_calls); TEST_ASSERT_EQUAL(1, s_post_executor_nb_of_calls); /* stop esp_cli and wait for task to finish (emulate pthread_join) */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(NULL)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* wait for the esp_cli task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, pdMS_TO_TICKS(2000))); /* check that all callbacks were called the right number of times */ TEST_ASSERT_EQUAL(1, s_on_stop_nb_of_calls); TEST_ASSERT_EQUAL(1, s_on_enter_nb_of_calls); TEST_ASSERT_EQUAL(1, s_on_exit_nb_of_calls); TEST_ASSERT_EQUAL(2, s_pre_executor_nb_of_calls); TEST_ASSERT_EQUAL(2, s_post_executor_nb_of_calls); /* make sure calling stop fails because the esp_cli instance is no longer running */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* destroy the esp_cli instance */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_destroy(NULL)); esp_cli_teardown(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl); } TEST_CASE("esp_cli() exits when esp_cli_stop() called from the task running esp_cli()", "[esp_cli]") { SemaphoreHandle_t start_sem, done_sem; esp_linenoise_handle_t linenoise_hdl; esp_cli_handle_t cli_hdl; esp_cli_setup(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl); /* create the esp_cli instance task */ task_args_t args = {.start_sem = start_sem, .done_sem = done_sem, .hdl = cli_hdl}; BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task", 4096, &args, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* start esp_cli instance */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl)); wait_ms(100); /* wait for the esp_cli instance task to signal it started */ TEST_ASSERT_TRUE(xSemaphoreTake(start_sem, pdMS_TO_TICKS(2000))); /* send the quit command */ const char *quit_cmd_line = "quit \n"; test_send_characters(s_socket_fd[1], quit_cmd_line); /* wait for the esp_cli instance task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, pdMS_TO_TICKS(2000))); esp_cli_teardown(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl); } TEST_CASE("create and destroy several instances of esp_cli", "[esp_cli]") { /* create semaphores */ SemaphoreHandle_t start_sem_a, start_sem_b; SemaphoreHandle_t done_sem_a, done_sem_b; esp_cli_handle_t cli_hdl_a, cli_hdl_b; esp_linenoise_handle_t linenoise_hdl_a, linenoise_hdl_b; int socket_fd_a[2], socket_fd_b[2]; /* create 2 instances of esp_cli*/ esp_cli_setup(&start_sem_a, &done_sem_a, socket_fd_a, &linenoise_hdl_a, &cli_hdl_a); esp_cli_setup(&start_sem_b, &done_sem_b, socket_fd_b, &linenoise_hdl_b, &cli_hdl_b); /* create the esp_cli instance task A */ task_args_t args_a = {.start_sem = start_sem_a, .done_sem = done_sem_a, .hdl = cli_hdl_a}; BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task_a", 4096, &args_a, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* create the esp_cli instance task B */ task_args_t args_b = {.start_sem = start_sem_b, .done_sem = done_sem_b, .hdl = cli_hdl_b}; rc = xTaskCreate(esp_cli_task, "esp_cli_task_b", 4096, &args_b, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* start esp_cli instance */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_a)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_b)); wait_ms(500); /* wait for the esp_cli instance tasks to signal it started */ TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_a, pdMS_TO_TICKS(2000))); TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_b, pdMS_TO_TICKS(2000))); /* terminate instance A */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_a)); /* wait for the esp_cli instance task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_a, pdMS_TO_TICKS(2000))); /* terminate instance B */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_b)); /* wait for the esp_cli instance task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_b, pdMS_TO_TICKS(2000))); esp_cli_teardown(&start_sem_a, &done_sem_a, socket_fd_a, &linenoise_hdl_a, &cli_hdl_a); esp_cli_teardown(&start_sem_b, &done_sem_b, socket_fd_b, &linenoise_hdl_b, &cli_hdl_b); } ================================================ FILE: esp_cli/host_test/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running esp_cli component host tests\n"); unity_run_menu(); } ================================================ FILE: esp_cli/host_test/pytest_host_esp_cli.py ================================================ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @pytest.mark.parametrize('target', ['linux'], indirect=['target']) def host_test_esp_cli(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_cli/host_test/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_EN=n CONFIG_ESP_CLI_HAS_QUIT_CMD=y ================================================ FILE: esp_cli/idf_component.yml ================================================ version: "0.1.2" description: "esp_cli — A command-line interface component that uses a REPL as its main execution loop." url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli dependencies: espressif/esp_linenoise: "*" espressif/esp_cli_commands: "*" sbom: manifests: - path: sbom_esp_cli.yml dest: . ================================================ FILE: esp_cli/include/esp_cli.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include "esp_err.h" #include "esp_linenoise.h" #include "esp_cli_commands.h" /** * @brief Handle to a esp_cli instance. */ typedef struct esp_cli_instance *esp_cli_handle_t; /** * @brief Function prototype called at the beginning of esp_cli(). * * @param ctx User-defined context pointer. * @param handle Handle to the esp_cli instance. */ typedef void (*esp_cli_on_enter_fn)(void *ctx, esp_cli_handle_t handle); /** * @brief Enter callback configuration structure for the esp_cli. */ typedef struct esp_cli_on_enter { esp_cli_on_enter_fn func; /**!< Function called at the beginning of esp_cli() */ void *ctx; /**!< Context passed to the enter function */ } esp_cli_on_enter_t; /** * @brief Function prototype called before executing a command. * * @param ctx User-defined context pointer. * @param buf Buffer containing the command. * @param reader_ret_val Return value from the reader function. * * @return ESP_OK to continue execution, error code to abort. */ typedef esp_err_t (*esp_cli_pre_executor_fn)(void *ctx, const char *buf, esp_err_t reader_ret_val); /** * @brief Pre-executor configuration structure for the esp_cli. */ typedef struct esp_cli_pre_executor { esp_cli_pre_executor_fn func; /**!< Function to run before command execution */ void *ctx; /**!< Context passed to the pre-executor function */ } esp_cli_pre_executor_t; /** * @brief Function prototype called after executing a command. * * @param ctx User-defined context pointer. * @param buf Command that was executed. * @param executor_ret_val Return value from the executor function. * @param cmd_ret_val Command-specific return value. * * @return ESP_OK on success, error code otherwise. */ typedef esp_err_t (*esp_cli_post_executor_fn)(void *ctx, const char *buf, esp_err_t executor_ret_val, int cmd_ret_val); /** * @brief Post-executor configuration structure for the esp_cli. */ typedef struct esp_cli_post_executor { esp_cli_post_executor_fn func; /**!< Function called after command execution */ void *ctx; /**!< Context passed to the post-executor function */ } esp_cli_post_executor_t; /** * @brief Function prototype called when the esp_cli is stopping. * * This callback allows the user to unblock the reader (or perform other * cleanup) so that the esp_cli can return from `esp_cli()`. * * @param ctx User-defined context pointer. * @param handle Handle to the esp_cli instance. */ typedef void (*esp_cli_on_stop_fn)(void *ctx, esp_cli_handle_t handle); /** * @brief Stop callback configuration structure for the esp_cli. */ typedef struct esp_cli_on_stop { esp_cli_on_stop_fn func; /**!< Function called when esp_cli stop is requested */ void *ctx; /**!< Context passed to the on_stop function */ } esp_cli_on_stop_t; /** * @brief Function prototype called when the esp_cli exits. * * @param ctx User-defined context pointer. * @param handle Handle to the esp_cli instance. */ typedef void (*esp_cli_on_exit_fn)(void *ctx, esp_cli_handle_t handle); /** * @brief Exit callback configuration structure for the esp_cli. */ typedef struct esp_cli_on_exit { esp_cli_on_exit_fn func; /**!< Function called on esp_cli exit */ void *ctx; /**!< Context passed to the exit function */ } esp_cli_on_exit_t; /** * @brief Configuration structure to initialize a esp_cli instance. */ typedef struct esp_cli_config { esp_linenoise_handle_t linenoise_handle; /**!< Handle to the esp_linenoise instance */ esp_cli_command_set_handle_t command_set_handle; /**!< Handle to a set of commands */ size_t max_cmd_line_size; /**!< Maximum allowed command line size */ const char *history_save_path; /**!< Path to file to save the history */ esp_cli_on_enter_t on_enter; /**!< Enter callback and context */ esp_cli_pre_executor_t pre_executor; /**!< Pre-executor callback and context */ esp_cli_post_executor_t post_executor; /**!< Post-executor callback and context */ esp_cli_on_stop_t on_stop; /**!< Stop callback and context */ esp_cli_on_exit_t on_exit; /**!< Exit callback and context */ } esp_cli_config_t; /** * @brief Create a esp_cli instance. * * @param config Pointer to the configuration structure. * @param out_handle Pointer to store the created esp_cli instance handle. * * @return ESP_OK on success, error code otherwise. */ esp_err_t esp_cli_create(const esp_cli_config_t *config, esp_cli_handle_t *out_handle); /** * @brief Destroy a esp_cli instance. * * @param handle esp_cli instance handle to destroy. * * @return ESP_OK on success, error code otherwise. */ esp_err_t esp_cli_destroy(esp_cli_handle_t handle); /** * @brief Start a esp_cli instance. * * @param handle esp_cli instance handle. * * @return ESP_OK on success, error code otherwise. */ esp_err_t esp_cli_start(esp_cli_handle_t handle); /** * @brief Stop a esp_cli instance. * * @note This function will internally call 'esp_linenoise_abort' first to try to return from * 'esp_linenoise_get_line'. If the user has provided a custom read to the esp_linenoise * instance used by the esp_cli instance, it is the responsibility of the user to provide * the mechanism to return from this custom read by providing a callback to the 'on_stop' field * in the esp_cli_config_t. * * Return Values: * - ESP_OK: Returned if the user has not provided a custom read and the abort operation succeeds. * - ESP_ERR_INVALID_STATE: Returned if the user has provided a custom read. In this case, the user * is responsible for implementing an abort mechanism that ensures a successful return from * their custom read. This can be achieved by placing the logic in the on_stop callback. * * Behavior: * - When a custom read is registered, ESP_ERR_INVALID_STATE indicates that esp_cli_stop() cannot * forcibly return from the read. The user must handle the return themselves via on_stop(). * - From the perspective of esp_cli_stop(), this scenario is treated as successful, and its * return value should be set to ESP_OK. * * @param handle esp_cli instance handle. * * @return ESP_OK on success, error code otherwise. */ esp_err_t esp_cli_stop(esp_cli_handle_t handle); /** * @brief Run the esp_cli loop. * * @param handle esp_cli instance handle. */ void esp_cli(esp_cli_handle_t handle); #ifdef __cplusplus } #endif ================================================ FILE: esp_cli/sbom_esp_cli.yml ================================================ name: esp_cli description: Command handling component url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli version: 1.0.0 cpe: cpe:2.3:a:espressif:esp_cli:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems' ================================================ FILE: esp_cli/src/esp_cli.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "esp_cli.h" #include "esp_err.h" #include "esp_cli_commands.h" #include "esp_linenoise.h" typedef enum { ESP_CLI_STATE_RUNNING, ESP_CLI_STATE_STOPPED } esp_cli_state_e; typedef struct esp_cli_state { esp_cli_state_e state; TaskHandle_t task_hdl; SemaphoreHandle_t mux; } esp_cli_state_t; typedef struct esp_cli_instance { esp_cli_config_t config; esp_cli_state_t state; } esp_cli_instance_t; #if CONFIG_ESP_CLI_HAS_QUIT_CMD #define _ESP_CLI_STRINGIFY(x) #x #define ESP_CLI_STRINGIFY(x) _ESP_CLI_STRINGIFY(x) #define ESP_CLI_QUIT_CMD quit #define ESP_CLI_QUIT_CMD_STR ESP_CLI_STRINGIFY(ESP_CLI_QUIT_CMD) #define ESP_CLI_QUIT_CMD_SIZE strlen(ESP_CLI_QUIT_CMD_STR) /* dummy command function callback to allow the registration for the quit command * to succeed */ static int esp_cli_quit_cmd(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { return 0; } ESP_CLI_COMMAND_REGISTER(ESP_CLI_QUIT_CMD, "esp_cli", "This command will trigger the exit mechanism to exit from esp_cli() main loop", esp_cli_quit_cmd, NULL, NULL, NULL ); #endif // CONFIG_ESP_CLI_HAS_QUIT_CMD #define ESP_CLI_CHECK_INSTANCE(handle) do { \ if(handle == NULL) { \ return ESP_ERR_INVALID_ARG; \ } \ } while(0) esp_err_t esp_cli_create(const esp_cli_config_t *config, esp_cli_handle_t *out_handle) { if (!config || !out_handle) { return ESP_ERR_INVALID_ARG; } if ((config->linenoise_handle == NULL) || (config->max_cmd_line_size == 0)) { return ESP_ERR_INVALID_ARG; } esp_cli_instance_t *instance = malloc(sizeof(esp_cli_instance_t)); if (!instance) { return ESP_ERR_NO_MEM; } instance->config = *config; instance->state.state = ESP_CLI_STATE_STOPPED; instance->state.mux = xSemaphoreCreateMutex(); if (!instance->state.mux) { free(instance); return ESP_FAIL; } /* take the mutex right away to prevent the task to start running until * the user explicitly calls esp_cli_start */ xSemaphoreTake(instance->state.mux, portMAX_DELAY); *out_handle = instance; return ESP_OK; } esp_err_t esp_cli_destroy(esp_cli_handle_t handle) { ESP_CLI_CHECK_INSTANCE(handle); esp_cli_state_t *state = &handle->state; /* the instance has to be not running for esp_cli to destroy it */ if (state->state != ESP_CLI_STATE_STOPPED) { return ESP_ERR_INVALID_STATE; } vSemaphoreDelete(state->mux); free(handle); return ESP_OK; } esp_err_t esp_cli_start(esp_cli_handle_t handle) { ESP_CLI_CHECK_INSTANCE(handle); esp_cli_state_t *state = &handle->state; if (state->state != ESP_CLI_STATE_STOPPED) { return ESP_ERR_INVALID_STATE; } state->state = ESP_CLI_STATE_RUNNING; xSemaphoreGive(state->mux); return ESP_OK; } esp_err_t esp_cli_stop(esp_cli_handle_t handle) { ESP_CLI_CHECK_INSTANCE(handle); esp_cli_config_t *config = &handle->config; esp_cli_state_t *state = &handle->state; if (state->state != ESP_CLI_STATE_RUNNING) { return ESP_ERR_INVALID_STATE; } /* update the state to force the while loop in esp_cli to return */ state->state = ESP_CLI_STATE_STOPPED; /** This function forces esp_linenoise_get_line() to return. * * Return Values: * - ESP_OK: Returned if the user has not provided a custom read and the abort operation succeeds. * - ESP_ERR_INVALID_STATE: Returned if the user has provided a custom read. In this case, the user * is responsible for implementing an abort mechanism that ensures a successful return from * their custom read. This can be achieved by placing the logic in the on_stop callback. * * Behavior: * - When a custom read is registered, ESP_ERR_INVALID_STATE indicates that esp_cli_stop() cannot * forcibly return from the read. The user must handle the return of their custom read via on_stop(). * - From the perspective of esp_cli_stop(), this scenario is treated as successful, and its * return value should be set to ESP_OK. */ esp_err_t ret_val = esp_linenoise_abort(config->linenoise_handle); if (ret_val == ESP_ERR_INVALID_STATE) { ret_val = ESP_OK; } /* Call the on_stop callback to let the user unblock esp_linenoise * if a custom read is provided */ if (config->on_stop.func != NULL) { config->on_stop.func(config->on_stop.ctx, handle); } /* Wait for esp_cli() to finish and signal completion, in the event of * esp_cli_stop() is called from the same task running esp_cli() (e.g., * called from a "quit" command), do not take the mutex to avoid a deadlock. * * If esp_cli_stop() is called from the same task, it assures that this task * is not blocking in esp_linenoise_get_line() so the while loop in esp_cli() * will return as we updated the state above */ if (state->task_hdl && state->task_hdl != xTaskGetCurrentTaskHandle()) { xSemaphoreTake(state->mux, portMAX_DELAY); } return ret_val; } void esp_cli(esp_cli_handle_t handle) { if (!handle) { return; } esp_cli_config_t *config = &handle->config; esp_cli_state_t *state = &handle->state; /* trigger a user defined callback before the function gets into the while loop * if the user wants to perform some logic that needs to be done within the task * running the esp_cli instance */ if (config->on_enter.func != NULL) { config->on_enter.func(config->on_enter.ctx, handle); } /* get the task handle of the task running this function. * It is necessary to gather this information in case esp_cli_stop() * is called from the same task as the one running esp_cli() (e.g., * through the execution of a command) */ state->task_hdl = xTaskGetCurrentTaskHandle(); /* allocate memory for the command line buffer */ const size_t cmd_line_size = config->max_cmd_line_size; char *cmd_line = calloc(1, cmd_line_size); if (!cmd_line) { return; } /* Waiting for task notify. This happens when `esp_cli_start` * function is called. */ xSemaphoreTake(state->mux, portMAX_DELAY); esp_linenoise_handle_t l_hdl = config->linenoise_handle; esp_cli_command_set_handle_t c_set = config->command_set_handle; /* esp_cli REPL loop */ while (state->state == ESP_CLI_STATE_RUNNING) { /* try to read a command line */ const esp_err_t read_ret = esp_linenoise_get_line(l_hdl, cmd_line, cmd_line_size); /* Add the command to the history */ esp_linenoise_history_add(l_hdl, cmd_line); /* Save command history to filesystem */ if (config->history_save_path) { esp_linenoise_history_save(l_hdl, config->history_save_path); } /* forward the raw command line to the pre executor callback (e.g., save in history). * this callback is not necessary for the user to register, continue if it isn't */ if (config->pre_executor.func != NULL) { config->pre_executor.func(config->pre_executor.ctx, cmd_line, read_ret); } /* at this point, if the command is NULL, skip the executing part */ if (read_ret != ESP_OK) { continue; } #if CONFIG_ESP_CLI_HAS_QUIT_CMD /* evaluate the command name. make sure that the first argument of the cmd_line * is ESP_CLI_QUIT_CMD_STR and the character following that is either a space * or a null character */ if ((strncmp(ESP_CLI_QUIT_CMD_STR, cmd_line, ESP_CLI_QUIT_CMD_SIZE) == 0) && ((cmd_line[ESP_CLI_QUIT_CMD_SIZE] == ' ') || (cmd_line[ESP_CLI_QUIT_CMD_SIZE] == '\0'))) { /* quit command received, call esp_cli_stop() */ if (esp_cli_stop(handle) == ESP_OK) { /* if esp_cli_stop() was successful, retry the while condition. * the esp_cli state should have been changed which will force * the while to break */ continue; } } #endif // CONFIG_ESP_CLI_HAS_QUIT_CMD /* try to run the command */ int cmd_func_ret; esp_cli_commands_exec_arg_t cmd_args; esp_err_t get_ret = esp_linenoise_get_out_fd(handle->config.linenoise_handle, &(cmd_args.out_fd)); if (get_ret != ESP_OK) { cmd_args.out_fd = STDOUT_FILENO; } get_ret = esp_linenoise_get_write(handle->config.linenoise_handle, &(cmd_args.write_func)); if (get_ret != ESP_OK) { cmd_args.write_func = write; } const esp_err_t exec_ret = esp_cli_commands_execute(cmd_line, &cmd_func_ret, c_set, &cmd_args); /* forward the raw command line to the post executor callback (e.g., save in history). * this callback is not necessary for the user to register, continue if it isn't */ if (config->post_executor.func != NULL) { config->post_executor.func(config->post_executor.ctx, cmd_line, exec_ret, cmd_func_ret); } /* reset the cmd_line for next loop */ memset(cmd_line, 0x00, cmd_line_size); } /* free the memory allocated for the cmd_line buffer */ free(cmd_line); /* call the on_exit callback before returning from esp_cli */ if (config->on_exit.func != NULL) { config->on_exit.func(config->on_exit.ctx, handle); } /* release the semaphore to indicate esp_cli_stop that the esp_cli returned */ xSemaphoreGive(state->mux); } ================================================ FILE: esp_cli/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_cli_test) ================================================ FILE: esp_cli/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_esp_cli.c" "test_main.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity vfs esp_driver_uart esp_driver_usb_serial_jtag WHOLE_ARCHIVE) ================================================ FILE: esp_cli/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_cli: version: "*" override_path: "../.." ================================================ FILE: esp_cli/test_apps/main/test_esp_cli.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "unity.h" #include "esp_cli.h" #include "esp_linenoise.h" #include "esp_cli_commands.h" #include "sdkconfig.h" #include "esp_vfs.h" #include "esp_vfs_common.h" #include "driver/esp_private/uart_vfs.h" #include "driver/uart_vfs.h" #include "driver/uart.h" #include "driver/esp_private/usb_serial_jtag_vfs.h" #include "driver/usb_serial_jtag_vfs.h" #include "driver/usb_serial_jtag.h" static size_t s_on_enter_nb_of_calls = 0; static size_t s_pre_executor_nb_of_calls = 0; static size_t s_post_executor_nb_of_calls = 0; static size_t s_on_stop_nb_of_calls = 0; static size_t s_on_exit_nb_of_calls = 0; void test_on_enter(void *ctx, esp_cli_handle_t handle) { s_on_enter_nb_of_calls++; return; } esp_err_t test_pre_executor(void *ctx, const char *buf, esp_err_t reader_ret_val) { s_pre_executor_nb_of_calls++; return ESP_OK; } esp_err_t test_post_executor(void *ctx, const char *buf, esp_err_t executor_ret_val, int cmd_ret_val) { s_post_executor_nb_of_calls++; return ESP_OK; } void test_on_stop(void *ctx, esp_cli_handle_t handle) { s_on_stop_nb_of_calls++; return; } void test_on_exit(void *ctx, esp_cli_handle_t handle) { s_on_exit_nb_of_calls++; return; } /* Pass two semaphores: * - start_sem: child gives it when it reached esp_cli() (so main knows child started) * - done_sem: child gives it just before deleting itself (so main can "join") */ typedef struct task_args { SemaphoreHandle_t start_sem; SemaphoreHandle_t done_sem; esp_cli_handle_t hdl; } task_args_t; static void esp_cli_task(void *args) { task_args_t *task_args = (task_args_t *)args; /* signal to main that task started and esp_cli() will run */ xSemaphoreGive(task_args->start_sem); /* run the esp_cli REPL loop (will return when stopped) */ esp_cli(task_args->hdl); /* signal completion (emulates pthread_join notification) */ xSemaphoreGive(task_args->done_sem); /* self-delete */ vTaskDelete(NULL); } static void test_uart_install(int *in_fd, int *out_fd) { /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); /* Configure UART. Note that REF_TICK/XTAL is used so that the baud rate remains * correct while APB frequency is changing in light sleep mode. */ #if SOC_UART_SUPPORT_REF_TICK uart_sclk_t clk_source = UART_SCLK_REF_TICK; // REF_TICK clock can't provide a high baudrate if (CONFIG_ESP_CONSOLE_UART_BAUDRATE > 1 * 1000 * 1000) { clk_source = UART_SCLK_DEFAULT; ESP_LOGW(TAG, "light sleep UART wakeup might not work at the configured baud rate"); } #elif SOC_UART_SUPPORT_XTAL_CLK uart_sclk_t clk_source = UART_SCLK_XTAL; #else #error "No UART clock source is aware of DFS" #endif // SOC_UART_SUPPORT_xxx const uart_config_t uart_config = { .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .source_clk = clk_source, }; uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config); /* Install UART driver for interrupt-driven reads and writes */ TEST_ASSERT_EQUAL(ESP_OK, uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0)); /* Tell VFS to use UART driver */ uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); /* register the vfs, create a FD used to interface the UART */ const esp_vfs_fs_ops_t *uart_vfs = esp_vfs_uart_get_vfs(); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_register_fs("/dev/test_uart", uart_vfs, 0, NULL)); /* open in blocking mode */ const int uart_fd = open("/dev/test_uart/0", 0); TEST_ASSERT(uart_fd != -1); *in_fd = uart_fd; *out_fd = uart_fd; } static void test_uart_uninstall(const int fd) { /* close the stream */ const int ret = close(fd); TEST_ASSERT(ret != -1); /* unregister the vfs */ TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_unregister("/dev/test_uart")); /* uninstall the driver for the default uart */ uart_vfs_dev_use_nonblocking(CONFIG_ESP_CONSOLE_UART_NUM); TEST_ASSERT_EQUAL(ESP_OK, uart_driver_delete(CONFIG_ESP_CONSOLE_UART_NUM)); } static void test_usj_install(int *in_fd, int *out_fd) { /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ usb_serial_jtag_vfs_set_rx_line_endings(ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ usb_serial_jtag_vfs_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */ usb_serial_jtag_driver_config_t usb_serial_jtag_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); TEST_ASSERT_EQUAL(ESP_OK, usb_serial_jtag_driver_install(&usb_serial_jtag_config)); /* register the vfs, create a FD used to interface the USJ */ const esp_vfs_fs_ops_t *usj_vfs = esp_vfs_usb_serial_jtag_get_vfs(); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_register_fs("/dev/test_usj", usj_vfs, 0, NULL)); /* open in blocking mode */ const int usj_fd = open("/dev/test_usj/0", 0); TEST_ASSERT(usj_fd != -1); *in_fd = usj_fd; *out_fd = usj_fd; } static void test_usj_uninstall(const int fd) { /* close the stream */ const int ret = close(fd); TEST_ASSERT(ret != -1); /* unregister the vfs */ TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_unregister("/dev/test_usj")); /* uninstall the driver */ TEST_ASSERT_EQUAL(ESP_OK, usb_serial_jtag_driver_uninstall()); } static void test_esp_cli_teardown(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl) { /* destroy the instance of esp_cli */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_destroy(*cli_hdl)); /* delete the linenoise instance */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(*linenoise_hdl)); /* cleanup semaphores */ vSemaphoreDelete(*start_sem); vSemaphoreDelete(*done_sem); s_on_stop_nb_of_calls = 0; s_on_exit_nb_of_calls = 0; s_on_enter_nb_of_calls = 0; s_pre_executor_nb_of_calls = 0; s_post_executor_nb_of_calls = 0; } static void test_esp_cli_setup(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, int in_fd, int out_fd, esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl) { /* create semaphores */ *start_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(start_sem); *done_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(done_sem); /* ensure both semaphores are in the "taken/empty" state: taking with 0 timeout guarantees they are empty afterwards regardless of the create semantics on this FreeRTOS build. */ xSemaphoreTake(*start_sem, 0); xSemaphoreTake(*done_sem, 0); esp_linenoise_config_t linenoise_config; esp_linenoise_get_instance_config_default(&linenoise_config); linenoise_config.in_fd = in_fd; linenoise_config.out_fd = out_fd; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, linenoise_hdl)); TEST_ASSERT_NOT_NULL(*linenoise_hdl); esp_cli_config_t cli_config = { .linenoise_handle = *linenoise_hdl, .command_set_handle = NULL, .max_cmd_line_size = 256, .history_save_path = NULL, .on_enter = { .func = test_on_enter, .ctx = NULL }, .pre_executor = { .func = test_pre_executor, .ctx = NULL }, .post_executor = { .func = test_post_executor, .ctx = NULL }, .on_stop = { .func = test_on_stop, .ctx = NULL }, .on_exit = { .func = test_on_exit, .ctx = NULL } }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_create(&cli_config, cli_hdl)); TEST_ASSERT_NOT_NULL(*cli_hdl); s_on_stop_nb_of_calls = 0; s_on_exit_nb_of_calls = 0; s_on_enter_nb_of_calls = 0; s_pre_executor_nb_of_calls = 0; s_post_executor_nb_of_calls = 0; } TEST_CASE("esp_cli() loop calls callbacks and exit on call to esp_cli_stop", "[esp_cli]") { SemaphoreHandle_t start_sem, done_sem; esp_linenoise_handle_t linenoise_hdl; esp_cli_handle_t cli_hdl; int in_fd, out_fd; test_uart_install(&in_fd, &out_fd); test_esp_cli_setup(&start_sem, &done_sem, in_fd, out_fd, &linenoise_hdl, &cli_hdl); /* create the esp_cli instance task */ task_args_t args = {.start_sem = start_sem, .done_sem = done_sem, .hdl = cli_hdl}; BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task", 2048, &args, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* should fail before esp_cli instance is started */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* start esp_cli instance */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_start(NULL)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl)); /* wait for the esp_cli task to signal it started */ TEST_ASSERT_TRUE(xSemaphoreTake(start_sem, pdMS_TO_TICKS(2000))); /* wait for a bit so esp_cli() has time to loop back into esp_linenoise_get_line */ vTaskDelay(pdMS_TO_TICKS(500)); /* stop esp_cli and wait for task to finish (emulate pthread_join) */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(NULL)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* wait for the esp_cli task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, pdMS_TO_TICKS(2000))); /* check that all callbacks were called the right number of times */ TEST_ASSERT_EQUAL(1, s_on_stop_nb_of_calls); TEST_ASSERT_EQUAL(1, s_on_enter_nb_of_calls); TEST_ASSERT_EQUAL(1, s_on_exit_nb_of_calls); /* make sure calling stop fails because the esp_cli instance is no longer running */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl)); /* destroy the esp_cli instance */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_destroy(NULL)); test_esp_cli_teardown(&start_sem, &done_sem, &linenoise_hdl, &cli_hdl); /* uninstall the uart driver */ test_uart_uninstall(in_fd); /* make sure the cleanup of the deleted task is done to not bias * the memory leak calculations */ vTaskDelay(pdMS_TO_TICKS(500)); } TEST_CASE("create and destroy several instances of esp_cli", "[esp_cli]") { /* create semaphores */ SemaphoreHandle_t start_sem_a, start_sem_b; SemaphoreHandle_t done_sem_a, done_sem_b; esp_cli_handle_t cli_hdl_a, cli_hdl_b; esp_linenoise_handle_t linenoise_hdl_a, linenoise_hdl_b; int in_fd_uart, in_fd_usj, out_fd_uart, out_fd_usj; /* install uart and usb serial jtag drivers */ test_uart_install(&in_fd_uart, &out_fd_uart); test_usj_install(&in_fd_usj, &out_fd_usj); /* create 2 instances of esp_cli*/ test_esp_cli_setup(&start_sem_a, &done_sem_a, in_fd_uart, out_fd_uart, &linenoise_hdl_a, &cli_hdl_a); test_esp_cli_setup(&start_sem_b, &done_sem_b, in_fd_usj, out_fd_usj, &linenoise_hdl_b, &cli_hdl_b); /* create the esp_cli instance task A */ task_args_t args_a = {.start_sem = start_sem_a, .done_sem = done_sem_a, .hdl = cli_hdl_a}; BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task_a", 4096, &args_a, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* create the esp_cli instance task B */ task_args_t args_b = {.start_sem = start_sem_b, .done_sem = done_sem_b, .hdl = cli_hdl_b}; rc = xTaskCreate(esp_cli_task, "esp_cli_task_b", 4096, &args_b, 5, NULL); TEST_ASSERT_EQUAL(pdPASS, rc); /* start esp_cli instance */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_a)); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_b)); vTaskDelay(pdMS_TO_TICKS(500)); /* wait for the esp_cli instance tasks to signal it started */ TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_a, pdMS_TO_TICKS(2000))); TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_b, pdMS_TO_TICKS(2000))); /* terminate instance A */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_a)); /* wait for the esp_cli instance task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_a, pdMS_TO_TICKS(2000))); /* terminate instance B */ TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_b)); /* wait for the esp_cli instance task to signal completion */ TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_b, pdMS_TO_TICKS(2000))); test_esp_cli_teardown(&start_sem_a, &done_sem_a, &linenoise_hdl_a, &cli_hdl_a); test_esp_cli_teardown(&start_sem_b, &done_sem_b, &linenoise_hdl_b, &cli_hdl_b); test_uart_uninstall(in_fd_uart); test_usj_uninstall(in_fd_usj); /* make sure the cleanup of the deleted task is done to not bias * the memory leak calculations */ vTaskDelay(pdMS_TO_TICKS(500)); } TEST_CASE("create more esp_linenoise instances that possible based on CONFIG_ESP_LINENOISE_MAX_INSTANCE_NB", "[esp_cli]") { esp_linenoise_config_t linenoise_config; esp_linenoise_get_instance_config_default(&linenoise_config); const size_t hdl_array_size = CONFIG_ESP_LINENOISE_MAX_INSTANCE_NB + 1; esp_linenoise_handle_t hdl_array[hdl_array_size]; memset(hdl_array, 0x00, sizeof(hdl_array)); /* try to create more instances than allowed */ for (size_t i = 0; i < hdl_array_size; i++) { if (i < CONFIG_ESP_LINENOISE_MAX_INSTANCE_NB) { /* we don't exceed the max number of instance yet, success expected */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, &hdl_array[i])); TEST_ASSERT_NOT_NULL(hdl_array[i]); } else { /* we exceed the max number of instance, failure expected */ TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, &hdl_array[i])); TEST_ASSERT_NULL(hdl_array[i]); } } /* free the instances that were successfully created */\ for (size_t i = 0; i < hdl_array_size; i++) { if (hdl_array[i] != NULL) { TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(hdl_array[i])); hdl_array[i] = NULL; } } /* try to create an instance again and deleted */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, &hdl_array[0])); TEST_ASSERT_NOT_NULL(hdl_array[0]); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(hdl_array[0])); } ================================================ FILE: esp_cli/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { /* the threshold is necessary because on esp_linenoise instance * creation, a bunch of heap memory is being used to initialize (e.g., * eventfd and vfs internals) */ unity_utils_evaluate_leaks_direct(500); } void app_main(void) { printf("Running esp_cli component tests\n"); unity_run_menu(); } ================================================ FILE: esp_cli/test_apps/pytest_esp_cli.py ================================================ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @pytest.mark.parametrize('target', ['esp32s3'], indirect=['target']) def test_esp_cli(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_cli/test_apps/sdkconfig.defaults ================================================ CONFIG_ESP_LINENOISE_MAX_INSTANCE_NB=2 CONFIG_ESP_TASK_WDT_EN=n CONFIG_ESP_CLI_HAS_QUIT_CMD=y CONFIG_VFS_SUPPORT_IO=y ================================================ FILE: esp_cli_commands/.build-test-rules.yml ================================================ esp_cli_commands/test_apps: enable: - if: IDF_TARGET in ["esp32", "linux"] disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 esp_cli_commands/examples/command_set: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 esp_cli_commands/examples/command_with_arg: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 esp_cli_commands/examples/dynamic_registration: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 esp_cli_commands/examples/integration_with_argtable: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 esp_cli_commands/examples/static_registration: enable: - if: IDF_TARGET == "linux" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 ================================================ FILE: esp_cli_commands/CMakeLists.txt ================================================ idf_build_get_property(idf_target IDF_TARGET) set(srcs "src/esp_cli_commands.c" "src/esp_cli_dynamic_commands.c" "src/esp_cli_commands_helpers.c") idf_component_register( SRCS ${srcs} INCLUDE_DIRS include PRIV_INCLUDE_DIRS private_include LDFRAGMENTS linker.lf WHOLE_ARCHIVE) if(${idf_target} STREQUAL "linux") # Add custom ld file target_link_options(${COMPONENT_TARGET} INTERFACE "-Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/linux/esp_cli_commands.ld") endif() ================================================ FILE: esp_cli_commands/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright Espressif Systems Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_cli_commands/README.md ================================================ # ESP Commands The `esp_cli_commands` component provides a flexible command registration and execution framework for ESP-IDF applications. It allows applications to define console-like commands with metadata (help text, hints, glossary entries) and register them dynamically or statically. --- ## Features - Define commands with: - Command name - Group categorization - Help text - Optional hints and glossary callbacks - Register commands at runtime or at compile-time (via section placement macros). - Execute commands from command line strings. - Provide command completion, hints, and glossary callback registration mechanism. - Create and manage subsets of commands (command sets). - Customizable configuration for command parsing and hint display. --- ## Configuration By default, the component is initialized with a default configuration. It is however possible for the user to update this configuration with the call of the following API: ```c esp_cli_commands_config_t config = { .heap_caps_used = , .max_cmdline_length = , .max_cmdline_args = , .hint_color = , .hint_bold = }; esp_cli_commands_update_config(&config); ``` - `write_func`: The custom write function used by esp_cli_commands to output data (default to posix write is not specified) - `max_cmdline_length`: Maximum command line buffer length (bytes). - `max_cmdline_args`: Maximum number of arguments parsed. - `hint_color`: ANSI color code used for hints. - `hint_bold`: Whether hints are displayed in bold. ## Usage Examples For real-world usage and demonstration, see the following example projects in this repository: - [hello_command_static](examples/hello_command_static): Static registration and API usage - [math_op_static](examples/math_op_static): Static registration, argument parsing, error handling - [debug_and_unregister_dynamic](examples/debug_and_unregister_dynamic): Dynamic registration and command lifecycle - [command_set_example](examples/command_set_example): Command set creation, filtering, and concatenation You can find these in the `esp_cli_commands/examples/` directory. Each example contains a README and complete source code. These examples are the recommended starting point for learning how to use this component in your own project. --- ## API Reference - **Configuration**: `esp_cli_commands_update_config()` - **Registration**: `esp_cli_commands_register_cmd()`, `esp_cli_commands_unregister_cmd()` - **Execution**: `esp_cli_commands_execute()`, `esp_cli_commands_find_command()` - **Completion & Help APIs**: `esp_cli_commands_get_completion()`, `esp_cli_commands_get_hint()`, `esp_cli_commands_get_glossary()` - **Command Sets**: `esp_cli_commands_create_cmd_set()`, `esp_cli_commands_concat_cmd_set()`, `esp_cli_commands_destroy_cmd_set()` --- ================================================ FILE: esp_cli_commands/examples/command_set/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(command_set) ================================================ FILE: esp_cli_commands/examples/command_set/README.md ================================================ # Command Set Example This example demonstrates the use of command sets in esp_cli_commands: - Two commands are created, each belonging to a different group. - Two command sets are created, one for each group. - Each command is executed with each set to test filtering. - The sets are concatenated and both commands are executed with the combined set. - All sets and commands are cleaned up at the end. ## Files - main/command_set_main.c: Main example source code - main/CMakeLists.txt: Build configuration - main/idf_component.yml: ESP-IDF component manifest ## Usage Build and flash as a standard ESP-IDF example. Output will show which commands are executed or filtered by the command set. ================================================ FILE: esp_cli_commands/examples/command_set/main/CMakeLists.txt ================================================ # Main CMakeLists.txt for command_set example cmake_minimum_required(VERSION 3.22) idf_component_register( SRCS "command_set_main.c" INCLUDE_DIRS "" "../../utils" REQUIRES esp_cli_commands ) ================================================ FILE: esp_cli_commands/examples/command_set/main/command_set_main.c ================================================ /* * Example: Command Set Functionality Demonstration * * This example creates two commands, each belonging to a different group. * It demonstrates the use of command sets for filtering and executing commands. * * Steps: * 1. Create two commands (cmd_a, cmd_b) in groups (group_a, group_b). * 2. Create two command sets, each for one group. * 3. Execute each command with each command set (4 cases). * 4. Concatenate the sets and execute both commands with the combined set. * 5. Clean up all sets and commands. */ #include #include #include "esp_cli_commands.h" #include "command_utils.h" static int cmd_a_handler(void *context, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)context; (void)argc; (void)argv; const char *msg = "cmd_a executed\n"; const size_t msg_len = sizeof(msg) - 1; const int nwrite = cmd_arg->write_func(cmd_arg->out_fd, msg, msg_len); return nwrite == msg_len ? 0 : -1; } static int cmd_b_handler(void *context, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)context; (void)argc; (void)argv; const char *msg = "cmd_b executed\n"; const size_t msg_len = sizeof(msg) - 1; const int nwrite = cmd_arg->write_func(cmd_arg->out_fd, msg, msg_len); return nwrite == msg_len ? 0 : -1; } void app_main(void) { printf("esp_cli_commands command_set example started.\n"); esp_cli_commands_exec_arg_t cmd_args = { .out_fd = STDOUT_FILENO, .write_func = write, .dynamic_ctx = NULL }; // Define two commands esp_cli_command_t cmd_a = { .name = "cmd_a", .group = "group_a", .help = "Command A", .func = cmd_a_handler, .func_ctx = NULL, .hint_cb = NULL, .glossary_cb = NULL }; esp_cli_command_t cmd_b = { .name = "cmd_b", .group = "group_b", .help = "Command B", .func = cmd_b_handler, .func_ctx = NULL, .hint_cb = NULL, .glossary_cb = NULL }; ESP_ERROR_CHECK(esp_cli_commands_register_cmd(&cmd_a)); ESP_ERROR_CHECK(esp_cli_commands_register_cmd(&cmd_b)); // create command sets. One with command name, another with group name const char *cmd_set_a[] = { "cmd_a" }; const char *cmd_set_b[] = { "group_b" }; esp_cli_command_set_handle_t set_a = ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_set_a, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); esp_cli_command_set_handle_t set_b = ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_set_b, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); // Test all combinations int ret = -1; printf("-- Executing cmd_a with set_a (should succeed) --\n"); ESP_ERROR_CHECK(esp_cli_commands_execute("cmd_a", &ret, set_a, &cmd_args)); printf("-- Executing cmd_b with set_b (should succeed) --\n"); ESP_ERROR_CHECK(esp_cli_commands_execute("cmd_b", &ret, set_b, &cmd_args)); printf("-- Executing cmd_a with set_b (should fail) --\n"); esp_err_t ret_val = esp_cli_commands_execute("cmd_a", &ret, set_b, &cmd_args); if (ret_val != ESP_OK) { printf("Expected failure: cmd_a not in set_b\n"); } printf("-- Executing cmd_b with set_a (should fail) --\n"); ret_val = esp_cli_commands_execute("cmd_b", &ret, set_a, &cmd_args); if (ret_val != ESP_OK) { printf("Expected failure: cmd_b not in set_a\n"); } // Concatenate sets esp_cli_command_set_handle_t set_concat = esp_cli_commands_concat_cmd_set(set_a, set_b); printf("-- Executing cmd_a with concatenated set (should succeed) --\n"); ESP_ERROR_CHECK(esp_cli_commands_execute("cmd_a", &ret, set_concat, &cmd_args)); printf("-- Executing cmd_b with concatenated set (should succeed) --\n"); ESP_ERROR_CHECK(esp_cli_commands_execute("cmd_b", &ret, set_concat, &cmd_args)); // Cleanup esp_cli_commands_destroy_cmd_set(&set_concat); ESP_ERROR_CHECK(esp_cli_commands_unregister_cmd("cmd_a")); ESP_ERROR_CHECK(esp_cli_commands_unregister_cmd("cmd_b")); printf("end of example\n"); } ================================================ FILE: esp_cli_commands/examples/command_set/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: "*" ================================================ FILE: esp_cli_commands/examples/command_set/pytest_command_set.py ================================================ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_command_set(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_cli_commands/examples/command_with_arg/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(command_with_arg) ================================================ FILE: esp_cli_commands/examples/command_with_arg/README.md ================================================ # command_with_arg This example demonstrates static registration of a CLI command (`math_op`) that performs basic math operations (add, sub, mul, div) on two arguments, with error handling for invalid input and division by zero. ================================================ FILE: esp_cli_commands/examples/command_with_arg/main/CMakeLists.txt ================================================ idf_component_register( SRCS "command_with_arg_main.c" INCLUDE_DIRS "." "../../utils" REQUIRES esp_cli_commands ) ================================================ FILE: esp_cli_commands/examples/command_with_arg/main/command_with_arg_main.c ================================================ #include #include #include "esp_cli_commands.h" #include "command_utils.h" // Handler function signature must match: int (*)(void *, esp_cli_commands_exec_arg_t *, int, char **) static int math_op_cmd_handler(void *ctx, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)ctx; if (argc != 4) { int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "math_op", &color, &bold); WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Usage: math_op %s\n", hint ? hint : " "); return -1; } const char *op = argv[1]; int a = atoi(argv[2]); int b = atoi(argv[3]); int result = 0; WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Performing operation: %s %d %d\n", op, a, b); if (strcmp(op, "add") == 0) { result = a + b; } else if (strcmp(op, "sub") == 0) { result = a - b; } else if (strcmp(op, "mul") == 0) { result = a * b; } else if (strcmp(op, "div") == 0) { if (b == 0) { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Error: Division by zero\n"); return -2; } result = a / b; } else { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Unknown operation: %s\n", op); return -3; } WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Result: %d\n", result); return 0; } // Hint callback signature: const char *(*)(void *) static const char *math_op_cmd_hint_cb(void *ctx) { (void)ctx; return " "; } // Glossary callback signature: const char *(*)(void *) static const char *math_op_cmd_glossary_cb(void *ctx) { (void)ctx; return "Performs a math operation (add, sub, mul, div) on two integers."; } // Static registration of the math_op command with all fields ESP_CLI_COMMAND_REGISTER( math_op, // Command name example, // Command group "Performs math operation on two integers", // Help string math_op_cmd_handler, // Handler function NULL, // Context pointer math_op_cmd_hint_cb, // Hint callback math_op_cmd_glossary_cb // Glossary callback ); void app_main(void) { printf("esp_cli_commands command_with_arg example started.\n"); esp_cli_commands_exec_arg_t cmd_args = { .out_fd = STDOUT_FILENO, .write_func = write, .dynamic_ctx = NULL }; // Print help output for all commands int ret = -1; esp_err_t err = esp_cli_commands_execute("help", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'help' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'help' command, error: %d\n", err); } // Find the 'math_op' command by name esp_cli_command_t *cmd = esp_cli_commands_find_command(NULL, "math_op"); if (cmd) { printf("Found command: %s\n", cmd->name); } else { printf("Command 'math_op' not found!\n"); } // Execute the 'math_op' command programmatically (example: add 3 5) ret = -1; err = esp_cli_commands_execute("math_op add 3 5", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'math_op' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'math_op' command, error: %d\n", err); } // Execute the 'math_op' command with wrong arguments (missing b) ret = -1; err = esp_cli_commands_execute("math_op add 3", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'math_op' command (wrong args) executed, return value: %d\n", ret); } else { printf("Failed to execute 'math_op' command (wrong args), error: %d\n", err); } // Get hint for the 'math_op' command int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "math_op", &color, &bold); printf("Hint for 'math_op': %s (color: %d, bold: %d)\n", hint ? hint : "none", color, bold); // Get glossary for the 'math_op' command const char *glossary = esp_cli_commands_get_glossary(NULL, "math_op"); printf("Glossary for 'math_op': %s\n", glossary ? glossary : "none"); printf("end of example\n"); } ================================================ FILE: esp_cli_commands/examples/command_with_arg/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: '*' ================================================ FILE: esp_cli_commands/examples/command_with_arg/pytest_command_with_arg.py ================================================ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_command_with_arg(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_cli_commands/examples/conftest.py ================================================ def pytest_ignore_collect(collection_path, config): skip_dirs = {'utils', 'managed_components'} return any(part in skip_dirs for part in collection_path.parts) ================================================ FILE: esp_cli_commands/examples/dynamic_registration/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(dynamic_registration) ================================================ FILE: esp_cli_commands/examples/dynamic_registration/README.md ================================================ # dynamic_registration This example demonstrates dynamic registration of a `debug` command and an `unregister` command at runtime. The `unregister` command can remove dynamically registered commands, including itself and `debug`. ================================================ FILE: esp_cli_commands/examples/dynamic_registration/main/CMakeLists.txt ================================================ # Main CMakeLists.txt for debug_and_unregister_dynamic example cmake_minimum_required(VERSION 3.22) idf_component_register( SRCS "dynamic_registration_main.c" INCLUDE_DIRS "" "../../utils" REQUIRES esp_cli_commands ) ================================================ FILE: esp_cli_commands/examples/dynamic_registration/main/dynamic_registration_main.c ================================================ #include #include #include "esp_cli_commands.h" #include "command_utils.h" static int debug_cmd_handler(void *ctx, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)ctx; WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Debug info: CLI is running.\n"); return 0; } static const char *debug_cmd_hint_cb(void *ctx) { (void)ctx; return "[No arguments]"; } static const char *debug_cmd_glossary_cb(void *ctx) { (void)ctx; return "Prints debug information."; } // Unregister command context static int unregister_cmd_handler(void *ctx, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)ctx; if (argc != 2) { int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "unregister", &color, &bold); WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Usage: unregister %s\n", hint ? hint : ""); return -1; } const char *cmd_name = argv[1]; esp_err_t err = esp_cli_commands_unregister_cmd(cmd_name); if (err == ESP_OK) { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Command '%s' unregistered successfully.\n", cmd_name); // If unregistering itself, print a message if (strcmp(cmd_name, "unregister") == 0) { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "'unregister' command has removed itself.\n"); } } else { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Failed to unregister command '%s', error: %d\n", cmd_name, err); } return err; } static const char *unregister_cmd_hint_cb(void *ctx) { (void)ctx; return ""; } static const char *unregister_cmd_glossary_cb(void *ctx) { (void)ctx; return "Unregisters a dynamically registered command, including itself."; } void app_main(void) { printf("esp_cli_commands dynamic_registration example started.\n"); esp_cli_commands_exec_arg_t cmd_args = { .out_fd = STDOUT_FILENO, .write_func = write, .dynamic_ctx = NULL }; int ret = -1; esp_err_t err = ESP_FAIL; // Dynamically register debug command esp_cli_command_t debug_cmd = { .name = "debug", .group = "example", .help = "Prints debug information", .func = debug_cmd_handler, .func_ctx = NULL, .hint_cb = debug_cmd_hint_cb, .glossary_cb = debug_cmd_glossary_cb }; ESP_ERROR_CHECK(esp_cli_commands_register_cmd(&debug_cmd)); // Dynamically register unregister commandF esp_cli_command_t unregister_cmd = { .name = "unregister", .group = "example", .help = "Unregisters a command by name", .func = unregister_cmd_handler, .func_ctx = NULL, .hint_cb = unregister_cmd_hint_cb, .glossary_cb = unregister_cmd_glossary_cb }; ESP_ERROR_CHECK(esp_cli_commands_register_cmd(&unregister_cmd)); // Show that debug and unregister commands are available ret = -1; err = esp_cli_commands_execute("help", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'help' command executed successfully after dynamic registration, return value: %d\n", ret); } else { printf("Failed to execute 'help' command after dynamic registration, error: %d\n", err); } // Execute debug command ret = -1; err = esp_cli_commands_execute("debug", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'debug' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'debug' command, error: %d\n", err); } // Unregister debug command using unregister command ret = -1; err = esp_cli_commands_execute("unregister debug", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'unregister debug' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'unregister debug' command, error: %d\n", err); } // Unregister itself ret = -1; err = esp_cli_commands_execute("unregister unregister", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'unregister unregister' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'unregister unregister' command, error: %d\n", err); } // Show that debug and unregister commands are no longer registered ret = -1; err = esp_cli_commands_execute("help", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'help' command executed successfully after dynamic registration, return value: %d\n", ret); } else { printf("Failed to execute 'help' command after dynamic registration, error: %d\n", err); } printf("end of example\n"); } ================================================ FILE: esp_cli_commands/examples/dynamic_registration/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: '*' ================================================ FILE: esp_cli_commands/examples/dynamic_registration/pytest_dynamic_registration.py ================================================ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_dynamic_registration(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(integration_with_argtable) ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/README.md ================================================ # integration_with_argtable This example demonstrates how to integrate argtable3 together with esp_cli_commands in order to generates hints and glossary for commands. ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/main/CMakeLists.txt ================================================ idf_component_register( SRCS "integration_with_argtable_main.c" INCLUDE_DIRS "." "../../utils" REQUIRES esp_cli_commands argtable3 ) ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: '*' espressif/argtable3: '*' ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/main/integration_with_argtable_main.c ================================================ #include #include #include "esp_cli_commands.h" #include "argtable3/argtable3.h" #include "command_utils.h" static struct { struct arg_str *operator; struct arg_int *operand_a; struct arg_int *operand_b; struct arg_end *end; } math_op_args; static void math_op_args_init(void) { math_op_args.operator = arg_str1("o", "operator", "", "operation to perform (add, sub, mul, div)"); math_op_args.operand_a = arg_int1("a", "operand-a", "", "left side operand"); math_op_args.operand_b = arg_int1("b", "operand-b", "", "right side operand"); math_op_args.end = arg_end(3); } // Handler function signature must match: int (*)(void *, esp_cli_commands_exec_arg_t *, int, char **) static int math_op_cmd_handler(void *ctx, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)ctx; if (argc != 4) { int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "math_op", &color, &bold); WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Usage: math_op %s\n", hint ? hint : " "); return -1; } const char *op = argv[1]; int a = atoi(argv[2]); int b = atoi(argv[3]); int result = 0; WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Performing operation: %s %d %d\n", op, a, b); if (strcmp(op, "add") == 0) { result = a + b; } else if (strcmp(op, "sub") == 0) { result = a - b; } else if (strcmp(op, "mul") == 0) { result = a * b; } else if (strcmp(op, "div") == 0) { if (b == 0) { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Error: Division by zero\n"); return -2; } result = a / b; } else { WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Unknown operation: %s\n", op); return -3; } WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Result: %d\n", result); return 0; } // Hint callback signature: const char *(*)(void *) static const char *math_op_cmd_hint_cb(void *ctx) { (void)ctx; arg_dstr_t ds = arg_dstr_create(); arg_print_syntax_ds(ds, (void *)&math_op_args, NULL); const char *hint_str = strdup(arg_dstr_cstr(ds)); arg_dstr_destroy(ds); return hint_str; } // Glossary callback signature: const char *(*)(void *) static const char *math_op_cmd_glossary_cb(void *ctx) { (void)ctx; arg_dstr_t ds = arg_dstr_create(); arg_print_glossary_ds(ds, (void *)&math_op_args, NULL); const char *glossary_str = strdup(arg_dstr_cstr(ds)); arg_dstr_destroy(ds); return glossary_str; } // Static registration of the math_op command with all fields ESP_CLI_COMMAND_REGISTER( math_op, // Command name example, // Command group "Performs math operation on two integers", // Help string math_op_cmd_handler, // Handler function NULL, // Context pointer math_op_cmd_hint_cb, // Hint callback math_op_cmd_glossary_cb // Glossary callback ); void app_main(void) { printf("esp_cli_commands integration_with_argtable example started.\n"); esp_cli_commands_exec_arg_t cmd_args = { .out_fd = STDOUT_FILENO, .write_func = write, .dynamic_ctx = NULL }; math_op_args_init(); // Get hint for the 'math_op' command int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "math_op", &color, &bold); printf("Hint for 'math_op': %s (color: %d, bold: %d)\n", hint ? hint : "none", color, bold); // Get glossary for the 'math_op' command const char *glossary = esp_cli_commands_get_glossary(NULL, "math_op"); printf("Glossary for 'math_op': %s\n", glossary ? glossary : "none"); int ret = -1; esp_err_t err = esp_cli_commands_execute("math_op add 3 5", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'math_op' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'math_op' command, error: %d\n", err); } printf("end of example\n"); } ================================================ FILE: esp_cli_commands/examples/integration_with_argtable/pytest_integration_with_argtable.py ================================================ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_integration_with_argtable(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_cli_commands/examples/static_registration/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(static_registration) ================================================ FILE: esp_cli_commands/examples/static_registration/README.md ================================================ # static_registration This example demonstrates static registration of a simple CLI command (`hello`) using esp_cli_commands.. ================================================ FILE: esp_cli_commands/examples/static_registration/main/CMakeLists.txt ================================================ idf_component_register( SRCS "static_registration_main.c" INCLUDE_DIRS ".." "../../utils" REQUIRES esp_cli_commands ) ================================================ FILE: esp_cli_commands/examples/static_registration/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: '*' ================================================ FILE: esp_cli_commands/examples/static_registration/main/static_registration_main.c ================================================ #include #include "esp_cli_commands.h" #include "command_utils.h" // Context for the command (must be known at compile time for static registration) static int hello_cmd_ctx = 0; // Handler function signature must match: int (*)(void *, esp_cli_commands_exec_arg_t *, int, char **) static int hello_cmd_handler(void *ctx, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { (void)ctx; (void)argc; (void)argv; WRITE_FN(cmd_arg->write_func, cmd_arg->out_fd, "Hello! This is the esp_cli_commands static example.\n"); return 0; } // Hint callback signature: const char *(*)(void *) static const char *hello_cmd_hint_cb(void *ctx) { (void)ctx; return "[No arguments]"; } // Glossary callback signature: const char *(*)(void *) static const char *hello_cmd_glossary_cb(void *ctx) { (void)ctx; return "This command prints a hello message for demonstration purposes."; } // Static registration of the hello command with all fields ESP_CLI_COMMAND_REGISTER( hello, // Command name example, // Command group "Prints a hello message", // Help string hello_cmd_handler, // Handler function &hello_cmd_ctx, // Context pointer (must be address of static object) (optional argument) hello_cmd_hint_cb, // Hint callback (optional argument) hello_cmd_glossary_cb // Glossary callback (optional argument) ); void app_main(void) { printf("esp_cli_commands static_registration example started.\n"); esp_cli_commands_exec_arg_t cmd_args = { .out_fd = STDOUT_FILENO, .write_func = write, .dynamic_ctx = NULL }; // Print help output for all commands int ret = -1; esp_err_t err = esp_cli_commands_execute("help", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'help' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'help' command, error: %d\n", err); } // Find the 'hello' command by name esp_cli_command_t *cmd = esp_cli_commands_find_command(NULL, "hello"); if (cmd) { printf("Found command: %s\n", cmd->name); } else { printf("Command 'hello' not found!\n"); } // Execute the 'hello' command programmatically ret = -1; err = esp_cli_commands_execute("hello", &ret, NULL, &cmd_args); if (err == ESP_OK) { printf("'hello' command executed successfully, return value: %d\n", ret); } else { printf("Failed to execute 'hello' command, error: %d\n", err); } // Get hint for the 'hello' command int color = 0; bool bold = false; const char *hint = esp_cli_commands_get_hint(NULL, "hello", &color, &bold); printf("Hint for 'hello': %s (color: %d, bold: %d)\n", hint ? hint : "none", color, bold); // Get glossary for the 'hello' command const char *glossary = esp_cli_commands_get_glossary(NULL, "hello"); printf("Glossary for 'hello': %s\n", glossary ? glossary : "none"); printf("end of example\n"); } ================================================ FILE: esp_cli_commands/examples/static_registration/pytest_static_registration.py ================================================ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_static_registration(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_cli_commands/examples/utils/command_utils.h ================================================ #pragma once #include "unistd.h" #define WRITE_FN(fn, fd, fmt, ...) do { \ char _buf[256]; \ int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ if (_len > 0) \ fn(fd, _buf, _len); \ } while(0) ================================================ FILE: esp_cli_commands/idf_component.yml ================================================ version: "0.1.3" description: "esp_cli_commands - Command handling component" url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli_commands dependencies: idf: ">=5.3" sbom: manifests: - path: sbom_esp_cli_commands.yml dest: . ================================================ FILE: esp_cli_commands/include/esp_cli_commands.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include "esp_cli_commands_utils.h" #include "esp_heap_caps.h" #include "esp_err.h" /** * @brief Update the component configuration * * @param config Configuration data to update * @return ESP_OK if successful * ESP_ERR_INVALID_ARG if config pointer is NULL */ esp_err_t esp_cli_commands_update_config(const esp_cli_commands_config_t *config); /** * @brief macro registering a command and placing it in a specific section of flash.rodata * @note see the linker.lf file for more information concerning the section characteristics */ #define _ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ static_assert((cmd_func) != NULL); \ static const esp_cli_command_t cmd_name __attribute__((used, section(".esp_cli_commands" "." _ESP_REPL_STRINGIFY(cmd_name)), aligned(4))) = { \ .name = _ESP_REPL_STRINGIFY(cmd_name), \ .group = _ESP_REPL_STRINGIFY(cmd_group), \ .help = cmd_help, \ .func = cmd_func, \ .func_ctx = cmd_func_ctx, \ .hint_cb = cmd_hint_cb, \ .glossary_cb = cmd_glossary_cb \ }; #define ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ _ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) /** * @brief Register a command * * @param cmd Pointer to the command structure * @return ESP_OK if successful * Other esp_err_t on error */ esp_err_t esp_cli_commands_register_cmd(esp_cli_command_t *cmd); /** * @brief Unregister a command by name or group * * @param cmd_name Name or group of the command to unregister * @return ESP_OK if successful * Other esp_err_t on error */ esp_err_t esp_cli_commands_unregister_cmd(const char *cmd_name); /** * @brief Execute a command line * * @param cmd_line Command line string to execute * @param cmd_ret Return value from the command function. If -1, standard output will be used. * @param cmd_set Set of commands allowed to execute. If NULL, all registered commands are allowed * @param cmd_arg Structure containing dynamic arguments necessary for the command * callback to execute properly * @return ESP_OK on success * ESP_ERR_INVALID_ARG if the command line is empty or only whitespace * ESP_ERR_NOT_FOUND if command is not found in cmd_set * ESP_ERR_NO_MEM if internal memory allocation fails */ esp_err_t esp_cli_commands_execute(const char *cmdline, int *cmd_ret, esp_cli_command_set_handle_t cmd_set, esp_cli_commands_exec_arg_t *cmd_args); /** * @brief Find a command by name within a specific command set. * * This function searches a command whose name matches the provided string. * * @param cmd_set Handle to the command set to search in. Must be a valid * `esp_cli_command_set_handle_t` or `NULL` if the search should be performed * on all statically and dynamically registered commands. * @param name String containing the name of the command to search for. * * @return pointer to the matching command or NULL if no command is found. */ esp_cli_command_t *esp_cli_commands_find_command(esp_cli_command_set_handle_t cmd_set, const char *name); /** * @brief Provide command completion for linenoise library * * @param cmd_set Set of commands allowed for completion. If NULL, all registered commands are used * @param buf Input string typed by the user * @param cb_ctx context passed to the completion callback * @param completion_cb Callback to return completed command names */ void esp_cli_commands_get_completion(esp_cli_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_cli_command_get_completion_t completion_cb); /** * @brief Provide command hint for linenoise library * * @param cmd_set Set of commands allowed for hinting. If NULL, all registered commands are used * @param buf Input string typed by the user * @param[out] color ANSI color code for hint text * @param[out] bold True if hint should be displayed in bold * @return Persistent string containing the hint; must not be freed */ const char *esp_cli_commands_get_hint(esp_cli_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold); /** * @brief Retrieve glossary for a command line * * @param cmd_set Set of commands allowed * @param buf Command line typed by the user * @return Persistent string containing the glossary; must not be freed */ const char *esp_cli_commands_get_glossary(esp_cli_command_set_handle_t cmd_set, const char *buf); /** * @brief Create a command set from an array of command names * * @param cmd_set Array of command names * @param cmd_set_size Number of entries in cmd_set * @param get_field Function to retrieve the field from esp_cli_command_t for comparison * @return Handle to the created command set */ esp_cli_command_set_handle_t esp_cli_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_cli_commands_get_field_t get_field); /** * @brief Convenience macro to create a command set * * @param cmd_set Array of command names * @param accessor Field accessor function */ #define ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_set, accessor) \ esp_cli_commands_create_cmd_set(cmd_set, sizeof(cmd_set) / sizeof((cmd_set)[0]), accessor) /** * @brief Concatenate two command sets * * @note If one set is NULL, the other is returned * @note If both are NULL, returns NULL * @note Duplicates are not removed * * @param cmd_set_a First command set * @param cmd_set_b Second command set * @return New command set containing all commands from both sets */ esp_cli_command_set_handle_t esp_cli_commands_concat_cmd_set(esp_cli_command_set_handle_t cmd_set_a, esp_cli_command_set_handle_t cmd_set_b); /** * @brief Destroy a command set * * @param cmd_set Pointer to the handle of the command set to destroy */ void esp_cli_commands_destroy_cmd_set(esp_cli_command_set_handle_t *cmd_set); /** * @brief Split a command line and populate argc and argv parameters * * @param line the line that has to be split into arguments * @param argv array of arguments created from the line * @param argv_size size of the argument array * @return size_t number of arguments found in the line and stored * in argv */ size_t esp_cli_commands_split_argv(char *line, char **argv, size_t argv_size); #ifdef __cplusplus } #endif ================================================ FILE: esp_cli_commands/include/esp_cli_commands_utils.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #define _ESP_REPL_STRINGIFY(x) #x #define ESP_REPL_STRINGIFY(x) _ESP_REPL_STRINGIFY(x) /** * @brief Function pointer type for writing bytes. * * @param fd File descriptor. * @param buf Buffer containing bytes to write. * @param count Number of bytes to write. * @return Number of bytes written, or -1 on error. */ typedef ssize_t (*esp_cli_commands_write_t)(int fd, const void *buf, size_t count); /** * @brief Structure containing dynamic argument necessary for the * command callback to execute properly. * * @note Since a command function callback can be executed from * a random context, the callback has to be aware of what file descriptor * and what write function to use in order to print data to the expected * destination. */ typedef struct esp_cli_commands_exec_arg { int out_fd; /*!< file descriptor that the command function has to use to print data in the environment it was called from */ esp_cli_commands_write_t write_func; /*!< write function the command function has to use to print data in the environment it was called from */ void *dynamic_ctx; /*!< dynamic context passed to the command function */ } esp_cli_commands_exec_arg_t; /** * @brief Console command main function type with user context * * This function type is used to implement a console command. * * @param context User-defined context passed at invocation * @param cmd_arg Structure containing dynamic arguments necessary for the command * @param argc Number of arguments * @param argv Array of argc entries, each pointing to a null-terminated string argument * @return Return code of the console command; 0 indicates success */ typedef int (*esp_cli_command_func_t)(void *context, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv); /** * @brief Callback to generate a command hint * * This function is called to retrieve a short hint for a command, * typically used for auto-completion or UI help. * * @param context Context registered when the command was registered * @return Persistent string containing the generated hint */ typedef const char *(*esp_cli_command_hint_t)(void *context); /** * @brief Callback to generate a command glossary entry * * This function is called to retrieve detailed description or glossary * information for a command. * * @param context Context registered when the command was registered * @return Persistent string containing the generated glossary */ typedef const char *(*esp_cli_command_glossary_t)(void *context); /** * @brief Structure describing a console command * * @note The `group` field allows categorizing commands into groups, * which can simplify filtering or listing commands. */ typedef struct esp_cli_command { const char *name; /*!< Name of the command */ const char *group; /*!< Command group to which this command belongs */ const char *help; /*!< Short help text for the command */ esp_cli_command_func_t func; /*!< Function implementing the command */ void *func_ctx; /*!< User-defined context for the command function */ esp_cli_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ esp_cli_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ } esp_cli_command_t; /** * @brief Configuration parameters for esp_cli_commands_manager initialization */ typedef struct esp_cli_commands_config { uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ int hint_color; /*!< ANSI color code used for hint text */ bool hint_bold; /*!< If true, display hint text in bold */ } esp_cli_commands_config_t; /** * @brief Callback for a completed command name * * This callback is called when a command is successfully completed. * * @param cb_ctx Opaque pointer pointing at the context passed to the callback * @param completed_cmd_name Completed command name */ typedef void (*esp_cli_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); /** * @brief Callback to retrieve a string field of esp_cli_command_t * * @param cmd Command object * @return Value of the requested string field */ typedef const char *(*esp_cli_commands_get_field_t)(const esp_cli_command_t *cmd); /** * @brief Opaque handle to a set of commands */ typedef struct esp_cli_command_sets *esp_cli_command_set_handle_t; /** * @brief Macro to define a forced inline accessor for a string field of esp_cli_command_t * * @param NAME Field name of the esp_cli_command_t structure */ #define DEFINE_FIELD_ACCESSOR(NAME) \ static inline __attribute__((always_inline)) \ const char *get_##NAME(const esp_cli_command_t *cmd) { \ if (!cmd) { \ return NULL; \ } \ return cmd->NAME; \ } /** * @brief Macro expanding to * static inline __attribute__((always_inline)) const char *get_name(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } * return cmd->name; * } */ DEFINE_FIELD_ACCESSOR(name) /** * @brief Macro expanding to * static inline __attribute__((always_inline)) const char *get_group(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } * return cmd->group; * } */ DEFINE_FIELD_ACCESSOR(group) /** * @brief Macro expanding to * static inline __attribute__((always_inline)) const char *get_help(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } * return cmd->help; * } */ DEFINE_FIELD_ACCESSOR(help) /** * @brief Macro to create the accessor function name for a field of esp_cli_command_t * * @note Those accessor functions are defined in esp_cli_commands_internal.h * * @param NAME Field name of esp_cli_command_t */ #define ESP_CLI_COMMAND_FIELD_ACCESSOR(NAME) get_##NAME #ifdef __cplusplus } #endif ================================================ FILE: esp_cli_commands/linker.lf ================================================ [sections:esp_cli_commands] entries: .esp_cli_commands+ [scheme:esp_cli_commands_default] entries: esp_cli_commands -> flash_rodata [mapping:esp_cli_commands] archive: * entries: * (esp_cli_commands_default); esp_cli_commands -> flash_rodata KEEP() SORT(name) SURROUND(esp_cli_commands) ================================================ FILE: esp_cli_commands/linux/esp_cli_commands.ld ================================================ SECTIONS { .esp_cli_commands : { PROVIDE(_esp_cli_commands_start = .); KEEP(*(SORT(.esp_cli_commands*))) /* Concatenate all .esp_cli_commands */ PROVIDE(_esp_cli_commands_end = .); } } INSERT AFTER .rodata; ================================================ FILE: esp_cli_commands/private_include/esp_cli_commands_internal.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include "esp_heap_caps.h" /** * @brief Component specific implementation of malloc * * @note This function uses heap_caps_malloc together * with the set of capabilities provided by the user in the * config structure. Implemented in esp_cli_commands.c * * @param malloc_size * @return void* */ void *esp_cli_commands_malloc(const size_t malloc_size); #ifdef __cplusplus } #endif ================================================ FILE: esp_cli_commands/private_include/esp_cli_dynamic_commands.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include "sys/queue.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_cli_commands.h" /** * @brief Structure representing a fixed set of commands. * * This is typically used for static or predefined command lists. */ typedef struct esp_cli_command_set { esp_cli_command_t **cmd_ptr_set; /*!< Array of pointers to commands. */ size_t cmd_set_size; /*!< Number of commands in the set. */ } esp_cli_command_set_t; /** * @brief Internal structure for a dynamically registered command. * * Each dynamic command is stored as an `esp_cli_command_t` plus * linked list metadata for insertion/removal. */ typedef struct esp_cli_command_internal { esp_cli_command_t cmd; /*!< Command instance. */ SLIST_ENTRY(esp_cli_command_internal) next_item; /*!< Linked list entry metadata. */ } esp_cli_command_internal_t; /** * @brief Linked list head type for dynamic command storage. */ typedef SLIST_HEAD(esp_cli_command_internal_ll, esp_cli_command_internal) esp_cli_command_internal_ll_t; /** * @brief Iterate over a set of commands, either from a static set or dynamic list. * * This macro supports iterating over: * - A provided `esp_cli_command_set_t` (static set), OR * - The global dynamic command list if `cmd_set` is `NULL`. * * @param cmd_set Pointer to a command set (`esp_cli_command_set_t`) or `NULL` for dynamic commands. * @param item_cmd Iterator variable of type `esp_cli_command_t *` that will point to each command. * * @note Internally, the macro uses `_node` and `_i` as hidden variables. */ #define FOR_EACH_DYNAMIC_COMMAND(cmd_set, item_cmd) \ __attribute__((unused)) esp_cli_command_internal_t *_node = \ ((cmd_set) == NULL ? SLIST_FIRST(esp_cli_dynamic_commands_get_list()) \ : NULL); \ __attribute__((unused)) size_t _i = 0; \ for (; \ ((cmd_set) == NULL \ ? ((_node != NULL) && ((item_cmd) = &_node->cmd)) \ : (_i < (cmd_set)->cmd_set_size && \ ((item_cmd) = (cmd_set)->cmd_ptr_set[_i]))); \ ((cmd_set) == NULL \ ? (_node = SLIST_NEXT(_node, next_item)) \ : (void)++_i)) /** * @brief Acquire the dynamic commands lock. * * This function must be called before modifying or iterating over * the dynamic command list to ensure thread safety. */ void esp_cli_dynamic_commands_lock(void); /** * @brief Release the dynamic commands lock. * * Call this after operations on the dynamic command list are complete. */ void esp_cli_dynamic_commands_unlock(void); /** * @brief Get the internal linked list of dynamic commands. * * @return Pointer to the dynamic command linked list head. * * @warning The returned list is internal; do not modify it directly. * Use provided API functions to modify dynamic commands. */ const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void); /** * @brief Add a new command to the dynamic command list. * * @param cmd Pointer to the command to add. * @return * - `ESP_OK` on success. * - Appropriate error code on failure. * * @note The function acquires the lock internally. */ esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd); /** * @brief Replace an existing command in the dynamic command list. * * If a command with the same name exists, it will be replaced. * * @param old_cmd Pointer to the existing command to be replaced. * @param new_cmd Pointer to the new command data. * @return * - `ESP_OK` on success. * - Appropriate error code on failure. */ esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *old_cmd, esp_cli_command_t *new_cmd); /** * @brief Remove a command from the dynamic command list. * * @param item_cmd Pointer to the command to remove. * @return * - `ESP_OK` on success. * - Appropriate error code on failure. */ esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd); /** * @brief Get the number of registered dynamic commands. * * @return The total number of dynamic commands currently registered. */ size_t esp_cli_dynamic_commands_get_number_of_cmd(void); #ifdef __cplusplus } #endif ================================================ FILE: esp_cli_commands/sbom_esp_cli_commands.yml ================================================ name: esp_cli_commands description: Command handling component url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli_commands version: 1.0.0 cpe: cpe:2.3:a:espressif:esp_cli_commands:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems' ================================================ FILE: esp_cli_commands/src/esp_cli_commands.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_heap_caps.h" #include "esp_cli_commands.h" #include "esp_cli_commands_internal.h" #include "esp_cli_dynamic_commands.h" #include "esp_err.h" /* Default foreground color */ #define ANSI_COLOR_DEFAULT 39 /* static mutex used to protect access to the static configuration */ static SemaphoreHandle_t s_esp_cli_commands_mutex = NULL; static StaticSemaphore_t s_esp_cli_commands_mutex_buf; /* Pointers to the first and last command in the dedicated section. * See linker.lf for detailed information about the section */ extern esp_cli_command_t _esp_cli_commands_start; extern esp_cli_command_t _esp_cli_commands_end; typedef struct esp_cli_command_sets { esp_cli_command_set_t static_set; esp_cli_command_set_t dynamic_set; } esp_cli_command_sets_t; /** run-time configuration options */ static esp_cli_commands_config_t s_config = { .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = ANSI_COLOR_DEFAULT, .max_cmdline_args = 32, .max_cmdline_length = 256 }; /** * @brief go through all commands registered in the * memory section starting at _esp_cli_commands_start * and ending at _esp_cli_commands_end OR go through all * the commands listed in cmd_set if not NULL */ #define FOR_EACH_STATIC_COMMAND(cmd_set, cmd) \ for (size_t _i = 0; \ ((cmd_set) == NULL \ ? (((cmd) = &_esp_cli_commands_start + _i), \ (&_esp_cli_commands_start + _i) < &_esp_cli_commands_end) \ : (((cmd) = (cmd_set)->cmd_ptr_set[_i]), \ _i < (cmd_set)->cmd_set_size)); \ ++_i) /** * @brief Get the number of commands registered in the .esp_cli_commands section */ #define ESP_CLI_COMMANDS_COUNT \ (((uintptr_t)&_esp_cli_commands_end - (uintptr_t)&_esp_cli_commands_start) / sizeof(esp_cli_command_t)) /** * @brief Lock access to the s_config static structure */ static void esp_cli_commands_lock(void) { if (s_esp_cli_commands_mutex == NULL) { s_esp_cli_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_mutex_buf); assert(s_esp_cli_commands_mutex != NULL); } xSemaphoreTake(s_esp_cli_commands_mutex, portMAX_DELAY); } /** * @brief Unlock access to the s_config static structure */ static void esp_cli_commands_unlock(void) { xSemaphoreGive(s_esp_cli_commands_mutex); } /** * @brief check the location of the pointer to esp_cli_command_t * * @param cmd the pointer to the command to check * @return true if the command was registered statically * false if the command was registered dynamically */ static inline __attribute__((always_inline)) bool command_is_static(esp_cli_command_t *cmd) { if (cmd >= &_esp_cli_commands_start && cmd <= &_esp_cli_commands_end) { return true; } return false; } typedef bool (*walker_t)(void *walker_ctx, esp_cli_command_t *cmd); static inline __attribute__((always_inline)) void go_through_commands(esp_cli_command_sets_t *cmd_sets, void *cmd_walker_ctx, walker_t cmd_walker) { if (!cmd_walker) { return; } esp_cli_command_t *cmd = NULL; bool continue_walk = false; /* cmd_sets is composed of 2 sets (static and dynamic). * - If cmd_sets is NULL, go through all the statically AND dynamically registered commands. * - If cmd_sets is not NULL and either the static or the dynamic set is empty, then the macros * FOR_EACH_XX_COMMAND will not go through the whole list of static (resp. dynamic) commands but * through the empty set, so no command will be walked. */ esp_cli_command_set_t *static_set = cmd_sets ? &cmd_sets->static_set : NULL; /* it is possible that the set is empty, in which case set static_set to NULL * to prevent the for loop to try to access a list of commands pointer set to NULL */ if (static_set && !static_set->cmd_ptr_set) { static_set = NULL; } FOR_EACH_STATIC_COMMAND(static_set, cmd) { continue_walk = cmd_walker(cmd_walker_ctx, cmd); if (!continue_walk) { return; } } esp_cli_command_set_t *dynamic_set = cmd_sets ? &cmd_sets->dynamic_set : NULL; /* Note: unlike FOR_EACH_STATIC_COMMAND which uses the comma operator, * FOR_EACH_DYNAMIC_COMMAND uses && short-circuit evaluation, so it is safe * to pass a set with cmd_ptr_set == NULL and cmd_set_size == 0. * We must NOT null out the set here, because FOR_EACH_DYNAMIC_COMMAND(NULL, ...) * means "walk ALL dynamic commands", bypassing the set filter. */ esp_cli_dynamic_commands_lock(); FOR_EACH_DYNAMIC_COMMAND(dynamic_set, cmd) { continue_walk = cmd_walker(cmd_walker_ctx, cmd); if (!continue_walk) { esp_cli_dynamic_commands_unlock(); return; } } esp_cli_dynamic_commands_unlock(); } typedef struct find_cmd_ctx { const char *name; /*!< the name to check commands against */ esp_cli_command_t *cmd; /*!< the command matching the name */ } find_cmd_ctx_t; static inline __attribute__((always_inline)) bool compare_command_name(void *ctx, esp_cli_command_t *cmd) { /* called by esp_cli_commands_find_command through go_through_commands, * ctx cannot be NULL */ find_cmd_ctx_t *cmd_ctx = (find_cmd_ctx_t *)ctx; /* called by go_through_commands, thus cmd cannot be NULL */ if (strcmp(cmd->name, cmd_ctx->name) == 0) { /* command found, store it in the ctx so esp_cli_commands_find_command * can process it. Notify go_through_commands to stop the walk by * returning false */ cmd_ctx->cmd = cmd; return false; } /* command not matching with the name from the ctx, continue the walk */ return true; } void *esp_cli_commands_malloc(const size_t malloc_size) { esp_cli_commands_lock(); const uint32_t caps = s_config.heap_caps_used; esp_cli_commands_unlock(); return heap_caps_malloc(malloc_size, caps); } esp_err_t esp_cli_commands_update_config(const esp_cli_commands_config_t *config) { if (!config || (config->max_cmdline_args == 0) || (config->max_cmdline_length == 0)) { return ESP_ERR_INVALID_ARG; } esp_cli_commands_lock(); memcpy(&s_config, config, sizeof(s_config)); /* if the heap_caps_used field is set to 0, set * it to MALLOC_CAP_DEFAULT */ if (s_config.heap_caps_used == 0) { s_config.heap_caps_used = MALLOC_CAP_DEFAULT; } esp_cli_commands_unlock(); return ESP_OK; } esp_err_t esp_cli_commands_register_cmd(esp_cli_command_t *cmd) { if (cmd == NULL || (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || (cmd->func == NULL)) { return ESP_ERR_INVALID_ARG; } /* try to find the command in the static and dynamic lists. * if the dynamic list is empty, the mutex locking will fail * in esp_cli_commands_find_command and the function will return after * checking the static list only. */ esp_cli_command_t *list_item_cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd->name); esp_err_t ret_val = ESP_FAIL; if (!list_item_cmd) { /* command with given name not found, it is a new command, we can allocate * the list item and the command itself */ ret_val = esp_cli_dynamic_commands_add(cmd); } else if (command_is_static(list_item_cmd)) { /* a command with matching name is found in the list of commands * that were registered at runtime, in which case it cannot be * replaced with the new command */ ret_val = ESP_FAIL; } else { /* an item with matching name was found in the list of dynamically * registered commands. Replace the command on spot with the new esp_cli_command_t. */ ret_val = esp_cli_dynamic_commands_replace(list_item_cmd, cmd); } return ret_val; } esp_err_t esp_cli_commands_unregister_cmd(const char *cmd_name) { /* only items dynamically registered can be unregistered. * try to remove the item with the given name from the list * of dynamically registered commands */ esp_cli_command_t *cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd_name); if (!cmd) { return ESP_ERR_NOT_FOUND; } else if (command_is_static(cmd)) { return ESP_ERR_INVALID_ARG; } else { return esp_cli_dynamic_commands_remove(cmd); } } esp_err_t esp_cli_commands_execute(const char *cmdline, int *cmd_ret, esp_cli_command_set_handle_t cmd_set, esp_cli_commands_exec_arg_t *cmd_args) { esp_cli_commands_lock(); const size_t copy_max_cmdline_args = s_config.max_cmdline_args; const size_t opy_max_cmdline_length = s_config.max_cmdline_length; esp_cli_commands_unlock(); /* the life time of those variables is not exceeding the scope of this function. Use the stack. */ char *argv[copy_max_cmdline_args]; memset(argv, 0x00, sizeof(argv)); char tmp_line_buf[opy_max_cmdline_length]; memset(tmp_line_buf, 0x00, sizeof(tmp_line_buf)); /* copy the raw command line into the temp buffer */ strlcpy(tmp_line_buf, cmdline, opy_max_cmdline_length); /* parse and split the raw command line */ size_t argc = esp_cli_commands_split_argv(tmp_line_buf, argv, copy_max_cmdline_args); if (argc == 0) { return ESP_ERR_INVALID_ARG; } /* try to find the command from the first argument in the command line */ const esp_cli_command_t *cmd = NULL; esp_cli_command_sets_t *temp_set = cmd_set; bool is_cmd_help = false; if (strcmp("help", argv[0]) == 0) { /* set the set to NULL because the help is not in the set passed by the user * since this command is registered by esp_cli_commands itself */ temp_set = (esp_cli_command_sets_t *)NULL; /* keep in mind that the command being executed is the help. This is needed * when calling the help command function, to pass a specific dynamic context */ is_cmd_help = true; } cmd = esp_cli_commands_find_command(temp_set, argv[0]); if (cmd == NULL) { return ESP_ERR_NOT_FOUND; } if (cmd->func) { if (is_cmd_help) { esp_cli_commands_exec_arg_t help_args; /* reuse the out_fd and write_func received as parameter by esp_cli_commands_execute * to allow the help command function to print information on the correct IO. Use * default values in case the parameters provided are not set */ help_args.out_fd = (cmd_args && cmd_args->out_fd != -1) ? cmd_args->out_fd : STDOUT_FILENO; help_args.write_func = (cmd_args && cmd_args->write_func) ? cmd_args->write_func : write; /* the help command needs the cmd_set to be able to only print the help for commands * in the user set of commands */ help_args.dynamic_ctx = cmd_set; /* call the help command function with the specific dynamic context */ *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); } else { /* regular command function has to be called, just passed the cmd_args as provided * to the esp_cli_commands_execute function */ *cmd_ret = (*cmd->func)(cmd->func_ctx, cmd_args, argc, argv); } } return ESP_OK; } esp_cli_command_t *esp_cli_commands_find_command(esp_cli_command_set_handle_t cmd_set, const char *name) { /* no need to check that cmd_set is NULL, if it is, then FOR_EACH_XX_COMMAND * will go through all registered commands */ if (!name) { return NULL; } find_cmd_ctx_t ctx = { .cmd = NULL, .name = name }; go_through_commands(cmd_set, &ctx, compare_command_name); /* The "help" command is a built-in registered by esp_cli_commands itself. * It must always be found regardless of the command set passed by the user. * If not found in the filtered set, search all commands. */ if (!ctx.cmd && cmd_set != NULL && strcmp(name, "help") == 0) { go_through_commands(NULL, &ctx, compare_command_name); } /* if command was found during the walk, cmd field will be populated with * the command matching the name given in parameter, otherwise it will still * be NULL (value set as default value above) */ return ctx.cmd; } typedef struct create_cmd_set_ctx { esp_cli_commands_get_field_t get_field; const char *cmd_set_name; esp_cli_command_t **static_cmd_ptrs; size_t static_cmd_count; esp_cli_command_t **dynamic_cmd_ptrs; size_t dynamic_cmd_count; } create_cmd_set_ctx_t; static inline __attribute__((always_inline)) bool fill_temp_set_info(void *caller_ctx, esp_cli_command_t *cmd) { /* called by esp_cli_commands_find_command through go_through_commands, * ctx cannot be NULL */ create_cmd_set_ctx_t *ctx = (create_cmd_set_ctx_t *)caller_ctx; /* called by go_through_commands, thus cmd cannot be NULL */ if (strcmp(ctx->get_field(cmd), ctx->cmd_set_name) == 0) { // it's a match, add the pointer to command to the cmd ptr set if (command_is_static(cmd)) { ctx->static_cmd_ptrs[ctx->static_cmd_count] = cmd; ctx->static_cmd_count++; } else { ctx->dynamic_cmd_ptrs[ctx->dynamic_cmd_count] = cmd; ctx->dynamic_cmd_count++; } } /* command not matching with the name from the ctx, continue the walk */ return true; } static inline __attribute__((always_inline)) esp_err_t update_cmd_set_with_temp_info(esp_cli_command_set_t *cmd_set, size_t cmd_count, esp_cli_command_t **cmd_ptrs) { if (cmd_count == 0) { cmd_set->cmd_ptr_set = NULL; cmd_set->cmd_set_size = 0; } else { const size_t alloc_cmd_ptrs_size = sizeof(esp_cli_command_t *) * cmd_count; cmd_set->cmd_ptr_set = esp_cli_commands_malloc(alloc_cmd_ptrs_size); if (!cmd_set->cmd_ptr_set) { return ESP_ERR_NO_MEM; } else { /* copy the temp set of pointer in to the final destination */ memcpy(cmd_set->cmd_ptr_set, cmd_ptrs, alloc_cmd_ptrs_size); cmd_set->cmd_set_size = cmd_count; } } return ESP_OK; } esp_cli_command_set_handle_t esp_cli_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_cli_commands_get_field_t get_field) { if (!cmd_set || cmd_set_size == 0) { return NULL; } esp_cli_command_sets_t *cmd_ptr_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); if (!cmd_ptr_sets) { return NULL; } esp_cli_command_t *static_cmd_ptrs_temp[ESP_CLI_COMMANDS_COUNT]; esp_cli_command_t *dynamic_cmd_ptrs_temp[esp_cli_dynamic_commands_get_number_of_cmd()]; create_cmd_set_ctx_t ctx = { .cmd_set_name = NULL, .get_field = get_field, .static_cmd_ptrs = static_cmd_ptrs_temp, .static_cmd_count = 0, .dynamic_cmd_ptrs = dynamic_cmd_ptrs_temp, .dynamic_cmd_count = 0 }; /* populate the temporary cmd pointer sets */ for (size_t i = 0; i < cmd_set_size; i++) { ctx.cmd_set_name = cmd_set[i]; go_through_commands(NULL, &ctx, fill_temp_set_info); } /* if no static command was found, return a static set with 0 items in it */ esp_err_t ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->static_set, ctx.static_cmd_count, ctx.static_cmd_ptrs); if (ret_val == ESP_ERR_NO_MEM) { free(cmd_ptr_sets); return NULL; } /* if no dynamic command was found, return a dynamic set with 0 items in it */ ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->dynamic_set, ctx.dynamic_cmd_count, ctx.dynamic_cmd_ptrs); if (ret_val == ESP_ERR_NO_MEM) { free(cmd_ptr_sets->static_set.cmd_ptr_set); free(cmd_ptr_sets); return NULL; } return (esp_cli_command_set_handle_t)cmd_ptr_sets; } esp_cli_command_set_handle_t esp_cli_commands_concat_cmd_set(esp_cli_command_set_handle_t cmd_set_a, esp_cli_command_set_handle_t cmd_set_b) { if (!cmd_set_a && !cmd_set_b) { return NULL; } else if (cmd_set_a && !cmd_set_b) { return cmd_set_a; } else if (!cmd_set_a && cmd_set_b) { return cmd_set_b; } /* Reaching this point, both cmd_set_a and cmd_set_b are set. * Create a new cmd_set that can host the items from both sets, * assign the items to the new set and free the input sets */ esp_cli_command_sets_t *concat_cmd_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); if (!concat_cmd_sets) { return NULL; } const size_t new_static_set_size = cmd_set_a->static_set.cmd_set_size + cmd_set_b->static_set.cmd_set_size; concat_cmd_sets->static_set.cmd_ptr_set = calloc(new_static_set_size, sizeof(esp_cli_command_t *)); if (!concat_cmd_sets->static_set.cmd_ptr_set) { free(concat_cmd_sets); return NULL; } const size_t new_dynamic_set_size = cmd_set_a->dynamic_set.cmd_set_size + cmd_set_b->dynamic_set.cmd_set_size; concat_cmd_sets->dynamic_set.cmd_ptr_set = calloc(new_dynamic_set_size, sizeof(esp_cli_command_t *)); if (!concat_cmd_sets->static_set.cmd_ptr_set) { free(concat_cmd_sets->static_set.cmd_ptr_set); free(concat_cmd_sets); return NULL; } /* update the new cmd set sizes */ concat_cmd_sets->static_set.cmd_set_size = new_static_set_size; concat_cmd_sets->dynamic_set.cmd_set_size = new_dynamic_set_size; /* fill the list of command pointers */ memcpy(concat_cmd_sets->static_set.cmd_ptr_set, cmd_set_a->static_set.cmd_ptr_set, sizeof(esp_cli_command_t *) * cmd_set_a->static_set.cmd_set_size); memcpy(concat_cmd_sets->static_set.cmd_ptr_set + cmd_set_a->static_set.cmd_set_size, cmd_set_b->static_set.cmd_ptr_set, sizeof(esp_cli_command_t *) * cmd_set_b->static_set.cmd_set_size); memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set, cmd_set_a->dynamic_set.cmd_ptr_set, sizeof(esp_cli_command_t *) * cmd_set_a->dynamic_set.cmd_set_size); memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set + cmd_set_a->dynamic_set.cmd_set_size, cmd_set_b->dynamic_set.cmd_ptr_set, sizeof(esp_cli_command_t *) * cmd_set_b->dynamic_set.cmd_set_size); esp_cli_commands_destroy_cmd_set(&cmd_set_a); esp_cli_commands_destroy_cmd_set(&cmd_set_b); return (esp_cli_command_set_handle_t)concat_cmd_sets; } void esp_cli_commands_destroy_cmd_set(esp_cli_command_set_handle_t *cmd_set) { if (!cmd_set || !*cmd_set) { return; } if ((*cmd_set)->static_set.cmd_ptr_set) { free((*cmd_set)->static_set.cmd_ptr_set); } if ((*cmd_set)->dynamic_set.cmd_ptr_set) { free((*cmd_set)->dynamic_set.cmd_ptr_set); } free(*cmd_set); *cmd_set = NULL; } typedef struct call_completion_cb_ctx { const char *buf; const size_t buf_len; void *cb_ctx; esp_cli_command_get_completion_t completion_cb; } call_completion_cb_ctx_t; static bool call_completion_cb(void *caller_ctx, esp_cli_command_t *cmd) { call_completion_cb_ctx_t *ctx = (call_completion_cb_ctx_t *)caller_ctx; /* Check if command starts with buf */ if ((strlen(cmd->name) >= ctx->buf_len) && (strncmp(ctx->buf, cmd->name, ctx->buf_len) == 0)) { ctx->completion_cb(ctx->cb_ctx, cmd->name); } return true; } void esp_cli_commands_get_completion(esp_cli_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_cli_command_get_completion_t completion_cb) { size_t len = strlen(buf); if (len == 0) { return; } call_completion_cb_ctx_t ctx = { .buf = buf, .buf_len = len, .cb_ctx = cb_ctx, .completion_cb = completion_cb }; go_through_commands(cmd_set, &ctx, call_completion_cb); /* The "help" command is a built-in that must always be completable * regardless of the command set */ if (cmd_set != NULL && len <= 4 && strncmp(buf, "help", len) == 0) { completion_cb(cb_ctx, "help"); } } const char *esp_cli_commands_get_hint(esp_cli_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) { esp_cli_commands_lock(); *color = s_config.hint_color; *bold = s_config.hint_bold; esp_cli_commands_unlock(); esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); if (cmd && cmd->hint_cb != NULL) { return cmd->hint_cb(cmd->func_ctx); } return NULL; } const char *esp_cli_commands_get_glossary(esp_cli_command_set_handle_t cmd_set, const char *buf) { esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); if (cmd && cmd->glossary_cb != NULL) { return cmd->glossary_cb(cmd->func_ctx); } return NULL; } /* -------------------------------------------------------------- */ /* help command related code */ /* -------------------------------------------------------------- */ #define ESP_CLI_COMMANDS_FD_PRINT(fd, write, fmt, ...) do { \ esp_cli_commands_lock(); \ char _buf[s_config.max_cmdline_length]; \ esp_cli_commands_unlock(); \ int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ if (_len > 0) { \ ssize_t _ignored __attribute__((unused)); \ _ignored = write(fd, _buf, \ _len < (int)sizeof(_buf) ? _len : (int)sizeof(_buf) - 1); \ } \ } while (0) static void print_arg_help(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) { /* First line: command name and hint * Pad all the hints to the same column */ ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "%s", it->name); const char *hint = NULL; if (it->hint_cb) { hint = it->hint_cb(it->func_ctx); } if (hint) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->hint_cb(it->func_ctx)); } else { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); } /* Second line: print help */ /* TODO: replace the simple print with a function that * replaces arg_print_formatted */ if (it->help) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->help); } else { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); } /* Third line: print the glossary*/ const char *glossary = NULL; if (it->glossary_cb) { glossary = it->glossary_cb(it->func_ctx); } if (glossary) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->glossary_cb(it->func_ctx)); } else { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); } ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "\n"); } static void print_arg_command(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); if (it->hint_cb) { const char *hint = it->hint_cb(it->func_ctx); if (hint) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s", it->hint_cb(it->func_ctx)); } } ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "\n"); } typedef enum { HELP_VERBOSE_LEVEL_0 = 0, HELP_VERBOSE_LEVEL_1 = 1, HELP_VERBOSE_LEVEL_MAX_NUM = 2 } help_verbose_level_e; typedef void (*const fn_print_arg_t)(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *); static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { print_arg_command, print_arg_help, }; typedef struct call_cmd_ctx { esp_cli_commands_exec_arg_t *cmd_args; help_verbose_level_e verbose_level; const char *command_name; bool command_found; } call_cmd_ctx_t; static inline __attribute__((always_inline)) bool call_command_funcs(void *caller_ctx, esp_cli_command_t *cmd) { call_cmd_ctx_t *ctx = (call_cmd_ctx_t *)caller_ctx; if (!ctx->command_name) { /* ctx->command_name is empty, print all commands */ print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); } else if (ctx->command_name && (strcmp(ctx->command_name, cmd->name) == 0)) { /* we found the command name, print the help and return */ print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); ctx->command_found = true; return false; } return true; } static int help_command(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { (void)context; /* this is NULL and useless for the help command */ char *command_name = NULL; help_verbose_level_e verbose_level = HELP_VERBOSE_LEVEL_1; /* argc can never be superior to 4 given than the format is: * help cmd_name -v 0 */ if (argc <= 0 || argc > 4) { /* unknown issue, return error */ ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid number of arguments %d\n", argc); return 1; } esp_cli_command_sets_t *cmd_sets = (esp_cli_command_sets_t *)cmd_args->dynamic_ctx; if (argc > 1) { /* more than 1 arg, figure out if only verbose level argument * was passed and if a specific command was passed. * start from the second argument since the first one is "help" */ for (int i = 1; i < argc; i++) { if ((strcmp(argv[i], "-v") == 0) || (strcmp(argv[i], "--verbose") == 0)) { /* check if the following argument is either 0, or 1 */ if (i + 1 >= argc) { /* format error, return with error */ ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: arguments not provided in the right format\n"); return 1; } else if (strcmp(argv[i + 1], "0") == 0) { verbose_level = 0; } else if (strcmp(argv[i + 1], "1") == 0) { verbose_level = 1; } else { /* wrong command format, return error */ ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid verbose level %s\n", argv[i + 1]); return 1; } /* we found the -v / --verbose, bump i to skip the value of * the verbose argument since it was just parsed */ i++; } else { /* the argument is not -v or --verbose, it is then the command name * of which we should print the hint, store it for latter */ command_name = argv[i]; } } } /* at this point we should have figured out all the arguments of the help * command. if command_name is NULL, then print all commands. if command_name * is not NULL, find the command and only print the help for this command. if the * command is not found, return with error */ call_cmd_ctx_t ctx = { .cmd_args = cmd_args, .verbose_level = verbose_level, .command_name = command_name, .command_found = false }; go_through_commands(cmd_sets, &ctx, call_command_funcs); /* The "help" command is a built-in that must always appear in listings * regardless of the command set */ if (cmd_sets != NULL) { esp_cli_command_t *help_cmd = esp_cli_commands_find_command(NULL, "help"); if (help_cmd) { if (!command_name) { /* Listing all commands: also print the help entry */ print_verbose_level_arr[verbose_level](cmd_args, help_cmd); } else if (strcmp(command_name, "help") == 0) { /* User asked specifically for "help help" */ print_verbose_level_arr[verbose_level](cmd_args, help_cmd); ctx.command_found = true; } } } if (command_name && !ctx.command_found) { ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid command name %s\n", command_name); return 1; } return 0; } static const char *get_help_hint(void *context) { (void)context; return "[] [-v <0|1>]"; } static const char *get_help_glossary(void *context) { (void)context; return " Name of command\n" " -v, --verbose <0|1> If specified, list console commands with given verbose level";; } static const char help_str[] = "Print the summary of all registered commands if no arguments " "are given, otherwise print summary of given command."; ESP_CLI_COMMAND_REGISTER(help, /* name of the heap command */ help, /* group of the help command */ help_str, /* help string of the help command */ help_command, /* func */ NULL, /* the context is null here, it will provided by the exec function */ get_help_hint, /* hint callback */ get_help_glossary); /* glossary callback */ ================================================ FILE: esp_cli_commands/src/esp_cli_commands_helpers.c ================================================ /* * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "esp_cli_commands.h" #define SS_FLAG_ESCAPE 0x8 typedef enum { /* parsing the space between arguments */ SS_SPACE = 0x0, /* parsing an argument which isn't quoted */ SS_ARG = 0x1, /* parsing a quoted argument */ SS_QUOTED_ARG = 0x2, /* parsing an escape sequence within unquoted argument */ SS_ARG_ESCAPED = SS_ARG | SS_FLAG_ESCAPE, /* parsing an escape sequence within a quoted argument */ SS_QUOTED_ARG_ESCAPED = SS_QUOTED_ARG | SS_FLAG_ESCAPE, } split_state_t; /* helper macro, called when done with an argument */ #define END_ARG() do { \ char_out = 0; \ argv[argc++] = next_arg_start; \ state = SS_SPACE; \ } while(0) size_t esp_cli_commands_split_argv(char *line, char **argv, size_t argv_size) { const int QUOTE = '"'; const int ESCAPE = '\\'; const int SPACE = ' '; split_state_t state = SS_SPACE; size_t argc = 0; char *next_arg_start = line; char *out_ptr = line; for (char *in_ptr = line; argc < argv_size - 1; ++in_ptr) { int char_in = (unsigned char) * in_ptr; if (char_in == 0) { break; } int char_out = -1; switch (state) { case SS_SPACE: if (char_in == SPACE) { /* skip space */ } else if (char_in == QUOTE) { next_arg_start = out_ptr; state = SS_QUOTED_ARG; } else if (char_in == ESCAPE) { next_arg_start = out_ptr; state = SS_ARG_ESCAPED; } else { next_arg_start = out_ptr; state = SS_ARG; char_out = char_in; } break; case SS_QUOTED_ARG: if (char_in == QUOTE) { END_ARG(); } else if (char_in == ESCAPE) { state = SS_QUOTED_ARG_ESCAPED; } else { char_out = char_in; } break; case SS_ARG_ESCAPED: case SS_QUOTED_ARG_ESCAPED: if (char_in == ESCAPE || char_in == QUOTE || char_in == SPACE) { char_out = char_in; } else { /* unrecognized escape character, skip */ } state = (split_state_t)(state & (~SS_FLAG_ESCAPE)); break; case SS_ARG: if (char_in == SPACE) { END_ARG(); } else if (char_in == ESCAPE) { state = SS_ARG_ESCAPED; } else { char_out = char_in; } break; } /* need to output anything? */ if (char_out >= 0) { *out_ptr = char_out; ++out_ptr; } } /* make sure the final argument is terminated */ *out_ptr = 0; /* finalize the last argument */ if (state != SS_SPACE && argc < argv_size - 1) { argv[argc++] = next_arg_start; } /* add a NULL at the end of argv */ argv[argc] = NULL; return argc; } ================================================ FILE: esp_cli_commands/src/esp_cli_dynamic_commands.c ================================================ /* * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_cli_commands_internal.h" #include "esp_cli_dynamic_commands.h" #include "esp_cli_commands.h" #define CONTAINER_OF(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) static esp_cli_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_cli_command_internal); static size_t s_number_of_registered_commands = 0; static SemaphoreHandle_t s_esp_cli_commands_dyn_mutex = NULL; static StaticSemaphore_t s_esp_cli_commands_dyn_mutex_buf; void esp_cli_dynamic_commands_lock(void) { /* check if the mutex needs to be initialized and initialized it only * is requested in by the state of the create parameter */ if (s_esp_cli_commands_dyn_mutex == NULL) { s_esp_cli_commands_dyn_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_dyn_mutex_buf); assert(s_esp_cli_commands_dyn_mutex != NULL); } xSemaphoreTake(s_esp_cli_commands_dyn_mutex, portMAX_DELAY); } void esp_cli_dynamic_commands_unlock(void) { if (s_esp_cli_commands_dyn_mutex == NULL) { return; } xSemaphoreGive(s_esp_cli_commands_dyn_mutex); } const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void) { return &s_dynamic_cmd_list; } esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd) { if (!cmd) { return ESP_ERR_INVALID_ARG; } esp_cli_command_internal_t *list_item = esp_cli_commands_malloc(sizeof(esp_cli_command_internal_t)); if (!list_item) { return ESP_ERR_NO_MEM; } memcpy(&list_item->cmd, cmd, sizeof(esp_cli_command_t)); esp_cli_command_internal_t *last = NULL; esp_cli_command_internal_t *it = NULL; /* this could be called on an empty list, make sure the * mutex is initialized */ esp_cli_dynamic_commands_lock(); SLIST_FOREACH(it, &s_dynamic_cmd_list, next_item) { if (strcmp(it->cmd.name, list_item->cmd.name) > 0) { break; } last = it; } if (last == NULL) { SLIST_INSERT_HEAD(&s_dynamic_cmd_list, list_item, next_item); } else { SLIST_INSERT_AFTER(last, list_item, next_item); } s_number_of_registered_commands++; esp_cli_dynamic_commands_unlock(); return ESP_OK; } esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *old_cmd, esp_cli_command_t *new_cmd) { esp_cli_dynamic_commands_lock(); esp_cli_command_internal_t *list_item = CONTAINER_OF(old_cmd, esp_cli_command_internal_t, cmd); memcpy(&list_item->cmd, new_cmd, sizeof(esp_cli_command_t)); esp_cli_dynamic_commands_unlock(); return ESP_OK; } esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd) { esp_cli_dynamic_commands_lock(); esp_cli_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_cli_command_internal_t, cmd); SLIST_REMOVE(&s_dynamic_cmd_list, list_item, esp_cli_command_internal, next_item); s_number_of_registered_commands--; esp_cli_dynamic_commands_unlock(); free(list_item); return ESP_OK; } size_t esp_cli_dynamic_commands_get_number_of_cmd(void) { esp_cli_dynamic_commands_lock(); size_t nb_of_registered_cmd = s_number_of_registered_commands; esp_cli_dynamic_commands_unlock(); return nb_of_registered_cmd; } ================================================ FILE: esp_cli_commands/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_cli_commands_test) ================================================ FILE: esp_cli_commands/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_esp_cli_commands.c" "test_main.c" PRIV_INCLUDE_DIRS "." "include" PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: esp_cli_commands/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_cli_commands: version: "*" override_path: "../.." ================================================ FILE: esp_cli_commands/test_apps/main/include/test_esp_cli_commands_utils.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #define NB_OF_REGISTERED_CMD 8 #define GET_NAME(NAME, SUFFIX) NAME##SUFFIX #define GET_STR(STR) #STR #define CREATE_CMD_FUNC(NAME) \ static int GET_NAME(NAME, _func)(void *ctx, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { \ printf(GET_STR(NAME) GET_STR(_func)); \ printf("\n"); \ return 0; \ } #define CREATE_FUNC(NAME, SUFFIX) \ static const char *GET_NAME(NAME, SUFFIX)(void *context) { \ return #NAME #SUFFIX; \ } /* static command functions*/ CREATE_CMD_FUNC(cmd_a) CREATE_CMD_FUNC(cmd_b) CREATE_CMD_FUNC(cmd_c) CREATE_CMD_FUNC(cmd_d) CREATE_CMD_FUNC(cmd_e) CREATE_CMD_FUNC(cmd_f) CREATE_CMD_FUNC(cmd_g) CREATE_CMD_FUNC(cmd_h) /* static hint functions*/ CREATE_FUNC(cmd_a, _hint) CREATE_FUNC(cmd_b, _hint) CREATE_FUNC(cmd_c, _hint) CREATE_FUNC(cmd_d, _hint) CREATE_FUNC(cmd_e, _hint) CREATE_FUNC(cmd_f, _hint) CREATE_FUNC(cmd_g, _hint) CREATE_FUNC(cmd_h, _hint) /* static glossary functions*/ CREATE_FUNC(cmd_a, _glossary) CREATE_FUNC(cmd_b, _glossary) CREATE_FUNC(cmd_c, _glossary) CREATE_FUNC(cmd_d, _glossary) CREATE_FUNC(cmd_e, _glossary) CREATE_FUNC(cmd_f, _glossary) CREATE_FUNC(cmd_g, _glossary) CREATE_FUNC(cmd_h, _glossary) /* command registration */ ESP_CLI_COMMAND_REGISTER(cmd_a, group_1, GET_STR(cmd_a_help), cmd_a_func, NULL, cmd_a_hint, cmd_a_glossary); ESP_CLI_COMMAND_REGISTER(cmd_b, group_1, GET_STR(cmd_b_help), cmd_b_func, NULL, cmd_b_hint, cmd_b_glossary); ESP_CLI_COMMAND_REGISTER(cmd_c, group_2, GET_STR(cmd_c_help), cmd_c_func, NULL, cmd_c_hint, cmd_c_glossary); ESP_CLI_COMMAND_REGISTER(cmd_d, group_2, GET_STR(cmd_d_help), cmd_d_func, NULL, cmd_d_hint, cmd_d_glossary); ESP_CLI_COMMAND_REGISTER(cmd_e, group_3, GET_STR(cmd_e_help), cmd_e_func, NULL, cmd_e_hint, cmd_e_glossary); ESP_CLI_COMMAND_REGISTER(cmd_f, group_3, GET_STR(cmd_f_help), cmd_f_func, NULL, cmd_f_hint, cmd_f_glossary); ESP_CLI_COMMAND_REGISTER(cmd_g, group_4, GET_STR(cmd_g_help), cmd_g_func, NULL, cmd_g_hint, cmd_g_glossary); ESP_CLI_COMMAND_REGISTER(cmd_h, group_4, GET_STR(cmd_h_help), cmd_h_func, NULL, cmd_h_hint, cmd_h_glossary); #ifdef __cplusplus } #endif ================================================ FILE: esp_cli_commands/test_apps/main/test_esp_cli_commands.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "unity.h" #include "esp_heap_caps.h" #include "esp_cli_commands.h" #include "test_esp_cli_commands_utils.h" /* * IMPORTANT: * - 8 commands are created in test_esp_cli_commands_utils.h (cmd_a - cmd_h) * - the commands are divided in 4 groups (group_1 - group_4) * - each group contains 2 commands. * - group_1 contains cmd_a and cmd_b, * [...] * - group_4 contains cmd_g and cmd_h */ static void test_setup(void) { const esp_cli_commands_config_t config = { .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = 39, .max_cmdline_args = 32, .max_cmdline_length = 256 }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_update_config(&config)); } TEST_CASE("help command - called without command set", "[esp_cli_commands]") { test_setup(); /* call esp_cli_commands_execute to run help command with verbosity 0 */ int cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_cli_commands_execute to run help command with verbosity 1 */ cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_cli_commands_execute to run help command on a registered command */ cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_cli_commands_execute to run help command on an unregistered command */ cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_w", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_cli_commands_execute to run help command on a registered command with wrong * verbosity syntax */ cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v=1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_cli_commands_execute to run help command with too many command names */ cmd_ret = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a cmd_b -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); } TEST_CASE("test command set error handling", "[esp_cli_commands]") { test_setup(); /* create a command set with NULL passed as list of command id */ TEST_ASSERT_NULL(esp_cli_commands_create_cmd_set(NULL, 2, ESP_CLI_COMMAND_FIELD_ACCESSOR(group))); /* create a command set with 0 as size of list of command id */ const char *group_set_a[] = {"b", "group_4"}; TEST_ASSERT_NULL(esp_cli_commands_create_cmd_set(group_set_a, 0, ESP_CLI_COMMAND_FIELD_ACCESSOR(group))); /* concatenate 2 NULL sets */ TEST_ASSERT_NULL(esp_cli_commands_concat_cmd_set(NULL, NULL)); /* redefinition of esp_cli_command_set_t so we can access the fields * and test their values */ typedef struct cmd_set { esp_cli_command_t **cmd_ptr_set; size_t cmd_set_size; } cmd_set_t; /* pass wrong command name in array, expect a non null command set handle with 0 items in it*/ const char *group_set_b[] = {"group2", "group4"}; esp_cli_command_set_handle_t group_set_handle_b = esp_cli_commands_create_cmd_set(group_set_b, 2, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); cmd_set_t *cmd_set = (cmd_set_t *)group_set_handle_b; TEST_ASSERT_NOT_NULL(group_set_handle_b); TEST_ASSERT_NULL(cmd_set->cmd_ptr_set); TEST_ASSERT_EQUAL(0, cmd_set->cmd_set_size); esp_cli_commands_destroy_cmd_set(&group_set_handle_b); } typedef struct cmd_test_sequence { const char *cmd_list[NB_OF_REGISTERED_CMD]; int expected_ret_val[NB_OF_REGISTERED_CMD]; } cmd_test_sequence_t; static void run_cmd_test(esp_cli_command_set_handle_t handle, const char **cmd_list, const int *expected_ret_val, size_t nb_cmds) { for (size_t i = 0; i < nb_cmds; i++) { int cmd_ret = -1; esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; TEST_ASSERT_EQUAL(expected, esp_cli_commands_execute(cmd_list[i], &cmd_ret, handle, NULL)); TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); } } TEST_CASE("test static command set", "[esp_cli_commands]") { test_setup(); const char *cmd_list[] = {"cmd_a", "cmd_b", "cmd_c", "cmd_d", "cmd_e", "cmd_f", "cmd_g", "cmd_h"}; const size_t nb_cmds = sizeof(cmd_list) / sizeof(cmd_list[0]); int expected_ret_val[nb_cmds]; /* create sets by group */ const char *group_set_a[] = {"group_1", "group_3"}; esp_cli_command_set_handle_t handle_set_a = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set_a, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_a); const char *group_set_b[] = {"group_2", "group_4"}; esp_cli_command_set_handle_t handle_set_b = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set_b, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_b); /* test set_a by group */ int tmp_ret[] = {0, 0, -1, -1, 0, 0, -1, -1}; memcpy(expected_ret_val, tmp_ret, sizeof(tmp_ret)); run_cmd_test(handle_set_a, cmd_list, expected_ret_val, nb_cmds); /* test set_b by group */ int tmp_ret_b[] = {-1, -1, 0, 0, -1, -1, 0, 0}; memcpy(expected_ret_val, tmp_ret_b, sizeof(tmp_ret_b)); run_cmd_test(handle_set_b, cmd_list, expected_ret_val, nb_cmds); /* test help command with set of static commands */ int cmd_ret; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_a, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_b, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* destroy sets */ esp_cli_commands_destroy_cmd_set(&handle_set_a); esp_cli_commands_destroy_cmd_set(&handle_set_b); /* create sets by name */ const char *cmd_name_set_a[] = {"cmd_a", "cmd_b", "cmd_c"}; handle_set_a = esp_cli_commands_create_cmd_set(cmd_name_set_a, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_a); const char *cmd_name_set_b[] = {"cmd_f", "cmd_g", "cmd_h"}; handle_set_b = esp_cli_commands_create_cmd_set(cmd_name_set_b, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_b); int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; memcpy(expected_ret_val, tmp_ret2, sizeof(tmp_ret2)); run_cmd_test(handle_set_a, cmd_list, expected_ret_val, nb_cmds); int tmp_ret3[] = {-1, -1, -1, -1, -1, 0, 0, 0}; memcpy(expected_ret_val, tmp_ret3, sizeof(tmp_ret3)); run_cmd_test(handle_set_b, cmd_list, expected_ret_val, nb_cmds); /* concatenate sets */ esp_cli_command_set_handle_t handle_set_c = esp_cli_commands_concat_cmd_set(handle_set_a, handle_set_b); TEST_ASSERT_NOT_NULL(handle_set_c); int tmp_ret4[] = {0, 0, 0, -1, -1, 0, 0, 0}; memcpy(expected_ret_val, tmp_ret4, sizeof(tmp_ret4)); run_cmd_test(handle_set_c, cmd_list, expected_ret_val, nb_cmds); esp_cli_commands_destroy_cmd_set(&handle_set_c); } static int dummy_cmd_func(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { (void)cmd_args; (void)context; printf("dynamic command called\n"); return 0; // always return success } TEST_CASE("test dynamic command set", "[esp_cli_commands]") { test_setup(); const char *cmd_list[] = {"cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8"}; const size_t nb_cmds = sizeof(cmd_list) / sizeof(cmd_list[0]); int expected_ret_val[nb_cmds]; /* dynamically register commands */ for (size_t i = 0; i < nb_cmds; i++) { esp_cli_command_t cmd = { .name = cmd_list[i], .group = (i % 2 == 0) ? "group_a" : "group_b", .help = "dummy help", .func = dummy_cmd_func, // implement a simple dummy function returning i%2 .func_ctx = NULL, .hint_cb = NULL, .glossary_cb = NULL }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); } /* test execution by group_a */ const char *group_set[] = {"group_a"}; esp_cli_command_set_handle_t handle_set_1 = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_1); int tmp_ret[] = {0, -1, 0, -1, 0, -1, 0, -1}; memcpy(expected_ret_val, tmp_ret, sizeof(tmp_ret)); run_cmd_test(handle_set_1, cmd_list, expected_ret_val, nb_cmds); /* test execution by command name */ const char *cmd_name_set[] = {"cmd_1", "cmd_2", "cmd_3"}; esp_cli_command_set_handle_t handle_set_2 = esp_cli_commands_create_cmd_set(cmd_name_set, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_2); int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; memcpy(expected_ret_val, tmp_ret2, sizeof(tmp_ret2)); run_cmd_test(handle_set_2, cmd_list, expected_ret_val, nb_cmds); /* test help command with set of dynamic commands */ int cmd_ret; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_1, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_2, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* unregister dynamically registered commands */ for (size_t i = 0; i < nb_cmds; i++) { TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd(cmd_list[i])); } esp_cli_commands_destroy_cmd_set(&handle_set_1); esp_cli_commands_destroy_cmd_set(&handle_set_2); } TEST_CASE("test static and dynamic command sets", "[esp_cli_commands]") { test_setup(); // --- dynamic commands --- const char *dyn_cmd_list[] = {"cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8"}; const size_t nb_dyn_cmds = sizeof(dyn_cmd_list) / sizeof(dyn_cmd_list[0]); for (size_t i = 0; i < nb_dyn_cmds; i++) { esp_cli_command_t cmd = { .name = dyn_cmd_list[i], .group = (i % 2 == 0) ? "group_a" : "group_b", .help = "dummy help", .func = dummy_cmd_func, .func_ctx = NULL, .hint_cb = NULL, .glossary_cb = NULL }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); } // --- create static command sets (already registered statically) --- const char *static_groups[] = {"group_1", "group_3"}; esp_cli_command_set_handle_t handle_static_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(static_groups, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_static_set); // --- create dynamic command sets --- const char *dyn_groups[] = {"group_a"}; esp_cli_command_set_handle_t handle_dynamic_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(dyn_groups, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_dynamic_set); // --- combine static and dynamic sets --- esp_cli_command_set_handle_t handle_combined_set = esp_cli_commands_concat_cmd_set(handle_static_set, handle_dynamic_set); TEST_ASSERT_NOT_NULL(handle_combined_set); // --- run tests for combined set --- const char *all_cmds[] = {"cmd_a", "cmd_b", "cmd_c", "cmd_d", "cmd_e", "cmd_f", "cmd_g", "cmd_h", "cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8" }; int expected_ret[] = {0, 0, -1, -1, 0, 0, -1, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; run_cmd_test(handle_combined_set, all_cmds, expected_ret, 16); /* test help command with set of dynamic commands */ int cmd_ret; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_combined_set, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); // --- cleanup --- esp_cli_commands_destroy_cmd_set(&handle_combined_set); for (size_t i = 0; i < nb_dyn_cmds; i++) { TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd(dyn_cmd_list[i])); } } static size_t completion_nb_of_calls = 0; static void test_completion_cb(void *cb_ctx, const char *completed_cmd_name) { completion_nb_of_calls++; } TEST_CASE("test completion callback", "[esp_cli_commands]") { test_setup(); /* create sets by group */ const char *set_a[] = {"group_1", "group_3"}; esp_cli_command_set_handle_t handle_set_a = ESP_CLI_COMMANDS_CREATE_CMD_SET(set_a, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_a); /* register a command dynamically and add it to the set */ esp_cli_command_t cmd = { .name = "dyn_cmd", .group = "dyn_cmd_group", .help = "dummy help", .func = dummy_cmd_func, .func_ctx = NULL, .hint_cb = NULL, .glossary_cb = NULL }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); const char *set_b[] = {"dyn_cmd"}; esp_cli_command_set_handle_t handle_set_b = ESP_CLI_COMMANDS_CREATE_CMD_SET(set_b, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_b); esp_cli_command_set_handle_t handle_concat_set = esp_cli_commands_concat_cmd_set(handle_set_a, handle_set_b); TEST_ASSERT_NOT_NULL(handle_concat_set); esp_cli_commands_get_completion(NULL, "a", NULL, test_completion_cb); TEST_ASSERT_EQUAL(0, completion_nb_of_calls); esp_cli_commands_get_completion(handle_concat_set, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(4, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; esp_cli_commands_get_completion(NULL, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(8, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; esp_cli_commands_get_completion(NULL, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; esp_cli_commands_get_completion(handle_concat_set, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; esp_cli_commands_destroy_cmd_set(&handle_concat_set); TEST_ASSERT_NULL(handle_concat_set); esp_cli_commands_unregister_cmd("dyn_cmd"); } typedef struct hint_cb_ctx { const char *message; } hint_cb_ctx_t; static const char *test_hint_cb(void *context) { hint_cb_ctx_t *ctx = (hint_cb_ctx_t *)context; return ctx->message; } static const char *test_glossary_cb(void *context) { hint_cb_ctx_t *ctx = (hint_cb_ctx_t *)context; return ctx->message; } TEST_CASE("test hint and glossary callbacks", "[esp_cli_commands]") { test_setup(); hint_cb_ctx_t ctx_a = { .message = "msg_a" }; hint_cb_ctx_t ctx_b = { .message = "msg_b" }; esp_cli_command_t cmd_a = { .name = "dyn_cmd_a", .group = "dyn_cmd_group", .help = "dummy help", .func = dummy_cmd_func, .func_ctx = &ctx_a, .hint_cb = test_hint_cb, .glossary_cb = test_glossary_cb }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd_a)); esp_cli_command_t cmd_b = { .name = "dyn_cmd_b", .group = "dyn_cmd_group", .help = "dummy help", .func = dummy_cmd_func, .func_ctx = &ctx_b, .hint_cb = test_hint_cb, .glossary_cb = test_glossary_cb }; TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd_b)); bool bold = true; int color = 0; const char *dyn_cmd_a_msg_hint = esp_cli_commands_get_hint(NULL, "dyn_cmd_a", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint, ctx_a.message)); TEST_ASSERT_EQUAL(false, bold); /* bold set a false by default in the component config */ TEST_ASSERT_EQUAL(39, color); /* color set to 39 by default in the component config */ const char *dyn_cmd_b_msg_hint = esp_cli_commands_get_hint(NULL, "dyn_cmd_b", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_hint, ctx_b.message)); const char *dyn_cmd_a_msg_glossary = esp_cli_commands_get_glossary(NULL, "dyn_cmd_a"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary, ctx_a.message)); const char *dyn_cmd_b_msg_glossary = esp_cli_commands_get_glossary(NULL, "dyn_cmd_b"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_glossary, ctx_b.message)); /* create a set with only dyn_cmd_a and check that the hint cb is called for * dyn_cmd_a but not for dyn_cmd_b */ const char *set[] = {"dyn_cmd_a"}; esp_cli_command_set_handle_t handle_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(set, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set); const char *dyn_cmd_a_msg_hint_bis = esp_cli_commands_get_hint(handle_set, "dyn_cmd_a", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint_bis, ctx_a.message)); const char *dyn_cmd_b_msg_hint_bis = esp_cli_commands_get_hint(handle_set, "dyn_cmd_b", &color, &bold); TEST_ASSERT_NULL(dyn_cmd_b_msg_hint_bis); const char *dyn_cmd_a_msg_glossary_bis = esp_cli_commands_get_glossary(handle_set, "dyn_cmd_a"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary_bis, ctx_a.message)); const char *dyn_cmd_b_msg_glossary_bis = esp_cli_commands_get_glossary(handle_set, "dyn_cmd_b"); TEST_ASSERT_NULL(dyn_cmd_b_msg_glossary_bis); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd("dyn_cmd_a")); TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd("dyn_cmd_b")); esp_cli_commands_destroy_cmd_set(&handle_set); } ================================================ FILE: esp_cli_commands/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running esp_cli_commands component tests\n"); unity_run_menu(); } ================================================ FILE: esp_cli_commands/test_apps/pytest_esp_cli_commands.py ================================================ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['linux', 'esp32'], indirect=['target']) def test_esp_cli_commands(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_cli_commands/test_apps/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_EN=n ================================================ FILE: esp_daylight/.build-test-rules.yml ================================================ # Build and test rules for esp_daylight component # This file can be empty - the component will be built with default rules ================================================ FILE: esp_daylight/CHANGELOG.md ================================================ # Changelog All notable changes to the ESP Daylight component will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.0.1] - 2025-09-10 Moved from esp-rainmaker to idf-extra-components ## Added - Unit test suite and example application ## [1.0.0] - 2025-09-09 ### Added - Initial release of ESP Daylight component - NOAA Solar Calculator implementation for sunrise/sunset calculations. Reference: https://gml.noaa.gov/grad/solcalc/ - Support for global locations including polar regions - Time offset functionality for scheduling applications ================================================ FILE: esp_daylight/CMakeLists.txt ================================================ idf_component_register(SRCS "src/esp_daylight.c" INCLUDE_DIRS "include" REQUIRES "esp_common") ================================================ FILE: esp_daylight/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_daylight/README.md ================================================ # ESP Daylight Component A standalone C library for calculating sunrise and sunset times using NOAA Solar Calculator equations. This component provides accurate solar calculations for any location and date, designed for integration with ESP-IDF projects. ## Features - **Accurate NOAA calculations**: Uses proven NOAA Solar Calculator equations - **Global coverage**: Works for any location worldwide (handles polar regions) - **Lightweight**: Minimal memory footprint and dependencies - **ESP-IDF ready**: Designed for embedded systems - **Time zone agnostic**: Returns UTC timestamps for easy conversion ## API Reference ### Core Functions ```c bool esp_daylight_calc_sunrise_sunset_utc(int year, int month, int day, double latitude, double longitude, time_t *sunrise_utc, time_t *sunset_utc); ``` Calculate sunrise and sunset times for a specific date and location. **Parameters:** - `year`: Year (e.g., 2024) - `month`: Month (1-12) - `day`: Day of month (1-31) - `latitude`: Latitude in decimal degrees (-90 to +90, positive North) - `longitude`: Longitude in decimal degrees (-180 to +180, positive East) - `sunrise_utc`: Output sunrise time as UTC timestamp - `sunset_utc`: Output sunset time as UTC timestamp **Returns:** `true` on success, `false` if sun doesn't rise/set (polar regions) ### Helper Functions ```c time_t esp_daylight_apply_offset(time_t base_time, int offset_minutes); ``` Apply time offset to a base timestamp. ```c bool esp_daylight_get_sunrise_today(const esp_daylight_location_t *location, time_t *sunrise_utc); bool esp_daylight_get_sunset_today(const esp_daylight_location_t *location, time_t *sunset_utc); ``` Get sunrise/sunset for current date at given location. ## Usage Example ```c #include "esp_daylight.h" void calculate_solar_times(void) { time_t sunrise_utc, sunset_utc; // Calculate for Pune, India on August 29, 2025 bool ok = esp_daylight_calc_sunrise_sunset_utc( 2025, 8, 29, // Date 18.5204, 73.8567, // Pune coordinates &sunrise_utc, &sunset_utc ); if (ok) { // Apply offset: 30 minutes before sunset time_t light_on_time = esp_daylight_apply_offset(sunset_utc, -30); struct tm *tm_info = gmtime(&light_on_time); printf("Turn on light at: %02d:%02d:%02d UTC\n", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec); } else { printf("No sunrise/sunset for this location and date\n"); } } ``` ## Location Coordinates ### Popular Cities | City | Latitude | Longitude | |------|----------|-----------| | New York | 40.7128 | -74.0060 | | London | 51.5074 | -0.1278 | | Pune | 18.5204 | 73.8567 | | Shanghai | 31.2304 | 121.4737 | | Sydney | -33.8688 | 151.2093 | ### Coordinate Format - **Latitude**: -90 to +90 degrees (positive = North, negative = South) - **Longitude**: -180 to +180 degrees (positive = East, negative = West) ## Integration with ESP Schedule This component integrates seamlessly with ESP Schedule for solar-based scheduling: ```c esp_schedule_config_t schedule_config = { .name = "sunset_light", .trigger.type = ESP_SCHEDULE_TYPE_SUNSET, .trigger.solar.latitude = 18.5204, .trigger.solar.longitude = 73.8567, .trigger.solar.offset_minutes = -30, // 30 min before sunset .trigger.solar.repeat_days = ESP_SCHEDULE_DAY_EVERYDAY, .trigger_cb = light_control_callback, }; ``` ## Algorithm Details The component implements the NOAA Solar Calculator algorithm with: - **Zenith angle**: 90.833° (accounts for atmospheric refraction) - **Precision**: Typically accurate to within 1-2 minutes - **Edge cases**: Handles polar day/night conditions gracefully ## Dependencies - ESP-IDF (esp_common) - Standard C math library (`-lm`) ## Component Structure ``` esp_daylight/ ├── CMakeLists.txt ├── include/ │ └── esp_daylight.h ├── src/ │ └── esp_daylight.c └── README.md ``` ================================================ FILE: esp_daylight/examples/get_started/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_daylight_example) ================================================ FILE: esp_daylight/examples/get_started/README.md ================================================ # ESP Daylight Example This example demonstrates how to use the ESP Daylight component to calculate sunrise and sunset times for various locations around the world. ## What This Example Does The example showcases several key features of the ESP Daylight component: 1. **Basic Calculations**: Calculate sunrise/sunset for multiple cities worldwide 2. **Seasonal Variations**: Show how daylight hours change throughout the year 3. **Time Offsets**: Demonstrate scheduling events relative to sunrise/sunset 4. **Polar Regions**: Handle special cases like midnight sun and polar night 5. **Practical Scheduling**: Real-world smart home lighting automation example ## How to Use ### Build and Flash Create the example project: ```bash idf.py create-project-from-example "espressif/esp_daylight:get_started" cd get_started idf.py set-target esp32 idf.py build flash monitor ``` Alternatively, if you have the component source locally: ```bash cd examples/get_started idf.py set-target esp32 idf.py build flash monitor ``` ### Expected Output The example will display sunrise and sunset times for various locations and demonstrate different use cases: ``` ESP Daylight Component Example ============================ === Basic Sunrise/Sunset Calculation === Calculating sunrise/sunset for 2025-08-29: New York, USA : Sunrise 10:12:34 UTC, Sunset 23:45:12 UTC (Daylight: 13:32) London, UK : Sunrise 05:23:45 UTC, Sunset 19:34:56 UTC (Daylight: 14:11) Pune, India : Sunrise 01:15:23 UTC, Sunset 13:02:45 UTC (Daylight: 11:47) ... ``` ## Key Locations Tested The example includes calculations for these major cities: - New York, USA (40.7128°N, 74.0060°W) - London, UK (51.5074°N, 0.1278°W) - Pune, India (18.5204°N, 73.8567°E) - Shanghai, China (31.2304°N, 121.4737°E) - Sydney, Australia (33.8688°S, 151.2093°E) - Moscow, Russia (55.7558°N, 37.6173°E) - Tokyo, Japan (35.6762°N, 139.6503°E) - Rio de Janeiro, Brazil (22.9068°S, 43.1729°W) ## Customization ### Change Location Modify the coordinates in the code to match your location: ```c esp_daylight_location_t my_location = { .latitude = YOUR_LATITUDE, .longitude = YOUR_LONGITUDE, .name = "My Location" }; ``` ### Change Date Update the date parameters in the calculation functions: ```c int year = 2025, month = 8, day = 29; // Change to your desired date ``` ### Add Time Zone Support The component returns UTC timestamps. To display local time, you can convert using standard C library functions or ESP-IDF timezone support. ## Integration with Scheduling The example shows how to integrate with scheduling systems: ```c // Calculate sunset time time_t sunset_utc; esp_daylight_calc_sunrise_sunset_utc(2025, 8, 29, lat, lon, NULL, &sunset_utc); // Schedule event 30 minutes before sunset time_t light_on_time = esp_daylight_apply_offset(sunset_utc, -30); // Use with ESP Schedule component (if available) esp_schedule_config_t config = { .trigger.type = ESP_SCHEDULE_TYPE_SUNSET, .trigger.solar.latitude = lat, .trigger.solar.longitude = lon, .trigger.solar.offset_minutes = -30, .callback = your_callback_function }; ``` ## Troubleshooting ### No Output for Polar Regions If you see "No sunrise/sunset" messages, this is normal for polar regions during certain times of year (midnight sun in summer, polar night in winter). ### Accuracy The calculations use NOAA Solar Calculator equations and are typically accurate to within 1-2 minutes for most locations. ## Next Steps - Modify coordinates for your specific location - Integrate with your IoT scheduling system - Add timezone conversion for local time display - Implement automated device control based on solar events - Create recurring schedules that automatically adjust throughout the year ================================================ FILE: esp_daylight/examples/get_started/main/CMakeLists.txt ================================================ idf_component_register(SRCS "esp_daylight_example_main.c" INCLUDE_DIRS ".") ================================================ FILE: esp_daylight/examples/get_started/main/esp_daylight_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "esp_log.h" #include "esp_daylight.h" static const char *TAG = "esp_daylight_example"; /* Example locations around the world */ static const esp_daylight_location_t example_locations[] = { {40.7128, -74.0060, "New York, USA"}, {51.5074, -0.1278, "London, UK"}, {18.5204, 73.8567, "Pune, India"}, {31.2304, 121.4737, "Shanghai, China"}, {-33.8688, 151.2093, "Sydney, Australia"}, {55.7558, 37.6173, "Moscow, Russia"}, {35.6762, 139.6503, "Tokyo, Japan"}, {-22.9068, -43.1729, "Rio de Janeiro, Brazil"} }; static const size_t num_locations = sizeof(example_locations) / sizeof(example_locations[0]); /* Helper function to format time as string */ static void format_time_string(time_t timestamp, char *buffer, size_t buffer_size) { struct tm *time_info = gmtime(×tamp); strftime(buffer, buffer_size, "%H:%M:%S UTC", time_info); } /* Helper function to calculate and display daylight duration */ static void display_daylight_info(const esp_daylight_location_t *location, int year, int month, int day) { time_t sunrise_utc, sunset_utc; char sunrise_str[32], sunset_str[32]; bool result = esp_daylight_calc_sunrise_sunset_location( year, month, day, location, &sunrise_utc, &sunset_utc ); if (result) { format_time_string(sunrise_utc, sunrise_str, sizeof(sunrise_str)); format_time_string(sunset_utc, sunset_str, sizeof(sunset_str)); /* Calculate daylight duration */ int daylight_seconds = (int)(sunset_utc - sunrise_utc); /* Handle day boundary crossing (sunset next day) */ if (daylight_seconds < 0) { daylight_seconds += 24 * 60 * 60; /* Add 24 hours */ } int daylight_minutes = daylight_seconds / 60; int hours = daylight_minutes / 60; int minutes = daylight_minutes % 60; ESP_LOGI(TAG, "%-20s: Sunrise %s, Sunset %s (Daylight: %02d:%02d)", location->name, sunrise_str, sunset_str, hours, minutes); } else { ESP_LOGI(TAG, "%-20s: No sunrise/sunset (polar day/night)", location->name); } } /* Demonstrate basic sunrise/sunset calculation */ static void example_basic_calculation(void) { ESP_LOGI(TAG, "=== Basic Sunrise/Sunset Calculation ==="); /* Calculate for today's date (example: August 29, 2025) */ int year = 2025, month = 8, day = 29; ESP_LOGI(TAG, "Calculating sunrise/sunset for %04d-%02d-%02d:", year, month, day); ESP_LOGI(TAG, ""); for (size_t i = 0; i < num_locations; i++) { display_daylight_info(&example_locations[i], year, month, day); } ESP_LOGI(TAG, ""); } /* Demonstrate seasonal variations */ static void example_seasonal_variations(void) { ESP_LOGI(TAG, "=== Seasonal Variations Example ==="); /* Use London as example location */ const esp_daylight_location_t *london = &example_locations[1]; /* Test different seasons */ struct { int month, day; const char *season; } seasons[] = { {3, 21, "Spring Equinox"}, {6, 21, "Summer Solstice"}, {9, 23, "Autumn Equinox"}, {12, 21, "Winter Solstice"} }; ESP_LOGI(TAG, "Seasonal daylight variations in %s (2025):", london->name); ESP_LOGI(TAG, ""); for (size_t i = 0; i < 4; i++) { ESP_LOGI(TAG, "%s (%02d-%02d):", seasons[i].season, seasons[i].month, seasons[i].day); display_daylight_info(london, 2025, seasons[i].month, seasons[i].day); ESP_LOGI(TAG, ""); } } /* Demonstrate time offset functionality */ static void example_time_offsets(void) { ESP_LOGI(TAG, "=== Time Offset Example ==="); /* Use Pune as example */ const esp_daylight_location_t *pune = &example_locations[2]; time_t sunrise_utc, sunset_utc; bool result = esp_daylight_calc_sunrise_sunset_location( 2025, 8, 29, pune, &sunrise_utc, &sunset_utc ); if (result) { char time_str[32]; ESP_LOGI(TAG, "Original times for %s:", pune->name); format_time_string(sunrise_utc, time_str, sizeof(time_str)); ESP_LOGI(TAG, " Sunrise: %s", time_str); format_time_string(sunset_utc, time_str, sizeof(time_str)); ESP_LOGI(TAG, " Sunset: %s", time_str); ESP_LOGI(TAG, ""); /* Apply various offsets */ struct { int offset_minutes; const char *description; } offsets[] = { {-30, "30 minutes before sunset (lights on)"}, {30, "30 minutes after sunrise (morning routine)"}, {-60, "1 hour before sunset (dinner prep)"}, {15, "15 minutes after sunrise (wake up)"} }; ESP_LOGI(TAG, "Time offset examples:"); for (size_t i = 0; i < 4; i++) { time_t base_time = (i % 2 == 0) ? sunset_utc : sunrise_utc; time_t offset_time = esp_daylight_apply_offset(base_time, offsets[i].offset_minutes); format_time_string(offset_time, time_str, sizeof(time_str)); ESP_LOGI(TAG, " %s: %s", offsets[i].description, time_str); } ESP_LOGI(TAG, ""); } } /* Demonstrate polar region handling */ static void example_polar_regions(void) { ESP_LOGI(TAG, "=== Polar Region Example ==="); /* Test Arctic locations */ esp_daylight_location_t arctic_locations[] = { {71.0, 8.0, "Svalbard, Norway"}, {80.0, 0.0, "High Arctic"}, {-77.8, 166.7, "McMurdo, Antarctica"} }; /* Test summer and winter conditions */ struct { int month, day; const char *season; } polar_seasons[] = { {6, 21, "Summer (Midnight Sun)"}, {12, 21, "Winter (Polar Night)"} }; for (size_t s = 0; s < 2; s++) { ESP_LOGI(TAG, "%s conditions:", polar_seasons[s].season); for (size_t i = 0; i < 3; i++) { time_t sunrise_utc, sunset_utc; bool result = esp_daylight_calc_sunrise_sunset_location( 2025, polar_seasons[s].month, polar_seasons[s].day, &arctic_locations[i], &sunrise_utc, &sunset_utc ); if (result) { char sunrise_str[32], sunset_str[32]; format_time_string(sunrise_utc, sunrise_str, sizeof(sunrise_str)); format_time_string(sunset_utc, sunset_str, sizeof(sunset_str)); ESP_LOGI(TAG, " %-20s: Sunrise %s, Sunset %s", arctic_locations[i].name, sunrise_str, sunset_str); } else { ESP_LOGI(TAG, " %-20s: No sunrise/sunset (24h %s)", arctic_locations[i].name, (polar_seasons[s].month == 6) ? "daylight" : "darkness"); } } ESP_LOGI(TAG, ""); } } /* Demonstrate practical scheduling use case */ static void example_practical_scheduling(void) { ESP_LOGI(TAG, "=== Practical Scheduling Example ==="); /* Simulate a smart home lighting system */ const esp_daylight_location_t *home_location = &example_locations[2]; /* Pune */ time_t sunrise_utc, sunset_utc; bool result = esp_daylight_calc_sunrise_sunset_location( 2025, 8, 29, home_location, &sunrise_utc, &sunset_utc ); if (result) { ESP_LOGI(TAG, "Smart Home Lighting Schedule for %s:", home_location->name); ESP_LOGI(TAG, ""); /* Define lighting events */ struct { time_t event_time; const char *action; const char *description; } lighting_events[6]; /* Calculate event times */ lighting_events[0].event_time = esp_daylight_apply_offset(sunrise_utc, -30); lighting_events[0].action = "Turn OFF"; lighting_events[0].description = "30 min before sunrise"; lighting_events[1].event_time = sunrise_utc; lighting_events[1].action = "Dim to 20%"; lighting_events[1].description = "At sunrise"; lighting_events[2].event_time = esp_daylight_apply_offset(sunrise_utc, 60); lighting_events[2].action = "Turn OFF"; lighting_events[2].description = "1 hour after sunrise"; lighting_events[3].event_time = esp_daylight_apply_offset(sunset_utc, -45); lighting_events[3].action = "Turn ON 50%"; lighting_events[3].description = "45 min before sunset"; lighting_events[4].event_time = sunset_utc; lighting_events[4].action = "Turn ON 80%"; lighting_events[4].description = "At sunset"; lighting_events[5].event_time = esp_daylight_apply_offset(sunset_utc, 120); lighting_events[5].action = "Turn ON 100%"; lighting_events[5].description = "2 hours after sunset"; /* Display schedule */ for (size_t i = 0; i < 6; i++) { char time_str[32]; format_time_string(lighting_events[i].event_time, time_str, sizeof(time_str)); ESP_LOGI(TAG, " %s - %-15s (%s)", time_str, lighting_events[i].action, lighting_events[i].description); } ESP_LOGI(TAG, ""); /* Show integration with scheduling system */ ESP_LOGI(TAG, "Integration with ESP Schedule:"); ESP_LOGI(TAG, " esp_schedule_config_t config = {"); ESP_LOGI(TAG, " .name = \"smart_lighting\","); ESP_LOGI(TAG, " .trigger.type = ESP_SCHEDULE_TYPE_SUNSET,"); ESP_LOGI(TAG, " .trigger.solar.latitude = %.4f,", home_location->latitude); ESP_LOGI(TAG, " .trigger.solar.longitude = %.4f,", home_location->longitude); ESP_LOGI(TAG, " .trigger.solar.offset_minutes = -45,"); ESP_LOGI(TAG, " .trigger_cb = lighting_control_callback,"); ESP_LOGI(TAG, " .timestamp_cb = schedule_timestamp_callback"); ESP_LOGI(TAG, " };"); ESP_LOGI(TAG, " esp_schedule_handle_t handle = esp_schedule_create(&config);"); ESP_LOGI(TAG, " esp_schedule_enable(handle);"); ESP_LOGI(TAG, ""); // Note: The above is a demonstration of how to configure the schedule. // Actual integration would require the esp_schedule component and real callback implementations. } } void app_main(void) { ESP_LOGI(TAG, "ESP Daylight Component Example"); ESP_LOGI(TAG, "============================"); ESP_LOGI(TAG, ""); /* Run all examples */ example_basic_calculation(); example_seasonal_variations(); example_time_offsets(); example_polar_regions(); example_practical_scheduling(); ESP_LOGI(TAG, "Example completed successfully!"); ESP_LOGI(TAG, ""); ESP_LOGI(TAG, "Next steps:"); ESP_LOGI(TAG, "- Modify coordinates to match your location"); ESP_LOGI(TAG, "- Integrate with your scheduling system"); ESP_LOGI(TAG, "- Add timezone conversion for local time display"); ESP_LOGI(TAG, "- Implement automated lighting/irrigation control"); } ================================================ FILE: esp_daylight/examples/get_started/main/idf_component.yml ================================================ dependencies: esp_daylight: override_path: "../../.." ================================================ FILE: esp_daylight/examples/get_started/pytest_esp_daylight_example.py ================================================ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import pytest from pytest_embedded import Dut @pytest.mark.generic def test_esp_daylight_example(dut: Dut) -> None: """ Test esp_daylight example application """ # Wait for the example to start dut.expect_exact('ESP Daylight Component Example') # Check that basic calculations are performed dut.expect('Basic Sunrise/Sunset Calculation', timeout=30) dut.expect('New York, USA', timeout=10) dut.expect('London, UK', timeout=10) dut.expect('Pune, India', timeout=10) # Check seasonal variations dut.expect('Seasonal Variations Example', timeout=10) dut.expect('Spring Equinox', timeout=10) dut.expect('Summer Solstice', timeout=10) # Check time offsets dut.expect('Time Offset Example', timeout=10) dut.expect('30 minutes before sunset', timeout=10) # Check polar regions dut.expect('Polar Region Example', timeout=10) dut.expect('Midnight Sun', timeout=10) # Check practical scheduling dut.expect('Practical Scheduling Example', timeout=10) dut.expect('Smart Home Lighting Schedule', timeout=10) # Wait for completion dut.expect('Example completed successfully!', timeout=30) ================================================ FILE: esp_daylight/idf_component.yml ================================================ ## IDF Component Manager Manifest File version: "1.0.1" description: NOAA-based sunrise/sunset calculation library for ESP-IDF url: https://github.com/espressif/idf-extra-components/tree/master/esp_daylight dependencies: idf: ">=5.1" ================================================ FILE: esp_daylight/include/esp_daylight.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include /** * @brief Location information for daylight calculations */ typedef struct { double latitude; /**< Latitude in decimal degrees (-90 to +90, positive North) */ double longitude; /**< Longitude in decimal degrees (-180 to +180, positive East) */ char name[32]; /**< Optional location name for reference */ } esp_daylight_location_t; /** * @brief Calculate sunrise and sunset times for a given date and location * * Uses NOAA Solar Calculator equations with zenith angle of 90.833° for * geometric sunrise/sunset including atmospheric refraction. * * @param[in] year Year (e.g., 2024) * @param[in] month Month (1-12) * @param[in] day Day of month (1-31) * @param[in] latitude Latitude in decimal degrees (-90 to +90, positive North) * @param[in] longitude Longitude in decimal degrees (-180 to +180, positive East) * @param[out] sunrise_utc Sunrise time as UTC timestamp (seconds since epoch) * @param[out] sunset_utc Sunset time as UTC timestamp (seconds since epoch) * * @return true on success, false if sun doesn't rise/set (polar day/night) */ bool esp_daylight_calc_sunrise_sunset_utc(int year, int month, int day, double latitude, double longitude, time_t *sunrise_utc, time_t *sunset_utc); /** * @brief Calculate sunrise and sunset times using location struct * * @param[in] year Year (e.g., 2024) * @param[in] month Month (1-12) * @param[in] day Day of month (1-31) * @param[in] location Location information * @param[out] sunrise_utc Sunrise time as UTC timestamp * @param[out] sunset_utc Sunset time as UTC timestamp * * @return true on success, false if sun doesn't rise/set (polar day/night) */ bool esp_daylight_calc_sunrise_sunset_location(int year, int month, int day, const esp_daylight_location_t *location, time_t *sunrise_utc, time_t *sunset_utc); /** * @brief Apply time offset to a base timestamp * * @param[in] base_time Base timestamp in seconds since epoch * @param[in] offset_minutes Offset in minutes (positive = after, negative = before) * * @return Adjusted timestamp */ time_t esp_daylight_apply_offset(time_t base_time, int offset_minutes); /** * @brief Get sunrise time for current date at given location * * @param[in] location Location information * @param[out] sunrise_utc Sunrise time as UTC timestamp * * @return true on success, false on failure */ bool esp_daylight_get_sunrise_today(const esp_daylight_location_t *location, time_t *sunrise_utc); /** * @brief Get sunset time for current date at given location * * @param[in] location Location information * @param[out] sunset_utc Sunset time as UTC timestamp * * @return true on success, false on failure */ bool esp_daylight_get_sunset_today(const esp_daylight_location_t *location, time_t *sunset_utc); #ifdef __cplusplus } #endif ================================================ FILE: esp_daylight/src/esp_daylight.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_daylight.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* * NOAA Solar Calculator Implementation * Reliable sunrise/sunset (UTC) calculations using NOAA equations * Reference: https://gml.noaa.gov/grad/solcalc/ */ /** * @brief Howard Hinnant's days_from_civil algorithm (public domain) * Convert civil date to days since Unix epoch */ static int64_t days_from_civil(int y, unsigned m, unsigned d) { y -= m <= 2; const int era = (y >= 0 ? y : y - 399) / 400; const unsigned yoe = (unsigned)(y - era * 400); // [0, 399] const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + yoe / 400 + doy; // [0, 146096] return era * 146097 + (int)doe - 719468; // days since 1970-01-01 } /** * @brief Get UTC midnight timestamp for given date */ static time_t utc_midnight_epoch(int year, int month, int day) { return (time_t)(days_from_civil(year, (unsigned)month, (unsigned)day) * 86400LL); } /** * @brief Clamp value between min and max */ static double clamp(double v, double lo, double hi) { return v < lo ? lo : (v > hi ? hi : v); } /** * @brief Calculate fractional year gamma in radians * Used for NOAA solar equations */ static double fractional_year_gamma(int year, int month, int day) { // Day-of-year N (1..365/366) static const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int ly = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); int N = day; for (int i = 1; i < month; i++) { N += mdays[i]; } if (ly && month > 2) { N += 1; } // NOAA form with hour≈0 -> gamma ~= 2π/365 * (N-1 - 12/24) return 2.0 * M_PI / (ly ? 366.0 : 365.0) * ((double)N - 1.0 - 0.5); } /** * @brief Calculate Equation of Time in minutes * NOAA polynomial approximation */ static double equation_of_time_min(double gamma) { double s = sin(gamma); double c = cos(gamma); double s2 = sin(2 * gamma); double c2 = cos(2 * gamma); // 229.18 * (0.000075 + 0.001868*cosγ - 0.032077*sinγ - 0.014615*cos2γ - 0.040849*sin2γ) return 229.18 * (0.000075 + 0.001868 * c - 0.032077 * s - 0.014615 * c2 - 0.040849 * s2); } /** * @brief Calculate solar declination in radians * NOAA polynomial approximation */ static double solar_declination_rad(double gamma) { double s = sin(gamma); double c = cos(gamma); double s2 = sin(2 * gamma); double c2 = cos(2 * gamma); double s3 = sin(3 * gamma); double c3 = cos(3 * gamma); // δ = 0.006918 - 0.399912 cosγ + 0.070257 sinγ - 0.006758 cos2γ // + 0.000907 sin2γ - 0.002697 cos3γ + 0.00148 sin3γ return 0.006918 - 0.399912 * c + 0.070257 * s - 0.006758 * c2 + 0.000907 * s2 - 0.002697 * c3 + 0.001480 * s3; } bool esp_daylight_calc_sunrise_sunset_utc(int year, int month, int day, double latitude, double longitude, time_t *sunrise_utc, time_t *sunset_utc) { // Constants const double ZENITH_DEG = 90.833; // geometric zenith for sunrise/sunset const double Z = ZENITH_DEG * (M_PI / 180.0); const double phi = latitude * (M_PI / 180.0); // 1) Day parameters double gamma = fractional_year_gamma(year, month, day); double EoT_min = equation_of_time_min(gamma); double decl = solar_declination_rad(gamma); // 2) Hour angle at sunrise/sunset (rad) // cos(H0) = (cos(Z) - sin(phi) sin(dec)) / (cos(phi) cos(dec)) double cosH0 = (cos(Z) - sin(phi) * sin(decl)) / (cos(phi) * cos(decl)); if (cosH0 < -1.0) { // Sun above horizon all day (midnight sun) -> no distinct sunrise/sunset return false; } if (cosH0 > 1.0) { // Sun below horizon all day (polar night) return false; } double H0 = acos(clamp(cosH0, -1.0, 1.0)); // radians double H0_deg = H0 * 180.0 / M_PI; // 3) Solar noon in UTC minutes // NOAA: SolarNoon_UTC (minutes) = 720 - 4*longitude - EoT // (longitude positive east, negative west) double solar_noon_min_utc = 720.0 - 4.0 * longitude - EoT_min; // 4) Sunrise/Sunset UTC minutes // Daylength (minutes) = 8 * H0 (deg) [since 1 deg hour angle = 4 minutes] double delta_min = 4.0 * H0_deg; // minutes from noon to event double sunrise_min_utc = solar_noon_min_utc - delta_min; double sunset_min_utc = solar_noon_min_utc + delta_min; // Normalize to [0,1440) to stay within the same civil day in UTC. // (Edge cases near poles can roll into prev/next day; we keep them bounded.) while (sunrise_min_utc < 0) { sunrise_min_utc += 1440.0; } while (sunrise_min_utc >= 1440.0) { sunrise_min_utc -= 1440.0; } while (sunset_min_utc < 0) { sunset_min_utc += 1440.0; } while (sunset_min_utc >= 1440.0) { sunset_min_utc -= 1440.0; } // 5) Convert to epoch seconds (UTC) time_t midnight_utc = utc_midnight_epoch(year, month, day); if (sunrise_utc) { *sunrise_utc = midnight_utc + (time_t)llround(sunrise_min_utc * 60.0); } if (sunset_utc) { *sunset_utc = midnight_utc + (time_t)llround(sunset_min_utc * 60.0); } return true; } bool esp_daylight_calc_sunrise_sunset_location(int year, int month, int day, const esp_daylight_location_t *location, time_t *sunrise_utc, time_t *sunset_utc) { if (!location) { return false; } return esp_daylight_calc_sunrise_sunset_utc(year, month, day, location->latitude, location->longitude, sunrise_utc, sunset_utc); } time_t esp_daylight_apply_offset(time_t base_time, int offset_minutes) { return base_time + (offset_minutes * 60); } bool esp_daylight_get_sunrise_today(const esp_daylight_location_t *location, time_t *sunrise_utc) { if (!location || !sunrise_utc) { return false; } time_t now; time(&now); struct tm *tm_info = gmtime(&now); return esp_daylight_calc_sunrise_sunset_location( tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday, location, sunrise_utc, NULL ); } bool esp_daylight_get_sunset_today(const esp_daylight_location_t *location, time_t *sunset_utc) { if (!location || !sunset_utc) { return false; } time_t now; time(&now); struct tm *tm_info = gmtime(&now); return esp_daylight_calc_sunrise_sunset_location( tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday, location, NULL, sunset_utc ); } ================================================ FILE: esp_daylight/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp_daylight_test) ================================================ FILE: esp_daylight/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_app_main.c" "test_esp_daylight.c" INCLUDE_DIRS "." PRIV_REQUIRES unity esp_daylight WHOLE_ARCHIVE) ================================================ FILE: esp_daylight/test_apps/main/idf_component.yml ================================================ dependencies: esp_daylight: path: "../.." ================================================ FILE: esp_daylight/test_apps/main/test_app_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); /* clean up some of the newlib's lazy allocations */ unity_utils_evaluate_leaks_direct(50); } void app_main(void) { printf("Running esp_daylight component tests\n"); unity_run_menu(); } ================================================ FILE: esp_daylight/test_apps/main/test_esp_daylight.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "unity.h" #include "esp_log.h" #include "esp_daylight.h" static const char *TAG = "esp_daylight_test"; /* Test tolerance for time calculations (in seconds) */ #define TIME_TOLERANCE_SEC 120 /* 2 minutes tolerance for sunrise/sunset calculations */ /* Helper function to check if two timestamps are within tolerance */ __attribute__((unused)) static bool time_within_tolerance(time_t actual, time_t expected, int tolerance_sec) { int diff = abs((int)(actual - expected)); return diff <= tolerance_sec; } TEST_CASE("Test basic sunrise/sunset calculation", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test for Pune, India on August 29, 2025 */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 8, 29, /* Date */ 18.5204, 73.8567, /* Pune coordinates */ &sunrise_utc, &sunset_utc ); TEST_ASSERT_TRUE(result); TEST_ASSERT_NOT_EQUAL(0, sunrise_utc); TEST_ASSERT_NOT_EQUAL(0, sunset_utc); /* Note: Don't assert sunset > sunrise due to potential day boundary crossing in UTC */ /* Convert to readable format for logging */ struct tm sunrise_tm_buf, sunset_tm_buf; struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf); struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf); ESP_LOGI(TAG, "Pune 2025-08-29: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC", sunrise_tm->tm_hour, sunrise_tm->tm_min, sunset_tm->tm_hour, sunset_tm->tm_min); /* Sanity check: sunrise should be around 01:00 UTC (06:30 IST) */ /* sunset should be around 13:00 UTC (18:30 IST) */ TEST_ASSERT_TRUE(sunrise_tm->tm_hour >= 0 && sunrise_tm->tm_hour <= 3); TEST_ASSERT_TRUE(sunset_tm->tm_hour >= 12 && sunset_tm->tm_hour <= 15); } TEST_CASE("Test location struct interface", "[esp_daylight]") { esp_daylight_location_t location = { .latitude = 40.7128, .longitude = -74.0060, .name = "New York" }; time_t sunrise_utc, sunset_utc; bool result; result = esp_daylight_calc_sunrise_sunset_location( 2025, 6, 21, /* Summer solstice */ &location, &sunrise_utc, &sunset_utc ); TEST_ASSERT_TRUE(result); TEST_ASSERT_NOT_EQUAL(0, sunrise_utc); TEST_ASSERT_NOT_EQUAL(0, sunset_utc); TEST_ASSERT_GREATER_THAN(sunset_utc, sunrise_utc); struct tm sunrise_tm_buf, sunset_tm_buf; struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf); struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf); ESP_LOGI(TAG, "New York 2025-06-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC", sunrise_tm->tm_hour, sunrise_tm->tm_min, sunset_tm->tm_hour, sunset_tm->tm_min); } TEST_CASE("Test polar regions - midnight sun", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test Arctic location during summer (midnight sun) */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 6, 21, /* Summer solstice */ 80.0, 0.0, /* High Arctic latitude */ &sunrise_utc, &sunset_utc ); /* Should return false for midnight sun conditions */ TEST_ASSERT_FALSE(result); ESP_LOGI(TAG, "Arctic midnight sun test: correctly returned false"); } TEST_CASE("Test polar regions - polar night", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test Arctic location during winter (polar night) */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 12, 21, /* Winter solstice */ 80.0, 0.0, /* High Arctic latitude */ &sunrise_utc, &sunset_utc ); /* Should return false for polar night conditions */ TEST_ASSERT_FALSE(result); ESP_LOGI(TAG, "Arctic polar night test: correctly returned false"); } TEST_CASE("Test time offset functionality", "[esp_daylight]") { time_t base_time = 1640995200; /* 2022-01-01 00:00:00 UTC */ time_t offset_time; /* Test positive offset (30 minutes after) */ offset_time = esp_daylight_apply_offset(base_time, 30); TEST_ASSERT_EQUAL(base_time + 1800, offset_time); /* Test negative offset (45 minutes before) */ offset_time = esp_daylight_apply_offset(base_time, -45); TEST_ASSERT_EQUAL(base_time - 2700, offset_time); /* Test zero offset */ offset_time = esp_daylight_apply_offset(base_time, 0); TEST_ASSERT_EQUAL(base_time, offset_time); ESP_LOGI(TAG, "Time offset tests passed"); } TEST_CASE("Test input validation", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; /* Test invalid date */ (void) esp_daylight_calc_sunrise_sunset_utc( 2025, 13, 1, /* Invalid month */ 18.5204, 73.8567, &sunrise_utc, &sunset_utc ); /* Implementation should handle this gracefully */ /* Test extreme latitudes */ (void) esp_daylight_calc_sunrise_sunset_utc( 2025, 6, 21, 91.0, 0.0, /* Invalid latitude > 90 */ &sunrise_utc, &sunset_utc ); /* Should handle gracefully */ /* Test extreme longitudes */ (void) esp_daylight_calc_sunrise_sunset_utc( 2025, 6, 21, 0.0, 181.0, /* Invalid longitude > 180 */ &sunrise_utc, &sunset_utc ); /* Should handle gracefully */ ESP_LOGI(TAG, "Input validation tests completed"); } TEST_CASE("Test known reference values", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test London on summer solstice 2025 */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 6, 21, 51.5074, -0.1278, /* London coordinates */ &sunrise_utc, &sunset_utc ); TEST_ASSERT_TRUE(result); struct tm sunrise_tm_buf, sunset_tm_buf; struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf); struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf); ESP_LOGI(TAG, "London 2025-06-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC", sunrise_tm->tm_hour, sunrise_tm->tm_min, sunset_tm->tm_hour, sunset_tm->tm_min); /* London summer solstice: sunrise around 04:43 UTC, sunset around 20:21 UTC */ TEST_ASSERT_TRUE(sunrise_tm->tm_hour >= 3 && sunrise_tm->tm_hour <= 6); TEST_ASSERT_TRUE(sunset_tm->tm_hour >= 19 && sunset_tm->tm_hour <= 22); } TEST_CASE("Test equatorial location", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test Singapore (near equator) */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 3, 21, /* Equinox */ 1.3521, 103.8198, /* Singapore coordinates */ &sunrise_utc, &sunset_utc ); TEST_ASSERT_TRUE(result); struct tm sunrise_tm_buf, sunset_tm_buf; struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf); struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf); ESP_LOGI(TAG, "Singapore 2025-03-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC", sunrise_tm->tm_hour, sunrise_tm->tm_min, sunset_tm->tm_hour, sunset_tm->tm_min); /* Near equator, day length should be close to 12 hours */ /* Handle day boundary crossing - if sunset appears before sunrise, add 24 hours */ int day_length_minutes; if (sunset_utc >= sunrise_utc) { day_length_minutes = (sunset_utc - sunrise_utc) / 60; } else { /* Day boundary crossing - sunset is next day */ day_length_minutes = ((sunset_utc + 24 * 3600) - sunrise_utc) / 60; } TEST_ASSERT_INT_WITHIN(30, 12 * 60, day_length_minutes); /* Within 30 minutes of 12 hours */ } TEST_CASE("Test southern hemisphere", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test Sydney, Australia */ result = esp_daylight_calc_sunrise_sunset_utc( 2025, 12, 21, /* Summer solstice in southern hemisphere */ -33.8688, 151.2093, /* Sydney coordinates */ &sunrise_utc, &sunset_utc ); TEST_ASSERT_TRUE(result); struct tm sunrise_tm_buf, sunset_tm_buf; struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf); struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf); ESP_LOGI(TAG, "Sydney 2025-12-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC", sunrise_tm->tm_hour, sunrise_tm->tm_min, sunset_tm->tm_hour, sunset_tm->tm_min); /* Should have valid sunrise/sunset times */ TEST_ASSERT_NOT_EQUAL(0, sunrise_utc); TEST_ASSERT_NOT_EQUAL(0, sunset_utc); TEST_ASSERT_GREATER_THAN(sunset_utc, sunrise_utc); } TEST_CASE("Test NULL pointer handling", "[esp_daylight]") { time_t sunrise_utc, sunset_utc; bool result; /* Test NULL location pointer */ result = esp_daylight_calc_sunrise_sunset_location( 2025, 6, 21, NULL, &sunrise_utc, &sunset_utc ); TEST_ASSERT_FALSE(result); /* Test NULL output pointers (should not crash) */ (void) esp_daylight_calc_sunrise_sunset_utc( 2025, 6, 21, 0.0, 0.0, NULL, NULL ); /* Should handle gracefully */ ESP_LOGI(TAG, "NULL pointer handling tests completed"); } ================================================ FILE: esp_daylight/test_apps/pytest_esp_daylight.py ================================================ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import pytest from pytest_embedded import Dut @pytest.mark.generic def test_esp_daylight(dut: Dut) -> None: """ Test esp_daylight component functionality """ dut.run_all_single_board_cases(timeout=60) ================================================ FILE: esp_daylight/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: esp_delta_ota/.build-test-rules.yml ================================================ esp_delta_ota/examples: enable: - if: IDF_TARGET == "esp32" reason: Delta OTA example currently only tested on ESP32 esp_delta_ota/test_apps: enable: - if: IDF_TARGET == "esp32" reason: Delta OTA test app currently only tested on ESP32 ================================================ FILE: esp_delta_ota/CHANGELOG.md ================================================ ## 1.1.4 ### Enhancements: - Added support to pass user data to read callback with new callback function: `src_read_cb_with_user_ctx_t` ## 1.1.3 ### Bugfixes: - Fixed `esp_delta_ota_patch_gen.py` patch generation, by ignoring the case in regex which is used to get validation hash of binary through esptool command, caused due to breaking change in esptool. ## 1.1.2 ### Bugfixes: - Fixed `esp_delta_ota_patch_gen.py` python command for creating patch in README.md file of esp_delta_ota/https_delta_ota example. - Added `Verifying patch` section in the README.md and corresponding python command. ## 1.1.1 ### Bugfixes: - Fixed issue causing `esp_delta_ota_patch_gen.py` to be non-functional on Windows OS. - Replaced earlier use of subprocess commands with direct `esptool` module import based method for handling ESP chip-specific operations across all platforms. - Replaced earlier use of subprocess commands with direct `detools` module import based method for creating and applying patches. ## 1.1.0 ### Enhancements: - Added support to pass user data to write callback with new callback function: `merged_stream_write_cb_with_user_ctx_t` - The older write callback function: `merged_stream_write_cb_t` has been deprecated and will be removed in the next major release. ================================================ FILE: esp_delta_ota/CMakeLists.txt ================================================ idf_component_register(SRCS "src/esp_delta_ota.c" "detools/c/detools.c" "detools/c/heatshrink/heatshrink_decoder.c" INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "detools/c" "detools/c/heatshrink") target_compile_options(${COMPONENT_LIB} PRIVATE "-DDETOOLS_CONFIG_FILE_IO=0") target_compile_options(${COMPONENT_LIB} PRIVATE "-DDETOOLS_CONFIG_COMPRESSION_NONE=0") target_compile_options(${COMPONENT_LIB} PRIVATE "-DDETOOLS_CONFIG_COMPRESSION_LZMA=0") target_compile_options(${COMPONENT_LIB} PRIVATE "-DDETOOLS_CONFIG_COMPRESSION_CRLE=0") ================================================ FILE: esp_delta_ota/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Thesis projects Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_delta_ota/README.md ================================================ # ESP Delta OTA ## Getting Started Compressed Delta OTA Updates aims at enabling Over-the-Air firmware update with compressed delta binaries. ### Compressed Delta Image for OTA ![Image Format](https://raw.githubusercontent.com/espressif/idf-extra-components/master/esp_delta_ota/image_format.png) ### Advantages of using this design for OTA update: * Patch file have smaller size than the original firmware file. This reduces the time and network usage to download the file from server. * No additional storage partition is required for the "patch". * Only firmware level changes are required for integrating this component. No bootloader related changes required. * This scheme can be implemented for the existing devices in the field. ## Workflow Block diagram ![ESP Delta OTA Block Diagram](https://raw.githubusercontent.com/espressif/idf-extra-components/master/esp_delta_ota/esp_delta_ota_block_diagram.png) ## Prerequisites * **ESP-IDF v4.3 and above** You can visit the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#installation-step-by-step) for the installation steps. * **detools v0.49.0 and above** Binary delta encoding in Python 3.6+. You can install detools using the following command: ``` pip install -r examples/https_delta_ota/tools/requirements.txt ``` More information about the package [here](https://pypi.org/project/detools/). ## Usage Refer to the [https_delta_ota](https://github.com/espressif/idf-extra-components/blob/master/esp_delta_ota/examples/https_delta_ota/) example to see the use of `esp_delta_ota` component for OTA updates. ## API Reference To learn more about how to use this component, please check API Documentation from header file [esp_delta_ota.h](https://github.com/espressif/idf-extra-components/blob/master/esp_delta_ota/include/esp_delta_ota.h) ## License * Distributed under the Apache-2.0 License. Refer [esp_delta_ota LICENSE](https://github.com/espressif/idf-extra-components/blob/master/esp_delta_ota/LICENSE) for more information. * `detools` : Distributed under the BSD 2-Clause License. Refer [detools LICENSE](https://github.com/eerimoq/detools/blob/master/LICENSE) for more information. * `heatshrink` : Distributed under the ISC License. Refer [heatshrink LICENSE](https://github.com/eerimoq/detools/blob/master/c/heatshrink/README.rst) for more information. ================================================ FILE: esp_delta_ota/examples/https_delta_ota/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(https_delta_ota) ================================================ FILE: esp_delta_ota/examples/https_delta_ota/README.md ================================================ # HTTPS Delta OTA Compressed Delta OTA Updates aims at enabling Over-the-Air firmware update with compressed delta binaries. Instead of the original binary to be hosted on the OTA update server, a patch file is hosted which is the difference between the base firmware and the new firmware in compressed form. This reduces the size of the file to be downloaded when performing OTA updates thus reducing the network usage. This also reduces the time taken to complete the OTA process. This example demonstrates OTA updates with compressed patch binary using `esp_delta_ota` component's APIs and tool. Compressed patch generated for the new firmware must be hosted on OTA update server. See [How to generate the patch](#creating-patch). This patch will be downloaded and then applied on the current running firmware on device. The new firmware after applying the patch will be written in a new partition. ## ESP Delta OTA Abstraction Layer This example uses `esp_delta_ota` component hosted at [idf-extra-components/esp_delta_ota](https://github.com/espressif/idf-extra-components/blob/master/esp_delta_ota) and available though the [IDF component manager](https://components.espressif.com/component/espressif/esp_delta_ota). Please refer to its documentation [here](https://github.com/espressif/idf-extra-components/blob/master/esp_delta_ota/README.md) for more details. ## How to use the example ### Configure the project Open the project configuration menu(`idf.py menuconfig`) In the `Example Connection Configuration` menu: * Choose the network interface in the `Connect using` option based on your board. Currently both Wi-Fi and Ethernet are supported * If the Wi-Fi interface is used, provide the Wi-Fi SSID and password of the AP you wish to connect to * If using the Ethernet interface, set the PHY model under `Ethernet PHY Device` option, e.g. `IP101` In the `Example Configuration` menu: * Set the URL of the firmware to download in the `Firmware Upgrade URL` option. The format should be `https://:/`, e.g. `https://192.168.2.106:8070/hello_world.bin` ### Build and Flash example ``` idf.py build flash ``` ### Creating Patch Now we will consider this firmware as the base firmware which we will flash into the device. We need a new firmware to which we want to upgrade to. You can try building the `hello_world` example present in ESP-IDF. We need to create a patch file for this new firmware. Install required packages: ``` pip install -r tools/requirements.txt ``` To create a patch file, use the [python tool](./tools/esp_delta_ota_patch_gen.py) ``` python esp_delta_ota_patch_gen.py create_patch --chip --base_binary --new_binary --patch_file_name ``` This will generate the patch file for the new binary which needs to be hosted on the OTA update server. > **_NOTE:_** Make sure that the firmware present in the device is used as `base_binary` while creating the patch file. For this purpose, user should keep backup of the firmware running in the device as it is required for creating the patch file. ### Verifying Patch To verify that the patch file is correctly created from the provided base binary and new binary, use the following command: ``` python esp_delta_ota_patch_gen.py verify_patch --chip --base_binary --new_binary --patch_file_name ``` ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/CMakeLists.txt ================================================ set(SRCS "main.c") set(INCLUDE_DIRS ".") set(EMBED_TXTFILES "tests/certs/servercert.pem") set(PRIV_REQS "esp_http_client esp_partition nvs_flash app_update esp_timer esp_wifi console") if(CONFIG_EXAMPLE_ENABLE_CI_TEST) list(APPEND SRCS "tests/test_local_server_ota.c") list(APPEND INCLUDE_DIRS "tests") list(APPEND EMBED_TXTFILES "tests/certs/prvtkey.pem") endif() idf_component_register(SRCS ${SRCS} INCLUDE_DIRS ${INCLUDE_DIRS} EMBED_TXTFILES ${EMBED_TXTFILES} PRIV_REQUIRES ${PRIV_REQS}) if(CONFIG_EXAMPLE_ENABLE_CI_TEST) target_link_libraries(${COMPONENT_LIB} PRIVATE idf::esp_https_server) endif() ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/Kconfig.projbuild ================================================ menu "Example Configuration" config EXAMPLE_FIRMWARE_UPG_URL string "Firmware Upgrade URL" default "https://192.168.20.194:8070/patch.bin" help URL of server which hosts the firmware image. config EXAMPLE_SKIP_COMMON_NAME_CHECK bool "Skip server certificate CN fieldcheck" default n help This allows you to skip the validation of OTA server certificate CN field. config EXAMPLE_SKIP_VERSION_CHECK bool "Skip firmware version check" default n help This allows you to skip the firmware version check. config EXAMPLE_OTA_RECV_TIMEOUT int "OTA Receive Timeout" default 5000 help Maximum time for reception config EXAMPLE_FIRMWARE_UPG_URL_FROM_STDIN bool default y if EXAMPLE_FIRMWARE_UPG_URL = "FROM_STDIN" config EXAMPLE_ENABLE_CI_TEST bool "Enable the CI test code" default n help This enables the CI test code i.e. https local server code. endmenu ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/ca_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDWDCCAkACCQCbF4+gVh/MLjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJJ TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j b20wHhcNMjEwNzEyMTIzNjI3WhcNNDEwNzA3MTIzNjI3WjBuMQswCQYDVQQGEwJJ TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhxF/y7bygndxPwiWL SwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQuc32W ukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2mKRbQ S5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO2fEz YaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnvL6Oz 3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdOAoap rFTRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAItw24y565k3C/zENZlxyzto44ud IYPQXN8Fa2pBlLe1zlSIyuaA/rWQ+i1daS8nPotkCbWZyf5N8DYaTE4B0OfvoUPk B5uGDmbuk6akvlB5BGiYLfQjWHRsK9/4xjtIqN1H58yf3QNROuKsPAeywWS3Fn32 3//OpbWaClQePx6udRYMqAitKR+QxL7/BKZQsX+UyShuq8hjphvXvk0BW8ONzuw9 RcoORxM0FzySYjeQvm4LhzC/P3ZBhEq0xs55aL2a76SJhq5hJy7T/Xz6NFByvlrN lFJJey33KFrAf5vnV9qcyWFIo7PYy2VsaaEjFeefr7q3sTFSMlJeadexW2Y= -----END CERTIFICATE----- ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File version: "1.0.0" description: Delta OTA Example dependencies: espressif/esp_delta_ota: version: '1.*' override_path: '../../../' protocol_examples_common: path: ${IDF_PATH}/examples/common_components/protocol_examples_common ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/main.c ================================================ /* HTTPS Delta OTA example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_log.h" #include "esp_err.h" #include "esp_timer.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_wifi.h" #include "esp_netif.h" #include "protocol_examples_common.h" #include "esp_ota_ops.h" #include "esp_app_format.h" #include "esp_app_desc.h" #include "esp_partition.h" #include "esp_http_client.h" #include "esp_delta_ota.h" #define BUFFSIZE 1024 #define PATCH_HEADER_SIZE 64 #define DIGEST_SIZE 32 #define OTA_URL_SIZE 256 static uint32_t esp_delta_ota_magic = 0xfccdde10; static const char *TAG = "https_delta_ota_example"; static char ota_write_data[BUFFSIZE + 1] = { 0 }; extern const uint8_t server_cert_pem_start[] asm("_binary_servercert_pem_start"); extern const uint8_t server_cert_pem_end[] asm("_binary_servercert_pem_end"); #ifdef CONFIG_EXAMPLE_ENABLE_CI_TEST #include "test_local_server_ota.h" #endif const esp_partition_t *current_partition, *destination_partition; static esp_ota_handle_t ota_handle; #define IMG_HEADER_LEN sizeof(esp_image_header_t) static bool verify_chip_id(void *bin_header_data) { esp_image_header_t *header = (esp_image_header_t *)bin_header_data; if (header->chip_id != CONFIG_IDF_FIRMWARE_CHIP_ID) { ESP_LOGE(TAG, "Mismatch chip id, expected %d, found %d", CONFIG_IDF_FIRMWARE_CHIP_ID, header->chip_id); return false; } return true; } #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) static esp_err_t write_cb(const uint8_t *buf_p, size_t size, void *user_data) #else static esp_err_t write_cb(const uint8_t *buf_p, size_t size) #endif { if (size <= 0) { return ESP_ERR_INVALID_ARG; } static char header_data[IMG_HEADER_LEN]; static bool chip_id_verified = false; static int header_data_read = 0; int index = 0; if (!chip_id_verified) { if (header_data_read + size <= IMG_HEADER_LEN) { memcpy(header_data + header_data_read, buf_p, size); header_data_read += size; return ESP_OK; } else { index = IMG_HEADER_LEN - header_data_read; memcpy(header_data + header_data_read, buf_p, index); if (!verify_chip_id(header_data)) { return ESP_ERR_INVALID_VERSION; } chip_id_verified = true; // Write data in header_data buffer. esp_err_t err = esp_ota_write(ota_handle, header_data, IMG_HEADER_LEN); if (err != ESP_OK) { return err; } } } return esp_ota_write(ota_handle, buf_p + index, size - index); } static esp_err_t read_cb(uint8_t *buf_p, size_t size, int src_offset) { if (size <= 0) { return ESP_ERR_INVALID_ARG; } return esp_partition_read(current_partition, src_offset, buf_p, size); } static void reboot(void) { for (int i = 5; i > 0; i--) { ESP_LOGI(TAG, "Rebooting in %d seconds...", i); vTaskDelay(1000 / portTICK_PERIOD_MS); } esp_restart(); } static void http_cleanup(esp_http_client_handle_t client) { esp_http_client_close(client); esp_http_client_cleanup(client); } static bool verify_patch_header(void *img_hdr_data) { if (!img_hdr_data) { return false; } uint32_t recv_magic = *(uint32_t *)img_hdr_data; uint8_t *digest = (uint8_t *)(img_hdr_data + 4); if (recv_magic != esp_delta_ota_magic) { ESP_LOGE(TAG, "Invalid magic word in patch"); return false; } uint8_t sha_256[DIGEST_SIZE] = { 0 }; esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256); if (memcmp(sha_256, digest, DIGEST_SIZE) != 0) { ESP_LOGE(TAG, "SHA256 of current firmware differs from than in patch header. Invalid patch for current firmware"); return false; } return true; } static void ota_example_task(void *pvParameter) { esp_err_t err; esp_http_client_config_t config = { .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL, .cert_pem = (char *)server_cert_pem_start, .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT, .keep_alive_enable = true, }; #ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK config.skip_cert_common_name_check = true; #endif #ifdef CONFIG_EXAMPLE_ENABLE_CI_TEST ESP_LOGI(TAG, "Reading OTA URL from stdin"); delta_ota_test_firmware_data_from_stdin(&config.url); #elif defined(CONFIG_EXAMPLE_FIRMWARE_UPG_URL_FROM_STDIN) if (strcmp(config.url, "FROM_STDIN") == 0) { ESP_LOGI(TAG, "Reading OTA URL from stdin"); char url_buf[OTA_URL_SIZE]; example_configure_stdin_stdout(); fgets(url_buf, OTA_URL_SIZE, stdin); int len = strlen(url_buf); url_buf[len - 1] = '\0'; config.url = url_buf; } else { ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url"); abort(); } #endif esp_http_client_handle_t client = esp_http_client_init(&config); if (client == NULL) { ESP_LOGE(TAG, "Failed to initialise HTTP connection"); vTaskDelete(NULL); } err = esp_http_client_open(client, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); vTaskSuspend(NULL); } esp_http_client_fetch_headers(client); current_partition = esp_ota_get_running_partition(); destination_partition = esp_ota_get_next_update_partition(NULL); if (current_partition == NULL || destination_partition == NULL) { ESP_LOGE(TAG, "Error getting partition information"); goto error; } if (current_partition->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX || destination_partition->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX) { goto error; } err = esp_ota_begin(destination_partition, OTA_SIZE_UNKNOWN, &(ota_handle)); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); goto error; } esp_delta_ota_cfg_t cfg = { .read_cb = &read_cb, }; #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) char *user_data = "https_delta_ota"; cfg.write_cb_with_user_data = &write_cb; cfg.user_data = user_data; #else cfg.write_cb = &write_cb; #endif esp_delta_ota_handle_t handle = esp_delta_ota_init(&cfg); if (handle == NULL) { ESP_LOGE(TAG, "delta_ota_set_cfg failed"); goto error; } // Read size equal to patch header to verify the header int data_read = esp_http_client_read(client, ota_write_data, PATCH_HEADER_SIZE); if (data_read != PATCH_HEADER_SIZE) { ESP_LOGE(TAG, "Patch Header not received"); goto error; } if (!verify_patch_header(ota_write_data)) { ESP_LOGE(TAG, "Patch Header verification failed"); goto error; } while (1) { int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE); if (data_read < 0) { ESP_LOGE(TAG, "Error: SSL data read error"); goto error; } else if (data_read > 0) { if (esp_delta_ota_feed_patch(handle, (const uint8_t *)ota_write_data, data_read) < 0) { ESP_LOGE(TAG, "Error while applying patch"); goto error; } } else if (data_read == 0) { if (esp_http_client_is_complete_data_received(client) == true) { ESP_LOGI(TAG, "Connection closed"); break; } if (errno == ECONNRESET || errno == ENOTCONN) { ESP_LOGE(TAG, "Connection closed, errno = %d", errno); break; } } } err = esp_delta_ota_finalize(handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_delta_ota_finalize() failed : %s", esp_err_to_name(err)); } err = esp_delta_ota_deinit(handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_delta_ota_deinit() failed : %s", esp_err_to_name(err)); } err = esp_ota_end(ota_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end() failed : %s", esp_err_to_name(err)); } err = esp_ota_set_boot_partition(destination_partition); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition() failed : %s", esp_err_to_name(err)); } http_cleanup(client); reboot(); error: http_cleanup(client); vTaskDelete(NULL); } void app_main(void) { ESP_LOGI(TAG, "Initialising WiFi Connection..."); ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); #ifdef CONFIG_EXAMPLE_ENABLE_CI_TEST /* Start the local HTTPS server for CI test */ ESP_ERROR_CHECK(delta_ota_test_start_webserver()); ESP_LOGI(TAG, "Local HTTPS server started for CI test"); #endif xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); } ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/tests/certs/prvtkey.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDhxF/y7bygndxP wiWLSwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQu c32WukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2m KRbQS5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO 2fEzYaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnv L6Oz3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdO AoaprFTRAgMBAAECggEAE0HCxV/N1Q1h+1OeDDGL5+74yjKSFKyb/vTVcaPCrmaH fPvp0ddOvMZJ4FDMAsiQS6/n4gQ7EKKEnYmwTqj4eUYW8yxGUn3f0YbPHbZT+Mkj z5woi3nMKi/MxCGDQZX4Ow3xUQlITUqibsfWcFHis8c4mTqdh4qj7xJzehD2PVYF gNHZsvVj6MltjBDAVwV1IlGoHjuElm6vuzkfX7phxcA1B4ZqdYY17yCXUnvui46z Xn2kUTOOUCEgfgvGa9E+l4OtdXi5IxjaSraU+dlg2KsE4TpCuN2MEVkeR5Ms3Y7Q jgJl8vlNFJDQpbFukLcYwG7rO5N5dQ6WWfVia/5XgQKBgQD74at/bXAPrh9NxPmz i1oqCHMDoM9sz8xIMZLF9YVu3Jf8ux4xVpRSnNy5RU1gl7ZXbpdgeIQ4v04zy5aw 8T4tu9K3XnR3UXOy25AK0q+cnnxZg3kFQm+PhtOCKEFjPHrgo2MUfnj+EDddod7N JQr9q5rEFbqHupFPpWlqCa3QmQKBgQDldWUGokNaEpmgHDMnHxiibXV5LQhzf8Rq gJIQXb7R9EsTSXEvsDyqTBb7PHp2Ko7rZ5YQfyf8OogGGjGElnPoU/a+Jij1gVFv kZ064uXAAISBkwHdcuobqc5EbG3ceyH46F+FBFhqM8KcbxJxx08objmh58+83InN P9Qr25Xw+QKBgEGXMHuMWgQbSZeM1aFFhoMvlBO7yogBTKb4Ecpu9wI5e3Kan3Al pZYltuyf+VhP6XG3IMBEYdoNJyYhu+nzyEdMg8CwXg+8LC7FMis/Ve+o7aS5scgG 1to/N9DK/swCsdTRdzmc/ZDbVC+TuVsebFBGYZTyO5KgqLpezqaIQrTxAoGALFCU 10glO9MVyl9H3clap5v+MQ3qcOv/EhaMnw6L2N6WVT481tnxjW4ujgzrFcE4YuxZ hgwYu9TOCmeqopGwBvGYWLbj+C4mfSahOAs0FfXDoYazuIIGBpuv03UhbpB1Si4O rJDfRnuCnVWyOTkl54gKJ2OusinhjztBjcrV1XkCgYEA3qNi4uBsPdyz9BZGb/3G rOMSw0CaT4pEMTLZqURmDP/0hxvTk1polP7O/FYwxVuJnBb6mzDa0xpLFPTpIAnJ YXB8xpXU69QVh+EBbemdJWOd+zp5UCfXvb2shAeG3Tn/Dz4cBBMEUutbzP+or0nG vSXnRLaxQhooWm+IuX9SuBQ= -----END PRIVATE KEY----- ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/tests/certs/servercert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDWDCCAkACCQCbF4+gVh/MLjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJJ TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j b20wHhcNMjEwNzEyMTIzNjI3WhcNNDEwNzA3MTIzNjI3WjBuMQswCQYDVQQGEwJJ TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhxF/y7bygndxPwiWL SwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQuc32W ukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2mKRbQ S5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO2fEz YaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnvL6Oz 3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdOAoap rFTRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAItw24y565k3C/zENZlxyzto44ud IYPQXN8Fa2pBlLe1zlSIyuaA/rWQ+i1daS8nPotkCbWZyf5N8DYaTE4B0OfvoUPk B5uGDmbuk6akvlB5BGiYLfQjWHRsK9/4xjtIqN1H58yf3QNROuKsPAeywWS3Fn32 3//OpbWaClQePx6udRYMqAitKR+QxL7/BKZQsX+UyShuq8hjphvXvk0BW8ONzuw9 RcoORxM0FzySYjeQvm4LhzC/P3ZBhEq0xs55aL2a76SJhq5hJy7T/Xz6NFByvlrN lFJJey33KFrAf5vnV9qcyWFIo7PYy2VsaaEjFeefr7q3sTFSMlJeadexW2Y= -----END CERTIFICATE----- ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/tests/test_local_server_ota.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ /* Delta OTA HTTPS example's test file * * This example code is in the Public Domain (or CC0 licensed, at your option.) * * Unless required by applicable law or agreed to in writing, this * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. */ #include "esp_https_server.h" #include "esp_log.h" #include "nvs_flash.h" #include "test_local_server_ota.h" #include "protocol_examples_common.h" #include "esp_partition.h" #include #include #include #define OTA_URL_SIZE 256 #define PARTITION_READ_BUFFER_SIZE 256 #define PARTITION_READ_SIZE PARTITION_READ_BUFFER_SIZE static const char *TAG = "test_local_server_ota"; static size_t patch_size = 0; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPG_URL_FROM_STDIN void delta_ota_test_firmware_data_from_stdin(const char **data) { char input_buf[OTA_URL_SIZE]; if (strcmp(*data, "FROM_STDIN") == 0) { example_configure_stdin_stdout(); fflush(stdin); char *url = NULL; char *tokens[OTA_URL_SIZE]; char *saveptr; int token_count = 0; if (fgets(input_buf, OTA_URL_SIZE, stdin) == NULL) { ESP_LOGE(TAG, "Failed to read URL from stdin"); abort(); } int len = strlen(input_buf); if (len == 0) { ESP_LOGE(TAG, "Empty URL read from stdin"); abort(); } if (input_buf[len - 1] == '\n') { input_buf[len - 1] = '\0'; len--; } char *token = strtok_r(input_buf, " ", &saveptr); if (token == NULL) { ESP_LOGE(TAG, "No URL token found in input"); return; } // First token is the URL url = token; tokens[token_count++] = url; // Process remaining tokens while ((token = strtok_r(NULL, " ", &saveptr)) != NULL) { tokens[token_count++] = token; } // Require patch_size to be provided (at least 2 tokens: URL and patch_size) if (token_count < 2) { ESP_LOGE(TAG, "Expected URL and patch_size, but only got %d token(s)", token_count); return; } *data = strdup(tokens[0]); // Assign the URL and additional data after the loop if (token_count > 1) { ESP_LOGI(TAG, "patch_size: %s\n", tokens[1]); patch_size = atoi(tokens[1]); // Assuming the next token is the patch size } // Tokens are collected in the tokens array } else { ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url"); abort(); } } #endif /* An HTTP GET handler */ static esp_err_t root_get_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/octet-stream"); // Find the patch_data partition where pytest writes the patch const esp_partition_t *p = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "patch_data"); if (p == NULL) { ESP_LOGE(TAG, "patch_data partition not found"); httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Partition not found"); return ESP_FAIL; } if (patch_size == 0) { ESP_LOGE(TAG, "Patch size is 0"); return ESP_FAIL; } int image_len = patch_size; char buffer[PARTITION_READ_BUFFER_SIZE]; int size = PARTITION_READ_SIZE; int offset = 0; do { /* Read file in chunks into the scratch buffer */ if (offset + size > image_len) { size = image_len - offset; } if (size == 0) { break; } esp_err_t ret = esp_partition_read(p, offset, buffer, size); if (ret == ESP_OK) { /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, buffer, size) != ESP_OK) { ESP_LOGE(TAG, "File sending failed!"); /* Abort sending file */ httpd_resp_sendstr_chunk(req, NULL); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); return ESP_FAIL; } } else { ESP_LOGE(TAG, "Partition read failed: %s", esp_err_to_name(ret)); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read partition"); return ESP_FAIL; } offset += size; /* Keep looping till the whole file is sent */ } while (offset < image_len); ESP_LOGI(TAG, "Patch file sending complete"); // Set headers httpd_resp_set_hdr(req, "Accept-Ranges", "bytes"); httpd_resp_set_hdr(req, "Connection", "close"); httpd_resp_send_chunk(req, NULL, 0); return ESP_OK; } static esp_err_t root_head_handler(httpd_req_t *req) { const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "patch_data"); if (partition == NULL) { ESP_LOGE(TAG, "Partition not found"); httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Partition not found"); return ESP_FAIL; } if (patch_size == 0) { return ESP_FAIL; } // Get the size of the patch httpd_resp_set_type(req, "application/octet-stream"); httpd_resp_set_hdr(req, "Accept-Ranges", "bytes"); httpd_resp_set_hdr(req, "Connection", "close"); // Complete HEAD response with no body return httpd_resp_send(req, NULL, patch_size); // No body for HEAD method } static const httpd_uri_t get_root = { .uri = "/patch.bin", .method = HTTP_GET, .handler = root_get_handler }; static const httpd_uri_t head_root = { .uri = "/patch.bin", .method = HTTP_HEAD, .handler = root_head_handler }; esp_err_t delta_ota_test_start_webserver(void) { httpd_handle_t server = NULL; // Start the httpd server ESP_LOGI(TAG, "Starting HTTPS server for CI test"); httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT(); extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start"); extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end"); conf.servercert = servercert_start; conf.servercert_len = servercert_end - servercert_start; extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start"); extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end"); conf.prvtkey_pem = prvtkey_pem_start; conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start; esp_err_t ret = httpd_ssl_start(&server, &conf); if (ESP_OK != ret) { ESP_LOGE(TAG, "Error starting server!"); return ret; } // Set URI handlers ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &get_root); httpd_register_uri_handler(server, &head_root); return ESP_OK; } ================================================ FILE: esp_delta_ota/examples/https_delta_ota/main/tests/test_local_server_ota.h ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #pragma once #ifdef CONFIG_EXAMPLE_ENABLE_CI_TEST /** * @brief starts the https server * * The server will serve the patch file from the patch_data partition. * The patch_size must be set via delta_ota_test_firmware_data_from_stdin() * before starting the server. NOTE - patch_size cannot be 0. */ esp_err_t delta_ota_test_start_webserver(void); /** * @brief Takes the firmware URL from the STDIN (if want to send * other data write the data in just one line by adding " " delimiter). * * @param data pointer to the firmware URL (or URL including other data) */ void delta_ota_test_firmware_data_from_stdin(const char **data); #endif ================================================ FILE: esp_delta_ota/examples/https_delta_ota/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 20K otadata, data, ota, 0xE000, 8K phy_init, data, phy, 0x10000, 4K ota_0, app, ota_0, 0x20000, 1280K ota_1, app, ota_1, 0x160000, 1280K patch_data, data, 0x40, 0x2A0000, 512K ================================================ FILE: esp_delta_ota/examples/https_delta_ota/pytest_https_delta_ota.py ================================================ # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import contextlib import io import os import subprocess import sys import pexpect from typing import Any import esptool import pytest from pytest_embedded import Dut PATCH_DATA_PARTITION_OFFSET = 0x2A0000 # Hardcoded offset for patch_data partition from partitions.csv def get_env_config_variable(env_name, var_name): return os.environ.get(f'{env_name}_{var_name}'.upper()) def _ensure_requirements_installed(): example_dir = os.path.dirname(os.path.abspath(__file__)) requirements_path = os.path.join(example_dir, 'tools', 'requirements.txt') if not os.path.exists(requirements_path): raise Exception(f'Requirements file not found at {requirements_path}') result = subprocess.run( [sys.executable, '-m', 'pip', 'install', '-r', requirements_path], cwd=os.path.dirname(requirements_path), check=False, capture_output=True, text=True, ) if result.returncode != 0: raise RuntimeError( f'Failed to install requirements from {requirements_path} (exit code {result.returncode}):\n' f'{result.stderr}' ) def setting_connection(dut: Dut, env_name: str | None = None) -> Any: if env_name is not None and dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: dut.expect('Please input ssid password:') ap_ssid = get_env_config_variable(env_name, 'ap_ssid') ap_password = get_env_config_variable(env_name, 'ap_password') dut.write(f'{ap_ssid} {ap_password}') try: ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode() print(f'Connected to AP/Ethernet with IP: {ip_address}') except pexpect.exceptions.TIMEOUT: raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') return ip_address def find_hello_world_binary(base_dir, chip_target='esp32'): """ Find the pre-built hello_world binary for the target chip. This function looks for hello_world_.bin in the tests directory under the given base directory (e.g., main/tests/). These binaries are pre-built and checked into the repository for testing. Args: base_dir: Base directory that contains the tests subdirectory (e.g., main/) chip_target: Target chip (default: 'esp32') Returns: Path to the hello_world binary file """ # Look for hello_world binary in tests directory binary_name = f'hello_world_{chip_target}.bin' binary_path = os.path.join(base_dir, 'tests', binary_name) if os.path.exists(binary_path): return binary_path # Fallback: try generic hello_world.bin fallback_path = os.path.join(base_dir, 'tests', 'hello_world.bin') if os.path.exists(fallback_path): print(f'Warning: Using generic hello_world.bin instead of {binary_name}') return fallback_path raise Exception(f'Hello world binary not found at {binary_path}. ' f'Expected pre-built binary: {binary_name} in tests/ directory. ' f'Base dir: {base_dir}') def generate_patch(base_binary, new_binary, patch_output, chip='esp32'): """Generate delta OTA patch using the esp_delta_ota_patch_gen.py tool.""" _ensure_requirements_installed() # Find the tool in the tools directory example_dir = os.path.dirname(os.path.abspath(__file__)) tool_path = os.path.join(example_dir, 'tools', 'esp_delta_ota_patch_gen.py') if not os.path.exists(tool_path): raise Exception(f'Patch generation tool not found at {tool_path}') # Verify input files exist if not os.path.exists(base_binary): raise Exception(f'Base binary not found at {base_binary}') if not os.path.exists(new_binary): raise Exception(f'New binary not found at {new_binary}') # Use the tool to generate patch cmd = [ sys.executable, tool_path, 'create_patch', '--chip', chip, '--base_binary', base_binary, '--new_binary', new_binary, '--patch_file_name', patch_output ] result = subprocess.run(cmd, capture_output=True, text=True) # Print output if result.stdout: print(result.stdout) if result.stderr: print('STDERR:', result.stderr) if result.returncode != 0: raise Exception(f'Patch generation failed with return code {result.returncode}') if not os.path.exists(patch_output): raise Exception(f'Patch file not created at {patch_output}') print(f'Patch created successfully: {patch_output} ({os.path.getsize(patch_output)} bytes)') def write_patch_to_partition(dut: Dut, patch_file: str): """Write the patch file to the patch_data partition on the device. Uses the existing esptool connection managed by pytest-embedded to avoid serial port conflicts. The device is hard-reset after writing so it boots fresh with the patch available on the partition. """ patch_size = os.path.getsize(patch_file) # Hardcoded offset for patch_data partition from partitions.csv # OTA partitions must be aligned to 0x10000 boundaries # Calculated as: phy_init ends at 0x11000, ota_0 aligned to 0x20000, ota_1 at 0x160000, patch_data at 0x2A0000 offset = PATCH_DATA_PARTITION_OFFSET print(f'Writing patch ({patch_size} bytes) to patch_data partition at offset {hex(offset)}') serial = dut.serial # Reuse the same pattern as EspSerial.use_esptool() decorator: # 1. stop the serial redirect thread (releases the pyserial port) # 2. let esptool reuse the existing connection # 3. resume the redirect thread when done # Use a local buffer instead of private attribute to avoid brittleness with serial.disable_redirect_thread(): esptool_output = io.StringIO() with contextlib.redirect_stdout(esptool_output): settings = serial.proc.get_settings() # Save the current serial settings serial.esp.connect() # Connect to the device using esptool esptool.main( ['--after', 'no-reset', 'write-flash', hex(offset), patch_file], esp=serial.esp, ) serial.proc.apply_settings(settings) # Restore the original serial settings # Log esptool output for debugging output = esptool_output.getvalue() if output: print(f'esptool output: {output}') # Hard-reset inside the disabled-redirect context so that when the redirect # thread resumes, the pexpect buffer only contains messages from this new boot # (no stale messages from earlier boots that would cause early pattern matches). serial.hard_reset() print('Successfully wrote patch to patch_data partition') @pytest.mark.parametrize('target', ['esp32']) @pytest.mark.generic def test_esp_delta_ota(dut: Dut): example_dir = os.path.dirname(os.path.abspath(__file__)) build_dir = dut.app.binary_path chip_target = getattr(dut, 'target', None) or os.environ.get('IDF_TARGET', 'esp32') try: # Step 1: Get base and new binaries base_binary = os.path.join(build_dir, 'https_delta_ota.bin') if not os.path.exists(base_binary): raise Exception(f'Base binary not found at {base_binary}. Device was flashed from build directory: {build_dir}') # Use find_hello_world_binary helper to locate the test binary new_binary = find_hello_world_binary(os.path.join(example_dir, 'main'), chip_target) # Step 2: Generate patch patch_file = os.path.join(build_dir, 'patch.bin') generate_patch(base_binary, new_binary, patch_file, chip_target) # Step 3: Write patch to the patch_data partition write_patch_to_partition(dut, patch_file) # Step 4: Wait for device to boot, start local server, and signal ready for stdin. # Same pattern as pre_encrypted_ota: expect the log right before fgets(), then write. patch_size = os.path.getsize(patch_file) device_ip = '127.0.0.1' ota_url = f'https://{device_ip}:443/patch.bin' dut.expect('Reading OTA URL from stdin', timeout=30) print(f'Providing OTA URL to device: {ota_url} {patch_size}') dut.write(f'{ota_url} {patch_size}\n') # Step 5: Wait for reboot and new firmware to boot dut.expect('Hello world!', timeout=60) print('Delta OTA test PASSED: Successfully updated from https_delta_ota to hello_world') except Exception as e: print(f'HTTPS Delta OTA test FAILED: {str(e)}') raise ================================================ FILE: esp_delta_ota/examples/https_delta_ota/sdkconfig.ci ================================================ CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN" CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_CONNECT_IPV6=n CONFIG_EXAMPLE_ENABLE_CI_TEST=y CONFIG_EXAMPLE_CONNECT_ETHERNET=n CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y CONFIG_ESP_HTTPS_SERVER_ENABLE=y ================================================ FILE: esp_delta_ota/examples/https_delta_ota/sdkconfig.defaults ================================================ CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" ================================================ FILE: esp_delta_ota/examples/https_delta_ota/tools/esp_delta_ota_patch_gen.py ================================================ #!/usr/bin/env python # # ESP Delta OTA Patch Generator Tool. This tool helps in generating the compressed patch file # using BSDiff and Heatshrink algorithms # # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import argparse import os import re import tempfile import hashlib import sys import esptool try: import detools except ImportError: print("Please install 'detools'. Use command `pip install -r tools/requirements.txt`") sys.exit(1) # Magic Byte is created using command: echo -n "esp_delta_ota" | sha256sum esp_delta_ota_magic = 0xfccdde10 MAGIC_SIZE = 4 # This is the size of the magic byte DIGEST_SIZE = 32 # This is the SHA256 of the base binary HEADER_SIZE = 64 RESERVED_HEADER = HEADER_SIZE - (MAGIC_SIZE + DIGEST_SIZE) # This is the reserved header size def calculate_sha256(file_path: str) -> str: """Calculate the SHA-256 hash of a file.""" sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: # Read the file in chunks to avoid memory issues for large files for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) # Return the hex representation of the hash return sha256_hash.hexdigest() def create_patch(chip: str, base_binary: str, new_binary: str, patch_file_name: str) -> None: command = ['--chip', chip, 'image_info', base_binary] output = sys.stdout sys.stdout = tempfile.TemporaryFile(mode='w+') try: esptool.main(command) sys.stdout.seek(0) content = sys.stdout.read() except Exception as e: print(f"Error during esptool command execution: {e}") finally: sys.stdout.close() sys.stdout = output x = re.search(r"Validation Hash: ([A-Za-z0-9]+) \(valid\)", content, re.IGNORECASE) if x is None: print("Failed to find validation hash in base binary.") return patch_file_without_header = "patch_file_temp.bin" try: with open(base_binary, 'rb') as b_binary, open(new_binary, 'rb') as n_binary, open(patch_file_without_header, 'wb') as p_binary: detools.create_patch(b_binary, n_binary, p_binary, compression='heatshrink') # b_binary is the base binary, n_binary is the new binary, p_binary is the patch file without header with open(patch_file_without_header, "rb") as p_binary, open(patch_file_name, "wb") as patch_file: patch_file.write(esp_delta_ota_magic.to_bytes(MAGIC_SIZE, 'little')) patch_file.write(bytes.fromhex(x[1])) patch_file.write(bytearray(RESERVED_HEADER)) patch_file.write(p_binary.read()) except Exception as e: print(f"Error during patch creation: {e}") finally: if os.path.exists(patch_file_without_header): os.remove(patch_file_without_header) print("Patch created successfully.") # Verifying the created patch file verify_patch(base_binary, patch_file_name, new_binary) # This API applies the patch file over the base_binary file and generates the binary.new file. Then it compares # the hash of new_binary and binary.new, if they are the same then the verification is successful, otherwise it fails. def verify_patch(base_binary: str, patch_to_verify: str, new_binary: str) -> None: with open(patch_to_verify, "rb") as original_file: original_file.seek(HEADER_SIZE) patch_content = original_file.read() temp_file_name = None try: with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_file.write(patch_content) temp_file.flush() temp_file_name = temp_file.name detools.apply_patch_filenames(base_binary, temp_file_name, "binary.new") except Exception as e: print(f"Failed to apply patch: {e}") finally: if temp_file_name and os.path.exists(temp_file_name): os.remove(temp_file_name) sha_of_new_created_binary = calculate_sha256("binary.new") sha_of_new_binary = calculate_sha256(new_binary) if sha_of_new_created_binary == sha_of_new_binary: print("Patch file verified successfully") else: print("Failed to verify the patch") os.remove("binary.new") def main() -> None: if len(sys.argv) < 2: print("Usage: python esp_delta_ota_patch_gen.py create_patch/verify_patch [arguments]") sys.exit(1) command = sys.argv[1] parser = argparse.ArgumentParser('Delta OTA Patch Generator Tool') if command == 'create_patch': parser.add_argument('--chip', help="Target", default="esp32") parser.add_argument('--base_binary', help="Path of Base Binary for creating the patch", required=True) parser.add_argument('--new_binary', help="Path of New Binary for which patch has to be created", required=True) parser.add_argument('--patch_file_name', help="Patch file path", default="patch.bin") args = parser.parse_args(sys.argv[2:]) create_patch(args.chip, args.base_binary, args.new_binary, args.patch_file_name) elif command == 'verify_patch': parser.add_argument('--base_binary', help="Path of Base Binary for verifying the patch", required=True) parser.add_argument('--patch_file_name', help="Patch file path", required=True) parser.add_argument('--new_binary', help="Path of New Binary for verifying the patch", required=True) args = parser.parse_args(sys.argv[2:]) verify_patch(args.base_binary, args.patch_file_name, args.new_binary) else: print("Invalid command. Use 'create_patch' or 'verify_patch'.") sys.exit(1) if __name__ == '__main__': main() ================================================ FILE: esp_delta_ota/examples/https_delta_ota/tools/requirements.txt ================================================ detools>=0.49.0 esptool ================================================ FILE: esp_delta_ota/idf_component.yml ================================================ version: "1.1.4" description: "ESP Delta OTA Library" url: https://github.com/espressif/idf-extra-components/tree/master/esp_delta_ota dependencies: idf: ">=4.3" ================================================ FILE: esp_delta_ota/include/esp_delta_ota.h ================================================ /* * SPDX-License-Identifier: Apache 2.0 License * * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD */ #pragma once #include "esp_err.h" #include #ifdef __cplusplus extern "C" { #endif #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) #define DEPRECATED_ATTRIBUTE __attribute__((deprecated)) #else #define DEPRECATED_ATTRIBUTE #endif typedef void *esp_delta_ota_handle_t; // Callback for reading the source data typedef esp_err_t (*src_read_cb_t)(uint8_t *buf_p, size_t size, int src_offset); typedef esp_err_t (*src_read_cb_with_user_ctx_t)(uint8_t *buf_p, size_t size, int src_offset, void *user_data); // Callback for working on the data generated by applying patch on the source data typedef esp_err_t (*merged_stream_write_cb_t)(const uint8_t *buf_p, size_t size); typedef esp_err_t (*merged_stream_write_cb_with_user_ctx_t)(const uint8_t *buf_p, size_t size, void *user_data); typedef struct esp_delta_ota_cfg { void *user_data; /*!< User Data */ union { src_read_cb_t read_cb; /*!< Read Callback */ src_read_cb_with_user_ctx_t read_cb_with_user_data; /*!< Read Callback with user data */ }; union { merged_stream_write_cb_with_user_ctx_t write_cb_with_user_data; /*!< Write Callback with user data */ merged_stream_write_cb_t write_cb DEPRECATED_ATTRIBUTE; /*!< Write Callback */ }; } esp_delta_ota_cfg_t; #undef DEPRECATED_ATTRIBUTE /** * @brief Initializes the delta OTA process * * @param[in] cfg pointer to esp_delta_ota_cfg_t structure. * @return - NULL On failure * - esp_delta_ota_handle_t handle */ esp_delta_ota_handle_t esp_delta_ota_init(esp_delta_ota_cfg_t *cfg); /** * @brief This function performs the patch applying operation on the source data. * * @param[in] handle esp_delta_ota_handle_t handle * @param[in] buf pointer to patch buffer * @param[in] size size of patch buffer. * @return - ESP_OK * - ESP_ERR_INVALID_ARG * - ESP_FAIL */ esp_err_t esp_delta_ota_feed_patch(esp_delta_ota_handle_t handle, const uint8_t *buf, int size); /** * @brief This function finishes the patch applying operation. * * @param[in] handle esp_delta_ota_handle_t * @return int */ esp_err_t esp_delta_ota_finalize(esp_delta_ota_handle_t handle); /** * @brief Clean-up delta ota process * * @param[in] handle esp_delta_ota_handle_t */ esp_err_t esp_delta_ota_deinit(esp_delta_ota_handle_t handle); #ifdef __cplusplus } #endif ================================================ FILE: esp_delta_ota/src/esp_delta_ota.c ================================================ /* * SPDX-License-Identifier: Apache 2.0 License * * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD */ #include #include #include #include #include "esp_err.h" #include "esp_log.h" #include "esp_delta_ota.h" #include "detools.h" static const char *TAG = "esp_delta_ota"; typedef struct esp_delta_ota_ctx { void *user_data; union { src_read_cb_t read_cb; /*!< Read Callback */ src_read_cb_with_user_ctx_t read_cb_with_user_data; /*!< Read Callback with user data */ }; union { merged_stream_write_cb_with_user_ctx_t write_cb_with_user_data; merged_stream_write_cb_t write_cb; }; struct detools_apply_patch_t *apply_patch; int src_offset; } esp_delta_ota_ctx; static int esp_delta_ota_write_cb(void *arg_p, const uint8_t *buf_p, size_t size) { if (size <= 0) { return ESP_ERR_INVALID_ARG; } esp_delta_ota_ctx *handle = (esp_delta_ota_ctx *)arg_p; esp_err_t err = ESP_OK; if (!handle->user_data) { err = handle->write_cb(buf_p, size); if (err != ESP_OK) { ESP_LOGE(TAG, "Error in write_cb(): %s", esp_err_to_name(err)); return ESP_FAIL; } } else { err = handle->write_cb_with_user_data(buf_p, size, handle->user_data); if (err != ESP_OK) { ESP_LOGE(TAG, "Error in write_cb_with_user_data(): %s", esp_err_to_name(err)); return ESP_FAIL; } } return ESP_OK; } static int esp_delta_ota_read_cb(void *arg_p, uint8_t *buf_p, size_t size) { if (size <= 0 || !arg_p) { return -ESP_ERR_INVALID_ARG; } esp_delta_ota_ctx *handle = (esp_delta_ota_ctx *)arg_p; esp_err_t err = ESP_OK; if (!handle->user_data) { err = handle->read_cb(buf_p, size, handle->src_offset); if (err != ESP_OK) { ESP_LOGE(TAG, "Error in read_cb(): %s", esp_err_to_name(err)); return ESP_FAIL; } } else { err = handle->read_cb_with_user_data(buf_p, size, handle->src_offset, handle->user_data); if (err != ESP_OK) { ESP_LOGE(TAG, "Error in read_cb_with_user_data(): %s", esp_err_to_name(err)); return ESP_FAIL; } } handle->src_offset += size; return ESP_OK; } static int esp_delta_ota_seek_cb(void *arg_p, int offset) { esp_delta_ota_ctx *handle = (esp_delta_ota_ctx *)arg_p; handle->src_offset += offset; return ESP_OK; } esp_delta_ota_handle_t esp_delta_ota_init(esp_delta_ota_cfg_t *cfg) { esp_delta_ota_ctx *ctx = calloc(1, sizeof(esp_delta_ota_ctx)); if (!ctx) { ESP_LOGE(TAG, "Unable to allocate memory"); return NULL; } ctx->user_data = cfg->user_data; ctx->read_cb = cfg->read_cb; ctx->write_cb_with_user_data = cfg->write_cb_with_user_data; ctx->apply_patch = calloc(1, sizeof(struct detools_apply_patch_t)); if (!ctx->apply_patch) { ESP_LOGE(TAG, "Unable to allocate memory"); free(ctx); ctx = NULL; return NULL; } int ret = detools_apply_patch_init(ctx->apply_patch, &esp_delta_ota_read_cb, &esp_delta_ota_seek_cb, 0, &esp_delta_ota_write_cb, ctx); if (ret < 0) { ESP_LOGE(TAG, "Error while initializing delta_ota: %s", detools_error_as_string(ret)); free(ctx->apply_patch); ctx->apply_patch = NULL; free(ctx); ctx = NULL; return NULL; } return (esp_delta_ota_handle_t)ctx; } esp_err_t esp_delta_ota_feed_patch(esp_delta_ota_handle_t handle, const uint8_t *buf, int size) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_delta_ota_ctx *ctx = (esp_delta_ota_ctx *)handle; int err = detools_apply_patch_process(ctx->apply_patch, (const uint8_t *)buf, size); if (err != 0) { ESP_LOGE(TAG, "Error while applying patch: %s", detools_error_as_string(err)); return ESP_FAIL; } return ESP_OK; } esp_err_t esp_delta_ota_finalize(esp_delta_ota_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_delta_ota_ctx *ctx = (esp_delta_ota_ctx *)handle; int err = detools_apply_patch_finalize(ctx->apply_patch); if (err < 0) { ESP_LOGE(TAG, "Error while finishing the patching: %s", detools_error_as_string(err)); return ESP_FAIL; } return ESP_OK; } esp_err_t esp_delta_ota_deinit(esp_delta_ota_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_delta_ota_ctx *ctx = (esp_delta_ota_ctx *)handle; free(ctx->apply_patch); ctx->apply_patch = NULL; free(ctx); ctx = NULL; return ESP_OK; } ================================================ FILE: esp_delta_ota/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_delta_ota_test) ================================================ FILE: esp_delta_ota/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "esp_delta_ota_test_main.c" "test_esp_delta_ota.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity EMBED_FILES assets/base.bin assets/new.bin assets/patch.bin assets/bad_patch.bin WHOLE_ARCHIVE) ================================================ FILE: esp_delta_ota/test_apps/main/esp_delta_ota_test_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running esp_delta_ota component tests\n"); unity_run_menu(); } ================================================ FILE: esp_delta_ota/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_delta_ota: version: "*" override_path: "../.." ================================================ FILE: esp_delta_ota/test_apps/main/test_esp_delta_ota.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "unity.h" #include "esp_delta_ota.h" extern const uint8_t base_bin_start[] asm("_binary_base_bin_start"); extern const uint8_t base_bin_end[] asm("_binary_base_bin_end"); extern const uint8_t new_bin_end[] asm("_binary_new_bin_end"); extern const uint8_t new_bin_start[] asm("_binary_new_bin_start"); extern const uint8_t patch_bin_start[] asm("_binary_patch_bin_start"); extern const uint8_t patch_bin_end[] asm("_binary_patch_bin_end"); extern const uint8_t bad_patch_bin_start[] asm("_binary_bad_patch_bin_start"); extern const uint8_t bad_patch_bin_end[] asm("_binary_bad_patch_bin_end"); static uint8_t output_buffer[1300] = {0}; static int output_index = 0; static esp_err_t write_cb(const uint8_t *buf_p, size_t size) { if (size <= 0) { return ESP_OK; } memcpy(output_buffer + output_index, buf_p, size); output_index += size; return ESP_OK; } static esp_err_t read_cb(uint8_t *buf_p, size_t size, int src_offset) { if (size <= 0) { return ESP_ERR_INVALID_ARG; } memcpy(buf_p, base_bin_start + src_offset, size); return ESP_OK; } TEST_CASE("Sending full patch at once", "[esp_delta_ota]") { memset(output_buffer, 0, 1000); output_index = 0; esp_delta_ota_cfg_t cfg = { .read_cb = &read_cb, .write_cb = &write_cb, }; esp_delta_ota_handle_t handle = esp_delta_ota_init(&cfg); TEST_ASSERT_NOT_NULL(handle); esp_err_t err = esp_delta_ota_feed_patch(handle, patch_bin_start, patch_bin_end - patch_bin_start); TEST_ESP_OK(err); err = esp_delta_ota_finalize(handle); TEST_ESP_OK(err); err = esp_delta_ota_deinit(handle); TEST_ESP_OK(err); TEST_ASSERT_EQUAL_INT(0, memcmp(new_bin_start, output_buffer, new_bin_end - new_bin_start)); } TEST_CASE("Applying wrong patch", "[esp_delta_ota]") { memset(output_buffer, 0, 1000); output_index = 0; esp_delta_ota_cfg_t cfg = { .read_cb = &read_cb, .write_cb = &write_cb, }; esp_delta_ota_handle_t handle = esp_delta_ota_init(&cfg); TEST_ASSERT_NOT_NULL(handle); esp_err_t err = esp_delta_ota_feed_patch(handle, bad_patch_bin_start, bad_patch_bin_end - bad_patch_bin_start); TEST_ESP_OK(err); err = esp_delta_ota_finalize(handle); TEST_ESP_OK(err); err = esp_delta_ota_deinit(handle); TEST_ESP_OK(err); TEST_ASSERT_NOT_EQUAL(0, memcmp(new_bin_start, output_buffer, new_bin_end - new_bin_start)); } TEST_CASE("Sending 1 byte of patch at once", "[esp_delta_ota]") { memset(output_buffer, 0, 1000); output_index = 0; esp_delta_ota_cfg_t cfg = { .read_cb = &read_cb, .write_cb = &write_cb, }; esp_delta_ota_handle_t handle = esp_delta_ota_init(&cfg); TEST_ASSERT_NOT_NULL(handle); esp_err_t err = ESP_OK; for (int i = 0; i < patch_bin_end - patch_bin_start; i++) { err = esp_delta_ota_feed_patch(handle, patch_bin_start + i, 1); TEST_ESP_OK(err); } err = esp_delta_ota_finalize(handle); TEST_ESP_OK(err); err = esp_delta_ota_deinit(handle); TEST_ESP_OK(err); TEST_ASSERT_EQUAL_INT(0, memcmp(new_bin_start, output_buffer, output_index)); } ================================================ FILE: esp_delta_ota/test_apps/pytest_esp_delta_ota.py ================================================ import pytest @pytest.mark.generic def test_esp_delta_ota(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_delta_ota/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: esp_encrypted_img/.build-test-rules.yml ================================================ esp_encrypted_img/examples: disable: - if: CONFIG_NAME == "CONFIG_PRE_ENCRYPTED_RSA_USE_DS" and (IDF_VERSION < "5.3" or SOC_HMAC_SUPPORTED != 1) temporary: true reason: IDF Version < 5.3 is not supported yet to use DS peripheral. Also skipping targets that do not support HMAC peripheral esp_encrypted_img/test_apps: disable: - if: CONFIG_NAME == "ds_peripheral" and (IDF_VERSION < "5.3" or SOC_HMAC_SUPPORTED != 1) temporary: true reason: IDF Version < 5.3 is not supported yet to use DS peripheral. Also skipping targets that do not support HMAC peripheral ================================================ FILE: esp_encrypted_img/CHANGELOG.md ================================================ ## 2.7.1 ### Enhancements: - Added support for ESP-IDF v6.0 release ## 2.7.0 ### Features: - Added support for PSA Crypto API and hence compatibility with ESP-IDF 6.0 release ## 2.6.0 ### Enhancements: - Added support to use DS peripheral for RSA based encrypted OTA scheme ## 2.5.1 ## Bugfixes: - Fixed the format of Kconfig file ## 2.5.0 ### Enhancements: - Added an API to export the public key from the private key: `esp_encrypted_img_export_public_key` ## 2.4.0 ### Enhancements: - Added support for ECIES based OTA encryption scheme ## 2.3.0 ### Enhancements: - Added pre_encrypted_ota example, which demonstrates the OTA using encrypted image file. ## 2.2.1 - Build system: fix the dependency for generating pre encrypted image ## 2.2.0 ### Enhancements: - Added an API to get the size of pre encrypted binary image header, this could be useful while computing entire decrypted image length: `esp_encrypted_img_get_header_size` ## 2.1.0 ### Enhancements: - Added an API to abort the decryption process: `esp_encrypted_img_decrypt_abort` - Added an API to check if the complete data has been received: `esp_encrypted_img_is_complete_data_received` ## 2.0.4 - `rsa_pub_key` member of `esp_decrypt_cfg_t` structure is now deprecated. Please use `rsa_priv_key` instead. - `rsa_pub_key_len` member of `esp_decrypt_cfg_t` structure is now deprecated. Please use `rsa_priv_key_len` instead. ================================================ FILE: esp_encrypted_img/CMakeLists.txt ================================================ set(ESP_ENCRYPT_SRCS "src/esp_encrypted_img.c") if(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) list(APPEND ESP_ENCRYPT_SRCS "src/esp_encrypted_img_utilities.c") endif() idf_component_register(SRCS "${ESP_ENCRYPT_SRCS}" INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "private_include" PRIV_REQUIRES mbedtls) if(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) idf_component_optional_requires(PRIVATE efuse) endif() ================================================ FILE: esp_encrypted_img/Kconfig ================================================ menu "Pre Encrypted OTA Configuration" choice PRE_ENCRYPTED_OTA_SCHEME prompt "Pre-encrypted OTA Scheme" default PRE_ENCRYPTED_OTA_USE_RSA help Select the cryptographic scheme to use for pre-encrypted Over-The-Air updates. config PRE_ENCRYPTED_OTA_USE_RSA bool "RSA-3072 encryption" help Use RSA for encrypting the GCM key. The device will decrypt the GCM key using its private RSA key. config PRE_ENCRYPTED_RSA_USE_DS depends on PRE_ENCRYPTED_OTA_USE_RSA depends on SOC_DIG_SIGN_SUPPORTED select MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL bool "Use DS Peripheral" help Use DS Peripheral for RSA OTA Encryption Scheme. The DS peripheral is used for decrypting the AES-GCM key config PRE_ENCRYPTED_OTA_USE_ECIES depends on SOC_HMAC_SUPPORTED select MBEDTLS_HKDF_C bool "ECIES encryption" help Use Elliptic Curve Cryptography (ECC) for key agreement. The GCM key will be derived using ECDH with a server public key and a device private key (potentially derived via HMAC). endchoice endmenu ================================================ FILE: esp_encrypted_img/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_encrypted_img/README.md ================================================ # ESP Encrypted Image Abstraction Layer [![Component Registry](https://components.espressif.com/components/espressif/esp_encrypted_img/badge.svg)](https://components.espressif.com/components/espressif/esp_encrypted_img) This component provides an API interface to decrypt data defined in "ESP Encrypted Image" format. This format is as specified at [Image Format](#image-format) This component can help in integrating pre encrypted firmware in over-the-air updates. Additionally, this component can also be used for other use-cases which requires addition of encryption layer for custom data. ## Image Format The ESP Encrypted Image format supports two primary cryptographic schemes: RSA-3072 and ECIES-P256. It consists of a header followed by the encrypted binary data. The total header size is 512 bytes for both RSA and ECIES-P256 schemes, but the internal layout of the header differs. ### RSA-3072 Image Header ```mermaid block-beta columns 3 magic["Magic (0–3) [4 bytes]"]:3 enc_gcm_key["AES-GCM key (4–387) [384 bytes]"]:3 iv["AES-GCM IV (388–403) [16 bytes]"]:3 bin_size["Binary Size (404–407) [4 bytes]"]:3 auth_tag["Auth Tag (408–423) [16 bytes]"]:3 reserved_rsa["Reserver Header (424–511) [88 bytes]"]:3 app_binary["Application binary"]:3 space:3 space:2 plain_text["Plain Text"]:1 space:2 rsa_enc["Encrypted by RSA-3072"]:1 space:2 aes_enc["Encrypted by AES-GCM key"]:1 style app_binary fill:#565 style enc_gcm_key fill:#356 style rsa_enc fill:#356 style aes_enc fill:#565 ``` The header for an image encrypted using the RSA-3072 scheme is structured as follows: ```c typedef struct { uint8_t magic[4]; // Magic bytes (e.g., derived from SHA256("esp_encrypted_img")) uint8_t enc_gcm_key[384]; // AES-GCM key encrypted with RSA-3072 public key uint8_t iv[16]; // Initialization Vector for AES-GCM uint8_t bin_size[4]; // Size of the original binary (little-endian) uint8_t auth_tag[16]; // AES-GCM authentication tag uint8_t reserved_rsa[88]; // Reserved for future use } esp_enc_img_rsa_header_t; ``` * **Magic (4 bytes):** Identifies the file type. * **Encrypted GCM Key (384 bytes):** The 32-byte AES-GCM key, encrypted using the RSA-3072 public key (PKCS#1 v1.5 padding). * **IV (16 bytes):** The Initialization Vector used for AES-GCM encryption. * **Binary Size (4 bytes):** The size of the original, unencrypted binary data, in little-endian format. * **Auth Tag (16 bytes):** The authentication tag generated by AES-GCM. * **Reserved (88 bytes):** Padding to make the header 512 bytes. ### ECIES-P256 Image Header The header for an image encrypted using the ECIES-P256 scheme is structured as follows: ```mermaid block-beta columns 3 magic["Magic (0–3) [4 bytes]"]:3 server_pub_key["Server Public Key (4–67) [64 bytes]"]:3 kdf_salt["KDF Salt (68–99) [32 bytes]"]:3 reserved_key_params["Reserved (100–387) [288 bytes]"]:3 iv["AES-GCM IV (388–403) [16 bytes]"]:3 bin_size["Binary Size (404–407) [4 bytes]"]:3 auth_tag["Auth Tag (408–423) [16 bytes]"]:3 reserved_final_padding["Reserved Header (424–511) [88 bytes]"]:3 app_binary["Encrypted app binary \nEncrypted with AES GCM key"]:3 space:3 space:2 plain_text["Plain Text"]:1 space:2 aes_enc["Encrypted by AES-GCM key"]:1 style app_binary fill:#565 style aes_enc fill:#565 ``` ```c typedef struct { uint8_t magic[4]; // Magic bytes (e.g., derived from SHA256("esp_encrypted_img_ecc")) uint8_t server_pub_key[64]; // Server's uncompressed ECC public key (P-256, X and Y coordinates) uint8_t kdf_salt[32]; // Salt used for KDF (HKDF) to derive AES-GCM key uint8_t reserved_key_params[288]; // Reserved to make the key parameters block (server_pub_key, kdf_salt, this field) 384 bytes uint8_t iv[16]; // Initialization Vector for AES-GCM uint8_t bin_size[4]; // Size of the original binary (little-endian) uint8_t auth_tag[16]; // AES-GCM authentication tag uint8_t reserved_final_padding[88]; // Reserved padding to make the total header 512 bytes } esp_enc_img_ecc_header_t; ``` * **Magic (4 bytes):** Identifies the file type. * **Server Public Key (64 bytes):** The uncompressed public key of the server/script (P-256 curve). This consists of the X and Y coordinates (32 bytes each). This key is used by the device, along with its own private key, to perform ECDH and derive the shared secret. * **KDF Salt (32 bytes):** The salt used with HKDF (based on SHA256) to derive the AES-GCM encryption key from the ECDH shared secret. * **Reserved (288 bytes):** Padding to align the key parameters section (server public key, KDF salt, and this field) to 384 bytes so that the offset of the encryption-related parameters is unchanged. * **AES-GCM IV (16 bytes):** The Initialization Vector used for AES-GCM encryption. * **Binary Size (4 bytes):** The size of the original, unencrypted binary data, in little-endian format. * **Auth Tag (16 bytes):** The authentication tag generated by AES-GCM. * **Reserved Header (88 bytes):** Additional padding to ensure the total header size is 512 bytes. The device's private key (required for ECDH on the device side) is typically derived on the device from an HMAC key. The `esp_enc_img_gen.py` script includes the *server's* public key in the header so the device can complete the ECDH handshake. Note: * RSA-3072 key is provided to the tool externally. You can generate an RSA key pair using the `esp_enc_img_gen.py` tool (recommended) or OpenSSL: * Using `esp_enc_img_gen.py`: ```bash python esp_enc_img_gen.py --generate_rsa_key ``` This will create `rsa_pub_key.pem` and `rsa_priv_key.pem`. * Using OpenSSL: ```bash openssl genrsa -out rsa_key/private.pem 3072 ``` * AES-GCM key and IV are generated by the tool itself. ### RSA with Digital Signature (DS) Peripheral For ESP32 devices that support the Digital Signature peripheral (such as ESP32-S2, ESP32-S3, ESP32-C3, etc.), you can enable hardware-accelerated RSA operations by selecting the `PRE_ENCRYPTED_RSA_USE_DS` configuration option. This provides enhanced security and performance compared to software-based RSA operations. **ESP-IDF Version Requirement:** The Digital Signature (DS) peripheral support for decryption was introduced in ESP-IDF v5.3. To use DS peripheral functionality with esp_encrypted_img, you must use ESP-IDF v5.3 or above. Projects using ESP-IDF versions prior to v5.3 cannot use the DS peripheral option. **Configuration:** In your project's menuconfig, navigate to: ``` Component config → Pre Encrypted OTA Configuration → Pre-encrypted OTA Scheme → RSA-3072 encryption → Use DS Peripheral ``` **Key Requirements:** When using the DS peripheral, the RSA private key must be securely provisioned using the ESP Secure Certificate Manager. The key is stored in a special format that can only be used by the DS peripheral. **Device Provisioning:** Use the `configure_esp_secure_cert.py` tool to provision the DS context: ```bash python configure_esp_secure_cert.py --target_chip --private-key --priv_key_algo RSA 3072 --configure_ds ``` This command generates the DS context and creates an `esp_secure_cert.bin` file that contains the encrypted private key and DS parameters. **Partition Table Setup:** Your project must include an `esp_secure_cert` partition in the partition table. The example project provides a reference partition table (`examples/pre_encrypted_ota/partitions.csv`) that includes: ```csv esp_secure_cert,0x3F,,,0x2000, ``` You can use this as a template for your own project's partition table. **Flashing the Secure Certificate:** After generating the DS context, you must flash the `esp_secure_cert.bin` file to the device. You need to determine the correct partition offset first: ```bash # Check your partition table to find the esp_secure_cert partition offset idf.py partition-table # Flash the secure certificate to the correct offset esptool.py write_flash esp_secure_cert.bin ``` **Note:** Replace `` with the actual offset shown in your partition table output. For the example partition table provided, this is 0x9000, but always verify with your specific partition table. Check the esp_secure_cert_manager documentation for more information on how to use the tool. **Important Notes:** - **ESP-IDF v5.3+ Required:** DS peripheral support requires ESP-IDF v5.3 or above - The DS peripheral requires the private key to be in a specific format managed by ESP Secure Certificate Manager - Public key export via `esp_encrypted_img_export_public_key()` is not supported when using DS peripheral (returns `ESP_ERR_NOT_SUPPORTED`) - This option is only available on ESP32 variants that support the Digital Signature peripheral - Enhanced security as the private key never leaves the secure hardware peripheral - The private key is stored securely in eFuse and cannot be read back in plaintext ## Tool Info The `esp_encrypted_img` component includes a Python script, `esp_enc_img_gen.py`, designed for generating and managing encrypted images. This tool supports two primary cryptographic schemes for image encryption: RSA-3072 and ECIES-P256. The script requires a key file for all encryption and decryption operations. If keys are not available, they must be generated beforehand using the appropriate `--generate_..._key` options. **Core Functionality:** * **Encryption**: Secures binary files using either RSA or ECC. Requires a public key file. * **Decryption**: Decrypts images. Requires the corresponding private key file. * **Key Management**: Assists in generating cryptographic keys via `--generate_ecc_key` and `--generate_rsa_key` options. ### Key Generation The tool provides options to generate cryptographic key pairs for both ECIES-P256 and RSA schemes. * **Generate ECIES-P256 Key Pair**: Use the `--generate_ecc_key` option to generate an ECC key pair. This will create `device_pub_key.pem` (device public key) and `device_hmac_key.bin` (HMAC key for deriving device key). ```bash python esp_enc_img_gen.py --generate_ecc_key ``` * **Generate RSA Key Pair**: Use the `--generate_rsa_key` option to generate an RSA-3072 key pair. This will create `rsa_pub_key.pem` (public key) and `rsa_priv_key.pem` (private key). ```bash python esp_enc_img_gen.py --generate_rsa_key ``` ### Encryption Schemes #### 1. RSA-3072 This scheme uses an RSA public key to encrypt an AES-GCM key, which is then used to encrypt the image. The corresponding RSA private key is required for decryption. **Key Generation:** An RSA-3072 key pair (public and private keys) is required. You can generate one using OpenSSL or the tool itself: Using OpenSSL: ```bash openssl genrsa -out rsa_private_key.pem 3072 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem ``` Using the tool: ```bash python esp_enc_img_gen.py --generate_rsa_key ``` This will create `rsa_pub_key.pem` and `rsa_priv_key.pem`. * The `rsa_public_key.pem` is used for encryption. * The `rsa_private_key.pem` is used for decryption. **Encrypting the image:** ```bash python esp_enc_img_gen.py encrypt /path/to/rsa_public_key.pem ``` **Decrypting the image:** ```bash python esp_enc_img_gen.py decrypt ``` #### 2. ECIES-P256 This scheme utilizes Elliptic Curve Cryptography (ECC) with a 256-bit curve. It employs an Elliptic Curve Diffie-Hellman (ECDH) key exchange to establish a shared secret. This shared secret is then used to derive an AES-GCM key for encrypting the image. A device public key file must be provided for encryption. The script will then generate an ephemeral server key pair for the ECDH process, and the server's public key is included in the image header. This allows the device (which possesses the corresponding device private key) to reconstruct the shared secret and decrypt the image. **General Encryption Command Structure:** ```bash python esp_enc_img_gen.py encrypt ``` **Key Generation and Management Files (ECIES-P256):** During ECIES-P256 encryption, various key-related files may be generated or used by the script: * `device_hmac_key.bin`: An HMAC key used to derive the device's ECC key pair. This file is saved if an HMAC key is generated by `--generate_ecc_key`. * `device_pub_key.pem`: The device's public ECC key (in PEM format). This is generated by `--generate_ecc_key` or can be provided by the user. * The server's public ECC key is generated by the script during encryption and included in the encrypted image header. It is not saved as a separate file. **Device Provisioning for ECIES-P256 with HMAC Key (Example: `pre_encrypted_ota`)** When using an HMAC-derived device key for ECIES-P256, the device hmac key must be securely provisioned onto the device. This is typically done by burning it into an eFuse key block. 1. **Burn the HMAC Key to eFuse:** Use the `idf.py` command to burn the `device_hmac_key.bin` to a specific eFuse key block. For example: ```bash idf.py efuse-burn-key BLOCK_KEY device_hmac_key.bin HMAC_UP ``` Replace `BLOCK_KEY` with the actual eFuse block you intend to use (e.g., `BLOCK_KEY0`, `BLOCK_KEY1`, etc., up to `BLOCK_KEY5` typically). 2. **Code Configuration:** The eFuse block chosen in the command above must match the HMAC key ID used in your application code. In the `pre_encrypted_ota` example, the HMAC key ID is defined in the source code and should be updated to match your chosen eFuse block. ```c #define HMAC_UP_KEY_ID 2 esp_decrypt_cfg_t cfg = { .hmac_key_id = HMAC_UP_KEY_ID, }; ``` **Selecting OTA Scheme in Example:** The `pre_encrypted_ota` example project uses Kconfig options to allow selection between RSA and ECIES-P256 schemes for OTA updates: * `PRE_ENCRYPTED_OTA_SCHEME`: This choice allows you to select either: * `PRE_ENCRYPTED_OTA_USE_RSA`: For RSA-based pre-encrypted OTA. * `PRE_ENCRYPTED_OTA_USE_ECIES`: For ECC-based pre-encrypted OTA. Make sure these configurations are set correctly in your project's configuration to match your chosen encryption scheme and hardware provisioning. ### ECC-256 Encryption Use Cases * **Use Case 1: No Pre-existing Device Keys** If you do not have existing ECC keys for the device, you must first generate them. The `--generate_ecc_key` option will create a device HMAC key (`device_hmac_key.bin`) and a device public key (`device_pub_key.pem`) derived from it. The `device_pub_key.pem` is then used for encryption. 1. Generate the necessary keys: ```bash python esp_enc_img_gen.py --generate_ecc_key ``` This command creates `device_hmac_key.bin` and `device_pub_key.pem` in the current directory. 2. Encrypt the image using the generated device public key: ```bash python esp_enc_img_gen.py encrypt device_pub_key.pem ``` * **Use Case 2: Pre-existing Device Public Key is Available** Uses a provided device public ECC key (e.g., `device_pub_key.pem`) directly. The script will use this key and generate a new server key pair for ECDH. ```bash python esp_enc_img_gen.py encrypt ``` The public key of the ephemeral server key pair used for ECDH is embedded in the image but not saved to a separate file. ## Per Device Unique Key Support The `esp_encrypted_img` component supports workflows where each device is provisioned with a unique key pair. This is essential for scenarios requiring per-device image encryption and secure provisioning. By exporting the public key from the device or key management system, you can automate image generation and ensure that only the intended device can decrypt its firmware or data. It is the application's responsibility to: - Generate the key using a good entropy source (the [ESP-IDF Random Number Generation APIs](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html) provide suggested approach using ESP-IDF APIs). - Flash the generated key to the device securely (e.g., to eFuse or in Flash). - Retrieve the public key (using the API below), send it to the server, and use it to generate pre-encrypted OTA binaries for each device.
How to Export the Public Key ### Exporting the Public Key Programmatically The component provides an API to retrieve the public key associated with the decryption context: ```c /** * @brief Export the public key corresponding to the private key. * The application should free the memory pointed by `pub_key` after use. * For RSA, the public key is in DER format and corresponds to the private key passed with `esp_encrypted_img_decrypt_start()`. * For ECIES, the public key is in DER format and is derived from the HMAC key ID passed with `esp_encrypted_img_decrypt_start()`. * * @param ctx esp_decrypt_handle_t handle * @param pub_key Pointer to store the public key * @param pub_key_len Pointer to store the length of the public key * @return esp_err_t Status of the operation */ esp_err_t esp_encrypted_img_export_public_key(esp_decrypt_handle_t ctx, uint8_t **pub_key, size_t *pub_key_len); ``` - **RSA-3072:** Returns the public key in DER format. - **ECIES-P256:** Returns the public key in DER format and is derived from the HMAC key ID.
### Getting More Help To explore all available options and commands for the tool, use: ```bash python esp_enc_img_gen.py --help ``` ## API Reference To learn more about how to use this component, please check API Documentation from header file [esp_encrypted_img.h](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/include/esp_encrypted_img.h) ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/CMakeLists.txt ================================================ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # The following five lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(pre_encrypted_ota) # Flash the pre_encrypted_ota_secure.bin to the OTA 1 partition. if(CONFIG_EXAMPLE_ENABLE_CI_TEST) set(partition ota_1) idf_build_get_property(build_dir BUILD_DIR) set(image_file ${build_dir}/pre_encrypted_ota_secure.bin) partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") esptool_py_flash_target_image(flash "${partition}" "${offset}" "${image_file}") endif() if (CONFIG_PRE_ENCRYPTED_RSA_USE_DS) set(partition esp_secure_cert) idf_build_get_property(project_dir PROJECT_DIR) set(image_file ${CMAKE_CURRENT_SOURCE_DIR}/esp_secure_cert_data/${partition}.bin) partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") esptool_py_flash_target_image(flash "${partition}" "${offset}" "${image_file}") endif() ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | # Encrypted Binary OTA This example demonstrates OTA updates with pre-encrypted binary using `esp_encrypted_img` component's APIs and tool. Pre-encrypted firmware binary must be hosted on OTA update server. This firmware will be fetched and then decrypted on device before being flashed. This allows firmware to remain `confidential` on the OTA update channel irrespective of underlying transport (e.g., non-TLS). * **NOTE:** Pre-encrypted OTA is a completely different scheme from Flash Encryption. Pre-encrypted OTA helps in ensuring the confidentiality of the firmware on the network channel, whereas Flash Encryption is intended for encrypting the contents of the ESP32's off-chip flash memory. > [!CAUTION] > Using the Pre-encrypted Binary OTA provides confidentiality of the firmware, but it does not ensure authenticity of the firmware. For ensuring that the firmware is coming from trusted source, please consider enabling secure boot feature along with the Pre-encrypted binary OTA. Please refer to security guide in the ESP-IDF docs for more details. ## ESP Encrypted Image Abstraction Layer This example uses `esp_encrypted_img` component hosted at [idf-extra-components/esp_encrypted_img](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img) and available though the [IDF component manager](https://components.espressif.com/component/espressif/esp_encrypted_img). Please refer to its documentation [here](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/README.md) for more details. ## How to use the example This example can use either RSA or ECIES-P256 for pre-encrypted OTA. You must first select your desired scheme: 1. Run `idf.py menuconfig`. 2. Navigate to `Component config` -> `Pre Encrypted OTA Configuration`. 3. Set `Pre-encrypted OTA Scheme` to your choice: * `RSA-3072 encryption` * `ECIES encryption` 4. If you selected `ECIES encryption` and will be using the HMAC-derived key, ensure `HMAC EFUSE KEY ID` is set to the eFuse block where `ecc_key/device_hmac_key.bin` will be burned. 5. Save the configuration and exit. Once the scheme is selected, follow the relevant sub-section below for key generation and specific setup. ### Creating RSA key for encryption You can generate a public and private RSA key pair using the `esp_enc_img_gen.py` tool or `openssl`. Using `esp_enc_img_gen.py`: ```bash python esp_enc_img_gen.py --generate_rsa_key ``` This will create `rsa_pub_key.pem` and `rsa_priv_key.pem` in the current directory. Using `openssl`: `openssl genrsa -out rsa_key/private.pem 3072` This generates a 3072-bit RSA key pair, and writes them to a file. Private key is required for decryption process and is used as input to the `esp_encrypted_img` component. Private key can either be embedded into the firmware or stored in NVS. Encrypted image generation tool will derive public key (from private key) and use it for encryption purpose. * **NOTE:** We highly recommend the use of flash encryption or NVS encryption to protect the RSA Private Key on the device. * **NOTE:** RSA key provided in the example is for demonstration purpose only. We recommend to create a new key for production applications. ### Steps for ECIES Scheme To test the ECIES-based encryption scheme: 1. **Configure for ECIES** (Ensure `ECIES encryption` is selected in `menuconfig` as described above): * Run `idf.py menuconfig` (if not already done, or to verify). * Navigate to `Component config` -> `Pre Encrypted OTA Configuration`. * Select `ECIES encryption` for the `Pre-encrypted OTA Scheme`. * Set the `HMAC EFUSE KEY ID` to the eFuse block number (0-5) where you will burn the `ecc_key/device_hmac_key.bin`. The default is -1 (disabled), so this must be changed. * Save the configuration and exit. 2. **Key Management**: * This example provides a pre-generated HMAC key and its corresponding public key in the `ecc_key/` directory (relative to this example). * `ecc_key/device_hmac_key.bin`: The HMAC key that needs to be burned into the device\'s eFuse. * `ecc_key/public.pem`: The device public key, derived from `device_hmac_key.bin`. This key will be used by the build system to encrypt the firmware. * **Burn the HMAC Key to eFuse**: Use the `idf.py efuse-burn-key` command to burn the `ecc_key/device_hmac_key.bin` to the eFuse block you configured in `menuconfig`. For example, if you set `HMAC EFUSE KEY ID` to 0: ```bash idf.py efuse-burn-key BLOCK_KEY0 ecc_key/device_hmac_key.bin HMAC_UP ``` Replace `BLOCK_KEY0` with the correct eFuse block if you chose a different ID (e.g., `BLOCK_KEY1` for ID 1). * **(Alternative) Generate New Keys**: If you prefer not to use the provided keys, you can generate a new set: ```bash python /tools/esp_enc_img_gen.py --generate_ecc_key ``` This will create `device_hmac_key.bin` and `device_pub_key.pem` in the current directory. You would then need to: 1. Replace `ecc_key/device_hmac_key.bin` and `ecc_key/public.pem` with these new files (or update the example to point to them). 2. Burn the new `device_hmac_key.bin` to the eFuse. 3. **Build, Flash, and OTA**: * Follow the steps in "Build and Flash example" and "Configure and start python based HTTPS Server" below. The build system will use the ECIES scheme and the public key (e.g., `ecc_key/public.pem`) to generate `build/pre_encrypted_ota_secure.bin`. * **NOTE:** The keys in the `ecc_key/` directory are for demonstration purposes only. We recommend creating a new key pair for production applications. ## Build and Flash example ``` idf.py build flash ``` * An encrypted image is automatically generated by build system. Upload the generated encrypted image (`build/pre_encrypted_ota_secure.bin`) to a server for performing OTA update. ### Configure and start python based HTTPS Server After a successful build, we need to create a self-signed certificate and run a simple HTTPS server as follows: ![create_self_signed_certificate](https://raw.githubusercontent.com/espressif/idf-extra-components/master/esp_encrypted_img/examples/pre_encrypted_ota/docs/ota_self_signature.gif) * Create server_certs directory, Navigate to server_certs directory `cd server_certs`. * To create a new self-signed certificate and key, run the command `openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes`. * When prompted for the `Common Name (CN)`, enter the name of the server that the "ESP-Dev-Board" will connect to. When running this example from a development machine, this is probably the IP address. The HTTPS client will check that the `CN` matches the address given in the HTTPS URL. You can start the server using following instructions: After the successful build, start the local python based HTTPS server using the certificate and key present in the 'server_certs' directory (certificate: ca_cert.pem and key: ca_key.pem). To start the server use the following command - ``` python pytest_pre_encrypted_ota.py build 8070 server_certs ``` 1. build - build directory (where the new firmware image is present) will be exposed 2. 8070 - server port (user can use any port) 3. server_certs - cert directory where the certificate and key is present (here same ca_cert.pem is used in main/pre_encrypted_ota.c and server_certs dir). If user wants to use own certificate and key just pass the directory name, in which the certificate and key is present. * Note - If you don't want to create certificates then just run the `pytest_pre_encrypted_ota.py` without passing `server_certs` directory, the server will use the hardcoded certificates present in `pytest_pre_encrypted_ota.py` ## Configuration Refer the README.md in the parent directory for the setup details. ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/ecc_key/public.pem ================================================ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6TXYz6Z507EZpQyXxI/+Lk5OunqO +Sv8zEFOGfhUGqt7P7KiAldMASLaLy5d9mOkl+FhwSRzf+vM54b7CoKhUg== -----END PUBLIC KEY----- ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/CMakeLists.txt ================================================ idf_build_get_property(project_dir PROJECT_DIR) set(SRCS "") set(INCLUDE_DIRS "") set(EMBED_TXTFILES "") if(CONFIG_EXAMPLE_ENABLE_CI_TEST) list(APPEND SRCS "tests/test_local_server_ota.c") list(APPEND INCLUDE_DIRS "tests") list(APPEND EMBED_TXTFILES "tests/certs/servercert.pem" "tests/certs/prvtkey.pem") endif() idf_component_register(SRCS "pre_encrypted_ota.c" ${SRCS} PRIV_REQUIRES esp_http_client app_update esp_https_ota nvs_flash esp_netif esp_wifi esp_netif esp_partition mbedtls INCLUDE_DIRS "." ${INCLUDE_DIRS} EMBED_TXTFILES ${project_dir}/rsa_key/private.pem ${project_dir}/server_certs/ca_cert.pem ${EMBED_TXTFILES}) if(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) set(KEY_FILE "${project_dir}/rsa_key/private.pem") elseif(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) set(KEY_FILE "${project_dir}/ecc_key/public.pem") else() message(FATAL_ERROR "Unsupported encryption scheme") endif() create_esp_enc_img(${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin ${KEY_FILE} ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}_secure.bin app) if(CONFIG_EXAMPLE_ENABLE_CI_TEST) target_link_libraries(${COMPONENT_LIB} PRIVATE idf::esp_https_server) endif() if(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) set(TARGET_CHIP $ENV{IDF_TARGET}) # This is skipped unless esp_secure_cert_mgr # can generate secure partition without providing port # create_esp_enc_img_secure_cert_data(${TARGET_CHIP} ${KEY_FILE} "RSA 3072") idf_component_optional_requires(PRIVATE espressif__esp_secure_cert_mgr) endif() ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/Kconfig.projbuild ================================================ menu "Example Configuration" config EXAMPLE_FIRMWARE_UPGRADE_URL string "firmware upgrade url endpoint" default "https://192.168.0.3:8070/hello_world.bin" help URL of server which hosts the encrypted firmware image. config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN bool default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN" config EXAMPLE_SKIP_COMMON_NAME_CHECK bool "Skip server certificate CN fieldcheck" default n help This allows you to skip the validation of OTA server certificate CN field. config EXAMPLE_SKIP_VERSION_CHECK bool "Skip firmware version check" default n help This allows you to skip the firmware version check. config EXAMPLE_OTA_RECV_TIMEOUT int "OTA Receive Timeout" default 5000 help Maximum time for reception config EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD bool "Enable partial HTTP download" default n select ESP_HTTPS_OTA_ENABLE_PARTIAL_DOWNLOAD select ESP_TLS_CLIENT_SESSION_TICKETS help This enables use of Range header in esp_https_ota component. The firmware image will be downloaded over multiple HTTP requests, with session resumption enabled between them. config EXAMPLE_HTTP_REQUEST_SIZE int "HTTP request size" default MBEDTLS_SSL_IN_CONTENT_LEN depends on EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD help This options specifies HTTP request size. Number of bytes specified in this option will be downloaded in single HTTP request. config EXAMPLE_ENABLE_CI_TEST bool "Enable the CI test code" default n help This enables the CI test code i.e. https local server code. config EXAMPLE_USE_CERT_BUNDLE bool "Enable certificate bundle" default y depends on MBEDTLS_CERTIFICATE_BUNDLE help Enable trusted root certificate bundle. This approach allows to have OTA updates functional with any public server without requirement to explicitly add its server certificate. endmenu ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/idf_component.yml ================================================ dependencies: espressif/esp_encrypted_img: version: '*' override_path: ../../../ protocol_examples_common: path: ${IDF_PATH}/examples/common_components/protocol_examples_common ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/pre_encrypted_ota.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ /* Pre Encrypted HTTPS OTA example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_event.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" #include "esp_idf_version.h" #include "nvs.h" #include "nvs_flash.h" #include "protocol_examples_common.h" #include "esp_encrypted_img.h" #ifdef CONFIG_EXAMPLE_USE_CERT_BUNDLE #include "esp_crt_bundle.h" #endif #if CONFIG_EXAMPLE_ENABLE_CI_TEST #include "test_local_server_ota.h" #endif #if CONFIG_EXAMPLE_CONNECT_WIFI #include "esp_wifi.h" #endif #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) #include "esp_secure_cert_read.h" #endif static const char *TAG = "pre_encrypted_ota_example"; extern const char server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); extern const char server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) extern const char rsa_private_pem_start[] asm("_binary_private_pem_start"); extern const char rsa_private_pem_end[] asm("_binary_private_pem_end"); #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) #define HMAC_UP_KEY_ID 2 #else #error "Please select a valid encryption algorithm in menuconfig" #endif static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) { if (new_app_info == NULL) { return ESP_ERR_INVALID_ARG; } const esp_partition_t *running = esp_ota_get_running_partition(); esp_app_desc_t running_app_info; if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); } #ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) { ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update."); return ESP_FAIL; } #endif return ESP_OK; } static esp_err_t _decrypt_cb(decrypt_cb_arg_t *args, void *user_ctx) { if (args == NULL || user_ctx == NULL) { ESP_LOGE(TAG, "_decrypt_cb: Invalid argument"); return ESP_ERR_INVALID_ARG; } esp_err_t err; pre_enc_decrypt_arg_t pargs = {}; pargs.data_in = args->data_in; pargs.data_in_len = args->data_in_len; err = esp_encrypted_img_decrypt_data((esp_decrypt_handle_t *)user_ctx, &pargs); if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) { ESP_LOGE(TAG, "Decrypt callback failed %d", err); free(pargs.data_out); return err; } static bool is_image_verified = false; if (pargs.data_out_len > 0) { args->data_out = pargs.data_out; args->data_out_len = pargs.data_out_len; if (!is_image_verified) { is_image_verified = true; const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t); // It is unlikely to not have App Descriptor available in first iteration of decrypt callback. assert(args->data_out_len >= app_desc_offset + sizeof(esp_app_desc_t)); esp_app_desc_t *app_info = (esp_app_desc_t *) &args->data_out[app_desc_offset]; err = validate_image_header(app_info); if (err != ESP_OK) { free(pargs.data_out); } return err; } } else { args->data_out_len = 0; } return ESP_OK; } void pre_encrypted_ota_task(void *pvParameter) { ESP_LOGI(TAG, "Starting Pre Encrypted OTA example"); esp_http_client_config_t config = { .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL, .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT, #ifdef CONFIG_EXAMPLE_USE_CERT_BUNDLE .crt_bundle_attach = esp_crt_bundle_attach, #else .cert_pem = (char *)server_cert_pem_start, #endif /* CONFIG_EXAMPLE_USE_CERT_BUNDLE */ .keep_alive_enable = true, #if ESP_IDF_VERSION_MAJOR > 5 || (ESP_IDF_VERSION_MAJOR == 5 && ESP_IDF_VERSION_MINOR > 2) #ifdef CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS .save_client_session = true, #endif #endif }; esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { ESP_LOGE(TAG, "Failed to get DS context"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) cfg.hmac_key_id = HMAC_UP_KEY_ID; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ esp_decrypt_handle_t decrypt_handle = esp_encrypted_img_decrypt_start(&cfg); if (!decrypt_handle) { ESP_LOGE(TAG, "OTA upgrade failed"); vTaskDelete(NULL); } #if CONFIG_EXAMPLE_ENABLE_CI_TEST example_test_firmware_data_from_stdin(&config.url); #endif #ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK config.skip_cert_common_name_check = true; #endif esp_https_ota_config_t ota_config = { .http_config = &config, #ifdef CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD .partial_http_download = true, .max_http_request_size = CONFIG_EXAMPLE_HTTP_REQUEST_SIZE, #endif .decrypt_cb = _decrypt_cb, .decrypt_user_ctx = (void *)decrypt_handle, .enc_img_header_size = esp_encrypted_img_get_header_size(), }; esp_https_ota_handle_t https_ota_handle = NULL; esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed"); vTaskDelete(NULL); } while (1) { err = esp_https_ota_perform(https_ota_handle); if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) { break; } // esp_https_ota_perform returns after every read operation which gives user the ability to // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image // data read so far. ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle)); } if (!esp_https_ota_is_complete_data_received(https_ota_handle)) { // the OTA image was not completely received and user can customise the response to this situation. ESP_LOGE(TAG, "Complete data was not received."); } else { err = esp_encrypted_img_decrypt_end(decrypt_handle); if (err != ESP_OK) { goto ota_end; } esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle); if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) { ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); } else { if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) { ESP_LOGE(TAG, "Image validation failed, image is corrupted"); } ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed 0x%x", ota_finish_err); vTaskDelete(NULL); } } ota_end: esp_https_ota_abort(https_ota_handle); esp_encrypted_img_decrypt_abort(decrypt_handle); ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed"); vTaskDelete(NULL); } void app_main(void) { // Initialize NVS. esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { // 1.OTA app partition table has a smaller NVS partition size than the non-OTA // partition table. This size mismatch may cause NVS initialization to fail. // 2.NVS partition contains data in new format and cannot be recognized by this version of code. // If this happens, we erase NVS partition and initialize NVS again. ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK( err ); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); #if CONFIG_EXAMPLE_CONNECT_WIFI /* Ensure to disable any WiFi power save mode, this allows best throughput * and hence timings for overall OTA operation. */ esp_wifi_set_ps(WIFI_PS_NONE); #endif // CONFIG_EXAMPLE_CONNECT_WIFI #if CONFIG_EXAMPLE_ENABLE_CI_TEST if (example_test_start_webserver() != ESP_OK) { ESP_LOGE(TAG, "Unable to start server"); } #endif xTaskCreate(&pre_encrypted_ota_task, "pre_encrypted_ota_task", 1024 * 8, NULL, 5, NULL); } ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/tests/certs/prvtkey.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al 3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg 0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX 49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc +3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D 0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin 7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr v/t+MeGJP/0Zw8v/X2CFll96 -----END PRIVATE KEY----- ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/tests/certs/servercert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= -----END CERTIFICATE----- ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/tests/test_local_server_ota.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ /* Pre Encrypted HTTPS OTA example's test file This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include "esp_https_server.h" #include "esp_log.h" #include "nvs_flash.h" #include "test_local_server_ota.h" #include "protocol_examples_common.h" #define OTA_URL_SIZE 256 #define PARTITION_READ_BUFFER_SIZE 256 #define PARTITION_READ_SIZE PARTITION_READ_BUFFER_SIZE static const char *TAG = "test_local_server_ota"; static size_t binary_size; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN void example_test_firmware_data_from_stdin(const char **data) { char input_buf[OTA_URL_SIZE]; if (strcmp(*data, "FROM_STDIN") == 0) { example_configure_stdin_stdout(); fflush(stdin); char *url = NULL; char *tokens[OTA_URL_SIZE], *saveptr; int token_count = 0; fgets(input_buf, OTA_URL_SIZE, stdin); int len = strlen(input_buf); if (len > 0 && input_buf[len - 1] == '\n') { input_buf[len - 1] = '\0'; } char *token = strtok_r(input_buf, " ", &saveptr); if (token == NULL) { return; } // First token is the URL url = token; tokens[token_count++] = url; // Process remaining tokens while ((token = strtok_r(NULL, " ", &saveptr)) != NULL) { tokens[token_count++] = token; } // Return if no further data is captured if (strchr(input_buf, ' ') != NULL) { return; } *data = strdup(tokens[0]); // Assign the URL and additional data after the loop if (token_count > 1) { ESP_LOGI(TAG, "binary_size: %s\n", tokens[1]); binary_size = atoi(tokens[1]); // Assuming the next token is the binary size } // Tokens are collected in the tokens array } else { ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url"); abort(); } } #endif /* An HTTP GET handler */ static esp_err_t root_get_handler(httpd_req_t *req) { httpd_resp_set_type(req, "text/plain"); const esp_partition_t *p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL); assert(p != NULL); if (binary_size == 0) { return ESP_FAIL; } int image_len = binary_size; char buffer[PARTITION_READ_BUFFER_SIZE]; int size = PARTITION_READ_SIZE; int offset = 0; do { /* Read file in chunks into the scratch buffer */ if (offset + size > image_len) { size = image_len - offset; } if (size == 0) { break; } esp_err_t ret = esp_partition_read(p, offset, buffer, size); if (ret == ESP_OK) { /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, buffer, size) != ESP_OK) { ESP_LOGE(TAG, "File sending failed!"); /* Abort sending file */ httpd_resp_sendstr_chunk(req, NULL); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); return ESP_FAIL; } } offset += size; /* Keep looping till the whole file is sent */ } while (offset <= image_len); ESP_LOGI(TAG, "File sending complete"); // Set headers httpd_resp_set_type(req, "application/octet-stream"); httpd_resp_set_hdr(req, "Accept-Ranges", "bytes"); httpd_resp_set_hdr(req, "Connection", "close"); httpd_resp_send_chunk(req, NULL, 0); return ESP_OK; } static esp_err_t root_head_handler(httpd_req_t *req) { const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL); if (partition == NULL) { ESP_LOGE(TAG, "Partition not found"); httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Partition not found"); return ESP_FAIL; } if (binary_size == 0) { return ESP_FAIL; } // Get the size of the binary httpd_resp_set_type(req, "application/octet-stream"); httpd_resp_set_hdr(req, "Accept-Ranges", "bytes"); httpd_resp_set_hdr(req, "Connection", "close"); // Complete HEAD response with no body return httpd_resp_send(req, NULL, binary_size); // No body for HEAD method } static const httpd_uri_t get_root = { .uri = "/", .method = HTTP_GET, .handler = root_get_handler }; static const httpd_uri_t head_root = { .uri = "/", .method = HTTP_HEAD, .handler = root_head_handler }; esp_err_t example_test_start_webserver(void) { httpd_handle_t server = NULL; // Start the httpd server ESP_LOGI(TAG, "Starting server"); httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT(); extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start"); extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end"); conf.servercert = servercert_start; conf.servercert_len = servercert_end - servercert_start; extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start"); extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end"); conf.prvtkey_pem = prvtkey_pem_start; conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start; esp_err_t ret = httpd_ssl_start(&server, &conf); if (ESP_OK != ret) { ESP_LOGI(TAG, "Error starting server!"); return ret; } // Set URI handlers ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &get_root); httpd_register_uri_handler(server, &head_root); return ESP_OK; } ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/main/tests/test_local_server_ota.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "esp_https_server.h" /** * @brief starts the https server * * @param bin_size the size of binary image which will be exposed * by the server. NOTE - bin_size cannot be 0. */ esp_err_t example_test_start_webserver(void); /** * @brief Takes the firmware URL from the STDIN (if want to send * other data write the data in just one line by adding " " deleminator). * * @param data pointer to the firmware URL (or URL including other data) */ void example_test_firmware_data_from_stdin(const char **data); ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags esp_secure_cert,0x3F,,,0x2000, nvs, data, nvs, , 0x4000, otadata, data, ota, , 0x2000, phy_init, data, phy, , 0x1000, factory, app, factory, , 0x13A000, ota_0, app, ota_0, , 0x13A000, ota_1, app, ota_1, , 0x13A000, ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/pytest_pre_encrypted_ota.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import http.server import multiprocessing import os import sys import socket import ssl from typing import Optional from typing import Callable import pexpect import pytest from pytest_embedded import Dut from pytest_embedded_idf.app import FlashFile from pytest_embedded_idf.serial import IdfSerial enc_bin_name = 'pre_encrypted_ota_secure.bin' host_ip = '127.0.0.1' server_port = 443 server_cert = '-----BEGIN CERTIFICATE-----\n'\ 'MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\n'\ 'BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx\n'\ 'MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ\n'\ 'UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n'\ 'ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T\n'\ 'sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k\n'\ 'qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd\n'\ 'GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4\n'\ 'sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb\n'\ 'jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/\n'\ 'ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud\n'\ 'EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3\n'\ 'emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY\n'\ 'W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx\n'\ 'bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN\n'\ 'ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl\n'\ 'hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=\n'\ '-----END CERTIFICATE-----\n' server_key = '-----BEGIN PRIVATE KEY-----\n'\ 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\n'\ 'JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw\n'\ 'h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT\n'\ 'aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al\n'\ '3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg\n'\ '0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB\n'\ 'vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui\n'\ 'f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9\n'\ 'Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y\n'\ 'JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX\n'\ '49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc\n'\ '+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6\n'\ 'pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D\n'\ '0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG\n'\ 'YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV\n'\ 'MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL\n'\ 'CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin\n'\ '7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1\n'\ 'noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8\n'\ '4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g\n'\ 'Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/\n'\ 'nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3\n'\ 'q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2\n'\ 'lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB\n'\ 'jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr\n'\ 'v/t+MeGJP/0Zw8v/X2CFll96\n'\ '-----END PRIVATE KEY-----\n' def start_https_server(ota_image_dir: str, server_ip: str, port: int, server_file: Optional[str] = None, key_file: Optional[str] = None) -> None: os.chdir(ota_image_dir) if server_file is None: server_file = os.path.join(ota_image_dir, 'server_cert.pem') cert_file_handle = open(server_file, 'w+') cert_file_handle.write(server_cert) cert_file_handle.close() if key_file is None: key_file = os.path.join(ota_image_dir, 'server_key.pem') key_file_handle = open('server_key.pem', 'w+') key_file_handle.write(server_key) key_file_handle.close() httpd = http.server.HTTPServer((server_ip, port), http.server.SimpleHTTPRequestHandler) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(certfile=server_file, keyfile=key_file) httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True) httpd.serve_forever() @pytest.mark.generic def test_examples_protocol_pre_encrypted_ota_example(dut: Dut) -> None: bin_path = os.path.join(dut.app.binary_path, enc_bin_name) bin_size = os.path.getsize(bin_path) # Construct the URI uri = f'https://{host_ip}:{server_port}/' try: dut.expect('Loaded app from partition at offset', timeout=30) dut.expect('Starting Pre Encrypted OTA example', timeout=30) dut.write(f'{uri} {bin_size}\n') dut.expect('Magic Verified', timeout=30) dut.expect('Reading RSA private key', timeout=30) dut.expect('upgrade successful. Rebooting', timeout=60) # after reboot dut.expect('Loaded app from partition at offset', timeout=30) finally: pass @pytest.mark.generic @pytest.mark.parametrize('config', ['partial_download',], indirect=True) def test_examples_protocol_pre_encrypted_ota_example_partial_download(dut: Dut, config) -> None: # Size of partial HTTP request request_size = int(dut.app.sdkconfig.get('EXAMPLE_HTTP_REQUEST_SIZE')) # File to be downloaded. This file is generated after compilation bin_path = os.path.join(dut.app.binary_path, enc_bin_name) bin_size = os.path.getsize(bin_path) uri = f'https://{host_ip}:{server_port}/' http_requests = int((bin_size / request_size) - 1) assert http_requests > 1 try: dut.expect('Loaded app from partition at offset', timeout=30) dut.expect('Starting Pre Encrypted OTA example', timeout=30) dut.write(f'{uri} {bin_size}\n') dut.expect('Magic Verified', timeout=60) dut.expect('Reading RSA private key', timeout=30) dut.expect('upgrade successful. Rebooting', timeout=60) # after reboot dut.expect('Loaded app from partition at offset', timeout=30) finally: pass if __name__ == '__main__': if sys.argv[2:]: # if two or more arguments are provided # Usage: python pytest_pre_encrypted_ota.py this_dir = os.path.dirname(os.path.realpath(__file__)) bin_dir = os.path.join(this_dir, sys.argv[1]) port = int(sys.argv[2]) cert_dir = bin_dir if not sys.argv[3:] else os.path.join(this_dir, sys.argv[3]) # optional argument print(f'Starting HTTPS server at "https://0.0.0.0:{port}"') server_file=os.path.join(cert_dir, 'ca_cert.pem') key_file=os.path.join(cert_dir, 'ca_key.pem') #check if file exits if not os.path.exists(server_file): server_file = None if not os.path.exists(key_file): key_file = None start_https_server(bin_dir, '', port, server_file, key_file) ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/rsa_key/private.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIG4wIBAAKCAYEAwiweYOoQ06RE5jAHJP5Y34j0PQR6T/unqQPVg0Z0NOstMcLW qzqRXL3f+fAc3ooxrN+vZkriKK6dcU0qM4g69BJwRKc+VKS4uRNfQhuAeCyFgTP0 MWJDlSZplphjDXnPoJM5WN5S/qRTQVMiBJdxycryIIqjPpVDxd3ET/xuHG2VTVlV MoqcqdXhKNOWGEAgWe8Kc8VpeQSdXGrhgmTdlJoLP2wy1nEOfIo/UZJV+vDqZvnX 8hZe7l0sl6SCUJ7P/VzzSOJreDxGCBVjSJkaL3xE+8C5bX85oLcFsbFS1M2zfgLG RJ0Ha/PMs6CarQzhn77GjqNUY0qYmdlInJcIiQ3bkPlTsBdgDZ9m/RrMzl49ndLI 2ZIWlTQr/gJh+kJUU02XEzRZ+bd0/v760JjIKtUKItMfiNa9OO2chvVuYs6FID+8 oICHmj90E2gz4O6WHsBf9+R9Rtn3KJ1d1d5IHYMispa+q3K6dqVFhLjgT7vVQbFE z2FPghtH3dZPv10BAgMBAAECggGBAL+bR7L85vPiMvcvR62Sq+KRw+n+ZDBPNghL t0MeoAekVum2yZ0YY18wIzgBYIudtR1RckUv+fKJNOYcbluBwCMfmte0bYabMYm4 exTCDMkJrghsWzjsLaKd0C4CXCRtIpzjCwEOCrorL9jTj0sWovutH7dK94IHS2SS zWjcwU+eN2mnkLIaJDRX0SM3f/KYPRRiFV9e3BDGo/4RnkzM+fbs99JzE8uWruPo jEkTbXL+j2BkhVroBm+TVDCj7tBdlUhhfFaBAUjwum2otO2ND4fEUdiV0PyIapP3 UFFEU+8bqGIlWNffDzLbRBiPjma1QX4ktjfsb18TdZu+OTTps2dgiivo6x8kau+I o3alg1RnQQyK+Wn4NRtE8Eknp33aT7HyRbH10/Vko5lnEfwTUyfdOVIGj5Jh5yvY heIDAQgRcvuCllr1ypDZlmd0wkqWC9nZRbLFN2NpLotSSrf69pYv3z4/beffzYsI QnGQmdYhX32+7BLqt+qEb4V+VlkkAQKBwQD4i9OSZYqD1iBXPGUZGioPY3ftPVIb 6kQ94AIgNZ+HLbYzYL4QNimakPtRSrE1VxsDAn+GG1A3ncvJIqw8+tHSKecpIM5G 4FaGzFqwpLnw3XOgHwgXRHcXRwFngf3G464KFHfZ4E6VkHeOxdfNdh+pOQlpLkYS WS4OuvTVJyUNvv2N3+7NELSQkAacdVf2yDIa4o17a7KP69FYxwW3Reco6MDeQU6E tlyXas/upGrle06DfYa02hiiF4tY5bOjCyECgcEAx/7Ye9JO0rA6ozzfFCF8RtPR WyKjypBXrZOmrAOzo1H0H9rB4pR+7NYa+ixN6tsv0dJylQsj7nszipzqms9WIvxA 9hH+k4+UoOKHnNeywNVVNEswfeTaaIXMxGWGx7QNTg58hVZZQgkdgIWJxznr4REq bEmWgEoyDtmN5x+N4p9fjjQkboWyatJ9r7eCoiG1wzAoI9hqqcEOf49B4jCXtHIk bsKOs6jTbZq7aCxMkYDxyMQFyutuq01F9GRWTPXhAoHAQEwb7ZFrJfPs5eRv2vCT 1OtMiQkGBsax5LfglOiKXnQK4Hu0b4kzdhLvkPYbpcrk6ABrcQv70od1wpC/sf7I 7O9+J3ufIWLDv5d6FpxmpdMEKHYep7ZEgLcTu+0684rO6TimUKzgZ3y6EStJSpO2 WRayQo1//xsm+RSQZdv8j/PKsDswEciyjXtU2oDYwrTDkYTuSPFxfh3pSGgkKGdj B4g+7MBESbzLczhklj3ekYM2qnl8saiCGtywZcz2jcVBAoHAWKNUYxyEntBITMzP ueZVZDbA1Pl3SnHKyj1kY1yIo1vRLMURpVBXKLSD5Fj6d5qJiR8SdYgodqvX3hlJ yS8XaA4Q5H55LAE4yE1d+V+H8/sY9kJUzZc+TZDvfiPZJm1gcDXvblEk4iWUE8Ab nlbHekrXWIMM1vMLWJWHVOYhRk2IVkg51VogB0QfPF/C4AS8wDN5ttlV/MJ5oINn mc4bjngAOa60/F9YxX0MjlED5oEVp/to7dSGihmHZZeKwDVBAoHAYVNuPLf2L08u ljOD5YnVfYFRIwfTUfOew7eQnPgfBNbgE0EUDR3ukIQKaZQzt3COA4oieSUd+dK9 XRUJBF6EzUkBCTC22ExtdedEjdn5s6fCX63Ad5k6Olr44cINqgJtuVp3a4RnxENr PdhiIMkqW3rp+/0HdZNHAzDhbKM6C8AVWX4chDEVUOIaRE53+Amfebd/PGQ/7WkT LuAz4IA2Abj0/VXr1txQwhVk3zloLYxyacyyqQHYn+GgWPHdmQw8 -----END RSA PRIVATE KEY----- ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci ================================================ CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_ENABLE_CI_TEST=y CONFIG_EXAMPLE_CONNECT_ETHERNET=n CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_ESP_HTTPS_SERVER_ENABLE=y CONFIG_COMPILER_OPTIMIZATION_SIZE=y ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci.partial_download ================================================ CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD=y CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_ENABLE_CI_TEST=y CONFIG_EXAMPLE_CONNECT_ETHERNET=n CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_ESP_HTTPS_SERVER_ENABLE=y CONFIG_COMPILER_OPTIMIZATION_SIZE=y ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.defaults ================================================ # Default sdkconfig parameters to use the OTA # partition table layout, with a 4MB flash size CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_TWO_OTA=y # Enable ESP HTTPS OTA decryption callback CONFIG_ESP_HTTPS_OTA_DECRYPT_CB=y # Certificate bundle configuration CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="server_certs/ca_cert.pem" ================================================ FILE: esp_encrypted_img/examples/pre_encrypted_ota/server_certs/ca_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= -----END CERTIFICATE----- ================================================ FILE: esp_encrypted_img/idf_component.yml ================================================ version: "2.7.1" description: ESP Encrypted Image Abstraction Layer url: https://github.com/espressif/idf-extra-components/tree/master/esp_encrypted_img dependencies: idf: version: ">=5.0" espressif/esp_secure_cert_mgr: version: ">=2.5.1" rules: - if: "idf_version >= 5.3" ================================================ FILE: esp_encrypted_img/include/esp_encrypted_img.h ================================================ /* * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #if 0 //High level layout for state machine // *INDENT-OFF* @startuml [*] --> READ_MAGIC READ_MAGIC --> READ_MAGIC : READ LEN < 4 READ_MAGIC --> DECODE_MAGIC : READ LEN = 4 DECODE_MAGIC --> READ_GCM : MAGIC VERIFIED DECODE_MAGIC --> ESP_FAIL : MAGIC VERIFICATION FAILED PROCESS_BINARY --> ESP_FAIL : DECRYPTION FAILED READ_GCM --> READ_GCM : READ_LEN < 384 READ_GCM --> DECRYPT_GCM : READ_LEN = 384 DECRYPT_GCM --> ESP_FAIL : DECRYPTION FAILED DECRYPT_GCM --> READ_IV : DECRYPTION SUCCESSFUL READ_IV --> READ_IV : READ LEN < 16 READ_IV --> READ_BIN_SIZE READ_BIN_SIZE --> READ_BIN_SIZE : READ LEN < 5 READ_BIN_SIZE --> READ_AUTH READ_AUTH --> READ_AUTH : READ LEN < 16 READ_AUTH --> PROCESS_BINARY PROCESS_BINARY --> PROCESS_BINARY : READ LEN < BIN_SIZE PROCESS_BINARY --> ESP_OK : READ LEN = BIN_SIZE ESP_OK --> [*] ESP_FAIL --> [*] @enduml // *INDENT-OFF* #endif #ifdef __cplusplus extern "C" { #endif #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) #define DEPRECATED_ATTRIBUTE __attribute__((deprecated)) #else #define DEPRECATED_ATTRIBUTE #endif #define MAGIC_SIZE 4 #define ENC_GCM_KEY_SIZE 384 #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) #define SERVER_ECC_KEY_LEN 64 #define KDF_SALT_SIZE 32 #define RESERVED_SIZE (384 - (SERVER_ECC_KEY_LEN + KDF_SALT_SIZE)) #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #define IV_SIZE 16 #define AUTH_SIZE 16 #define BIN_SIZE_DATA 4 #define RESERVED_HEADER 88 #define ESP_ERR_ENCRYPTED_IMAGE_HMAC_KEY_NOT_FOUND 1 typedef void *esp_decrypt_handle_t; typedef struct { #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) void *ds_data; /*!< Data context for DS */ #else union { const char *rsa_priv_key; /*!< 3072 bit RSA private key in PEM format */ const char *rsa_pub_key DEPRECATED_ATTRIBUTE; /*!< This name is kept for backward compatibility purpose, but it is not accurate (meaning wise) and hence it would be removed in the next major release */ }; union { size_t rsa_priv_key_len; /*!< Length of the buffer pointed to by rsa_priv_key */ size_t rsa_pub_key_len DEPRECATED_ATTRIBUTE; /*!< This name is kept for backward compatibility purpose, but it is not accurate (meaning wise) and hence it would be removed in the next major release */ }; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) int hmac_key_id; /*!< HMAC key ID to be used for HMAC generation */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ } esp_decrypt_cfg_t; #undef DEPRECATED_ATTRIBUTE typedef struct { const char *data_in; /*!< Pointer to data to be decrypted */ size_t data_in_len; /*!< Input data length */ char *data_out; /*!< Pointer to decrypted data */ size_t data_out_len; /*!< Output data length */ } pre_enc_decrypt_arg_t; /** * @brief This function returns esp_decrypt_handle_t handle. * * @param[in] cfg pointer to esp_decrypt_cfg_t structure * * @return * - NULL On failure * - esp_decrypt_handle_t handle */ esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg); /** * @brief This function performs decryption on input data. * * This function must be called only if esp_encrypted_img_decrypt_start() returns successfully. * This function must be called in a loop since input data might not contain whole binary at once. * This function must be called till it return ESP_OK. * * @note args->data_out must be freed after use provided args->data_out_len is greater than 0 * * @param[in] ctx esp_decrypt_handle_t handle * @param[in/out] args pointer to pre_enc_decrypt_arg_t * * @return * - ESP_FAIL On failure * - ESP_ERR_INVALID_ARG Invalid arguments * - ESP_ERR_NOT_FINISHED Decryption is in process * - ESP_OK Success */ esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t ctx, pre_enc_decrypt_arg_t *args); /** * @brief Clean-up decryption process. * * @param[in] ctx esp_decrypt_handle_t handle * * @note This API cleans the decrypt handle and return ESP_FAIL if the complete data has not been decrypted. Verify if complete data * has been decrypted using API `esp_encrypted_img_is_complete_data_received` to prevent an early call to this API. * * @return * - ESP_FAIL On failure * - ESP_ERR_INVALID_ARG Invalid argument * - ESP_OK Success */ esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t ctx); /** * @brief Checks if the complete data has been decrypted. * * @note This API checks if complete data has been supplied to `esp_encrypted_img_decrypt_data`. This can be used to prevent an early * call to `esp_encrypted_img_decrypt_end` which cleans up the decrypt handle. If this API returns true, then call `esp_encrypted_img_decrypt_end`. * If this API returns false, and there is some other error (like network error) due to which decryption process should be terminated, * call `esp_encrypted_img_decrypt_abort` to clean up the handle. * * @param[in] ctx esp_decrypt_handle_t handle * * @return * - true * - false */ bool esp_encrypted_img_is_complete_data_received(esp_decrypt_handle_t ctx); /** * @brief Abort the decryption process * * @param[in] ctx esp_decrypt_handle_t handle * * @return * - ESP_ERR_INVALID_ARG Invalid argument * - ESP_OK Success */ esp_err_t esp_encrypted_img_decrypt_abort(esp_decrypt_handle_t ctx); /** * @brief Get the size of pre encrypted binary image header (`struct pre_enc_bin_header`). The initial header in * the image contains magic, credentials (symmetric key) and few other parameters. This API could be useful * for scenarios where the entire decrypted image length must be computed by the application including the * image header. * * @return * - Header size of pre encrypted image */ uint16_t esp_encrypted_img_get_header_size(void); /** * @brief Export the public key corresponding to the private key. * The application should free the memory pointed by `pub_key` after use. * For RSA, the public key is in DER format and corresponds to the private key passed with `esp_encrypted_img_decrypt_start()`. * For ECIES, the public key is in DER format and is derived from the HMAC key ID passed with `esp_encrypted_img_decrypt_start()`. * * @param ctx esp_decrypt_handle_t handle * @param pub_key Pointer to store the public key * @param pub_key_len Pointer to store the length of the public key * @return esp_err_t Status of the operation */ esp_err_t esp_encrypted_img_export_public_key(esp_decrypt_handle_t ctx, uint8_t **pub_key, size_t *pub_key_len); #ifdef __cplusplus } #endif ================================================ FILE: esp_encrypted_img/private_include/esp_encrypted_img_priv.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "esp_err.h" #include "esp_encrypted_img.h" #include "mbedtls/version.h" #include "sdkconfig.h" #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) #include "psa/crypto.h" #else #include "mbedtls/gcm.h" #endif #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) #include "esp_hmac.h" #endif #ifdef __cplusplus extern "C" { #endif #define GCM_KEY_SIZE 32 #define CACHE_BUF_SIZE 16 typedef enum { ESP_PRE_ENC_IMG_READ_MAGIC, ESP_PRE_ENC_IMG_READ_GCM, ESP_PRE_ENC_IMG_READ_IV, ESP_PRE_ENC_IMG_READ_BINSIZE, ESP_PRE_ENC_IMG_READ_AUTH, ESP_PRE_ENC_IMG_READ_EXTRA_HEADER, ESP_PRE_ENC_DATA_DECODE_STATE, } esp_encrypted_img_state; /** * @brief Internal handle structure for encrypted image decryption */ typedef struct esp_encrypted_img_handle { #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if !defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) char *rsa_pem; size_t rsa_len; #endif #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) hmac_key_id_t hmac_key; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ uint32_t binary_file_len; uint32_t binary_file_read; char gcm_key[GCM_KEY_SIZE]; char iv[IV_SIZE]; char auth_tag[AUTH_SIZE]; esp_encrypted_img_state state; #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_aead_operation_t psa_aead_op; psa_key_id_t psa_gcm_key_id; bool psa_initialized; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) void *ds_data; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ #else mbedtls_gcm_context gcm_ctx; #endif size_t cache_buf_len; char *cache_buf; } esp_encrypted_img_t; typedef struct { char magic[MAGIC_SIZE]; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) char enc_gcm[ENC_GCM_KEY_SIZE]; #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) unsigned char server_ecc_pub_key[SERVER_ECC_KEY_LEN]; unsigned char kdf_salt[KDF_SALT_SIZE]; unsigned char reserved[RESERVED_SIZE]; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ char iv[IV_SIZE]; char bin_size[BIN_SIZE_DATA]; char auth[AUTH_SIZE]; char extra_header[RESERVED_HEADER]; } pre_enc_bin_header; #define HEADER_DATA_SIZE sizeof(pre_enc_bin_header) // Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum static const uint32_t esp_enc_img_magic = 0x0788b6cf; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) #define HMAC_OUTPUT_SIZE 32 #define PBKDF2_ITERATIONS 2048 #define HKDF_INFO_SIZE 16 #define DER_ASN1_OVERHEAD 30 #define SECP256R1_COORD_SIZE 32 #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #ifdef __cplusplus } #endif ================================================ FILE: esp_encrypted_img/private_include/esp_encrypted_img_utilities.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_efuse.h" #include "esp_hmac.h" /** * @brief Check if the HMAC key is burnt in efuse. * * @param hmac_key_id[in] The HMAC key ID to check. * * @return * - true If the HMAC key is burnt. * - false If the HMAC key is not burnt. */ bool esp_encrypted_is_hmac_key_burnt_in_efuse(hmac_key_id_t hmac_key_id); /** * @brief Perform PBKDF2 HMAC-SHA256 key derivation. * * @param hmac_key_id[in] HMAC key ID. * @param salt[in] Pointer to the salt. * @param salt_len[in] Length of the salt. * @param iteration_count[in] Number of iterations for the key derivation. * @param key_length[in] Desired length of the derived key. * @param output[out] Buffer to store the derived key. * * @return * - 0 on success. * - -1 on failure. */ int esp_encrypted_img_pbkdf2_hmac_sha256(hmac_key_id_t hmac_key_id, const unsigned char *salt, size_t salt_len, size_t iteration_count, size_t key_length, unsigned char *output); ================================================ FILE: esp_encrypted_img/project_include.cmake ================================================ set(ESP_IMG_GEN_TOOL_PATH ${CMAKE_CURRENT_LIST_DIR}/tools/esp_enc_img_gen.py) set(ESP_IMG_GEN_SECURE_CERT_DATA_TOOL_PATH ${CMAKE_SOURCE_DIR}/managed_components/espressif__esp_secure_cert_mgr/tools/configure_esp_secure_cert.py) idf_build_get_property(build_dir BUILD_DIR) if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES) set(app_dependency "${build_dir}/.signed_bin_timestamp") else() set(app_dependency "${build_dir}/.bin_timestamp") endif() function(create_esp_enc_img input_file key_file output_file app) cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") idf_build_get_property(python PYTHON) add_custom_command(OUTPUT ${output_file} COMMAND ${python} ${ESP_IMG_GEN_TOOL_PATH} encrypt ${input_file} ${key_file} ${output_file} DEPENDS "${app_dependency}" COMMENT "Generating pre-encrypted binary" VERBATIM ) get_filename_component(name ${output_file} NAME_WE) add_custom_target(enc_bin_target_${name} ALL DEPENDS ${output_file}) add_dependencies(enc_bin_target_${name} gen_project_binary) endfunction() # function(create_esp_enc_img_secure_cert_data target_chip input_key input_key_algo) # cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") # idf_build_get_property(python PYTHON) # separate_arguments(input_key_algo_args UNIX_COMMAND "${input_key_algo}") # add_custom_command(OUTPUT ${input_key} # COMMAND ${python} ${ESP_IMG_GEN_SECURE_CERT_DATA_TOOL_PATH} # --skip_flash # --target_chip ${target_chip} # --private-key ${input_key} # --priv_key_algo ${input_key_algo_args} # -p /dev/cu.usbserial-1230 # This needs to be removed when the esp_secure_cert_mgr tool is updated to not require a port # COMMENT "Generating secure data for ${target_chip}" # VERBATIM # ) # get_filename_component(name ${input_key} NAME_WE) # add_custom_target(enc_secure_cert_data_target_${name} ALL # DEPENDS ${input_key} # ) # add_dependencies(enc_secure_cert_data_target_${name} gen_project_binary) # endfunction() ================================================ FILE: esp_encrypted_img/src/esp_encrypted_img.c ================================================ /* * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "sys/param.h" #include "esp_encrypted_img.h" #include "esp_encrypted_img_priv.h" #include "mbedtls/version.h" #include "mbedtls/pk.h" #include "sdkconfig.h" #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" #endif #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) #include "mbedtls/ecp.h" #include "mbedtls/ecdh.h" #include "mbedtls/hkdf.h" #include "mbedtls/pkcs5.h" #endif #include "esp_random.h" #include "esp_encrypted_img_utilities.h" #if SOC_HMAC_SUPPORTED #include "esp_efuse.h" #include "esp_efuse_chip.h" #endif /* SOC_HMAC_SUPPORTED */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) #if __has_include("psa_crypto_driver_esp_rsa_ds.h") #include "psa_crypto_driver_esp_rsa_ds.h" #endif /* __has_include("psa_crypto_driver_esp_rsa_ds.h") */ #else #if __has_include("rsa_dec_alt.h") #include "rsa_dec_alt.h" #else #error "DS Peripheral is not supported on this version of ESP-IDF" #endif /* __has_include("rsa_dec_alt.h") */ #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #include "esp_random.h" static const char *TAG = "esp_encrypted_img"; /* * GCM Abstraction Layer Implementations */ static esp_err_t gcm_init_and_set_key(esp_encrypted_img_t *handle, const unsigned char *key, size_t key_bits) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, PSA_ALG_GCM); psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(&attributes, key_bits); status = psa_import_key(&attributes, key, key_bits / 8, &handle->psa_gcm_key_id); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_import_key for GCM failed: %d", (int)status); psa_reset_key_attributes(&attributes); return ESP_FAIL; } handle->psa_aead_op = psa_aead_operation_init(); status = psa_aead_decrypt_setup(&handle->psa_aead_op, handle->psa_gcm_key_id, PSA_ALG_GCM); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_aead_decrypt_setup failed: %d", (int)status); psa_aead_abort(&handle->psa_aead_op); psa_destroy_key(handle->psa_gcm_key_id); return ESP_FAIL; } return ESP_OK; #else mbedtls_gcm_init(&handle->gcm_ctx); int ret = mbedtls_gcm_setkey(&handle->gcm_ctx, MBEDTLS_CIPHER_ID_AES, key, key_bits); if (ret != 0) { ESP_LOGE(TAG, "Error: mbedtls_gcm_set_key: -0x%04x", (unsigned int) - ret); return ESP_FAIL; } return ESP_OK; #endif } static esp_err_t gcm_start(esp_encrypted_img_t *handle, const unsigned char *iv, size_t iv_len) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_status_t status = psa_aead_set_nonce(&handle->psa_aead_op, iv, iv_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_aead_set_nonce failed: %d", (int)status); psa_aead_abort(&handle->psa_aead_op); psa_destroy_key(handle->psa_gcm_key_id); return ESP_FAIL; } handle->psa_initialized = true; return ESP_OK; #else int ret; #if (MBEDTLS_VERSION_NUMBER < 0x03000000) ret = mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, iv_len, NULL, 0); #else ret = mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, iv_len); #endif if (ret != 0) { ESP_LOGE(TAG, "Error: mbedtls_gcm_starts: -0x%04x", (unsigned int) - ret); return ESP_FAIL; } return ESP_OK; #endif } static esp_err_t gcm_update(esp_encrypted_img_t *handle, const unsigned char *input, size_t input_len, unsigned char *output, size_t output_size, size_t *output_len) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_status_t status = psa_aead_update(&handle->psa_aead_op, input, input_len, output, output_size, output_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_aead_update failed: %d", (int)status); return ESP_FAIL; } return ESP_OK; #else int ret; #if (MBEDTLS_VERSION_NUMBER < 0x03000000) ret = mbedtls_gcm_update(&handle->gcm_ctx, input_len, input, output); if (output_len) { *output_len = input_len; } #else size_t olen; ret = mbedtls_gcm_update(&handle->gcm_ctx, input, input_len, output, output_size, &olen); if (output_len) { *output_len = olen; } #endif if (ret != 0) { return ESP_FAIL; } return ESP_OK; #endif } static esp_err_t gcm_finish_and_verify(esp_encrypted_img_t *handle, const unsigned char *tag, size_t tag_len) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) size_t output_len = 0; psa_status_t status = psa_aead_verify(&handle->psa_aead_op, NULL, 0, &output_len, tag, tag_len); if (status != PSA_SUCCESS) { if (status == PSA_ERROR_INVALID_SIGNATURE) { ESP_LOGE(TAG, "Invalid Auth Tag"); } else { ESP_LOGE(TAG, "psa_aead_verify failed: %d", (int)status); } return ESP_FAIL; } return ESP_OK; #else unsigned char got_auth[AUTH_SIZE] = {0}; int ret; #if (MBEDTLS_VERSION_NUMBER < 0x03000000) ret = mbedtls_gcm_finish(&handle->gcm_ctx, got_auth, tag_len); #else size_t olen; ret = mbedtls_gcm_finish(&handle->gcm_ctx, NULL, 0, &olen, got_auth, tag_len); #endif if (ret != 0) { ESP_LOGE(TAG, "mbedtls_gcm_finish failed: %d", ret); return ESP_FAIL; } if (memcmp(got_auth, tag, tag_len) != 0) { ESP_LOGE(TAG, "Invalid Auth"); return ESP_FAIL; } return ESP_OK; #endif } static void gcm_cleanup(esp_encrypted_img_t *handle) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) if (handle->psa_initialized) { psa_aead_abort(&handle->psa_aead_op); psa_destroy_key(handle->psa_gcm_key_id); handle->psa_initialized = false; } #else mbedtls_gcm_free(&handle->gcm_ctx); #endif } #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #define RSA_MPI_ASN1_HEADER_SIZE 11 #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) int esp_encrypted_random(void *ctx, unsigned char *buf, size_t len) { (void) ctx; esp_fill_random(buf, len); return 0; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ static int decipher_gcm_key(const char *enc_gcm, esp_encrypted_img_t *handle) { #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) if (handle == NULL || handle->ds_data == NULL) { ESP_LOGE(TAG, "Invalid argument: handle or ds_data is NULL"); return ESP_ERR_INVALID_ARG; } psa_key_id_t rsa_key_id = PSA_KEY_ID_NULL; psa_status_t status; psa_algorithm_t alg = PSA_ALG_RSA_PKCS1V15_CRYPT; esp_rsa_ds_opaque_key_t rsa_ds_opaque_key = {0}; rsa_ds_opaque_key.ds_data_ctx = (esp_ds_data_ctx_t *)handle->ds_data; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_set_key_type(&attributes, PSA_KEY_TYPE_RSA_KEY_PAIR); psa_set_key_bits(&attributes, rsa_ds_opaque_key.ds_data_ctx->rsa_length_bits); psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, alg); psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE); status = psa_import_key(&attributes, (const uint8_t *)&rsa_ds_opaque_key, sizeof(rsa_ds_opaque_key), &rsa_key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_import_key failed: %d", (int)status); return ESP_FAIL; } size_t olen = 0; status = psa_asymmetric_decrypt(rsa_key_id, alg, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, NULL, 0, (unsigned char *)handle->gcm_key, GCM_KEY_SIZE, &olen); psa_destroy_key(rsa_key_id); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_asymmetric_decrypt failed: %d", (int)status); return ESP_FAIL; } #else int ret; mbedtls_pk_context pk; mbedtls_pk_init(&pk); ret = mbedtls_pk_setup_rsa_alt(&pk, NULL, esp_ds_rsa_decrypt, NULL, esp_ds_get_keylen); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_setup_rsa_alt returned -0x%04x\n", (unsigned int) - ret); mbedtls_pk_free(&pk); return ret; } size_t olen = 0; ret = mbedtls_pk_decrypt(&pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE, esp_encrypted_random, NULL); mbedtls_pk_free(&pk); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret); return ret; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ void *tmp_buf = realloc(handle->cache_buf, CACHE_BUF_SIZE); if (!tmp_buf) { ESP_LOGE(TAG, "Failed to reallocate memory for cache buffer"); return ESP_ERR_NO_MEM; } handle->cache_buf = tmp_buf; handle->state = ESP_PRE_ENC_IMG_READ_IV; handle->binary_file_read = 0; handle->cache_buf_len = 0; return ESP_OK; } #else static int decipher_gcm_key(const char *enc_gcm, esp_encrypted_img_t *handle) { int ret = 1; size_t olen = 0; #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_key_id_t rsa_key_id = PSA_KEY_ID_NULL; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; mbedtls_pk_context *pk = calloc(1, sizeof(mbedtls_pk_context)); if (pk == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for mbedtls_pk_context"); return ESP_ERR_NO_MEM; } unsigned char *key_buf = NULL; size_t key_buf_size = MBEDTLS_MPI_MAX_SIZE * 2; ESP_LOGI(TAG, "Reading RSA private key (PSA)"); mbedtls_pk_init(pk); /* Parse RSA key from PEM using mbedtls */ ret = mbedtls_pk_parse_key(pk, (const unsigned char *)handle->rsa_pem, handle->rsa_len, NULL, 0); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_key returned -0x%04x\n", (unsigned int) - ret); goto exit; } /* Export to DER format for PSA import */ key_buf = calloc(1, key_buf_size); if (key_buf == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for key buffer"); ret = ESP_ERR_NO_MEM; goto exit; } ret = mbedtls_pk_write_key_der(pk, key_buf, key_buf_size); if (ret < 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_write_key_der returned -0x%04x\n", (unsigned int) - ret); goto exit; } /* DER is written at the end of the buffer */ size_t key_len = ret; unsigned char *key_start = key_buf + key_buf_size - key_len; /* Import RSA key to PSA */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, PSA_ALG_RSA_PKCS1V15_CRYPT); psa_set_key_type(&attributes, PSA_KEY_TYPE_RSA_KEY_PAIR); status = psa_import_key(&attributes, key_start, key_len, &rsa_key_id); mbedtls_pk_free(pk); free(pk); pk = NULL; if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_import_key failed: %d", (int)status); ret = ESP_FAIL; goto exit; } /* Perform RSA PKCS#1 v1.5 decryption */ status = psa_asymmetric_decrypt(rsa_key_id, PSA_ALG_RSA_PKCS1V15_CRYPT, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, NULL, 0, /* No salt */ (unsigned char *)handle->gcm_key, GCM_KEY_SIZE, &olen); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_asymmetric_decrypt failed: %d", (int)status); ret = ESP_FAIL; goto exit; } ret = 0; void *tmp_buf = realloc(handle->cache_buf, CACHE_BUF_SIZE); if (!tmp_buf) { ESP_LOGE(TAG, "Failed to reallocate memory for cache buffer"); ret = ESP_ERR_NO_MEM; goto exit; } handle->cache_buf = tmp_buf; handle->state = ESP_PRE_ENC_IMG_READ_IV; handle->binary_file_read = 0; handle->cache_buf_len = 0; exit: if (pk) { mbedtls_pk_free(pk); free(pk); } if (rsa_key_id != PSA_KEY_ID_NULL) { psa_destroy_key(rsa_key_id); } if (key_buf) { mbedtls_platform_zeroize(key_buf, key_buf_size); free(key_buf); } if (handle->rsa_pem) { mbedtls_platform_zeroize(handle->rsa_pem, handle->rsa_len); free(handle->rsa_pem); handle->rsa_pem = NULL; } return ret; #else /* !CONFIG_MBEDTLS_VER_4_X_SUPPORT */ mbedtls_pk_context pk; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; const char *pers = "mbedtls_pk_encrypt"; mbedtls_ctr_drbg_init( &ctr_drbg ); mbedtls_entropy_init( &entropy ); mbedtls_pk_init( &pk ); if ((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen(pers))) != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", (unsigned int) - ret); goto exit; } ESP_LOGI(TAG, "Reading RSA private key"); #if (MBEDTLS_VERSION_NUMBER < 0x03000000) if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0)) != 0) { #else if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) { #endif ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x\n", (unsigned int) - ret ); goto exit; } if (( ret = mbedtls_pk_decrypt( &pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE, mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 ) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret ); goto exit; } void *tmp_buf = realloc(handle->cache_buf, CACHE_BUF_SIZE); if (!tmp_buf) { ESP_LOGE(TAG, "Failed to reallocate memory for cache buffer"); ret = ESP_ERR_NO_MEM; goto exit; } handle->cache_buf = tmp_buf; handle->state = ESP_PRE_ENC_IMG_READ_IV; handle->binary_file_read = 0; handle->cache_buf_len = 0; exit: if (handle->rsa_pem) { mbedtls_platform_zeroize(handle->rsa_pem, handle->rsa_len); free(handle->rsa_pem); handle->rsa_pem = NULL; } mbedtls_pk_free( &pk ); mbedtls_entropy_free( &entropy ); mbedtls_ctr_drbg_free( &ctr_drbg ); return (ret); #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ } static esp_err_t esp_encrypted_img_export_rsa_pub_key(const char *rsa_pem, size_t rsa_len, uint8_t **pub_key, size_t *pub_key_len) { int ret = 0; if (rsa_pem == NULL) { ESP_LOGE(TAG, "RSA private key is not set"); return ESP_ERR_INVALID_ARG; } mbedtls_pk_context pk; mbedtls_pk_init(&pk); #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) /* For mbedtls 4.x, use PSA-based key parsing */ ret = mbedtls_pk_parse_key(&pk, (const unsigned char *)rsa_pem, rsa_len, NULL, 0); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_key returned -0x%04x\n", (unsigned int) - ret); goto exit; } /* Export public key in DER SubjectPublicKeyInfo format first */ size_t max_pub_key_size = MBEDTLS_MPI_MAX_SIZE * 2; unsigned char *der_buf = calloc(1, max_pub_key_size); if (der_buf == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for DER buffer"); goto exit; } ret = mbedtls_pk_write_pubkey_der(&pk, der_buf, max_pub_key_size); if (ret < 0) { ESP_LOGE(TAG, "Failed to write public key DER: -0x%04x", (unsigned int) - ret); free(der_buf); goto exit; } /* DER is written at the end of the buffer */ size_t der_len = ret; unsigned char *der_start = der_buf + max_pub_key_size - der_len; /* Parse SubjectPublicKeyInfo to extract raw public key (BIT STRING content) * Structure: SEQUENCE { SEQUENCE { OID, params }, BIT STRING { raw_key } } * We need to skip the outer SEQUENCE and algorithm SEQUENCE to get the BIT STRING */ unsigned char *p = der_start; size_t len; /* Skip outer SEQUENCE tag and length */ if (*p++ != 0x30) { /* SEQUENCE */ ESP_LOGE(TAG, "Invalid DER: expected SEQUENCE"); free(der_buf); goto exit; } /* Skip length (may be 1 or more bytes) */ if (*p & 0x80) { size_t len_bytes = *p++ & 0x7f; p += len_bytes; } else { p++; } /* Skip algorithm SEQUENCE */ if (*p++ != 0x30) { /* SEQUENCE */ ESP_LOGE(TAG, "Invalid DER: expected algorithm SEQUENCE"); free(der_buf); goto exit; } /* Get algorithm sequence length and skip it */ if (*p & 0x80) { size_t len_bytes = *p++ & 0x7f; len = 0; for (size_t i = 0; i < len_bytes; i++) { len = (len << 8) | *p++; } } else { len = *p++; } p += len; /* Skip algorithm sequence content */ /* Now at BIT STRING */ if (*p++ != 0x03) { /* BIT STRING */ ESP_LOGE(TAG, "Invalid DER: expected BIT STRING"); free(der_buf); goto exit; } /* Get BIT STRING length */ if (*p & 0x80) { size_t len_bytes = *p++ & 0x7f; len = 0; for (size_t i = 0; i < len_bytes; i++) { len = (len << 8) | *p++; } } else { len = *p++; } /* Skip unused bits byte (should be 0x00) */ p++; len--; /* p now points to raw public key, len is its length */ *pub_key = calloc(1, len); if (*pub_key == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for public key"); free(der_buf); goto exit; } memcpy(*pub_key, p, len); *pub_key_len = len; free(der_buf); mbedtls_pk_free(&pk); return ESP_OK; exit: if (*pub_key) { free(*pub_key); *pub_key = NULL; *pub_key_len = 0; } mbedtls_pk_free(&pk); return ESP_FAIL; #else /* !CONFIG_MBEDTLS_VER_4_X_SUPPORT */ mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ctr_drbg_init( &ctr_drbg ); mbedtls_entropy_init( &entropy ); #if (MBEDTLS_VERSION_NUMBER < 0x03000000) if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) rsa_pem, rsa_len, NULL, 0)) != 0) { #else if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) rsa_pem, rsa_len, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) { #endif ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_key returned -0x%04x\n", (unsigned int) - ret ); goto exit; } if (mbedtls_pk_get_type(&pk) != MBEDTLS_PK_RSA) { ESP_LOGE(TAG, "Public key is not RSA"); goto exit; } const mbedtls_rsa_context *rsa_ctx = mbedtls_pk_rsa(pk); if (rsa_ctx == NULL) { ESP_LOGE(TAG, "Failed to get RSA context from public key"); goto exit; } size_t max_pub_key_size = MBEDTLS_MPI_MAX_SIZE + RSA_MPI_ASN1_HEADER_SIZE; *pub_key = calloc(1, max_pub_key_size + 1); if (*pub_key == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for public key"); goto exit; } unsigned char *c = *pub_key + max_pub_key_size; ret = mbedtls_pk_write_pubkey(&c, *pub_key, &pk); if (ret < 0) { ESP_LOGE(TAG, "Failed to write public key: -0x%04x", (unsigned int) - ret); goto exit; } if (c - *pub_key < 0) { ESP_LOGE(TAG, "Public key buffer is too small"); goto exit; } // Adjust the length of the public key *pub_key_len = ret; // Move the memory to the start of the buffer // This is necessary because mbedtls_pk_write_pubkey writes the key in reverse order // and we need to adjust the pointer to point to the start of the key. memmove(*pub_key, c, *pub_key_len); // Resize the public key buffer to the actual length unsigned char *temp_pub_key = realloc(*pub_key, *pub_key_len); if (temp_pub_key == NULL) { ESP_LOGE(TAG, "Failed to resize public key buffer"); goto exit; } *pub_key = temp_pub_key; // Free the resources mbedtls_pk_free(&pk); mbedtls_entropy_free(&entropy); mbedtls_ctr_drbg_free(&ctr_drbg); return ESP_OK; exit: if (*pub_key) { free(*pub_key); *pub_key = NULL; *pub_key_len = 0; } mbedtls_entropy_free(&entropy); mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_pk_free(&pk); return ESP_FAIL; #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ } #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) static uint8_t pbkdf2_salt[32] = { 0x0e, 0x21, 0x60, 0x64, 0x2d, 0xae, 0x76, 0xd3, 0x34, 0x48, 0xe4, 0x3d, 0x77, 0x20, 0x12, 0x3d, 0x9f, 0x3b, 0x1e, 0xce, 0xb8, 0x8e, 0x57, 0x3a, 0x4e, 0x8f, 0x7f, 0xb9, 0x4f, 0xf0, 0xc8, 0x69 }; #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) static int mbedtls_esp_random(void *ctx, unsigned char *buf, size_t len) { esp_fill_random(buf, len); return 0; } #else static int psa_hkdf_derive(const uint8_t *ikm, size_t ikm_len, const uint8_t *salt, size_t salt_len, const uint8_t *info, size_t info_len, uint8_t *okm, size_t okm_len) { psa_key_derivation_operation_t operation = PSA_KEY_DERIVATION_OPERATION_INIT; psa_status_t status; status = psa_key_derivation_setup(&operation, PSA_ALG_HKDF(PSA_ALG_SHA_256)); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_key_derivation_setup failed: %d", (int)status); return ESP_FAIL; } /* Input salt */ status = psa_key_derivation_input_bytes(&operation, PSA_KEY_DERIVATION_INPUT_SALT, salt, salt_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_key_derivation_input_bytes (salt) failed: %d", (int)status); goto abort; } /* Input IKM (shared secret) */ status = psa_key_derivation_input_bytes(&operation, PSA_KEY_DERIVATION_INPUT_SECRET, ikm, ikm_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_key_derivation_input_bytes (secret) failed: %d", (int)status); goto abort; } /* Input info */ status = psa_key_derivation_input_bytes(&operation, PSA_KEY_DERIVATION_INPUT_INFO, info, info_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_key_derivation_input_bytes (info) failed: %d", (int)status); goto abort; } /* Output derived key */ status = psa_key_derivation_output_bytes(&operation, okm, okm_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_key_derivation_output_bytes failed: %d", (int)status); goto abort; } psa_key_derivation_abort(&operation); return ESP_OK; abort: psa_key_derivation_abort(&operation); return ESP_FAIL; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ static esp_err_t compute_ecc_key_with_hmac(hmac_key_id_t hmac_key, mbedtls_mpi *ecc_priv_key) { esp_err_t err = ESP_OK; if (ecc_priv_key == NULL) { ESP_LOGE(TAG, "ECC key buffer is NULL"); return ESP_ERR_INVALID_ARG; } int ret = 0; uint8_t hmac_output[HMAC_OUTPUT_SIZE] = {0}; mbedtls_ecp_group grp; mbedtls_ecp_group_init(&grp); mbedtls_mpi_init(ecc_priv_key); ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); if (ret != 0) { goto cleanup; } err = esp_encrypted_img_pbkdf2_hmac_sha256(hmac_key, pbkdf2_salt, sizeof(pbkdf2_salt), PBKDF2_ITERATIONS, HMAC_OUTPUT_SIZE, hmac_output); if (err != 0) { ESP_LOGE(TAG, "Failed to calculate ECC key: [0x%02X] (%s)", err, esp_err_to_name(err)); goto cleanup; } // Step 2: Convert output to scalar mod curve order ret = mbedtls_mpi_read_binary(ecc_priv_key, hmac_output, HMAC_OUTPUT_SIZE); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_mpi_read_binary returned -0x%04x\n", (unsigned int) - ret); goto cleanup; } ret = mbedtls_ecp_check_privkey(&grp, ecc_priv_key); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecp_check_privkey returned -0x%04x\n", (unsigned int) - ret); goto cleanup; } ESP_LOGI(TAG, "ECC key derived successfully"); cleanup: mbedtls_ecp_group_free(&grp); return ret; } static int derive_ota_ecc_device_key(hmac_key_id_t hmac_key, mbedtls_mpi *ecc_priv_key) { // Although we have checked this during the esp_encrypted_img_decrypt_start() call, // we will check again here to ensure that the HMAC key is valid. if (!esp_encrypted_is_hmac_key_burnt_in_efuse(hmac_key)) { ESP_LOGE(TAG, "Could not find HMAC key in configured eFuse block!"); return ESP_ERR_ENCRYPTED_IMAGE_HMAC_KEY_NOT_FOUND; } esp_err_t err = compute_ecc_key_with_hmac(hmac_key, ecc_priv_key); return err; } static mbedtls_ecp_point *get_server_public_point(const char *data, size_t len) { int ret; uint8_t *server_public_key = NULL; mbedtls_ecp_point *server_public_point = NULL; mbedtls_ecp_group grp; mbedtls_ecp_group_init(&grp); if ((ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1)) != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecp_group_load returned -0x%04x\n", (unsigned int) - ret); return NULL; } server_public_point = calloc(1, sizeof(mbedtls_ecp_point)); if (server_public_point == NULL) { ESP_LOGE(TAG, "failed to allocate memory for server public point"); goto cleanup; } mbedtls_ecp_point_init(server_public_point); server_public_key = calloc(1, len + 1); if (server_public_key == NULL) { ESP_LOGE(TAG, "failed to allocate memory for server public key"); mbedtls_ecp_point_free(server_public_point); server_public_point = NULL; goto cleanup; } server_public_key[0] = 0x04; // Uncompressed point memcpy(server_public_key + 1, data, len); ret = mbedtls_ecp_point_read_binary(&grp, server_public_point, (const unsigned char *)server_public_key, len + 1); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecp_point_read_binary returned -0x%04x\n", (unsigned int) - ret); mbedtls_ecp_point_free(server_public_point); free(server_public_key); server_public_key = NULL; return NULL; } ret = mbedtls_ecp_check_pubkey(&grp, server_public_point); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecp_check_pubkey returned -0x%04x\n", (unsigned int) - ret); mbedtls_ecp_point_free(server_public_point); free(server_public_key); server_public_key = NULL; return NULL; } cleanup: mbedtls_ecp_group_free(&grp); if (server_public_key) { memset(server_public_key, 0, len + 1); free(server_public_key); server_public_key = NULL; } return server_public_point; } static unsigned char *get_kdf_salt_from_header(const char *data, size_t len) { unsigned char *kdf_salt = NULL; if (len >= KDF_SALT_SIZE) { kdf_salt = calloc(1, KDF_SALT_SIZE); if (kdf_salt == NULL) { ESP_LOGE(TAG, "failed to allocate memory for kdf_salt"); return NULL; } memcpy(kdf_salt, data, KDF_SALT_SIZE); } return kdf_salt; } static int derive_gcm_key(const char *data, esp_encrypted_img_t *handle) { int ret = 0; uint8_t *derived_key = calloc(1, GCM_KEY_SIZE); if (derived_key == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for derived key"); return ESP_ERR_NO_MEM; } mbedtls_ecp_point *server_public_point = NULL; unsigned char *kdf_salt = NULL; uint8_t shared_secret_bytes[32] = {0}; mbedtls_ecp_group grp; mbedtls_ecp_group_init(&grp); if ((ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1)) != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecp_group_load returned -0x%04x\n", (unsigned int) - ret); goto exit; } server_public_point = get_server_public_point(data, SERVER_ECC_KEY_LEN); if (server_public_point == NULL) { ESP_LOGE(TAG, "Failed to get server public point"); ret = ESP_FAIL; goto exit; } kdf_salt = get_kdf_salt_from_header(data + SERVER_ECC_KEY_LEN, KDF_SALT_SIZE); mbedtls_mpi device_private_mpi; esp_err_t err = derive_ota_ecc_device_key(handle->hmac_key, &device_private_mpi); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to derive ECC device key"); goto exit; } #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) { psa_key_id_t device_key_id = PSA_KEY_ID_NULL; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; uint8_t priv_key_buf[32]; uint8_t server_pub_key_buf[65]; /* 0x04 || X || Y */ size_t shared_secret_len = 0; /* Convert device private key MPI to raw bytes */ ret = mbedtls_mpi_write_binary(&device_private_mpi, priv_key_buf, sizeof(priv_key_buf)); mbedtls_mpi_free(&device_private_mpi); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_mpi_write_binary returned -0x%04x\n", (unsigned int) - ret); goto exit; } /* Import private key to PSA */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE); psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, priv_key_buf, sizeof(priv_key_buf), &device_key_id); mbedtls_platform_zeroize(priv_key_buf, sizeof(priv_key_buf)); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_import_key failed: %d", (int)status); ret = ESP_FAIL; goto exit; } /* Prepare server public key in uncompressed format */ server_pub_key_buf[0] = 0x04; /* Uncompressed point */ mbedtls_mpi_write_binary(&server_public_point->MBEDTLS_PRIVATE(X), server_pub_key_buf + 1, 32); mbedtls_mpi_write_binary(&server_public_point->MBEDTLS_PRIVATE(Y), server_pub_key_buf + 33, 32); /* Perform ECDH using PSA */ status = psa_raw_key_agreement(PSA_ALG_ECDH, device_key_id, server_pub_key_buf, sizeof(server_pub_key_buf), shared_secret_bytes, sizeof(shared_secret_bytes), &shared_secret_len); psa_destroy_key(device_key_id); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_raw_key_agreement failed: %d", (int)status); ret = ESP_FAIL; goto exit; } /* Perform HKDF using PSA */ ret = psa_hkdf_derive(shared_secret_bytes, sizeof(shared_secret_bytes), kdf_salt, KDF_SALT_SIZE, (const uint8_t *)"_esp_enc_img_ecc", HKDF_INFO_SIZE, derived_key, GCM_KEY_SIZE); if (ret != ESP_OK) { ESP_LOGE(TAG, "psa_hkdf_derive failed"); goto exit; } } #else /* !CONFIG_MBEDTLS_VER_4_X_SUPPORT */ { mbedtls_mpi shared_secret; mbedtls_mpi_init(&shared_secret); ret = mbedtls_ecdh_compute_shared(&grp, &shared_secret, server_public_point, &device_private_mpi, mbedtls_esp_random, NULL); mbedtls_mpi_free(&device_private_mpi); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_ecdh_compute_shared returned -0x%04x\n", (unsigned int) - ret); goto exit; } ret = mbedtls_mpi_write_binary(&shared_secret, shared_secret_bytes, sizeof(shared_secret_bytes)); mbedtls_mpi_free(&shared_secret); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_mpi_write_binary returned -0x%04x\n", (unsigned int) - ret); goto exit; } unsigned char *hkdf_info = calloc(1, HKDF_INFO_SIZE); if (hkdf_info == NULL) { ESP_LOGE(TAG, "failed to allocate memory for hkdf_info"); ret = ESP_ERR_NO_MEM; goto exit; } memcpy(hkdf_info, "_esp_enc_img_ecc", HKDF_INFO_SIZE); ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), kdf_salt, KDF_SALT_SIZE, (const unsigned char *)shared_secret_bytes, sizeof(shared_secret_bytes), hkdf_info, HKDF_INFO_SIZE, derived_key, GCM_KEY_SIZE); mbedtls_platform_zeroize(hkdf_info, HKDF_INFO_SIZE); free(hkdf_info); if (ret != 0) { ESP_LOGE(TAG, "failed\n ! mbedtls_hkdf returned -0x%04x\n", (unsigned int) - ret); goto exit; } } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ memcpy(handle->gcm_key, derived_key, GCM_KEY_SIZE); ESP_LOGI(TAG, "GCM key derived successfully"); void *tmp_buf = realloc(handle->cache_buf, CACHE_BUF_SIZE); if (!tmp_buf) { ESP_LOGE(TAG, "Failed to reallocate memory for cache buffer"); ret = ESP_ERR_NO_MEM; goto exit; } handle->cache_buf = tmp_buf; handle->state = ESP_PRE_ENC_IMG_READ_IV; handle->binary_file_read = 0; handle->cache_buf_len = 0; exit: mbedtls_ecp_group_free(&grp); if (server_public_point) { mbedtls_ecp_point_free(server_public_point); free(server_public_point); } mbedtls_platform_zeroize(shared_secret_bytes, sizeof(shared_secret_bytes)); mbedtls_platform_zeroize(derived_key, GCM_KEY_SIZE); free(derived_key); if (kdf_salt) { mbedtls_platform_zeroize(kdf_salt, KDF_SALT_SIZE); free(kdf_salt); } return ret; } #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) static esp_err_t esp_encrypted_img_export_ecies_pub_key(hmac_key_id_t hmac_key, uint8_t **pub_key, size_t *pub_key_len) { esp_err_t err = ESP_FAIL; psa_status_t status; psa_key_id_t key_id = 0; mbedtls_mpi ecc_priv_key; uint8_t priv_key_bytes[32]; uint8_t raw_pub_key[65]; // 1 byte prefix + 32 bytes x + 32 bytes y size_t raw_pub_key_len; mbedtls_mpi_init(&ecc_priv_key); int ret = derive_ota_ecc_device_key(hmac_key, &ecc_priv_key); if (ret != 0) { ESP_LOGE(TAG, "Failed to derive ECC device key: -0x%04x", (unsigned int) - ret); goto exit; } // Convert MPI to bytes ret = mbedtls_mpi_write_binary(&ecc_priv_key, priv_key_bytes, sizeof(priv_key_bytes)); if (ret != 0) { ESP_LOGE(TAG, "Failed to convert private key to bytes: -0x%04x", (unsigned int) - ret); goto exit; } // Import private key to PSA psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT); psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, priv_key_bytes, sizeof(priv_key_bytes), &key_id); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Failed to import ECC key to PSA: %d", (int)status); goto exit; } // Export public key (raw format: 0x04 || X || Y for uncompressed point) status = psa_export_public_key(key_id, raw_pub_key, sizeof(raw_pub_key), &raw_pub_key_len); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Failed to export public key: %d", (int)status); goto exit; } // Convert raw public key to DER SubjectPublicKeyInfo format // Fixed ASN.1 header for secp256r1 EC public key static const uint8_t der_header[] = { 0x30, 0x59, // SEQUENCE, length 89 0x30, 0x13, // SEQUENCE, length 19 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // OID ecPublicKey (1.2.840.10045.2.1) 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, // OID secp256r1 (1.2.840.10045.3.1.7) 0x03, 0x42, 0x00 // BIT STRING, length 66, no unused bits }; size_t der_len = sizeof(der_header) + raw_pub_key_len; *pub_key = calloc(1, der_len); if (*pub_key == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for public key"); err = ESP_ERR_NO_MEM; goto exit; } memcpy(*pub_key, der_header, sizeof(der_header)); memcpy(*pub_key + sizeof(der_header), raw_pub_key, raw_pub_key_len); *pub_key_len = der_len; ESP_LOGI(TAG, "ECC public key derived successfully"); err = ESP_OK; exit: if (key_id != 0) { psa_destroy_key(key_id); } mbedtls_mpi_free(&ecc_priv_key); mbedtls_platform_zeroize(priv_key_bytes, sizeof(priv_key_bytes)); if (err != ESP_OK && *pub_key) { free(*pub_key); *pub_key = NULL; *pub_key_len = 0; } return err; } #else /* !CONFIG_MBEDTLS_VER_4_X_SUPPORT */ static esp_err_t esp_encrypted_img_export_ecies_pub_key(hmac_key_id_t hmac_key, uint8_t **pub_key, size_t *pub_key_len) { esp_err_t err = ESP_FAIL; mbedtls_mpi ecc_priv_key; mbedtls_pk_context pk_ctx; mbedtls_ecp_keypair *ecp_keypair; mbedtls_mpi_init(&ecc_priv_key); mbedtls_pk_init(&pk_ctx); int ret = derive_ota_ecc_device_key(hmac_key, &ecc_priv_key); if (ret != 0) { ESP_LOGE(TAG, "Failed to derive ECC device key: -0x%04x", (unsigned int) - ret); goto exit; } // Setup PK context for ECKEY ret = mbedtls_pk_setup(&pk_ctx, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); if (ret != 0) { ESP_LOGE(TAG, "Failed to setup PK context: -0x%04x", (unsigned int) - ret); goto exit; } ecp_keypair = mbedtls_pk_ec(pk_ctx); if (ecp_keypair == NULL) { ESP_LOGE(TAG, "Failed to get ECP keypair from PK context"); goto exit; } // Load the curve ret = mbedtls_ecp_group_load(&ecp_keypair->MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1); if (ret != 0) { ESP_LOGE(TAG, "Failed to load ECP group: -0x%04x", (unsigned int) - ret); goto exit; } // Set the private key ret = mbedtls_mpi_copy(&ecp_keypair->MBEDTLS_PRIVATE(d), &ecc_priv_key); if (ret != 0) { ESP_LOGE(TAG, "Failed to copy private key: -0x%04x", (unsigned int) - ret); goto exit; } // Compute the public key from private key ret = mbedtls_ecp_keypair_calc_public(ecp_keypair, mbedtls_esp_random, NULL); if (ret != 0) { ESP_LOGE(TAG, "Failed to compute public key: -0x%04x", (unsigned int) - ret); goto exit; } // Public key will be stored in DER format, which includes ASN.1 headers. // For SECP256R1, the maximum DER public key size is: // 30 bytes (ASN.1 overhead) + 2 * 32 bytes (uncompressed coordinates) = 94 bytes size_t max_pubkey_len = DER_ASN1_OVERHEAD + (2 * SECP256R1_COORD_SIZE); *pub_key = calloc(1, max_pubkey_len); if (*pub_key == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for public key"); err = ESP_ERR_NO_MEM; goto exit; } ret = mbedtls_pk_write_pubkey_der(&pk_ctx, *pub_key, max_pubkey_len); if (ret < 0) { ESP_LOGE(TAG, "Failed to write public key DER: -0x%04x", (unsigned int) - ret); goto exit; } *pub_key_len = ret; if (*pub_key_len > max_pubkey_len) { ESP_LOGE(TAG, "Public key length exceeds allocated buffer size"); err = ESP_ERR_INVALID_SIZE; goto exit; } // Move the memory to the start of the buffer // mbedtls_pk_write_pubkey_der writes the key in reverse order, so we need to adjust the pointer // to point to the start of the key. memmove(*pub_key, *pub_key + (max_pubkey_len - ret), ret); // Resize buffer to actual size unsigned char *temp_pub_key = realloc(*pub_key, *pub_key_len); if (temp_pub_key == NULL) { ESP_LOGE(TAG, "Failed to resize public key buffer"); err = ESP_ERR_NO_MEM; goto exit; } *pub_key = temp_pub_key; ESP_LOGI(TAG, "ECC public key derived successfully"); err = ESP_OK; exit: mbedtls_pk_free(&pk_ctx); mbedtls_mpi_free(&ecc_priv_key); if (err != ESP_OK && *pub_key) { free(*pub_key); *pub_key = NULL; *pub_key_len = 0; } return err; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ esp_err_t esp_encrypted_img_export_public_key(esp_decrypt_handle_t ctx, uint8_t **pub_key, size_t *pub_key_len) { if (ctx == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_export_public_key : Invalid argument"); return ESP_ERR_INVALID_ARG; } if (pub_key == NULL || pub_key_len == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_export_public_key : Invalid argument"); return ESP_ERR_INVALID_ARG; } esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if !defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) // In case of RSA, we return the public key corresponding to the private key // passed with esp_encrypted_img_decrypt_start() return esp_encrypted_img_export_rsa_pub_key(handle->rsa_pem, handle->rsa_len, pub_key, pub_key_len); #else // In case of RSA with DS, the private key is stored securely in the DS context, // and we cannot export the public key directly. // The public key is derived from the DS context, so we return an error. (void)handle; ESP_LOGE(TAG, "Public key export is not supported for RSA with DS"); return ESP_ERR_NOT_SUPPORTED; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) // In case of ECIES, we return the public key corresponding to the private key // derived from the HMAC key ID passed with esp_encrypted_img_decrypt_start() return esp_encrypted_img_export_ecies_pub_key(handle->hmac_key, pub_key, pub_key_len); #else ESP_LOGE(TAG, "No public key available for the current encryption scheme"); return ESP_ERR_NOT_FOUND; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ } esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg) { if (cfg == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); return NULL; } ESP_LOGI(TAG, "Initializing Decryption Handle"); esp_encrypted_img_t *handle = calloc(1, sizeof(esp_encrypted_img_t)); if (!handle) { ESP_LOGE(TAG, "Couldn't allocate memory to handle"); goto failure; } #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) if (cfg->ds_data == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); goto failure; } #if CONFIG_MBEDTLS_VER_4_X_SUPPORT handle->ds_data = cfg->ds_data; #else esp_err_t err = esp_ds_init_data_ctx(cfg->ds_data); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize DS context, err: %2x", err); goto failure; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ #else if (cfg->rsa_priv_key == NULL || cfg->rsa_priv_key_len == 0) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); goto failure; } handle->rsa_pem = calloc(1, cfg->rsa_priv_key_len); if (!handle->rsa_pem) { ESP_LOGE(TAG, "Couldn't allocate memory to handle->rsa_pem"); goto failure; } memcpy(handle->rsa_pem, cfg->rsa_priv_key, cfg->rsa_priv_key_len); handle->rsa_len = cfg->rsa_priv_key_len; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) if (cfg->hmac_key_id < 0 || cfg->hmac_key_id >= HMAC_KEY_MAX) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); goto failure; } // Check if the HMAC key is burnt in eFuse if (!esp_encrypted_is_hmac_key_burnt_in_efuse(cfg->hmac_key_id)) { ESP_LOGE(TAG, "Could not find HMAC key in configured eFuse block!"); goto failure; } handle->hmac_key = cfg->hmac_key_id; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ handle->cache_buf = calloc(1, ENC_GCM_KEY_SIZE); if (!handle->cache_buf) { ESP_LOGE(TAG, "Couldn't allocate memory to handle->cache_buf"); goto failure; } #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_status_t status = psa_crypto_init(); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_crypto_init failed: %d", (int)status); goto failure; } #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ handle->state = ESP_PRE_ENC_IMG_READ_MAGIC; esp_decrypt_handle_t ctx = (esp_decrypt_handle_t)handle; return ctx; failure: if (handle) { #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && !defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) free(handle->rsa_pem); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ if (handle->cache_buf) { free(handle->cache_buf); handle->cache_buf = NULL; } free(handle); } return NULL; } static esp_err_t process_bin(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int curr_index) { size_t data_len = args->data_in_len; size_t data_out_size = args->data_out_len; size_t output_len = 0; handle->binary_file_read += data_len - curr_index; int dec_len = 0; if (handle->binary_file_read != handle->binary_file_len) { size_t copy_len = 0; if ((handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % CACHE_BUF_SIZE > 0) { data_out_size = (handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % CACHE_BUF_SIZE; args->data_out = realloc(args->data_out, data_out_size); if (!args->data_out) { return ESP_ERR_NO_MEM; } } if (handle->cache_buf_len != 0) { copy_len = MIN(CACHE_BUF_SIZE - handle->cache_buf_len, data_len - curr_index); memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); handle->cache_buf_len += copy_len; if (handle->cache_buf_len != CACHE_BUF_SIZE) { args->data_out_len = 0; return ESP_ERR_NOT_FINISHED; } if (gcm_update(handle, (const unsigned char *)handle->cache_buf, CACHE_BUF_SIZE, (unsigned char *)args->data_out, data_out_size, &output_len) != ESP_OK) { return ESP_FAIL; } dec_len = CACHE_BUF_SIZE; } handle->cache_buf_len = (data_len - curr_index - copy_len) % CACHE_BUF_SIZE; if (handle->cache_buf_len != 0) { data_len -= handle->cache_buf_len; memcpy(handle->cache_buf, args->data_in + (data_len), handle->cache_buf_len); } if (data_len - copy_len - curr_index > 0) { if (gcm_update(handle, (const unsigned char *)args->data_in + curr_index + copy_len, data_len - copy_len - curr_index, (unsigned char *)args->data_out + dec_len, data_out_size - dec_len, &output_len) != ESP_OK) { return ESP_FAIL; } } args->data_out_len = dec_len + data_len - curr_index - copy_len; return ESP_ERR_NOT_FINISHED; } data_out_size = handle->cache_buf_len + data_len - curr_index; /* Handle zero-size allocation edge case to avoid undefined behavior */ if (data_out_size == 0) { if (args->data_out) { free(args->data_out); args->data_out = NULL; } args->data_out_len = 0; return ESP_OK; } /* Use temporary pointer to prevent memory leak if realloc fails */ void *temp = realloc(args->data_out, data_out_size); if (!temp) { /* Original pointer remains valid, caller should free it */ return ESP_ERR_NO_MEM; } args->data_out = temp; size_t copy_len = 0; copy_len = MIN(CACHE_BUF_SIZE - handle->cache_buf_len, data_len - curr_index); memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); handle->cache_buf_len += copy_len; if (gcm_update(handle, (const unsigned char *)handle->cache_buf, handle->cache_buf_len, (unsigned char *)args->data_out, data_out_size, &output_len) != ESP_OK) { return ESP_FAIL; } if (data_len - curr_index - copy_len > 0) { if (gcm_update(handle, (const unsigned char *)(args->data_in + curr_index + copy_len), data_len - curr_index - copy_len, (unsigned char *)(args->data_out + CACHE_BUF_SIZE), data_out_size - CACHE_BUF_SIZE, &output_len) != ESP_OK) { return ESP_FAIL; } } args->data_out_len = handle->cache_buf_len + data_len - copy_len - curr_index; handle->cache_buf_len = 0; return ESP_OK; } static void read_and_cache_data(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int *curr_index, int data_size) { const int data_left = data_size - handle->binary_file_read; const int data_recv = args->data_in_len - *curr_index; if (handle->state == ESP_PRE_ENC_IMG_READ_IV) { memcpy(handle->iv + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); } else if (handle->state == ESP_PRE_ENC_IMG_READ_AUTH) { memcpy(handle->auth_tag + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); } else { memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); } handle->cache_buf_len += MIN(data_recv, data_left); int temp = *curr_index; *curr_index += MIN(data_recv, data_left); handle->binary_file_read += MIN(args->data_in_len - temp, data_left); } static esp_err_t process_gcm_key(esp_encrypted_img_t *handle, const char *data_in, size_t data_in_len) { if (data_in_len < ENC_GCM_KEY_SIZE) { ESP_LOGE(TAG, "GCM key size is less than expected"); return ESP_FAIL; } #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) if (decipher_gcm_key(data_in, handle) != 0) { ESP_LOGE(TAG, "Unable to decipher GCM key"); return ESP_FAIL; } #elif defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) if (derive_gcm_key(data_in, handle) != ESP_OK) { ESP_LOGE(TAG, "Failed to derive GCM key"); return ESP_FAIL; } #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ return ESP_OK; } esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t ctx, pre_enc_decrypt_arg_t *args) { if (ctx == NULL || args == NULL || args->data_in == NULL) { return ESP_ERR_INVALID_ARG; } esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; if (handle == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); return ESP_ERR_INVALID_ARG; } esp_err_t err; int curr_index = 0; switch (handle->state) { case ESP_PRE_ENC_IMG_READ_MAGIC: if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= MAGIC_SIZE) { uint32_t recv_magic = *(uint32_t *)args->data_in; if (recv_magic != esp_enc_img_magic) { ESP_LOGE(TAG, "Magic Verification failed"); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && !defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) free(handle->rsa_pem); handle->rsa_pem = NULL; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ return ESP_FAIL; } curr_index += MAGIC_SIZE; } else { read_and_cache_data(handle, args, &curr_index, MAGIC_SIZE); if (handle->binary_file_read == MAGIC_SIZE) { uint32_t recv_magic = *(uint32_t *)handle->cache_buf; if (recv_magic != esp_enc_img_magic) { ESP_LOGE(TAG, "Magic Verification failed"); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && !defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) free(handle->rsa_pem); handle->rsa_pem = NULL; #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ return ESP_FAIL; } handle->binary_file_read = 0; handle->cache_buf_len = 0; } else { return ESP_ERR_NOT_FINISHED; } } ESP_LOGI(TAG, "Magic Verified"); handle->state = ESP_PRE_ENC_IMG_READ_GCM; /* falls through */ case ESP_PRE_ENC_IMG_READ_GCM: if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= ENC_GCM_KEY_SIZE) { if (process_gcm_key(handle, args->data_in + curr_index, ENC_GCM_KEY_SIZE) != ESP_OK) { ESP_LOGE(TAG, "Failed to process GCM key"); handle->cache_buf_len = 0; return ESP_FAIL; } curr_index += ENC_GCM_KEY_SIZE; } else { read_and_cache_data(handle, args, &curr_index, ENC_GCM_KEY_SIZE); if (handle->cache_buf_len == ENC_GCM_KEY_SIZE) { if (process_gcm_key(handle, handle->cache_buf, ENC_GCM_KEY_SIZE) != ESP_OK) { ESP_LOGE(TAG, "Failed to process GCM key"); handle->cache_buf_len = 0; return ESP_FAIL; } } else { return ESP_ERR_NOT_FINISHED; } } /* falls through */ case ESP_PRE_ENC_IMG_READ_IV: if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= IV_SIZE) { memcpy(handle->iv, args->data_in + curr_index, IV_SIZE); handle->binary_file_read = IV_SIZE; curr_index += IV_SIZE; } else { read_and_cache_data(handle, args, &curr_index, IV_SIZE); } if (handle->binary_file_read == IV_SIZE) { handle->state = ESP_PRE_ENC_IMG_READ_BINSIZE; handle->binary_file_read = 0; handle->cache_buf_len = 0; /* Initialize GCM with key and IV using abstraction layer */ if (gcm_init_and_set_key(handle, (const unsigned char *)handle->gcm_key, GCM_KEY_SIZE * 8) != ESP_OK) { return ESP_FAIL; } if (gcm_start(handle, (const unsigned char *)handle->iv, IV_SIZE) != ESP_OK) { return ESP_FAIL; } } else { return ESP_ERR_NOT_FINISHED; } /* falls through */ case ESP_PRE_ENC_IMG_READ_BINSIZE: if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= BIN_SIZE_DATA) { handle->binary_file_len = *(uint32_t *)(args->data_in + curr_index); curr_index += BIN_SIZE_DATA; } else { read_and_cache_data(handle, args, &curr_index, BIN_SIZE_DATA); if (handle->binary_file_read == BIN_SIZE_DATA) { handle->binary_file_len = *(uint32_t *)handle->cache_buf; } else { return ESP_ERR_NOT_FINISHED; } } handle->state = ESP_PRE_ENC_IMG_READ_AUTH; handle->binary_file_read = 0; handle->cache_buf_len = 0; /* falls through */ case ESP_PRE_ENC_IMG_READ_AUTH: if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= AUTH_SIZE) { memcpy(handle->auth_tag, args->data_in + curr_index, AUTH_SIZE); handle->binary_file_read = AUTH_SIZE; curr_index += AUTH_SIZE; } else { read_and_cache_data(handle, args, &curr_index, AUTH_SIZE); } if (handle->binary_file_read == AUTH_SIZE) { handle->state = ESP_PRE_ENC_IMG_READ_EXTRA_HEADER; handle->binary_file_read = 0; handle->cache_buf_len = 0; } else { return ESP_ERR_NOT_FINISHED; } /* falls through */ case ESP_PRE_ENC_IMG_READ_EXTRA_HEADER: { int temp = curr_index; curr_index += MIN(args->data_in_len - curr_index, RESERVED_HEADER - handle->binary_file_read); handle->binary_file_read += MIN(args->data_in_len - temp, RESERVED_HEADER - handle->binary_file_read); if (handle->binary_file_read == RESERVED_HEADER) { handle->state = ESP_PRE_ENC_DATA_DECODE_STATE; handle->binary_file_read = 0; handle->cache_buf_len = 0; } else { return ESP_ERR_NOT_FINISHED; } } /* falls through */ case ESP_PRE_ENC_DATA_DECODE_STATE: err = process_bin(handle, args, curr_index); return err; } return ESP_OK; } esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t ctx) { if (ctx == NULL) { return ESP_ERR_INVALID_ARG; } esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; esp_err_t err = ESP_OK; if (handle == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); return ESP_ERR_INVALID_ARG; } if (handle->state == ESP_PRE_ENC_DATA_DECODE_STATE) { if (handle->cache_buf_len != 0 || handle->binary_file_read != handle->binary_file_len) { ESP_LOGE(TAG, "Invalid operation"); err = ESP_FAIL; goto exit; } /* Verify authentication tag using abstraction layer */ if (gcm_finish_and_verify(handle, (const unsigned char *)handle->auth_tag, AUTH_SIZE) != ESP_OK) { err = ESP_FAIL; goto exit; } } else { // If the state is not ESP_PRE_ENC_DATA_DECODE_STATE, it means that the // decryption process was not completed successfully. ESP_LOGE(TAG, "Decryption process not completed successfully"); err = ESP_FAIL; goto exit; } err = ESP_OK; exit: gcm_cleanup(handle); free(handle->cache_buf); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) esp_ds_deinit_data_ctx(); #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ #else free(handle->rsa_pem); handle->rsa_pem = NULL; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ free(handle); return err; } bool esp_encrypted_img_is_complete_data_received(esp_decrypt_handle_t ctx) { esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; return (handle != NULL && handle->binary_file_len == handle->binary_file_read); } esp_err_t esp_encrypted_img_decrypt_abort(esp_decrypt_handle_t ctx) { esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; if (handle == NULL) { ESP_LOGE(TAG, "esp_encrypted_img_decrypt_abort: Invalid argument"); return ESP_ERR_INVALID_ARG; } gcm_cleanup(handle); free(handle->cache_buf); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) #if !defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) esp_ds_deinit_data_ctx(); #endif /* CONFIG_MBEDTLS_VER_4_X_SUPPORT */ #else free(handle->rsa_pem); handle->rsa_pem = NULL; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA */ free(handle); return ESP_OK; } uint16_t esp_encrypted_img_get_header_size(void) { return HEADER_DATA_SIZE; } ================================================ FILE: esp_encrypted_img/src/esp_encrypted_img_utilities.c ================================================ /* * SPDX-FileCopyrightText: 2025-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "esp_encrypted_img_utilities.h" #define SHA256_MD_SIZE 32 static const char *TAG = "esp_efuse_utilities"; static esp_efuse_block_t convert_key_type(hmac_key_id_t key_id) { return (esp_efuse_block_t)(EFUSE_BLK_KEY0 + (esp_efuse_block_t) key_id); } bool esp_encrypted_is_hmac_key_burnt_in_efuse(hmac_key_id_t hmac_key_id) { bool ret = false; esp_efuse_block_t hmac_key_blk = convert_key_type(hmac_key_id); esp_efuse_purpose_t hmac_efuse_blk_purpose = esp_efuse_get_key_purpose(hmac_key_blk); if (hmac_efuse_blk_purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_UP) { ret = true; } return ret; } int esp_encrypted_img_pbkdf2_hmac_sha256(hmac_key_id_t hmac_key_id, const unsigned char *salt, size_t salt_len, size_t iteration_count, size_t key_length, unsigned char *output) { int ret = -1; int j; unsigned int i; unsigned char md1[SHA256_MD_SIZE] = {0}; unsigned char work[SHA256_MD_SIZE] = {0}; // Considering that we only have SHA256, fixing the MD_SIZE to 32 bytes const size_t MD_SIZE = SHA256_MD_SIZE; size_t use_len; unsigned char *out_p = output; unsigned char counter[4] = {0}; counter[3] = 1; esp_err_t esp_ret = ESP_FAIL; uint8_t *hmac_input = (uint8_t *) calloc(1, salt_len + sizeof(counter) + 1); if (hmac_input == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for hmac input"); return -1; } while (key_length) { // U1 ends up in work size_t hmac_input_len = 0; memcpy(hmac_input, salt, salt_len); hmac_input_len = hmac_input_len + salt_len; memcpy(hmac_input + salt_len, counter, sizeof(counter)); hmac_input_len = hmac_input_len + sizeof(counter); esp_ret = esp_hmac_calculate(hmac_key_id, hmac_input, hmac_input_len, work); if (esp_ret != ESP_OK) { ESP_LOGE(TAG, "Could not calculate the HMAC value, returned %04X", esp_ret); ret = -1; goto cleanup; } memcpy(md1, work, MD_SIZE); for (i = 1; i < iteration_count; i++) { // U2 ends up in md1 esp_ret = esp_hmac_calculate(hmac_key_id, md1, MD_SIZE, md1); if (esp_ret != ESP_OK) { ESP_LOGE(TAG, "Could not calculate the HMAC value, returned %04X", esp_ret); ret = -1; goto cleanup; } // U1 xor U2 for (j = 0; j < MD_SIZE; j++) { work[j] ^= md1[j]; } } use_len = (key_length < MD_SIZE) ? key_length : MD_SIZE; memcpy(out_p, work, use_len); key_length -= (uint32_t) use_len; out_p += use_len; for (i = 4; i > 0; i--) { if (++counter[i - 1] != 0) { break; } } } //Success ret = 0; cleanup: /* Zeroise buffers to clear sensitive data from memory. */ free(hmac_input); memset(work, 0, SHA256_MD_SIZE); memset(md1, 0, SHA256_MD_SIZE); return ret; } ================================================ FILE: esp_encrypted_img/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_encrypted_img_test) if (CONFIG_PRE_ENCRYPTED_RSA_USE_DS) set(partition esp_secure_cert) idf_build_get_property(project_dir PROJECT_DIR) set(image_file ${project_dir}/esp_secure_cert_data/${partition}.bin) partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") esptool_py_flash_target_image(flash "${partition}" "${offset}" "${image_file}") endif() ================================================ FILE: esp_encrypted_img/test_apps/main/CMakeLists.txt ================================================ if (CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) set(EMBED_FILES "image_ecc.bin") else() set(EMBED_FILES "image.bin") endif() idf_component_register(SRCS "esp_encrypted_img_test.c" "test.c" "test_mocks.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity esp_encrypted_img efuse mbedtls EMBED_TXTFILES certs/test_rsa_private_key.pem EMBED_FILES "${EMBED_FILES}" WHOLE_ARCHIVE ) if (CONFIG_PRE_ENCRYPTED_RSA_USE_DS) target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_efuse_get_key_purpose") endif() ================================================ FILE: esp_encrypted_img/test_apps/main/certs/test_rsa_private_key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIG4wIBAAKCAYEA9eoohqolow2z6JsuBvqLLP+jfA/ha8t03skR0F4cKMXNFoc2 QbqdYjRBaFOrkoUD/KC2TzvCjU3OZ3wuGDDjFWURsezUeYCH+r4PcF/qE6vXW1kV ueE5jImTtxSsnt1HLFAGiUnILUJ7xi3cd86Y8mF0VH2wfmDKo8ESBRbev0eChCJA xdynuuyo0m+IN9r7cNStCKz3jglipjnI5+OxtJgw/0fMaCfjtn7KIFktGEeXqJ1r uRBALN+i70zZjjDtJj37FL5t4LCgrkImwLpBALVYFXy1wXhMXq7h5cU2Ec6bRVBl 6GH1xx2lABQ1LVFwrilgklLjY5UTdx8GHE/veR4Sla3dYyE5MKLJ3Kmc7NxlPlWn WTmUKAWYIJiRIwBHSBntUgGyBYBqzfBSYFWL3gmTmvV/JQ41laX7qykdabvgdGSR LJF6wZMdpJpvafYDBRwV7psvhPkHEXBUSe2dtfWKStiUPT7gcZE1ICMxT26Qlaf9 T2GvYwyq4WBkEntzAgMBAAECggGAJKYUChW7bDRzlnvh/SpDqZ4jmC6psq3sqfMf U4Vi/vSTnwLhpCQSpnsRMGIf1MM8F98/rElEslhhJW0NVY+bmCmq3HBmLgFowoam uGGi+fGHM9bv9PbK49XxDLzpCPgDTmhSwQ0c5xncZmmZTMWeZ6j8dEcTEZKNQKBa diW1Zp5apiSQsKw01xfEBTCYBXL+PA+GBh/4+NMPP6Sm+2AksLxpuPHTVcZ0GlOE /hMsNE0fHgLv9fGlDsr5dl5modlKg7fnBejEAhnCPQadVdP2DiO6sXJbSGIxRc0c WRT2nnmbN9UOAxKgO4Lz0ixgcPqBAD5ZDx4p5pVnakH1lB/Gh4VH4nAzxj8sUd0c QcTJ06oHA8OVL/M01IRn4JUMeWdnO+AcuV45uszAx805ZJGRVeQMw/oGzjtYGjuk jogdN279LcntDempzafBL8knD7GA/t2eCYf4Glwy7ZL9XW7b6w+J9pPD+4gduVCv KcvdJxw4SM4q0HJo8YqCDfzQiOKZAoHBAP7JVMrkALdg2AovnRYgzHiLF56JMLip I3l2jjz1fbi9dl+SRSD2LEhwuxy4/XWv5cu7O6dZJ4vG5RpjmFsCpLBRkuRp8qcx V15T9M+2wFZX1kYCSgU5nljM+Szchs75k+3rKbRbfMQhreAxxTVV3T7i6aTq9nuR PI26o8lNTDz6DCfGEkMM2Fg+ydTLGu+o2WIoSWO/o5xc1/l/aBbvbdZ5cRiRUbct CiAeIO75hGHZJBgGaAqJI+p9g6tQbFXKbQKBwQD3FgJsj86C7qNSj5sVvbUqNI6Y G0MfDLYlI01JAMtdiwgvafF91LNX2j/bHvglo4j6TI9zw2y4L/J0E3mMN0jCJXKE DK91vwKwU9yMzacvVH8XgC+ntN9NPG/048Y4j4D3zgfoiNl2/AoazOjpL0iE2gGw Cayx7JG4KrM41ZybpjafGfp/+ZgTeYZHiBEsNHQ/rSMw7bGkt2jKC4cywEtYCsM/ mbr0QcUWH74Tx6YWdm6xGODEERjnu7IdbYADsV8CgcEAl5qMzb0lf/gsFMOIISab BA8fmsHfL8HUze1xbWxVxptV2EBcyeQxLVmGvOyGRITJo5RhRo6SLWXH5Q/mFCFa hV/EnA0+yaVea05hmUcQ40+YvEeYa8uBIS22Bq+ht35iO2t2gU7+ymWP5Js40Seq YkT66Zq114jwExU/aASKnK3clb4SF7uI79lMl0XTXU+HKhT2tlfNrri/+kGJWjxV iwzv8sJlcS1nnPzQc+Icl2xxQapuNfasXFcbBdDw5YtxAoHAH8Zo0WU8/YGK51co bodTAPZ5T/5Rh3CvC9+aVMURYho7Fz3cnH36AlZC1/8Hkm+Rcf7eg9ih5p3j5CGN BAcoCC+gpnKrLc0+n0ZpmoHn+iI3peIKPtr3zIr1Kt0P5L4vq66HPdQ7gx2ufvvT CAnYnZ0bknPsDYWKx9BV8/0kgq/BXnyMxmBmujpqllBdRP4J5RZy7BvlOHWNuE37 OP+ZsNzRdyBh9n9uxQWYABswtLrOSWAVp6E7PrHYmgg26kKpAoHABDv9fxlBrrCh VGCRevH4ojHOoSvF0FqDzg0ixttPBxOYlKB6ggf+vOBjZVkE5ahON7IvTBvfiPAY /H9J3uV6OGufzZURuXNdad1NFbcQvfHWvIgGhBozZe5b1seImG21PXDWIb2uZotw Fg7eaXVYZDeA9jMA9roBbDnwypLAXO4Ve6/J9VXZmOBeXV9rbb0RuWEXgQ9FI1fT CG3od/wm0D0sbZsAoNMD/fVOYRhbxQESpzynsgoxlWfzv2+t3Wjn -----END RSA PRIVATE KEY----- ================================================ FILE: esp_encrypted_img/test_apps/main/esp_encrypted_img_test.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "soc/soc_caps.h" #include "unity_test_utils_memory.h" #include "soc/soc_caps.h" #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) #include "psa/crypto.h" #else #include "mbedtls/aes.h" #endif void setUp(void) { #if SOC_AES_SUPPORTED // Execute AES operation to allocate AES interrupt // allocation memory which is considered as leak otherwise const uint8_t plaintext[16] = {0}; uint8_t ciphertext[16]; const uint8_t key[16] = { 0 }; #if defined(CONFIG_MBEDTLS_VER_4_X_SUPPORT) psa_status_t status = psa_crypto_init(); TEST_ASSERT_EQUAL(PSA_SUCCESS, status); psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(&attributes, 128); psa_key_id_t key_id; status = psa_import_key(&attributes, key, sizeof(key), &key_id); TEST_ASSERT_EQUAL(PSA_SUCCESS, status); size_t output_length; status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, plaintext, sizeof(plaintext), ciphertext, sizeof(ciphertext), &output_length); TEST_ASSERT_EQUAL(PSA_SUCCESS, status); psa_destroy_key(key_id); /* Warm up AES-GCM with a large buffer to trigger one-time DMA/interrupt * allocations that occur when psa_aead_update processes large inputs */ const uint8_t gcm_key[32] = {0}; const uint8_t gcm_nonce[12] = {0}; psa_key_attributes_t gcm_attr = PSA_KEY_ATTRIBUTES_INIT; psa_set_key_type(&gcm_attr, PSA_KEY_TYPE_AES); psa_set_key_bits(&gcm_attr, 256); psa_set_key_usage_flags(&gcm_attr, PSA_KEY_USAGE_ENCRYPT); psa_set_key_algorithm(&gcm_attr, PSA_ALG_GCM); psa_key_id_t gcm_key_id; status = psa_import_key(&gcm_attr, gcm_key, sizeof(gcm_key), &gcm_key_id); if (status == PSA_SUCCESS) { psa_aead_operation_t aead_op = psa_aead_operation_init(); status = psa_aead_encrypt_setup(&aead_op, gcm_key_id, PSA_ALG_GCM); if (status == PSA_SUCCESS) { psa_aead_set_nonce(&aead_op, gcm_nonce, sizeof(gcm_nonce)); /* Use a large stack buffer to trigger DMA-mode AES-GCM */ uint8_t *gcm_buf = calloc(1, 4096); if (gcm_buf) { size_t gcm_olen; psa_aead_update(&aead_op, gcm_buf, 4096, gcm_buf, 4096, &gcm_olen); free(gcm_buf); } } psa_aead_abort(&aead_op); psa_destroy_key(gcm_key_id); } #else mbedtls_aes_context ctx; mbedtls_aes_init(&ctx); mbedtls_aes_setkey_enc(&ctx, key, 128); mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, plaintext, ciphertext); mbedtls_aes_free(&ctx); #endif #endif // SOC_AES_SUPPORTED unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(200); } void app_main(void) { printf("Running esp_encrypted_img component tests\n"); unity_run_menu(); } ================================================ FILE: esp_encrypted_img/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_encrypted_img: version: "*" override_path: "../.." ================================================ FILE: esp_encrypted_img/test_apps/main/test.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include "unity.h" #if __has_include("esp_random.h") #include "esp_random.h" #else #include "esp_system.h" #endif #include "esp_encrypted_img.h" #include "sdkconfig.h" #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) || \ (defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS)) #include "test_mocks.h" #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #include #ifdef CONFIG_HEAP_TRACING #include #define NUM_RECORDS 100 static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM #endif extern const uint8_t rsa_private_pem_start[] asm("_binary_test_rsa_private_key_pem_start"); extern const uint8_t rsa_private_pem_end[] asm("_binary_test_rsa_private_key_pem_end"); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) extern const uint8_t bin_start[] asm("_binary_image_ecc_bin_start"); extern const uint8_t bin_end[] asm("_binary_image_ecc_bin_end"); #else extern const uint8_t bin_start[] asm("_binary_image_bin_start"); extern const uint8_t bin_end[] asm("_binary_image_bin_end"); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) static const unsigned char encrypted_gcm_key_bin[] = { 0x42, 0xbf, 0x3a, 0xc5, 0x22, 0xd3, 0x1b, 0xca, 0x8e, 0x75, 0xa7, 0x42, 0x13, 0x1d, 0xcc, 0xcd, 0x1a, 0x1f, 0xcc, 0x37, 0x80, 0xbe, 0x05, 0x86, 0xd9, 0x83, 0x47, 0x95, 0x3f, 0x1e, 0x8d, 0xa1, 0xed, 0xd7, 0xcd, 0xfe, 0xae, 0xf4, 0xa5, 0xe3, 0x07, 0x89, 0xb1, 0xd2, 0x68, 0xf5, 0xd8, 0x8c, 0x53, 0x82, 0x5a, 0xa0, 0xde, 0x79, 0x84, 0x84, 0xba, 0x9f, 0xe3, 0x6a, 0x06, 0x28, 0x7a, 0xae, 0x32, 0x1b, 0x1f, 0x7d, 0x6b, 0xdb, 0x67, 0x27, 0x41, 0x7c, 0x38, 0xb0, 0x4d, 0x50, 0xe6, 0x52, 0x88, 0x61, 0x5a, 0xe5, 0x97, 0x2b, 0xce, 0x17, 0x6d, 0x5f, 0xc4, 0xbc, 0x6e, 0x87, 0x13, 0x34, 0xc5, 0xec, 0x96, 0xbc, 0x00, 0x7c, 0x14, 0xdc, 0x70, 0xa2, 0xa6, 0x29, 0x29, 0xe6, 0x88, 0x81, 0x0f, 0x16, 0x9e, 0x6d, 0xd5, 0xea, 0x62, 0xcb, 0x8b, 0xc8, 0x42, 0x9e, 0x2d, 0xd8, 0x0f, 0x34, 0x74, 0x60, 0xb0, 0xf8, 0x67, 0xa9, 0x43, 0x0a, 0x73, 0x51, 0x24, 0x69, 0xad, 0x6d, 0x5f, 0x58, 0x8a, 0x38, 0xed, 0xff, 0x76, 0x25, 0xb4, 0xff, 0xd7, 0xb8, 0x4d, 0x66, 0x77, 0xaa, 0x73, 0x25, 0x27, 0x00, 0xce, 0x10, 0x94, 0x69, 0xf3, 0x1b, 0xfc, 0x73, 0xe9, 0x00, 0xee, 0x9d, 0xc2, 0x36, 0x4f, 0xcf, 0xd6, 0x41, 0xf8, 0x61, 0x67, 0x85, 0xe2, 0xa9, 0xf6, 0xb2, 0x46, 0xad, 0x69, 0x6f, 0xf5, 0xe9, 0xb9, 0xc7, 0x64, 0x41, 0x10, 0xa1, 0x44, 0xe9, 0x24, 0x82, 0x2e, 0xe7, 0x38, 0x63, 0xa9, 0xd1, 0x06, 0x6b, 0x55, 0xf7, 0xed, 0x53, 0xaf, 0xa5, 0xc3, 0x21, 0xa0, 0xbe, 0x4b, 0xb3, 0x7d, 0xcb, 0x1a, 0x9d, 0x52, 0x62, 0x24, 0x4d, 0x0c, 0xf8, 0xdd, 0xaf, 0x64, 0x35, 0x0c, 0x90, 0xd0, 0x41, 0x08, 0x26, 0xd7, 0xa9, 0x09, 0xd8, 0x21, 0xf3, 0x88, 0xf3, 0x46, 0x59, 0xc3, 0xee, 0x41, 0x88, 0x5c, 0x7a, 0x6b, 0x69, 0xbd, 0x02, 0xaf, 0xba, 0x7c, 0xcd, 0x2f, 0xf8, 0x57, 0x8c, 0x6b, 0xcf, 0xbc, 0xa0, 0x2d, 0x13, 0x22, 0xfc, 0x28, 0x59, 0x60, 0xb2, 0x54, 0x6a, 0xfe, 0x38, 0x75, 0x5c, 0x4a, 0x9d, 0xdb, 0xaa, 0x36, 0x1d, 0x1e, 0xb4, 0xc2, 0x0d, 0xde, 0xea, 0x08, 0x85, 0xac, 0x64, 0x51, 0xa1, 0x5d, 0xd2, 0xef, 0x63, 0x28, 0xf4, 0x6b, 0x45, 0x33, 0x47, 0x55, 0x0e, 0xd3, 0xc5, 0x8b, 0xce, 0xf4, 0x15, 0xb0, 0x57, 0xf6, 0x5b, 0xae, 0x15, 0x41, 0x0c, 0x32, 0xa7, 0x6f, 0x99, 0x55, 0xf7, 0x42, 0x2b, 0x55, 0x5e, 0x09, 0x3f, 0x1c, 0xa8, 0xf4, 0x7f, 0x1b, 0x99, 0x15, 0xc2, 0xb4, 0x34, 0x7c, 0x9a, 0xef, 0x7c, 0x5e, 0x70, 0x6c, 0x86, 0x64, 0xae, 0x2b, 0x0c, 0x37, 0xeb, 0x9f, 0x27, 0x18, 0x72, 0x50, 0x5f, 0xf8, 0x03, 0xd5, 0xb1, 0x55, 0x03, 0x24, 0xb1 }; unsigned char encrypted_gcm_key_bin_v21[] = { 0x20, 0x1b, 0x4a, 0xac, 0x61, 0x81, 0x35, 0x04, 0xa6, 0x48, 0x5a, 0xc0, 0x53, 0x50, 0x80, 0xe3, 0xd4, 0xde, 0x27, 0xbb, 0xe4, 0xe3, 0x58, 0xca, 0xa4, 0x8c, 0xdc, 0x74, 0x5a, 0x4b, 0xc8, 0x33, 0x22, 0x62, 0x08, 0xdc, 0xd7, 0x21, 0x7f, 0x80, 0x4c, 0x71, 0xd7, 0x02, 0x5e, 0xc7, 0xfe, 0xb6, 0x2e, 0x62, 0x3b, 0x6a, 0x61, 0x03, 0x06, 0x42, 0xb7, 0x05, 0x02, 0x63, 0xf0, 0xc6, 0xba, 0x14, 0x3f, 0x37, 0x0c, 0x9f, 0x73, 0x4b, 0x34, 0x55, 0x2e, 0x07, 0x16, 0xd6, 0x9e, 0x07, 0xfe, 0xd3, 0x5b, 0xc4, 0x88, 0xdf, 0x96, 0xcd, 0x5a, 0x1c, 0x42, 0xb1, 0xe4, 0x16, 0xe0, 0x40, 0x51, 0xa8, 0x15, 0x03, 0xdd, 0xc8, 0x17, 0x59, 0xd1, 0x06, 0xa2, 0x8f, 0x79, 0xc4, 0x82, 0x81, 0xe1, 0xa2, 0xa8, 0xdb, 0x63, 0x4d, 0x93, 0x05, 0xcf, 0x43, 0x77, 0x34, 0x23, 0xa5, 0xb4, 0x9d, 0x85, 0x11, 0x95, 0xfc, 0x3c, 0x71, 0xd9, 0x15, 0xa8, 0xc3, 0xa0, 0xad, 0x49, 0xe8, 0xdb, 0xed, 0x72, 0x17, 0x72, 0x30, 0x36, 0x37, 0xb3, 0x08, 0xfa, 0xf7, 0x31, 0xfe, 0x34, 0x34, 0x8d, 0x2b, 0xbc, 0xf2, 0xda, 0x76, 0x69, 0x27, 0x15, 0x89, 0xe7, 0x3f, 0x7a, 0x47, 0xf9, 0x0b, 0x3a, 0xda, 0xee, 0x38, 0xed, 0xd0, 0x5c, 0xb2, 0xa1, 0x82, 0x89, 0xe7, 0x0d, 0x43, 0xc2, 0xf3, 0xa6, 0xb8, 0x9f, 0x40, 0x7f, 0xc3, 0xd7, 0x4c, 0x60, 0xd8, 0xb3, 0xe8, 0xba, 0xec, 0x93, 0x95, 0x32, 0x3e, 0xc5, 0x14, 0xd1, 0xf8, 0x47, 0xdf, 0x13, 0x69, 0x6f, 0xb6, 0xee, 0x37, 0x0e, 0xcb, 0xfd, 0xa8, 0x65, 0x06, 0x56, 0x8a, 0x47, 0x61, 0x72, 0xe1, 0x0a, 0x26, 0x8d, 0x73, 0xb8, 0x59, 0x1a, 0x13, 0x8f, 0x7d, 0x01, 0x92, 0x0e, 0xf0, 0x6c, 0x61, 0x7d, 0xf8, 0xea, 0xf5, 0xde, 0x91, 0xd9, 0x2b, 0x69, 0xa6, 0x77, 0x48, 0x0e, 0x3a, 0xd7, 0xb0, 0x14, 0x4a, 0xbb, 0xc0, 0x19, 0xe5, 0xfe, 0x81, 0xe3, 0x03, 0xb1, 0xab, 0x8e, 0x4e, 0x9a, 0xc7, 0xc5, 0x83, 0xe9, 0xc6, 0xb7, 0xba, 0x6b, 0x51, 0x8e, 0xd9, 0x0c, 0xdc, 0xde, 0x1a, 0xe5, 0xbd, 0xde, 0x93, 0xbf, 0xf8, 0xf3, 0xbf, 0xab, 0x63, 0xef, 0xa2, 0xe8, 0x4e, 0x62, 0x6a, 0x27, 0x15, 0xa3, 0x4d, 0xd8, 0xca, 0x47, 0x1e, 0x17, 0x7b, 0x96, 0xd9, 0x6f, 0xc9, 0xf4, 0x71, 0x03, 0xe9, 0xd6, 0xed, 0xb7, 0xc2, 0x24, 0x5e, 0x7a, 0x23, 0x1d, 0x40, 0xb2, 0xd7, 0x22, 0xdb, 0x5e, 0x99, 0x25, 0x1c, 0xac, 0x49, 0xc1, 0x96, 0x46, 0x58, 0x39, 0x46, 0xcd, 0x8c, 0xee, 0xc8, 0xd1, 0x18, 0xc0, 0xec, 0x66, 0x3c, 0xdd, 0x67, 0x49, 0x4e, 0xbf, 0x09, 0xc8, 0xe0, 0x1a, 0x7d, 0x98, 0x5e, 0x27, 0x83, 0x81, 0x15, 0xf0, 0x98, 0x4a, 0x18, 0x08, 0xfd }; unsigned int encrypted_gcm_key_bin_len = 384; TEST_CASE("test_rsa_ds_decrypt_v15_padding", "[encrypted_img]") { esp_err_t err; esp_decrypt_cfg_t cfg = {0}; esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; // Prepare input data: magic + Encrypted GCM key + IV + Binary Size + Auth Tag + Reserved size_t input_data_len = MAGIC_SIZE + ENC_GCM_KEY_SIZE; uint8_t *input_data = (uint8_t *)malloc(input_data_len); TEST_ASSERT_NOT_NULL(input_data); uint32_t magic = 0x0788b6cf; // esp_enc_img_magic memcpy(input_data, &magic, MAGIC_SIZE); memcpy(input_data + MAGIC_SIZE, encrypted_gcm_key_bin, ENC_GCM_KEY_SIZE); // Encrypted GCM key esp_decrypt_handle_t decrypt_ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(decrypt_ctx); pre_enc_decrypt_arg_t *args = (pre_enc_decrypt_arg_t *)calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); args->data_in = (char *)input_data; args->data_in_len = input_data_len; args->data_out = NULL; args->data_out_len = 0; err = esp_encrypted_img_decrypt_data(decrypt_ctx, args); TEST_ESP_ERR(ESP_ERR_NOT_FINISHED, err); err = esp_encrypted_img_decrypt_abort(decrypt_ctx); TEST_ASSERT_EQUAL(ESP_OK, err); esp_secure_cert_free_ds_ctx(ds_data); free(input_data); free(args); } #endif // CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS TEST_CASE("Sending all data at once", "[encrypted_img]") { esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); args->data_in = (char *)bin_start; args->data_in_len = bin_end - bin_start; err = esp_encrypted_img_decrypt_data(ctx, args); TEST_ESP_OK(err); printf("Successful\n"); err = esp_encrypted_img_decrypt_end(ctx); TEST_ESP_OK(err); if (args->data_out) { free(args->data_out); } #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ free(args); } TEST_CASE("Sending 1 byte data at once", "[encrypted_img]") { esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); int i = 0; do { args->data_in = (char *)(bin_start + i); i++; args->data_in_len = 1; err = esp_encrypted_img_decrypt_data(ctx, args); if (err == ESP_FAIL) { printf("ESP_FAIL ERROR\n"); break; } } while (err != ESP_OK); TEST_ESP_OK(err); err = esp_encrypted_img_decrypt_end(ctx); TEST_ESP_OK(err); if (args->data_out) { free(args->data_out); } free(args); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } TEST_CASE("Invalid Magic", "[encrypted_img]") { static uint8_t cipher_invalid_magic[] = { 0x34, 0x12, 0xbc, 0xec, 0xe3, 0x02, 0x92, 0xb0, 0x3c, 0x3c, 0x37, 0x61, 0x99, 0x1b, 0x32, 0xeb, 0x28, 0x43, 0x92, 0x34, 0x8e, 0xb2, 0x98, 0x8e, 0xb4, 0x4a, 0xcb, 0xaa, 0xa9, 0x21, 0x6b, 0xc0, 0x52, 0xef, 0x4a, 0x2d, 0x93, 0xee, 0x9a, 0xec, 0x76, 0xf6, 0x18, 0xef, 0x7e, 0xb0, 0xaf, 0xdb, 0xae, 0x0e, 0x2b, 0x2c, 0xa5, 0x5c, 0x84, 0x8f, 0xef, 0x93, 0x68, 0xed, 0xff, 0xfa, 0x0a, 0xbb, 0xb7, 0x0b, 0xd0, 0x9b, 0x6b, 0xba, 0xf1, 0xf3, 0x69, 0xb7, 0xe1, 0x8b, 0x94, 0x61, 0x60, 0x96, 0x20, 0xe7, 0xaf, 0x6a, 0x7c, 0xca, 0x4a, 0xab, 0xab, 0x8f, 0x6a, 0x77, 0xaa, 0xf8, 0x9e, 0x6d, 0xc8, 0x70, 0x6f, 0xae, 0x66, 0xd2, 0x50, 0xb1, 0xef, 0x97, 0xd5, 0xd3, 0xc9, 0x9d, 0x14, 0x80, 0x1b, 0xa8, 0xb7, 0xc7, 0xaa, 0x88, 0xa4, 0x29, 0xd6, 0x64, 0xe9, 0xb9, 0x8f, 0x88, 0xca, 0xa9, 0x28, 0x64, 0x54, 0xe5, 0x6e, 0xdf, 0xef, 0xab, 0xdd, 0x1f, 0xbe, 0xa7, 0xfc, 0x34, 0x12, 0xf5, 0xea, 0x09, 0x7c, 0x36, 0xe9, 0x13, 0xf8, 0xb4, 0x37, 0xa4, 0x4d, 0x8d, 0xf4, 0x8a, 0xd8, 0xfa, 0x67, 0x42, 0x37, 0x93, 0xe6, 0x70, 0x1c, 0x59, 0x91, 0x98, 0xf5, 0x4e, 0xfc, 0xde, 0x05, 0x4f, 0xa8, 0x46, 0x68, 0xf9, 0xed, 0x1a, 0x8a, 0x52, 0xc9, 0xdb, 0xe1, 0x00, 0x48, 0x66, 0xf5, 0xb0, 0xae, 0x27, 0x28, 0x57, 0xd2, 0xfd, 0xbb, 0xf2, 0x80, 0xf7, 0xe4, 0x01, 0x28, 0xc6, 0xc9, 0x12, 0x60, 0xce, 0xc1, 0x1f, 0x4a, 0x0b, 0x2f, 0x02, 0x44, 0xf5, 0x41, 0x19, 0x7e, 0xb4, 0xe6, 0x4a, 0x58, 0x7a, 0x2b, 0xf9, 0xe3, 0x29, 0x62, 0xad, 0x61, 0x6e, 0x27, 0xea, 0x07, 0x60, 0x3f, 0x83, 0x05, 0x59, 0x6c, 0xbd, 0x6e, 0xc8, 0x97, 0xeb, 0x2e, 0x6a, 0x7c, 0x2e, 0x3c, 0x82, 0xbf, 0xaf, 0x2f, 0xd0, 0x0a, 0x6a, 0xbf, 0x24, 0xc5, 0x2b, 0xe3, 0x8a, 0x00, 0x6c, 0x04, 0xd8, 0xb2, 0x63, 0xca, 0x96, 0x64, 0xd2, 0xf5, 0x86, 0xb9, 0xc6, 0xcd, 0x2f, 0xd5, 0x97, 0xd8, 0x3f, 0xaf, 0x80, 0x66, 0x72, 0x8f, 0x60, 0xa8, 0x3a, 0xdd, 0x48, 0x01, 0xbc, 0xc1, 0xcb, 0x8d, 0x18, 0xc9, 0x6f, 0x83, 0x4c, 0xda, 0xb5, 0xa1, 0xcd, 0x99, 0xb1, 0xa4, 0x9d, 0xef, 0xa7, 0xed, 0xaf, 0xdd, 0xfd, 0xc7, 0x08, 0xc4, 0xcd, 0xf7, 0x5a, 0xda, 0x3b, 0x92, 0xcd, 0xcf, 0xe1, 0xe8, 0x7a, 0x6b, 0xe2, 0x24, 0x97, 0x6f, 0xd3, 0x40, 0xcf, 0x7d, 0xaf, 0x9b, 0xf1, 0xf2, 0xa2, 0xcd, 0x8d, 0xe8, 0x4b, 0xe6, 0x89, 0xb0, 0x76, 0x1a, 0xc6, 0xe5, 0x63, 0x59, 0x1f, 0x6a, 0xa8, 0x22, 0xe8, 0x04, 0x97, 0xca, 0xdd, 0x3d, 0x6e, 0xd1, 0x28, 0x71, 0x4b, 0x9e, 0xb6, 0xe0, 0xa7, 0xbe, 0x35, 0x7b, 0x35, 0xdc, 0xb2, 0x26, 0x0f, 0xdc, 0x35, 0xb5, 0xea, 0xa1, 0x6c, 0x7d, 0xbd, 0x49, 0x27, 0x58, 0xcb, 0x70, 0x16, 0x66, 0x26, 0x0a, 0x00, 0x00, 0x00, 0xfa, 0x7c, 0x1b, 0xc3, 0xb8, 0x1f, 0xf5, 0x7d, 0xab, 0xd4, 0x16, 0xfc, 0xd7, 0x9b, 0x45, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x5d, 0x8c, 0x97, 0x1f, 0xd4, 0x77, 0xf7, 0xbb, 0x31 }; esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); args->data_in = (char *)cipher_invalid_magic; args->data_in_len = sizeof(cipher_invalid_magic); err = esp_encrypted_img_decrypt_data(ctx, args); TEST_ESP_ERR(ESP_FAIL, err); err = esp_encrypted_img_decrypt_end(ctx); free(args); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } TEST_CASE("Invalid Image", "[encrypted_img]") { //"Espressif" is encoded using GCM key. After successful decoding, "Espressif" will be printed. static uint8_t cipher_invalid_image[] = { 0xcf, 0xb6, 0x88, 0x07, 0xe3, 0x02, 0x92, 0xb0, 0x3c, 0x3c, 0x37, 0x61, 0x99, 0x1b, 0x32, 0xeb, 0x28, 0x43, 0x92, 0x34, 0x8e, 0xb2, 0x98, 0x8e, 0xb4, 0x4a, 0xcb, 0xaa, 0xa9, 0x21, 0x6b, 0xc0, 0x52, 0xef, 0x4a, 0x2d, 0x93, 0xee, 0x9a, 0xec, 0x76, 0xf6, 0x18, 0xef, 0x7e, 0xb0, 0xaf, 0xdb, 0xae, 0x0e, 0x2b, 0x2c, 0xa5, 0x5c, 0x84, 0x8f, 0xef, 0x93, 0x68, 0xed, 0xff, 0xfa, 0x0a, 0xbb, 0xb7, 0x0b, 0xd0, 0x9b, 0x6b, 0xba, 0xf1, 0xf3, 0x69, 0xb7, 0xe1, 0x8b, 0x94, 0x61, 0x60, 0x96, 0x20, 0xe7, 0xaf, 0x6a, 0x7c, 0xca, 0x4a, 0xab, 0xab, 0x8f, 0x6a, 0x77, 0xaa, 0xf8, 0x9e, 0x6d, 0xc8, 0x70, 0x6f, 0xae, 0x66, 0xd2, 0x50, 0xb1, 0xef, 0x97, 0xd5, 0xd3, 0xc9, 0x9d, 0x14, 0x80, 0x1b, 0xa8, 0xb7, 0xc7, 0xaa, 0x88, 0xa4, 0x29, 0xd6, 0x64, 0xe9, 0xb9, 0x8f, 0x88, 0xca, 0xa9, 0x28, 0x64, 0x54, 0xe5, 0x6e, 0xdf, 0xef, 0xab, 0xdd, 0x1f, 0xbe, 0xa7, 0xfc, 0x34, 0x12, 0xf5, 0xea, 0x09, 0x7c, 0x36, 0xe9, 0x13, 0xf8, 0xb4, 0x37, 0xa4, 0x4d, 0x8d, 0xf4, 0x8a, 0xd8, 0xfa, 0x67, 0x42, 0x37, 0x93, 0xe6, 0x70, 0x1c, 0x59, 0x91, 0x98, 0xf5, 0x4e, 0xfc, 0xde, 0x05, 0x4f, 0xa8, 0x46, 0x68, 0xf9, 0xed, 0x1a, 0x8a, 0x52, 0xc9, 0xdb, 0xe1, 0x00, 0x48, 0x66, 0xf5, 0xb0, 0xae, 0x27, 0x28, 0x57, 0xd2, 0xfd, 0xbb, 0xf2, 0x80, 0xf7, 0xe4, 0x01, 0x28, 0xc6, 0xc9, 0x12, 0x60, 0xce, 0xc1, 0x1f, 0x4a, 0x0b, 0x2f, 0x02, 0x44, 0xf5, 0x41, 0x19, 0x7e, 0xb4, 0xe6, 0x4a, 0x58, 0x7a, 0x2b, 0xf9, 0xe3, 0x29, 0x62, 0xad, 0x61, 0x6e, 0x27, 0xea, 0x07, 0x60, 0x3f, 0x83, 0x05, 0x59, 0x6c, 0xbd, 0x6e, 0xc8, 0x97, 0xeb, 0x2e, 0x6a, 0x7c, 0x2e, 0x3c, 0x82, 0xbf, 0xaf, 0x2f, 0xd0, 0x0a, 0x6a, 0xbf, 0x24, 0xc5, 0x2b, 0xe3, 0x8a, 0x00, 0x6c, 0x04, 0xd8, 0xb2, 0x63, 0xca, 0x96, 0x64, 0xd2, 0xf5, 0x86, 0xb9, 0xc6, 0xcd, 0x2f, 0xd5, 0x97, 0xd8, 0x3f, 0xaf, 0x80, 0x66, 0x72, 0x8f, 0x60, 0xa8, 0x3a, 0xdd, 0x48, 0x01, 0xbc, 0xc1, 0xcb, 0x8d, 0x18, 0xc9, 0x6f, 0x83, 0x4c, 0xda, 0xb5, 0xa1, 0xcd, 0x99, 0xb1, 0xa4, 0x9d, 0xef, 0xa7, 0xed, 0xaf, 0xdd, 0xfd, 0xc7, 0x08, 0xc4, 0xcd, 0xf7, 0x5a, 0xda, 0x3b, 0x92, 0xcd, 0xcf, 0xe1, 0xe8, 0x7a, 0x6b, 0xe2, 0x24, 0x97, 0x6f, 0xd3, 0x40, 0xcf, 0x7d, 0xaf, 0x9b, 0xf1, 0xf2, 0xa2, 0xcd, 0x8d, 0xe8, 0x4b, 0xe6, 0x89, 0xb0, 0x76, 0x1a, 0xc6, 0xe5, 0x63, 0x59, 0x1f, 0x6a, 0xa1, 0x22, 0xe8, 0x04, 0x97, 0xca, 0xdd, 0x3d, 0x6e, 0xd1, 0x28, 0x71, 0x4b, 0x92, 0xb6, 0xe0, 0xa7, 0xbe, 0x35, 0x7b, 0x35, 0xdc, 0xb2, 0x26, 0x0f, 0xdc, 0x35, 0xb5, 0xea, 0xa1, 0x6c, 0x7d, 0xbd, 0x49, 0x27, 0x58, 0xcb, 0x70, 0x16, 0x66, 0x26, 0x0a, 0x00, 0x00, 0x00, 0xfa, 0x7c, 0x1b, 0xc3, 0xb8, 0x1f, 0xf5, 0x7d, 0xab, 0xd4, 0x16, 0xfc, 0xd7, 0x9b, 0x35, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x5d, 0x8c, 0x97, 0x1f, 0xd4, 0x77, 0xf7, 0xbb, 0x31 }; esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); args->data_in = (char *)cipher_invalid_image; args->data_in_len = sizeof(cipher_invalid_image); err = esp_encrypted_img_decrypt_data(ctx, args); TEST_ESP_ERR(ESP_FAIL, err); err = esp_encrypted_img_decrypt_end(ctx); TEST_ESP_ERR(ESP_FAIL, err); if (args->data_out) { free(args->data_out); } free(args); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } TEST_CASE("Sending random size data at once", "[encrypted_img]") { esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); int i = 0; do { uint32_t y = esp_random(); y = (y % 16) + 1; uint32_t x = y < ((bin_end - bin_start) - i) ? y : ((bin_end - bin_start) - i); args->data_in = (char *)(bin_start + i); i += x; args->data_in_len = x; err = esp_encrypted_img_decrypt_data(ctx, args); if (err == ESP_FAIL) { printf("ESP_FAIL ERROR\n"); break; } } while (err != ESP_OK); TEST_ESP_OK(err); err = esp_encrypted_img_decrypt_end(ctx); TEST_ESP_OK(err); if (args->data_out) { free(args->data_out); } free(args); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } TEST_CASE("Sending incomplete data", "[encrypted_img]") { esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); args->data_in = (char *)bin_start; args->data_in_len = (bin_end - bin_start) - 1; err = esp_encrypted_img_decrypt_data(ctx, args); TEST_ESP_ERR(ESP_ERR_NOT_FINISHED, err); TEST_ESP_ERR(false, esp_encrypted_img_is_complete_data_received(ctx)); err = esp_encrypted_img_decrypt_abort(ctx); TEST_ESP_OK(err); if (args->data_out) { free(args->data_out); } free(args); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } TEST_CASE("Test canceling decryption frees memory", "[encrypted_img]") { esp_err_t err; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; #endif int free_bytes_start = xPortGetFreeHeapSize(); esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); err = esp_encrypted_img_decrypt_abort(ctx); TEST_ESP_OK(err); int free_bytes_end = xPortGetFreeHeapSize(); // +/- 16 bytes to allow for some small fluctuations TEST_ASSERT(abs(free_bytes_start - free_bytes_end) <= 16); #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ } #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) uint8_t server_pub[SERVER_ECC_KEY_LEN] = { 0x6b, 0x1e, 0xda, 0x43, 0xaf, 0xc4, 0x1d, 0x44, 0x28, 0x44, 0x5f, 0x06, 0x12, 0x07, 0xe3, 0x85, 0x8b, 0x93, 0x0d, 0x48, 0x09, 0xc3, 0xf2, 0x33, 0x3c, 0x04, 0x26, 0x64, 0xd6, 0x14, 0x1c, 0x62, 0x24, 0xd2, 0xf5, 0xc8, 0x2e, 0x10, 0x7c, 0xb5, 0x16, 0x2b, 0x4c, 0xd1, 0xa0, 0x1c, 0x93, 0xe4, 0x48, 0x2a, 0x03, 0x5d, 0x5e, 0x98, 0xb2, 0x82, 0xf6, 0x85, 0x70, 0x2a, 0x5e, 0x92, 0xd3, 0x6a, }; uint8_t kdf_salt[KDF_SALT_SIZE] = { 0x1e, 0x74, 0x5d, 0xe2, 0x86, 0x03, 0x3f, 0x03, 0x73, 0x15, 0x80, 0xcb, 0x6e, 0xe1, 0x37, 0xaa, 0x1b, 0x4c, 0x2f, 0x8d, 0x3e, 0x5a, 0x6f, 0x7b, 0x9c, 0x8d, 0x4e, 0x2b, 0x1a, 0x3f, 0xd5, 0xe7 }; TEST_CASE("test_invalid_hmac_key", "[encrypted_img]") { // --- Test Setup --- esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = -1; // Invalid HMAC key esp_decrypt_handle_t decrypt_ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NULL(decrypt_ctx); cfg.hmac_key_id = HMAC_KEY_MAX + 1; // Invalid HMAC key decrypt_ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NULL(decrypt_ctx); cfg.hmac_key_id = 1; // Invalid HMAC key decrypt_ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NULL(decrypt_ctx); } TEST_CASE("test_ecc_key_derivation", "[encrypted_img]") { // --- Test Setup --- esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 2; esp_decrypt_handle_t decrypt_ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(decrypt_ctx); pre_enc_decrypt_arg_t *args = (pre_enc_decrypt_arg_t *)calloc(1, sizeof(pre_enc_decrypt_arg_t)); TEST_ASSERT_NOT_NULL(args); // Prepare input data: magic + ECC header (server_pub_key + kdf_salt_from_header + reserved) size_t input_data_len = MAGIC_SIZE + SERVER_ECC_KEY_LEN + KDF_SALT_SIZE + RESERVED_SIZE; uint8_t *input_data = (uint8_t *)malloc(input_data_len); TEST_ASSERT_NOT_NULL(input_data); uint32_t magic = 0x0788b6cf; // esp_enc_img_magic memcpy(input_data, &magic, MAGIC_SIZE); memcpy(input_data + MAGIC_SIZE, server_pub, SERVER_ECC_KEY_LEN); // Dummy server_pub_key from header memcpy(input_data + MAGIC_SIZE + SERVER_ECC_KEY_LEN, kdf_salt, KDF_SALT_SIZE); // Dummy kdf_salt from header memset(input_data + MAGIC_SIZE + SERVER_ECC_KEY_LEN + KDF_SALT_SIZE, 0x00, RESERVED_SIZE); // Dummy reserved data args->data_in = (char *)input_data; args->data_in_len = input_data_len; args->data_out = NULL; args->data_out_len = 0; // --- Execute --- // This call will trigger reading magic, then ECC header. // derive_gcm_key -> derive_ota_ecc_device_key -> compute_ecc_key_with_hmac -> esp_encrypted_img_pbkdf2_hmac_sha256 esp_err_t err = esp_encrypted_img_decrypt_data(decrypt_ctx, args); // --- Assert --- // After processing the header, it expects more data (IV, etc.), so it should return ESP_ERR_NOT_FINISHED. TEST_ESP_ERR(ESP_ERR_NOT_FINISHED, err); esp_encrypted_img_decrypt_abort(decrypt_ctx); // Use abort as we didn't provide full data free(input_data); if (args->data_out) { free(args->data_out); } free(args); } #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES */ #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) unsigned char rsa_pub_der[] = { 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xf5, 0xea, 0x28, 0x86, 0xaa, 0x25, 0xa3, 0x0d, 0xb3, 0xe8, 0x9b, 0x2e, 0x06, 0xfa, 0x8b, 0x2c, 0xff, 0xa3, 0x7c, 0x0f, 0xe1, 0x6b, 0xcb, 0x74, 0xde, 0xc9, 0x11, 0xd0, 0x5e, 0x1c, 0x28, 0xc5, 0xcd, 0x16, 0x87, 0x36, 0x41, 0xba, 0x9d, 0x62, 0x34, 0x41, 0x68, 0x53, 0xab, 0x92, 0x85, 0x03, 0xfc, 0xa0, 0xb6, 0x4f, 0x3b, 0xc2, 0x8d, 0x4d, 0xce, 0x67, 0x7c, 0x2e, 0x18, 0x30, 0xe3, 0x15, 0x65, 0x11, 0xb1, 0xec, 0xd4, 0x79, 0x80, 0x87, 0xfa, 0xbe, 0x0f, 0x70, 0x5f, 0xea, 0x13, 0xab, 0xd7, 0x5b, 0x59, 0x15, 0xb9, 0xe1, 0x39, 0x8c, 0x89, 0x93, 0xb7, 0x14, 0xac, 0x9e, 0xdd, 0x47, 0x2c, 0x50, 0x06, 0x89, 0x49, 0xc8, 0x2d, 0x42, 0x7b, 0xc6, 0x2d, 0xdc, 0x77, 0xce, 0x98, 0xf2, 0x61, 0x74, 0x54, 0x7d, 0xb0, 0x7e, 0x60, 0xca, 0xa3, 0xc1, 0x12, 0x05, 0x16, 0xde, 0xbf, 0x47, 0x82, 0x84, 0x22, 0x40, 0xc5, 0xdc, 0xa7, 0xba, 0xec, 0xa8, 0xd2, 0x6f, 0x88, 0x37, 0xda, 0xfb, 0x70, 0xd4, 0xad, 0x08, 0xac, 0xf7, 0x8e, 0x09, 0x62, 0xa6, 0x39, 0xc8, 0xe7, 0xe3, 0xb1, 0xb4, 0x98, 0x30, 0xff, 0x47, 0xcc, 0x68, 0x27, 0xe3, 0xb6, 0x7e, 0xca, 0x20, 0x59, 0x2d, 0x18, 0x47, 0x97, 0xa8, 0x9d, 0x6b, 0xb9, 0x10, 0x40, 0x2c, 0xdf, 0xa2, 0xef, 0x4c, 0xd9, 0x8e, 0x30, 0xed, 0x26, 0x3d, 0xfb, 0x14, 0xbe, 0x6d, 0xe0, 0xb0, 0xa0, 0xae, 0x42, 0x26, 0xc0, 0xba, 0x41, 0x00, 0xb5, 0x58, 0x15, 0x7c, 0xb5, 0xc1, 0x78, 0x4c, 0x5e, 0xae, 0xe1, 0xe5, 0xc5, 0x36, 0x11, 0xce, 0x9b, 0x45, 0x50, 0x65, 0xe8, 0x61, 0xf5, 0xc7, 0x1d, 0xa5, 0x00, 0x14, 0x35, 0x2d, 0x51, 0x70, 0xae, 0x29, 0x60, 0x92, 0x52, 0xe3, 0x63, 0x95, 0x13, 0x77, 0x1f, 0x06, 0x1c, 0x4f, 0xef, 0x79, 0x1e, 0x12, 0x95, 0xad, 0xdd, 0x63, 0x21, 0x39, 0x30, 0xa2, 0xc9, 0xdc, 0xa9, 0x9c, 0xec, 0xdc, 0x65, 0x3e, 0x55, 0xa7, 0x59, 0x39, 0x94, 0x28, 0x05, 0x98, 0x20, 0x98, 0x91, 0x23, 0x00, 0x47, 0x48, 0x19, 0xed, 0x52, 0x01, 0xb2, 0x05, 0x80, 0x6a, 0xcd, 0xf0, 0x52, 0x60, 0x55, 0x8b, 0xde, 0x09, 0x93, 0x9a, 0xf5, 0x7f, 0x25, 0x0e, 0x35, 0x95, 0xa5, 0xfb, 0xab, 0x29, 0x1d, 0x69, 0xbb, 0xe0, 0x74, 0x64, 0x91, 0x2c, 0x91, 0x7a, 0xc1, 0x93, 0x1d, 0xa4, 0x9a, 0x6f, 0x69, 0xf6, 0x03, 0x05, 0x1c, 0x15, 0xee, 0x9b, 0x2f, 0x84, 0xf9, 0x07, 0x11, 0x70, 0x54, 0x49, 0xed, 0x9d, 0xb5, 0xf5, 0x8a, 0x4a, 0xd8, 0x94, 0x3d, 0x3e, 0xe0, 0x71, 0x91, 0x35, 0x20, 0x23, 0x31, 0x4f, 0x6e, 0x90, 0x95, 0xa7, 0xfd, 0x4f, 0x61, 0xaf, 0x63, 0x0c, 0xaa, 0xe1, 0x60, 0x64, 0x12, 0x7b, 0x73, 0x02, 0x03, 0x01, 0x00, 0x01 }; unsigned int rsa_pub_der_len = 398; #else uint8_t ecies_public_key[] = { 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x6d, 0x45, 0xfd, 0xc5, 0xdf, 0xc8, 0x83, 0x30, 0x7e, 0x28, 0x0b, 0x5a, 0xb1, 0x2b, 0x3d, 0x10, 0x30, 0x0f, 0x30, 0xb6, 0x3a, 0xca, 0x4a, 0x0e, 0x0f, 0x78, 0x43, 0xb9, 0xd0, 0x46, 0xb9, 0x4e, 0x2e, 0x57, 0xf4, 0x4b, 0xc9, 0x69, 0x79, 0x8a, 0x63, 0x9a, 0x9e, 0x7f, 0x4c, 0x64, 0x1b, 0xcc, 0x73, 0xe1, 0xaa, 0xfb, 0xeb, 0x3e, 0xce, 0x5b, 0x45, 0x7a, 0xef, 0xdb, 0xc5, 0xc8, 0x61, 0xbe }; size_t ecies_public_key_len = sizeof(ecies_public_key); #endif // CONFIG_PRE_ENCRYPTED_OTA_USE_RSA TEST_CASE("Test getting public key NULL", "[encrypted_img]") { #ifdef CONFIG_HEAP_TRACING heap_trace_init_standalone(trace_record, NUM_RECORDS); heap_trace_start(HEAP_TRACE_LEAKS); #endif size_t pub_key_len = 0; // We have not started decryption yet, so public key should be NULL uint8_t *pub_key = NULL; esp_err_t err = esp_encrypted_img_export_public_key(NULL, NULL, &pub_key_len); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); err = esp_encrypted_img_export_public_key(NULL, &pub_key, NULL); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); err = esp_encrypted_img_export_public_key(NULL, NULL, NULL); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); #ifdef CONFIG_HEAP_TRACING heap_trace_stop(); heap_trace_dump(); #endif } #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) TEST_CASE("Test getting public key wrong hmac id", "[encrypted_img]") { #ifdef CONFIG_HEAP_TRACING heap_trace_init_standalone(trace_record, NUM_RECORDS); heap_trace_start(HEAP_TRACE_LEAKS); #endif esp_decrypt_cfg_t cfg = {0}; cfg.hmac_key_id = 1; size_t pub_key_len = 0; // We have not started decryption yet, so public key should be NULL uint8_t *pub_key = NULL; esp_err_t err = esp_encrypted_img_export_public_key(NULL, &pub_key, &pub_key_len); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NULL(ctx); err = esp_encrypted_img_export_public_key(ctx, &pub_key, &pub_key_len); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); #ifdef CONFIG_HEAP_TRACING heap_trace_stop(); heap_trace_dump(); #endif } #endif // CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES TEST_CASE("Test getting public key", "[encrypted_img]") { #ifdef CONFIG_HEAP_TRACING heap_trace_init_standalone(trace_record, NUM_RECORDS); heap_trace_start(HEAP_TRACE_LEAKS); #endif esp_decrypt_cfg_t cfg = {0}; #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx(); if (ds_data == NULL) { printf("Failed to get DS context\n"); vTaskDelete(NULL); } cfg.ds_data = ds_data; #else cfg.rsa_priv_key = (char *)rsa_private_pem_start; cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #else cfg.hmac_key_id = 2; #endif size_t pub_key_len = 0; // We have not started decryption yet, so public key should be NULL uint8_t *pub_key = NULL; esp_err_t err = esp_encrypted_img_export_public_key(NULL, &pub_key, &pub_key_len); TEST_ASSERT_NULL(pub_key); TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err); // Start decryption to get the public key esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); TEST_ASSERT_NOT_NULL(ctx); err = esp_encrypted_img_export_public_key(ctx, &pub_key, &pub_key_len); #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) #if defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, err); TEST_ASSERT_NULL(pub_key); TEST_ASSERT(pub_key_len == 0); #else TEST_ESP_ERR(ESP_OK, err); TEST_ASSERT_NOT_NULL(pub_key); TEST_ASSERT(pub_key_len > 0); TEST_ASSERT_EQUAL(rsa_pub_der_len, pub_key_len); TEST_ASSERT_EQUAL_MEMORY(rsa_pub_der, pub_key, rsa_pub_der_len); #endif /* CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #endif // CONFIG_PRE_ENCRYPTED_OTA_USE_RSA // In case of RSA, public key is in DER format. #if defined(CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES) // In case of ECIES, public key is in DER format. TEST_ASSERT_EQUAL(ecies_public_key_len, pub_key_len); TEST_ASSERT_EQUAL_MEMORY(ecies_public_key, pub_key, ecies_public_key_len); #endif // Clean up esp_encrypted_img_decrypt_end(ctx); if (pub_key) { free(pub_key); } #if defined (CONFIG_PRE_ENCRYPTED_OTA_USE_RSA) && defined(CONFIG_PRE_ENCRYPTED_RSA_USE_DS) esp_secure_cert_free_ds_ctx(cfg.ds_data); #endif /* CONFIG_PRE_ENCRYPTED_OTA_USE_RSA && CONFIG_PRE_ENCRYPTED_RSA_USE_DS */ #ifdef CONFIG_HEAP_TRACING heap_trace_stop(); heap_trace_dump(); #endif } ================================================ FILE: esp_encrypted_img/test_apps/main/test_mocks.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "test_mocks.h" #include "esp_efuse_chip.h" #include "sdkconfig.h" #include #include uint8_t dummy_pbkdf2_output[32] = { 0x83, 0x17, 0x93, 0x66, 0x0d, 0xe4, 0x91, 0x33, 0x66, 0xae, 0x1e, 0x37, 0x9b, 0x2c, 0xeb, 0x43, 0x17, 0xc8, 0x87, 0x00, 0xcc, 0x07, 0x91, 0xd9, 0x8e, 0x5a, 0x2a, 0x2d, 0x5c, 0x71, 0xaf, 0x66 }; bool esp_encrypted_is_hmac_key_burnt_in_efuse(hmac_key_id_t hmac_key_id) { // Simulate the behavior of checking if the HMAC key is burnt in efuse // For this example, we'll assume that the key is always burnt in for key ID 2 if (hmac_key_id == 2) { return true; } return false; } int esp_encrypted_img_pbkdf2_hmac_sha256(hmac_key_id_t hmac_key_id, const unsigned char *salt, size_t salt_len, size_t iteration_count, size_t key_length, unsigned char *output) { // Simulate the behavior of PBKDF2 HMAC-SHA256 key derivation // For this example, we'll just fill the output with a known pattern memcpy(output, dummy_pbkdf2_output, key_length); return 0; // Indicate success } esp_err_t esp_ds_start_sign(const void *message, const esp_ds_data_t *data, hmac_key_id_t key_id, esp_ds_context_t **esp_ds_ctx) { return 0; } static const unsigned int expected_signature[] = { 0x006c3450, 0xdea677a2, 0x926820c4, 0x6d785259, 0x4b843538, 0x615aec9d, 0x56e0fcad, 0x749b45da, 0x3f791700, 0x967ce676, 0x58b031b8, 0xef426f54, 0xb4f2fd90, 0x75a7a818, 0xb39fa150, 0x21e1502e, 0xf7108fa4, 0x8c46f51c, 0x14e98795, 0x22667e59, 0xcb6cab5e, 0xdb961c2f, 0x0bdf10a7, 0xecc2fcc7, 0x570753d3, 0xcbc6e011, 0x2ea88de6, 0xd7c81c73, 0xa2d9f65c, 0xa74fd309, 0x2a7a764b, 0x750bc352, 0x8b27341a, 0x9ab95d23, 0x9caebeea, 0x5b410b4e, 0x5f26d119, 0xf1946d20, 0x8037e8f1, 0x5955b934, 0x2b7ef75d, 0x69b7e85a, 0x330f056c, 0x92e47389, 0xcb715480, 0xb551e0fe, 0x4c7b3beb, 0xed67a7d1, 0x53d19879, 0x3712444b, 0x25f6d982, 0x525ee85c, 0xba7d8521, 0xfcd73dbd, 0xe0ee096b, 0x779b61c7, 0xba30c40d, 0xf9d53b71, 0x1581062b, 0x15163231, 0x65dc89e5, 0x6de575fc, 0x32058194, 0x550b64da, 0xbff2ec40, 0x5fbd699d, 0x133656ff, 0xf34e3ac7, 0xbd054ce3, 0x8b89110d, 0xb9804481, 0xb7600a29, 0x78435580, 0x3e1e3757, 0xd2d619a0, 0x8f2c327c, 0xb1c4901d, 0xf319e804, 0x966adf5b, 0x1ac533a0, 0x76696abd, 0xe6289296, 0xcdbec067, 0xf77a5edd, 0xed0df021, 0xdd7cb0c2, 0x8bbc8f9b, 0xb9c41aaa, 0xc7eca1e0, 0xe4238236, 0x4b22b649, 0x0897f841, 0xb94c9516, 0x2344ab37, 0xa73de816, 0x00029aa6 }; static const unsigned int expected_signature_v21[] = { 0x6ca95ae, 0xd1c2279, 0xdaf72229, 0xc50df9a8, 0xd4e184e1, 0x3a883bd2, 0x3f04c148, 0x209cad26, 0xedcc056c, 0x40b043dd, 0x52cf3120, 0x70e5bd4d, 0x4028643c, 0x52dcbf4a, 0xb8993492, 0x2499b64b, 0x623b6eb9, 0x8036d4a7, 0x95fafae3, 0x84fc859a, 0x5155a788, 0x694ac880, 0x70e66556, 0xdbecc366, 0xa4ff26c, 0xc8a334bf, 0xeb4335a2, 0x982a8be2, 0x2ae8f5ac, 0x2525d6f1, 0x9f262467, 0x3586b3c9, 0xbb18232, 0x7554c1e8, 0x93c2bdfc, 0x6e19ebf5, 0x5aadad7a, 0x3d7ce80b, 0x4b18f02e, 0x1233a570, 0x975e6b8d, 0x23d2db5a, 0x5086f2b7, 0xd4af50b3, 0xe6ce39d9, 0x63f7a444, 0x70462926, 0x8a93269a, 0x652eb454, 0xe5490beb, 0x99e15fc1, 0x9b469a28, 0x41ac40b5, 0x293a6a47, 0xdefae40c, 0xa5698edf, 0xaeaf684d, 0xf3d89453, 0xd454cf12, 0xe7b06ff9, 0xcb44d09c, 0x146763ae, 0xbe8010f, 0x56320865, 0xb99cd520, 0x5f30746, 0x2dfda54, 0x167c9567, 0xc8adfa3a, 0x7bb6926e, 0x23a6d1f7, 0x778fdad9, 0xb6f44670, 0x80932338, 0xb84ead8a, 0x4fb237ca, 0x8621e9a9, 0x644be6e1, 0x885d1fa7, 0xfb005fea, 0xe52344e6, 0x99b1d23, 0x5f1abe8a, 0x7cf28a53, 0x9eefaf2d, 0x5dfe59fa, 0xaa5605bd, 0xf41bf913, 0xb988adf2, 0x5ba95896, 0xdb847d45, 0x76d1b452, 0xcb166f50, 0xeb48c6ca, 0x8c7960ae, 0xa12cd8 }; static const unsigned char padded_gcm_key_bin[] = { 0xb7, 0x40, 0x28, 0x42, 0x8b, 0xa1, 0xb5, 0x81, 0x4e, 0x98, 0x52, 0xfc, 0xbc, 0xc1, 0xd5, 0x40, 0x9a, 0x18, 0x5b, 0xdf, 0x43, 0x8e, 0x15, 0x29, 0xeb, 0x55, 0x90, 0xa2, 0x72, 0xa7, 0xd3, 0x81, 0x00, 0xad, 0xb1, 0x40, 0x2e, 0xa5, 0x17, 0x97, 0x51, 0x50, 0xf7, 0xee, 0x1d, 0xbf, 0x10, 0xb5, 0xba, 0x39, 0xf8, 0x66, 0x45, 0x1b, 0xc2, 0x0b, 0xd8, 0x5b, 0x8b, 0xfc, 0x35, 0xb8, 0x93, 0xc4, 0xd9, 0xf9, 0x97, 0x38, 0xbf, 0x26, 0x3a, 0xa8, 0xcb, 0x17, 0x69, 0x31, 0x83, 0xaa, 0x30, 0x54, 0x9f, 0x11, 0xb3, 0xea, 0x52, 0x2e, 0x0a, 0x14, 0xde, 0x93, 0x66, 0xc5, 0x69, 0x74, 0xa8, 0x1b, 0xe1, 0x94, 0x11, 0x1a, 0x22, 0x62, 0xe9, 0x1a, 0x17, 0x43, 0x60, 0x1f, 0x95, 0x1a, 0x46, 0x94, 0xc0, 0xa5, 0x81, 0x0f, 0x1a, 0x5b, 0x5a, 0x9c, 0x74, 0x29, 0xcd, 0xee, 0x79, 0xc0, 0xd9, 0xe6, 0x53, 0x3a, 0x10, 0x1b, 0xf7, 0xa2, 0xa2, 0xf0, 0xc3, 0x7a, 0x84, 0xd0, 0x51, 0x28, 0xa9, 0x15, 0x89, 0x65, 0xcb, 0xb1, 0xa2, 0x59, 0x69, 0x2c, 0x12, 0x4c, 0x3e, 0xf1, 0x77, 0xf6, 0xe1, 0x54, 0x06, 0xbd, 0x53, 0x0b, 0xd0, 0x5e, 0x88, 0xc6, 0xc7, 0x18, 0xa5, 0x85, 0x3f, 0xf7, 0x36, 0x0d, 0x81, 0xfa, 0x40, 0xbb, 0x83, 0xe2, 0x9c, 0x61, 0x34, 0xc6, 0x8e, 0x31, 0xd4, 0x38, 0x17, 0x32, 0xc2, 0xcb, 0x04, 0x88, 0x44, 0xee, 0xc1, 0x4c, 0x36, 0x7e, 0x74, 0x7f, 0xc6, 0x83, 0x45, 0xa8, 0x6b, 0x76, 0xa8, 0x21, 0xf6, 0x08, 0x69, 0x52, 0xb9, 0xfd, 0xf1, 0xd4, 0x8d, 0xbf, 0x1f, 0x60, 0x52, 0x21, 0x24, 0x0e, 0x2d, 0x8a, 0xd7, 0x72, 0x46, 0x39, 0x42, 0xb9, 0x3f, 0xc7, 0xe0, 0x19, 0xe7, 0xed, 0x4a, 0xbc, 0x24, 0x5e, 0xbd, 0x94, 0x38, 0x6f, 0xd8, 0xce, 0x0c, 0xff, 0x01, 0x2c, 0x29, 0x56, 0x7b, 0x19, 0xdc, 0xf3, 0x1b, 0x3a, 0x56, 0xa7, 0xef, 0x26, 0x84, 0x60, 0xdf, 0x22, 0x1a, 0x56, 0xe6, 0x05, 0xb1, 0x0f, 0x62, 0xff, 0x1d, 0x6c, 0x2c, 0x24, 0x4c, 0x3e, 0xb1, 0xf8, 0x53, 0x44, 0x32, 0xa5, 0xb6, 0x0d, 0xc2, 0x20, 0x13, 0x71, 0x85, 0x4b, 0xa2, 0x21, 0xd7, 0x6d, 0x95, 0x20, 0xc9, 0x4a, 0x85, 0xc2, 0x3a, 0xea, 0x6a, 0xa0, 0x87, 0x17, 0x3a, 0xe8, 0x6b, 0x82, 0xb6, 0x4d, 0x44, 0xd5, 0x8b, 0x11, 0x7e, 0x78, 0x22, 0xbc, 0x01, 0xd2, 0x1d, 0xcf, 0xfa, 0xd5, 0xfc, 0x6e, 0xa0, 0x5a, 0x21, 0x57, 0x70, 0x24, 0x27, 0x1d, 0x93, 0x7d, 0x96, 0xc6, 0x6f, 0xe4, 0xb4, 0x5d, 0x1d, 0xaa, 0x33, 0x26, 0xf3, 0x18, 0xac, 0x62, 0xf0, 0x14, 0xd3, 0x7e, 0x6f, 0x5a, 0xb1, 0x94, 0x62, 0xfb, 0x7d, 0x64, 0xea, 0xe3, 0x08, 0xb0, 0xd4, 0x4b, 0x7b, 0xf7, 0x02, 0x00 }; unsigned int padded_gcm_key_bin_len = 384; esp_ds_data_ctx_t *esp_secure_cert_get_ds_ctx() { esp_ds_data_ctx_t *ds_ctx = (esp_ds_data_ctx_t *)malloc(sizeof(esp_ds_data_ctx_t)); if (ds_ctx == NULL) { return NULL; } ds_ctx->esp_ds_data = calloc(1, sizeof(esp_ds_data_t)); if (ds_ctx->esp_ds_data == NULL) { free(ds_ctx); return NULL; } ds_ctx->esp_ds_data->rsa_length = 95; ds_ctx->efuse_key_id = 0; ds_ctx->rsa_length_bits = 3072; return ds_ctx; } void esp_secure_cert_free_ds_ctx(esp_ds_data_ctx_t *ds_ctx) { if (ds_ctx) { if (ds_ctx->esp_ds_data) { free(ds_ctx->esp_ds_data); } free(ds_ctx); } } esp_err_t esp_ds_finish_sign(void *signature, esp_ds_context_t *esp_ds_ctx) { unsigned char *sig = (unsigned char *)signature; if (sig[0] == 0xb1) { memcpy(signature, expected_signature, sizeof(expected_signature)); } else if (sig[0] == 0xa3) { memcpy(signature, padded_gcm_key_bin, sizeof(padded_gcm_key_bin)); } else { memcpy(signature, expected_signature_v21, sizeof(expected_signature_v21)); } return 0; } #if CONFIG_PRE_ENCRYPTED_RSA_USE_DS esp_efuse_purpose_t __wrap_esp_efuse_get_key_purpose(esp_efuse_block_t block) { // Simulate the behavior of getting the efuse key purpose // For this example, we'll assume that the purpose is always HMAC_DOWN_DIGITAL_SIGNATURE for key blocks 0 and 1 if (block == EFUSE_BLK_KEY0 || block == EFUSE_BLK_KEY1) { return ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE; } return 0; // Indicate no purpose } #endif // CONFIG_PRE_ENCRYPTED_RSA_USE_DS ================================================ FILE: esp_encrypted_img/test_apps/main/test_mocks.h ================================================ /* * SPDX-FileCopyrightText: 2025-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include typedef void *esp_ds_context_t; typedef int hmac_key_id_t; typedef int esp_err_t; #define ESP_DS_IV_BIT_LEN 128 #define ESP_DS_SIGNATURE_MAX_BIT_LEN 3072 #define ESP_DS_SIGNATURE_MD_BIT_LEN 256 #define ESP_DS_SIGNATURE_M_PRIME_BIT_LEN 32 #define ESP_DS_SIGNATURE_L_BIT_LEN 32 #define ESP_DS_SIGNATURE_PADDING_BIT_LEN 64 #define ESP_DS_C_LEN (((ESP_DS_SIGNATURE_MAX_BIT_LEN * 3 \ + ESP_DS_SIGNATURE_MD_BIT_LEN \ + ESP_DS_SIGNATURE_M_PRIME_BIT_LEN \ + ESP_DS_SIGNATURE_L_BIT_LEN \ + ESP_DS_SIGNATURE_PADDING_BIT_LEN) / 8)) typedef enum { ESP_DS_RSA_1024 = (1024 / 32) - 1, ESP_DS_RSA_2048 = (2048 / 32) - 1, ESP_DS_RSA_3072 = (3072 / 32) - 1, ESP_DS_RSA_4096 = (4096 / 32) - 1 } esp_digital_signature_length_t; typedef struct esp_digital_signature_data { esp_digital_signature_length_t rsa_length; uint32_t iv[ESP_DS_IV_BIT_LEN / 32]; uint8_t c[ESP_DS_C_LEN]; } esp_ds_data_t; typedef struct esp_ds_data_ctx { esp_ds_data_t *esp_ds_data; uint8_t efuse_key_id; /* efuse block id in which DS_KEY is stored e.g. 0,1*/ uint16_t rsa_length_bits; /* length of RSA private key in bits e.g. 2048 */ } esp_ds_data_ctx_t; #define HMAC_KEY_MAX 5 /** * @brief Check if the HMAC key is burnt in efuse. * * @param hmac_key_id[in] The HMAC key ID to check. * * @return * - true If the HMAC key is burnt. * - false If the HMAC key is not burnt. */ bool esp_encrypted_is_hmac_key_burnt_in_efuse(hmac_key_id_t hmac_key_id); /** * @brief Perform PBKDF2 HMAC-SHA256 key derivation. * * @param hmac_key_id[in] HMAC key ID. * @param salt[in] Pointer to the salt. * @param salt_len[in] Length of the salt. * @param iteration_count[in] Number of iterations for the key derivation. * @param key_length[in] Desired length of the derived key. * @param output[out] Buffer to store the derived key. * * @return * - 0 on success. * - -1 on failure. */ int esp_encrypted_img_pbkdf2_hmac_sha256(hmac_key_id_t hmac_key_id, const unsigned char *salt, size_t salt_len, size_t iteration_count, size_t key_length, unsigned char *output); /** * @brief Get the digital signature context for secure certificate. * * @return esp_ds_data_ctx_t* Pointer to the digital signature context. */ esp_ds_data_ctx_t *esp_secure_cert_get_ds_ctx(void); /** * @brief Free the digital signature context. * * @param ds_ctx Pointer to the digital signature context to free. */ void esp_secure_cert_free_ds_ctx(esp_ds_data_ctx_t *ds_ctx); ================================================ FILE: esp_encrypted_img/test_apps/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags esp_secure_cert,0x3F,,,0x2000, nvs, data, nvs, , 0x4000, otadata, data, ota, , 0x2000, phy_init, data, phy, , 0x1000, factory, app, factory, , 0x10B000, # ota_0, app, ota_0, , 0x10B000, # ota_1, app, ota_1, , 0x14B000, ================================================ FILE: esp_encrypted_img/test_apps/pytest_esp_encrypted_img.py ================================================ import pytest import os @pytest.mark.parametrize( "marker", [ pytest.param("qemu", marks=pytest.mark.qemu), pytest.param("generic", marks=pytest.mark.generic), ], ) def test_esp_encrypted_img(dut, marker) -> None: binary_path = getattr(dut.app, "binary_path", None) if not binary_path or not os.path.exists(binary_path): pytest.skip(f"Build was skipped or binary not found: {binary_path}") dut.run_all_single_board_cases() ================================================ FILE: esp_encrypted_img/test_apps/sdkconfig.ci.ds_peripheral ================================================ # CI sdkconfig for testing DS peripheral based RSA decryption # This config is only supported on IDF >= 5.3 and targets with HMAC support CONFIG_IDF_TARGET="esp32c3" CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_ESP_TASK_WDT_INIT=n CONFIG_PRE_ENCRYPTED_RSA_USE_DS=y ================================================ FILE: esp_encrypted_img/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: esp_encrypted_img/test_apps/sdkconfig.defaults.esp32 ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n CONFIG_PRE_ENCRYPTED_OTA_USE_RSA=y CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES=n ================================================ FILE: esp_encrypted_img/test_apps/sdkconfig.defaults.esp32s3 ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n CONFIG_PRE_ENCRYPTED_OTA_USE_RSA=n CONFIG_PRE_ENCRYPTED_OTA_USE_ECIES=y ================================================ FILE: esp_encrypted_img/tools/esp_enc_img_gen.py ================================================ #!/usr/bin/env python # # Encrypted image generation tool. This tool helps in generating encrypted binary image # in pre-defined format with assistance of RSA-3072 bit key. # # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import argparse import os import sys from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend import hashlib # Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum esp_enc_img_magic = 0x0788b6cf GCM_KEY_SIZE = 32 # Header sizes MAGIC_SIZE = 4 ENC_GCM_KEY_SIZE = 384 KDF_SALT_SIZE = 32 SERVER_PUB_KEY_SIZE = 64 RESERVED_SIZE_ECC = ENC_GCM_KEY_SIZE - (SERVER_PUB_KEY_SIZE + KDF_SALT_SIZE) IV_SIZE = 16 BIN_SIZE_DATA = 4 AUTH_SIZE = 16 RESERVED_HEADER = (512 - (MAGIC_SIZE + ENC_GCM_KEY_SIZE + IV_SIZE + BIN_SIZE_DATA + AUTH_SIZE)) HMAC_KEY_SIZE = 32 PREDEFINED_SALT = b'\x0e\x21\x60\x64\x2d\xae\x76\xd3\x34\x48\xe4\x3d\x77\x20\x12\x3d' \ b'\x9f\x3b\x1e\xce\xb8\x8e\x57\x3a\x4e\x8f\x7f\xb9\x4f\xf0\xc8\x69' ITERATIONS = 2048 def generate_key_GCM(size: int, shared_secret: bytes, random_salt: bytes = None) -> tuple: if shared_secret is None: return os.urandom(int(size)), None else: if random_salt is None: random_salt = os.urandom(KDF_SALT_SIZE) # Perform HKDF key derivation using the random salt info = "_esp_enc_img_ecc" info_bytes = info.encode('utf-8') derived_key = HKDF( algorithm=hashes.SHA256(), length=size, salt=random_salt, info=info_bytes, backend=default_backend() ).derive(shared_secret) return derived_key, random_salt def generate_IV_GCM() -> bytes: return os.urandom(IV_SIZE) def encrypt_binary(plaintext: bytes, key: bytes, IV: bytes) -> tuple: encobj = AESGCM(key) ct = encobj.encrypt(IV, plaintext, None) return ct[:len(plaintext)], ct[len(plaintext):] def load_rsa_key(key_file_name: str) -> str: if key_file_name is None: print('No key file provided') raise SystemExit(1) if not os.path.exists(key_file_name): print('Error: Key file does not exist') raise SystemExit(1) with open(key_file_name, 'rb') as key_file: key_data = key_file.read() if b"-BEGIN RSA PRIVATE KEY" in key_data or b"-BEGIN PRIVATE KEY" in key_data: private_key = serialization.load_pem_private_key(key_data, password=None) public_key = private_key.public_key() elif b"-BEGIN PUBLIC KEY" in key_data: private_key = None public_key = serialization.load_pem_public_key(key_data) else: print("Error: Please specify encryption key in PEM format") raise SystemExit(1) return public_key def load_ecc_key(key_file_name: str) -> tuple: if key_file_name is None: print('No key file provided, generating new keypair') _, device_pub_key = generate_hmac_key() else: with open(key_file_name, 'rb') as key_file: device_pub_key = serialization.load_pem_public_key(key_file.read(), default_backend()) server_priv_key, server_pub_key = generate_random_ecc_keypair() shared_secret = perform_ecdh(server_priv_key, device_pub_key) return shared_secret, server_pub_key def encrypt(input_file: str, key_file_name: str, output_file: str, scheme: str) -> None: print('Encrypting image ...') with open(input_file, 'rb') as image: data = image.read() iv = generate_IV_GCM() if scheme == 'RSA-3072': public_key = load_rsa_key(key_file_name) gcm_key, _ = generate_key_GCM(GCM_KEY_SIZE, None, None) encrypted_gcm_key = public_key.encrypt(gcm_key, padding.PKCS1v15()) elif scheme == 'ECC-256': shared_secret, public_key = load_ecc_key(key_file_name) gcm_key, kdf_salt = generate_key_GCM(GCM_KEY_SIZE, shared_secret, None) ciphertext, authtag = encrypt_binary(data, gcm_key, iv) with open(output_file, 'wb') as image: image.write(esp_enc_img_magic.to_bytes(MAGIC_SIZE, 'little')) if scheme == 'RSA-3072': image.write((encrypted_gcm_key)) elif scheme == 'ECC-256': # Write the raw ECC public key to the file public_key_to_write = public_key.public_bytes( encoding=serialization.Encoding.X962, format=serialization.PublicFormat.UncompressedPoint ) # Public key is 65 bytes, first byte is 0x04 # Remove the first byte and make the size back to 64 public_key_to_write = public_key_to_write[1:] image.write(public_key_to_write) image.write(kdf_salt) image.write(bytearray(RESERVED_SIZE_ECC)) image.write(iv) image.write(len(ciphertext).to_bytes(BIN_SIZE_DATA, 'little')) image.write(authtag) image.write(bytearray(RESERVED_HEADER)) image.write(ciphertext) print('Done') def decrypt_binary(ciphertext: bytes, authTag: bytes, key: bytes, IV: bytes) -> bytes: encobj = AESGCM(key) plaintext = encobj.decrypt(IV, ciphertext + authTag, None) return plaintext def decrypt(input_file: str, key_file: str, output_file: str, scheme: str) -> None: print('Decrypting image ...') if key_file is not None: with open(key_file, 'rb') as key_file: private_key = serialization.load_pem_private_key(key_file.read(), password=None) else: print('Error: No key file provided for decryption') raise SystemExit(1) with open(input_file, 'rb') as file: recv_magic = file.read(MAGIC_SIZE) if (int.from_bytes(recv_magic, 'little') != esp_enc_img_magic): print('Error: Magic Verification Failed', file=sys.stderr) raise SystemExit(1) if scheme == 'RSA-3072': encrypted_gcm_key = file.read(ENC_GCM_KEY_SIZE) gcm_key = private_key.decrypt(encrypted_gcm_key, padding.PKCS1v15()) elif scheme == 'ECC-256': server_pub_key = file.read(SERVER_PUB_KEY_SIZE) server_pub_key = b'\x04' + server_pub_key try: server_pub_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), server_pub_key) except ValueError: print('Error: Invalid server public key format', file=sys.stderr) raise SystemExit(1) shared_secret = perform_ecdh(private_key, server_pub_key) kdf_salt = file.read(KDF_SALT_SIZE) gcm_key, _ = generate_key_GCM(GCM_KEY_SIZE, shared_secret, kdf_salt) _ = file.read(RESERVED_SIZE_ECC) print('Magic verified successfully') iv = file.read(IV_SIZE) bin_size = int.from_bytes(file.read(BIN_SIZE_DATA), 'little') auth = file.read(AUTH_SIZE) print('Binary size:', bin_size) if scheme == 'RSA-3072': file.read(RESERVED_HEADER) elif scheme == 'ECC-256': file.read(RESERVED_HEADER) enc_bin = file.read(bin_size) decrypted_binary = decrypt_binary(enc_bin, auth, gcm_key, iv) with open(output_file, 'wb') as file: file.write(decrypted_binary) print('Done') def generate_hmac_key() -> ec.EllipticCurvePublicKey: valid_ecc_key = False while not valid_ecc_key: # Generate a HMAC key for ECC-256 hmac_key = os.urandom(HMAC_KEY_SIZE) curve = ec.SECP256R1() raw_hmac = hashlib.pbkdf2_hmac('sha256', hmac_key, PREDEFINED_SALT, ITERATIONS) candidate_scalar = int.from_bytes(raw_hmac, byteorder='big') if candidate_scalar == 0: print('Candidate scalar is zero, retrying...') continue private_key = ec.derive_private_key(candidate_scalar, curve, default_backend()) public_key = private_key.public_key() # Check if the private key is valid try: private_key.private_numbers() except ValueError: print('Invalid private key, retrying...') continue print('ECC-256 key generated successfully') valid_ecc_key = True # Save the public key to a file with open('device_pub_key.pem', 'wb') as key_file: key_file.write(public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo )) # Also save the hmac key to a file with open('device_hmac_key.bin', 'wb') as hmac_file: hmac_file.write(hmac_key) return private_key, public_key def generate_random_ecc_keypair() -> tuple: # Generate a random ECC keypair curve = ec.SECP256R1() private_key = ec.generate_private_key(curve, default_backend()) public_key = private_key.public_key() print('Server ECC-256 keypair generated successfully') return private_key, public_key def generate_rsa_keypair() -> tuple: # Generate a random RSA keypair private_key = rsa.generate_private_key( public_exponent=65537, key_size=3072, backend=default_backend() ) public_key = private_key.public_key() # Save the public key to a file with open('rsa_pub_key.pem', 'wb') as key_file: key_file.write(public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo )) # Save the private key to a file with open('rsa_priv_key.pem', 'wb') as key_file: key_file.write(private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() )) print('Server RSA-3072 keypair generated successfully') return private_key, public_key def perform_ecdh(priv_key, pub_key) -> bytes: # Perform ECDH key exchange shared_key = priv_key.exchange(ec.ECDH(), pub_key) print('ECDH key exchange completed successfully') return shared_key def get_scheme(key_file: str) -> str: # Based on the keyfile provided, we can determine the scheme if key_file is not None: with open(key_file, 'rb') as key_file: key_data = key_file.read() try: # Try reading as private key private_key = serialization.load_pem_private_key(key_data, password=None) public_key = private_key.public_key() # If we can read the private key, check if it is RSA or ECC if isinstance(private_key, ec.EllipticCurvePrivateKey): scheme = 'ECC-256' elif isinstance(private_key, rsa.RSAPrivateKey): scheme = 'RSA-3072' else: print('Error: Unsupported key type') raise SystemExit(1) except Exception: # If we cannot read the private key, check if it is public key try: public_key = serialization.load_pem_public_key(key_data) if isinstance(public_key, ec.EllipticCurvePublicKey): scheme = 'ECC-256' elif isinstance(public_key, rsa.RSAPublicKey): scheme = 'RSA-3072' else: print('Error: Unsupported key type') raise SystemExit(1) except Exception: print('Error: Invalid key file format') raise SystemExit(1) else: scheme = 'ECC-256' return scheme def main() -> None: parser = argparse.ArgumentParser('Encrypted Image Tool') parser.add_argument('--generate_ecc_key', action='store_true', help='Generate ECC keypair and exit') parser.add_argument('--generate_rsa_key', action='store_true', help='Generate RSA keypair and exit') subparsers = parser.add_subparsers(dest='operation', help='run enc_image -h for additional help') encrypt_parser = subparsers.add_parser('encrypt', help='Encrypt a binary') encrypt_parser.add_argument('input_file', help='Input file to encrypt') encrypt_parser.add_argument('key_file', help='Public key for encryption (PEM format)') encrypt_parser.add_argument('output_file', help='Output file for encrypted image') decrypt_parser = subparsers.add_parser('decrypt', help='Decrypt an encrypted image') decrypt_parser.add_argument('input_file', help='Input file to decrypt') decrypt_parser.add_argument('key_file', help='Private key for decryption') decrypt_parser.add_argument('output_file', help='Output file for decrypted binary') args = parser.parse_args() if args.generate_ecc_key: generate_hmac_key() generate_random_ecc_keypair() print('Key generation completed successfully') raise SystemExit(0) if args.generate_rsa_key: generate_rsa_keypair() print('Key generation completed successfully') raise SystemExit(0) if not args.operation: parser.print_help() raise SystemExit(1) # Get the scheme from the key file scheme = get_scheme(args.key_file) # Supported schemes will be rsa and ecc supported_schemes = ['RSA-3072', 'ECC-256'] if scheme not in supported_schemes: print('Error: Unsupported scheme, supported schemes are:', supported_schemes) raise SystemExit(1) if (args.operation == 'encrypt'): encrypt(args.input_file, args.key_file, args.output_file, scheme) elif (args.operation == 'decrypt'): decrypt(args.input_file, args.key_file, args.output_file, scheme) else: print('Error: Invalid operation specified') raise SystemExit(1) if __name__ == '__main__': main() ================================================ FILE: esp_ext_part_tables/.build-test-rules.yml ================================================ esp_ext_part_tables/examples/basic: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 2) or (IDF_VERSION_MAJOR >= 6)) and IDF_TARGET == "linux" reason: Host test is enough, linux build support is from IDF v5.2 ================================================ FILE: esp_ext_part_tables/CMakeLists.txt ================================================ set(srcs "src/esp_ext_part_tables.c" "src/esp_mbr.c" "src/esp_mbr_utils.c") set(requires "log" "esp_common") if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0") list(APPEND requires "esp_blockdev") endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" REQUIRES ${requires}) ================================================ FILE: esp_ext_part_tables/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Thesis projects Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_ext_part_tables/README.md ================================================ # ESP External Partition Tables This component provides an API to parse and generate external partition tables. Currently only [MBR (Master boot record)](https://en.wikipedia.org/wiki/Master_boot_record) is supported. ## Features - Parse MBR partition tables from raw data (e.g., SD card, USB drive) - Generate and manipulate partition lists in memory - Deep copy and de-initialize partition lists - Access partition information (address, size, type, label) - Example projects included ## Example code ```c // loaded_mbr -> Pointer to an array of 512 bytes containing MBR loaded from somewhere (SD card, etc.) #include #include #include "esp_err.h" #include "esp_ext_part_tables.h" #include "esp_mbr.h" esp_err_t err = ESP_OK; esp_ext_part_list_t part_list = {0}; err = esp_mbr_parse((void*) loaded_mbr, &part_list, NULL); // Parse the array containing MBR and fill `esp_ext_part_list_t part_list` structure if (err != ESP_OK) { return err; } esp_ext_part_list_item_t* item; item = esp_ext_part_list_item_head(&part_list); // Get the first partition for (int i = 0; item != NULL; i++) { printf("Partition %d:\n address: %" PRIu64 "\n size: %" PRIu64 "\n type: %" PRIu32 "\n, label: %s\n", i, item->info.address, item->info.size, (uint32_t) item->info.type, item->label ? item->label : ""); // item->info.type is of `esp_ext_part_type_known_t` enum type item = esp_ext_part_list_item_next(item); // Get the next partition } // ... // Clean up when done esp_ext_part_list_deinit(&part_list); ``` ## More Examples Runnable example projects can be found in [`examples/`](/esp_ext_part_tables/examples/) folder. ## API Reference See [`esp_ext_part_tables.h`](/esp_ext_part_tables/include/esp_ext_part_tables.h) for the full API documentation. More advanced API documentation can be found here: [`esp_mbr.h`](/esp_ext_part_tables/include/esp_mbr.h), [`esp_mbr_utils.h`](/esp_ext_part_tables/include/esp_mbr_utils.h). ================================================ FILE: esp_ext_part_tables/examples/basic/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp_ext_part_tables_example_basic) ================================================ FILE: esp_ext_part_tables/examples/basic/README.md ================================================ # esp_ext_part_tables basic example This example demonstrates how to use an ESP32 to read and parse the Master Boot Record (MBR) from a (micro)SD card. It shows how to extract and display information about the card's partitions, such as their type, size, and starting sector, using the ESP-IDF framework. ## Requirements - any ESP32 board which supports SPI with a (micro)SD card slot or breakout board connected - formatted (micro)SD card using MBR (not GPT) as a partition table ## Build and Flash The example runs on any ESP development board with SD card connected via SPI (use `idf.py menuconfig` -> `Example config` to set used GPIO pins). To build and run the code on e.g. ESP32-S3, use: ``` idf.py set-target esp32s3 idf.py menuconfig idf.py build flash monitor ``` *NOTE 1*: This example uses SDSPI instead of SDMMC to connect to SD card due to more ESP32 devices supporting it but it doesn't matter which one one you use in your project to load the first sector containing MBR. *NOTE 2*: ESP-IDF 5.1 or newer environment must be set properly before running the example. ## Example output MicroSD card used in the example showcase has capacity 64GiB and was formatted using Windows Disk Management program to two ~32GiB FAT32 partitions as seen in the picture: ![Screenshot of Disk Management Windows program showing removable Disk 0 (a microSD card) containing 2 FAT32 partitions both roughly 30GB is size](/esp_ext_part_tables/examples/basic/assets/two_fat_partitions.png) The example code parsed the MBR and printed the loaded partition information (`type 4` corresponds to `ESP_EXT_PART_TYPE_FAT32` in `esp_ext_part_type_known_t` enum, etc.). The second task generated MBR from `esp_ext_part_list_t` definition and then parsed it again and printed the output. ```log I (275) esp_ext_part_tables_example_basic: Example started I (275) esp_ext_part_tables_example_basic: Starting MBR parsing example task I (335) esp_ext_part_tables_example_basic: MBR loaded successfully I (335) esp_ext_part_tables_example_basic: MBR parsed successfully Partition 0: LBA start sector: 2048, address: 1048576, sector count: 59392000, size: 30408704000, type: FAT32 Partition 1: LBA start sector: 59394048, address: 30409752576, sector count: 62746624, size: 32126271488, type: FAT32 I (365) esp_ext_part_tables_example_basic: Starting MBR generation example task I (365) esp_ext_part_tables_example_basic: MBR generated successfully Partition 0: LBA start sector: 2048, address: 1048576, sector count: 7953, size: 4071936, type: FAT12 Partition 1: LBA start sector: 10240, address: 5242880, sector count: 10240, size: 5242880, type: FAT12 I (395) esp_ext_part_tables_example_basic: Example ended ``` Your output will be different based on (micro)SD card used, partitioning and formatting applied. ## Documentation See the esp_ext_part_tables component's README.md file. ================================================ FILE: esp_ext_part_tables/examples/basic/main/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) set(requires heap) if(NOT ${target} STREQUAL "linux") list(APPEND requires driver sdmmc esp_driver_sdspi) endif() idf_component_register( SRCS "main.c" "example_utils.c" REQUIRES ${requires} ) ================================================ FILE: esp_ext_part_tables/examples/basic/main/Kconfig.projbuild ================================================ menu "Example Configuration" depends on !IDF_TARGET_LINUX config EXAMPLE_PIN_MOSI int "MOSI GPIO number" default 15 if IDF_TARGET_ESP32 default 35 if IDF_TARGET_ESP32S2 default 4 if IDF_TARGET_ESP32S3 default 5 if IDF_TARGET_ESP32H2 default 36 if IDF_TARGET_ESP32P4 default 4 # C3 and others config EXAMPLE_PIN_MISO int "MISO GPIO number" default 2 if IDF_TARGET_ESP32 default 37 if IDF_TARGET_ESP32S2 default 5 if IDF_TARGET_ESP32S3 default 0 if IDF_TARGET_ESP32H2 default 47 if IDF_TARGET_ESP32P4 default 6 # C3 and others config EXAMPLE_PIN_CLK int "CLK GPIO number" default 14 if IDF_TARGET_ESP32 default 36 if IDF_TARGET_ESP32S2 default 2 if IDF_TARGET_ESP32S3 default 4 if IDF_TARGET_ESP32H2 default 53 if IDF_TARGET_ESP32P4 default 5 # C3 and others config EXAMPLE_PIN_CS int "CS GPIO number" default 13 if IDF_TARGET_ESP32 default 34 if IDF_TARGET_ESP32S2 default 8 if IDF_TARGET_ESP32S3 default 33 if IDF_TARGET_ESP32P4 default 1 # C3 and others config EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO depends on SOC_SDMMC_IO_POWER_EXTERNAL bool "SD power supply comes from internal LDO IO (READ HELP!)" default n help Only needed when the SD card is connected to specific IO pins which can be used for high-speed SDMMC. Please read the schematic first and check if the SD VDD is connected to any internal LDO output. Unselect this option if the SD card is powered by an external power supply. config EXAMPLE_SD_PWR_CTRL_LDO_IO_ID depends on SOC_SDMMC_IO_POWER_EXTERNAL && EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO int "LDO ID" default 4 if IDF_TARGET_ESP32P4 help Please read the schematic first and input your LDO ID. endmenu menu "Example Configuration" depends on IDF_TARGET_LINUX endmenu ================================================ FILE: esp_ext_part_tables/examples/basic/main/example_utils.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "esp_err.h" #include "esp_log.h" #if !CONFIG_IDF_TARGET_LINUX #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" #if SOC_SDMMC_IO_POWER_EXTERNAL #include "sd_pwr_ctrl_by_on_chip_ldo.h" #endif // SOC_SDMMC_IO_POWER_EXTERNAL #endif // !CONFIG_IDF_TARGET_LINUX #include "esp_ext_part_tables.h" static const char *TAG = "esp_ext_part_tables_example_basic_utils"; #define PIN_NUM_MISO CONFIG_EXAMPLE_PIN_MISO #define PIN_NUM_MOSI CONFIG_EXAMPLE_PIN_MOSI #define PIN_NUM_CLK CONFIG_EXAMPLE_PIN_CLK #define PIN_NUM_CS CONFIG_EXAMPLE_PIN_CS #if CONFIG_IDF_TARGET_LINUX // MBR with 2 FAT12 entries uint8_t mbr_bin[512] = { [440] = 0xc4, 0x9d, 0x92, 0x4d, 0x00, 0x00, 0x00, 0x20, 0x21, 0x00, 0x01, 0x9e, 0x2f, 0x00, 0x00, 0x08, 0x00, 0x00, 0x11, 0x1f, 0x00, 0x00, 0x00, 0xa2, 0x23, 0x00, 0x01, 0x46, 0x05, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa }; unsigned int mbr_bin_len = 512; #endif // CONFIG_IDF_TARGET_LINUX esp_err_t load_first_sector_from_sd_card(void *mbr_buffer) { ESP_LOGI(TAG, "Loading first sector from SD card"); #if CONFIG_IDF_TARGET_LINUX memcpy(mbr_buffer, mbr_bin, mbr_bin_len); #else // This function loads the first sector (MBR) from the SD card into the provided buffer // It uses SDSPI but can be adapted for SDMMC as well esp_err_t ret = ESP_OK; sdmmc_host_t host = SDSPI_HOST_DEFAULT(); // For SoCs where the SD power can be supplied both via an internal or external (e.g. on-board LDO) power supply. // When using specific IO pins (which can be used for ultra high-speed SDMMC) to connect to the SD card // and the internal LDO power supply, we need to initialize the power supply first. #if CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO sd_pwr_ctrl_ldo_config_t ldo_config = { .ldo_chan_id = CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_IO_ID, }; sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create a new on-chip LDO power control driver"); return; } host.pwr_ctrl_handle = pwr_ctrl_handle; #endif spi_bus_config_t bus_cfg = { .mosi_io_num = PIN_NUM_MOSI, .miso_io_num = PIN_NUM_MISO, .sclk_io_num = PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4000, }; ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize bus."); return ret; } sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); slot_config.gpio_cs = PIN_NUM_CS; slot_config.host_id = host.slot; ret = host.init(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize host."); spi_bus_free(host.slot); return ret; } int slot = -1; ret = sdspi_host_init_device((const sdspi_device_config_t *)&slot_config, &slot); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize SPI device."); spi_bus_free(host.slot); return ret; } host.slot = slot; sdmmc_card_t card = {0}; ret = sdmmc_card_init(&host, &card); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize SD card."); spi_bus_free(host.slot); return ret; } ret = sdmmc_read_sectors(&card, mbr_buffer, 0, 1); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read first sector from SD card."); spi_bus_free(host.slot); return ret; } #endif // !CONFIG_IDF_TARGET_LINUX return ESP_OK; } char *parsed_type_to_str(uint8_t type) { switch (type) { case ESP_EXT_PART_TYPE_NONE: return "none/empty"; case ESP_EXT_PART_TYPE_FAT12: return "FAT12"; case ESP_EXT_PART_TYPE_FAT16: return "FAR16"; case ESP_EXT_PART_TYPE_FAT32: return "FAT32"; case ESP_EXT_PART_TYPE_LITTLEFS: return "LittleFS"; default: break; } return "unknown"; } ================================================ FILE: esp_ext_part_tables/examples/basic/main/example_utils.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif esp_err_t load_first_sector_from_sd_card(void *mbr_buffer); char *parsed_type_to_str(uint8_t type); #ifdef __cplusplus } #endif ================================================ FILE: esp_ext_part_tables/examples/basic/main/idf_component.yml ================================================ dependencies: idf: ">=5.1" esp_ext_part_tables: version: "*" override_path: '../../..' ================================================ FILE: esp_ext_part_tables/examples/basic/main/main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_err.h" #include "esp_log.h" #include "esp_heap_caps.h" #include "esp_ext_part_tables.h" #include "esp_mbr.h" #include "esp_mbr_utils.h" #include "example_utils.h" static const char *TAG = "esp_ext_part_tables_example_basic"; void print_loaded_ext_partitions(esp_ext_part_list_item_t *head) { esp_ext_part_list_item_t *it = head; int i = 0; do { printf("Partition %d:\n\tLBA start sector: %" PRIu64 ", address: %" PRIu64 ",\n\tsector count: %" PRIu64 ", size: %" PRIu64 ",\n\ttype: %s\n\n", i, esp_ext_part_bytes_to_sector_count(it->info.address, ESP_EXT_PART_SECTOR_SIZE_512B), it->info.address, esp_ext_part_bytes_to_sector_count(it->info.size, ESP_EXT_PART_SECTOR_SIZE_512B), it->info.size, parsed_type_to_str(it->info.type)); i++; } while ((it = esp_ext_part_list_item_next(it)) != NULL); fflush(stdout); } void esp_ext_part_tables_mbr_parse_example_task(void *pvParameters) { TaskHandle_t main_task_handle; ESP_LOGI(TAG, "Starting MBR parsing example task"); esp_err_t err; // Allocate memory for the MBR mbr_t *mbr = (mbr_t *) heap_caps_malloc(sizeof(mbr_t), (MALLOC_CAP_DMA | MALLOC_CAP_8BIT)); if (mbr == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for MBR"); goto end_task; } // Load the first sector (MBR) from the SD card into the allocated buffer err = load_first_sector_from_sd_card(mbr); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to load MBR from SD card: %s", esp_err_to_name(err)); free(mbr); goto end_task; } ESP_LOGI(TAG, "MBR loaded successfully"); // Parse the MBR to get the partition list esp_ext_part_list_t part_list = {0}; err = esp_mbr_parse((void *) mbr, &part_list, NULL); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to parse MBR: %s", esp_err_to_name(err)); free(mbr); esp_ext_part_list_deinit(&part_list); goto end_task; } free(mbr); // Free the MBR buffer after parsing as it is no longer needed ESP_LOGI(TAG, "MBR parsed successfully"); // Get the first partition esp_ext_part_list_item_t *it = esp_ext_part_list_item_head(&part_list); if (it == NULL) { ESP_LOGE(TAG, "No partitions found in the MBR"); esp_ext_part_list_deinit(&part_list); goto end_task; } // Print the loaded partition list print_loaded_ext_partitions(it); // Deinitialize the partition list esp_ext_part_list_deinit(&part_list); ESP_LOGI(TAG, "MBR parsing example task completed successfully"); // Notify the main task that the example is done and end the current task end_task: main_task_handle = (TaskHandle_t) pvParameters; xTaskNotifyGive(main_task_handle); vTaskDelete(NULL); // Delete the current task } void esp_ext_part_tables_mbr_generate_example_task(void *pvParameters) { TaskHandle_t main_task_handle; ESP_LOGI(TAG, "Starting MBR generation example task"); esp_err_t err; esp_ext_part_list_t part_list = {0}; esp_mbr_generate_extra_args_t mbr_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, .alignment = ESP_EXT_PART_ALIGN_1MiB }; // 2 FAT12 partitions (random parameters) esp_ext_part_list_item_t item1 = { .info = { // Original MBR starts at 2048, but we use 8 for testing -> .address = esp_ext_part_sector_count_to_bytes(8, mbr_args.sector_size), // Should be round up to 2048 sectors (aligned to 1MiB) due to defined sector size and alignment in `esp_mbr_generate_extra_args_t args` above .size = esp_ext_part_sector_count_to_bytes(7953, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_FAT12, .label = NULL, } }; err = esp_ext_part_list_insert(&part_list, &item1); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to insert first partition: %s", esp_err_to_name(err)); goto end_task; } esp_ext_part_list_item_t item2 = { .info = { .address = esp_ext_part_sector_count_to_bytes(10240, mbr_args.sector_size), .size = esp_ext_part_sector_count_to_bytes(10240, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_FAT12, .label = NULL, } }; esp_ext_part_list_insert(&part_list, &item2); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to insert second partition: %s", esp_err_to_name(err)); goto end_task; } mbr_t *mbr = (mbr_t *) calloc(1, sizeof(mbr_t)); if (mbr == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for MBR"); esp_ext_part_list_deinit(&part_list); goto end_task; } // Generate the MBR err = esp_mbr_generate(mbr, &part_list, &mbr_args); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to generate MBR: %s", esp_err_to_name(err)); free(mbr); esp_ext_part_list_deinit(&part_list); goto end_task; } ESP_LOGI(TAG, "MBR generated successfully"); // Deinitialize the partition list esp_ext_part_list_deinit(&part_list); esp_ext_part_list_t part_list_from_gen_mbr = {0}; // Parse the generated MBR to get the partition list err = esp_mbr_parse((void *) mbr, &part_list_from_gen_mbr, NULL); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to parse generated MBR: %s", esp_err_to_name(err)); free(mbr); goto end_task; } free(mbr); // Free the MBR buffer after parsing as it is no longer needed // Get the first partition esp_ext_part_list_item_t *it = esp_ext_part_list_item_head(&part_list_from_gen_mbr); if (it == NULL) { ESP_LOGE(TAG, "No partitions found in the MBR"); esp_ext_part_list_deinit(&part_list_from_gen_mbr); goto end_task; } // Print the loaded partition list print_loaded_ext_partitions(it); // Deinitialize the partition list esp_ext_part_list_deinit(&part_list_from_gen_mbr); ESP_LOGI(TAG, "MBR generation example task completed successfully"); // Notify the main task that the example is done and end the current task end_task: main_task_handle = (TaskHandle_t) pvParameters; xTaskNotifyGive(main_task_handle); vTaskDelete(NULL); // Delete the current task } void app_main(void) { ESP_LOGI(TAG, "Example started"); TaskHandle_t main_task_handle = xTaskGetCurrentTaskHandle(); // Get the handle of the main task // Create new tasks with bigger stack size just in case xTaskCreate(esp_ext_part_tables_mbr_parse_example_task, "esp_ext_part_tables_mbr_parse_example_task", 4096, (void *) main_task_handle, 5, NULL); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for the example task to complete xTaskCreate(esp_ext_part_tables_mbr_generate_example_task, "esp_ext_part_tables_mbr_generate_example_task", 4096, (void *) main_task_handle, 5, NULL); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for the example task to complete ESP_LOGI(TAG, "Example ended"); } ================================================ FILE: esp_ext_part_tables/examples/basic/pytest_esp_ext_part_tables_example_basic.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.host_test @idf_parametrize('target', ['linux'], indirect=['target']) def test_esp_ext_part_tables_example_basic_linux(dut: Dut) -> None: dut.expect_exact('Starting MBR parsing example task') dut.expect_exact('MBR parsing example task completed successfully') dut.expect_exact('Starting MBR generation example task') dut.expect_exact('MBR generation example task completed successfully') ================================================ FILE: esp_ext_part_tables/examples/basic/sdkconfig.defaults.ci ================================================ CONFIG_IDF_TARGET="linux" CONFIG_IDF_TARGET_LINUX=y ================================================ FILE: esp_ext_part_tables/idf_component.yml ================================================ version: "0.2.0" description: ESP External Partition Tables url: https://github.com/espressif/idf-extra-components/tree/master/esp_ext_part_tables issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git dependencies: idf: version: ">=5.1" ================================================ FILE: esp_ext_part_tables/include/esp_ext_part_tables.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #include "esp_err.h" #include "esp_idf_version.h" #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #include "esp_blockdev.h" #endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #if __has_include() #include #else #include "sys/queue.h" #endif #ifdef __cplusplus extern "C" { #endif typedef enum { ESP_EXT_PART_SECTOR_SIZE_UNKNOWN = 0, // Unknown sector size ESP_EXT_PART_SECTOR_SIZE_512B = 512, // 512 B sector size (SD, eMMC, USB flash, legacy or emulated mode HDD/SSD) ESP_EXT_PART_SECTOR_SIZE_2KiB = 2048, // 2 KiB sector size (optical disks) ESP_EXT_PART_SECTOR_SIZE_4KiB = 4096, // 4 kiB sector size (modern HDD/SSD) } esp_ext_part_sector_size_t; typedef enum { ESP_EXT_PART_ALIGN_NONE = 0, // No alignment ESP_EXT_PART_ALIGN_4KiB = 4096, // 4 KiB alignment ESP_EXT_PART_ALIGN_1MiB = (1024 * 1024), // 1 MiB alignment } esp_ext_part_align_t; typedef enum __attribute__((packed)) { ESP_EXT_PART_TYPE_NONE = 0x00, ESP_EXT_PART_TYPE_FAT12, ESP_EXT_PART_TYPE_FAT16, /*!< FAT16 with LBA addressing */ ESP_EXT_PART_TYPE_FAT32, /*!< FAT32 with LBA addressing */ ESP_EXT_PART_TYPE_LITTLEFS, /*!< Possibly LittleFS (MBR CHS field => LittleFS block size hack) */ // Note: The following types are not supported, but we can return a type for them ESP_EXT_PART_TYPE_LINUX_ANY, /*!< Linux partition (any type) */ ESP_EXT_PART_TYPE_EXFAT_OR_NTFS, /*!< Not supported, but we can return a type for it */ ESP_EXT_PART_TYPE_GPT_PROTECTIVE_MBR, /*!< Not supported, but we can return a type for it */ } esp_ext_part_type_known_t; typedef enum { ESP_EXT_PART_FLAG_NONE = 0, ESP_EXT_PART_FLAG_ACTIVE = 1 << 0, /*!< Active / bootable partition */ ESP_EXT_PART_FLAG_EXTRA = 1 << 1, /*!< Additional information stored in `extra` field (e.g. LittleFS block size stored in CHS hack) */ } esp_ext_part_flags_t; typedef enum { ESP_EXT_PART_LIST_FLAG_NONE = 0, ESP_EXT_PART_LIST_FLAG_READ_ONLY = 1 << 0, /*!< Read-only partition list */ } esp_ext_part_list_flags_t; typedef enum { ESP_EXT_PART_LIST_SIGNATURE_MBR, /*!< MBR signature type */ } esp_ext_part_signature_type_t; typedef struct { uint32_t data[1]; esp_ext_part_signature_type_t type; } esp_ext_part_list_signature_t; typedef struct { uint64_t address; /*!< Start address in bytes */ uint64_t size; /*!< Size in bytes */ uint64_t extra; /*!< Extra information (e.g. LittleFS block size stored in CHS hack, etc.) */ char *label; esp_ext_part_flags_t flags; /*!< Flags for the partition */ uint8_t type; /*!< Known partition type for this component (usually a part of `esp_ext_part_type_known_t`) */ } esp_ext_part_t; typedef struct esp_ext_part_list_item_ { esp_ext_part_t info; SLIST_ENTRY(esp_ext_part_list_item_) next; } esp_ext_part_list_item_t; typedef struct { esp_ext_part_list_signature_t signature; /*!< Disk signature or identifier */ SLIST_HEAD(esp_ext_part_list_head_, esp_ext_part_list_item_) head; /*!< Head of the partition list */ esp_ext_part_list_flags_t flags; /*!< Flags for the partition list */ esp_ext_part_sector_size_t sector_size; /*!< Sector size (storage medium property) */ } esp_ext_part_list_t; /** * @brief Convert bytes to sector count based on the sector size. * * This function performs a ceiling division to ensure that any remaining bytes * that do not fill a complete sector are counted as an additional sector. * * @param total_bytes Total number of bytes. * @param sector_size Size of a single sector. * * @return Number of sectors or 0 if the sector size is unknown to avoid a division by zero. */ uint64_t esp_ext_part_bytes_to_sector_count(uint64_t total_bytes, esp_ext_part_sector_size_t sector_size); /** * @brief Convert sector count to bytes based on the sector size. * * @param sector_count Number of sectors. * @param sector_size Size of a single sector. * * @return Total size in bytes. */ uint64_t esp_ext_part_sector_count_to_bytes(uint64_t sector_count, esp_ext_part_sector_size_t sector_size); /** * @brief Deinitialize an external partition list structure and free all resources. * * This function releases all the memory and resources associated with the partition list referenced by 'part_list' parameter. * * @note This function is not thread-safe. * * @param[in] part_list Pointer to the partition list structure to deinitialize. * * @return * - ESP_OK: Deinitialization was successful. * - ESP_ERR_INVALID_ARG: `part_list` is NULL. */ esp_err_t esp_ext_part_list_deinit(esp_ext_part_list_t *part_list); /** * @brief Insert a partition item into an external partition list. * * This function inserts a copy of the given partition item into the partition list. * * @note This function is not thread-safe. * * @param[in] part_list Pointer to the partition list structure. * @param[in] item Pointer to the partition item to insert (will be copied). * * @return * - ESP_OK: Insertion was successful. * - ESP_ERR_INVALID_ARG: `part_list` or `item` is NULL. * - ESP_ERR_NO_MEM: Memory allocation failed. */ esp_err_t esp_ext_part_list_insert(esp_ext_part_list_t *part_list, esp_ext_part_list_item_t *item); /** * @brief Deep copy an external partition list. * * This function creates a deep copy of the source partition list into the destination partition list. * It allocates memory for the destination list and copies all items, including their labels. * * @note This function is not thread-safe. * * @param[out] dst Pointer to the destination partition list structure (must be allocated before but not initialized, i.e. "empty"). * @param[in] src Pointer to the source partition list structure to copy from. * * @return * - ESP_OK: Deep copy was successful. * - ESP_ERR_INVALID_ARG: `dst` or `src` is NULL. * - ESP_ERR_NO_MEM: Memory allocation failed. */ esp_err_t esp_ext_part_list_deep_copy(esp_ext_part_list_t *dst, esp_ext_part_list_t *src); /** * @brief Get the head (first item) of an external partition list. * * @param[in] part_list Pointer to the partition list structure. * * @return Pointer to the first partition list item, or NULL if the list is empty or uninitialized. */ esp_ext_part_list_item_t *esp_ext_part_list_item_head(esp_ext_part_list_t *part_list); /** * @brief Get the next item in an external partition list. * * @param[in] item Pointer to the current partition list item. * * @return Pointer to the next partition list item, or NULL if there are no more items. */ esp_ext_part_list_item_t *esp_ext_part_list_item_next(esp_ext_part_list_item_t *item); /** * @brief Get the signature of an external partition list. * * This function retrieves the disk signature or identifier from the partition list. * * @param[in] part_list Pointer to the partition list structure. * @param[out] signature Pointer to a buffer where the signature will be stored. * * @return * - ESP_OK: Signature retrieval was successful. * - ESP_ERR_INVALID_ARG: `part_list` or signature is NULL. */ esp_err_t esp_ext_part_list_signature_get(esp_ext_part_list_t *part_list, void *signature); /** * @brief Set the signature of an external partition list. * * This function sets the disk signature or identifier for the partition list. * * @param[in] part_list Pointer to the partition list structure. * @param[in] signature Pointer to the signature data to set. * @param[in] type Type of the signature (e.g., MBR). * * @return * - ESP_OK: Signature was successfully set. * - ESP_ERR_INVALID_ARG: `part_list` or `signature` is NULL. * - ESP_ERR_NOT_SUPPORTED: Unsupported signature type. */ esp_err_t esp_ext_part_list_signature_set(esp_ext_part_list_t *part_list, const void *signature, esp_ext_part_signature_type_t type); #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) /** * @brief Read a aprtition table and from a block device handle and parse it. * * This function reads the partition table from the specified block device and populates the provided partition list structure. * The type of partition table to read is specified by the 'type' parameter. * Additional arguments for parsing can be provided through the 'extra_args' parameter. * * @note This function is not thread-safe. * * @param[in] handle Block device handle to read from. * @param[out] part_list Pointer to the partition list structure to populate from the partition table. * @param[in] type Type of partition table to read (e.g., MBR). * @param[in] extra_args Pointer to additional arguments for parsing dependent on the partition type (optional, can be NULL). * * @return * - ESP_OK: Partition list was successfully loaded. * - ESP_ERR_INVALID_ARG: `handle` or `part_list` is NULL. * - ESP_ERR_NOT_SUPPORTED: Unsupported partition table type. * - ESP_ERR_NO_MEM: Memory allocation failed. * - propagated errors from BDL operations or partition table parsing functions. */ esp_err_t esp_ext_part_list_bdl_read(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args); /** * @brief Generate a partition table and write it to a block device handle. * * This function writes the provided partition list to the specified block device. * The type of partition table to write is specified by the 'type' parameter. * Additional arguments for generation can be provided through the 'extra_args' parameter. * * @note This function is not thread-safe. * * @param[in] handle Block device handle to write to. * @param[in] part_list Pointer to the partition list structure generate the partition table from. * @param[in] type Type of partition table to write (e.g., MBR). * @param[in] extra_args Pointer to additional arguments for generation dependent on the partition type (optional, can be NULL). * * @return * - ESP_OK: Partition list was successfully written. * - ESP_ERR_INVALID_ARG: `handle` or `part_list` is NULL. * - ESP_ERR_NOT_SUPPORTED: Unsupported partition table type. * - ESP_ERR_NO_MEM: Memory allocation failed. * - propagated errors from BDL operations or partition table generation functions. */ esp_err_t esp_ext_part_list_bdl_write(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args); #endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #ifdef __cplusplus } #endif ================================================ FILE: esp_ext_part_tables/include/esp_mbr.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #include "esp_err.h" #include "esp_ext_part_tables.h" #ifdef __cplusplus extern "C" { #endif #define MBR_SIZE 512 #define MBR_SIGNATURE 0xAA55 #define MBR_COPY_PROTECTED 0x5A5A #define MBR_PARTITION_TABLE_OFFSET 0x1BE #define MBR_PARTITION_STATUS_ACTIVE 0x80 #define MBR_MAX_PARTITION_COUNT 4 // MBR partition entry structure - https://en.wikipedia.org/wiki/Master_boot_record#Partition_table_entries #pragma pack(push, 1) typedef struct { uint8_t status; union { struct { uint8_t h_start; uint16_t cs_start; }; uint8_t chs_start[3]; }; uint8_t type; union { struct { uint8_t h_end; uint16_t cs_end; }; uint8_t chs_end[3]; }; uint32_t lba_start; uint32_t sector_count; } mbr_partition_t; #pragma pack(pop) // MBR structure - https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout #pragma pack(push, 1) typedef struct { union { uint8_t bootstrap_code_classical[446]; struct { uint8_t bootstrap_code_modern_part1[218]; uint16_t _reserved; uint8_t original_physical_drive; uint8_t seconds; uint8_t minutes; uint8_t hours; uint8_t bootstrap_code_modern_part2[216]; uint32_t disk_signature; uint16_t copy_protected; }; }; mbr_partition_t partition_table[4]; uint16_t boot_signature; } mbr_t; #pragma pack(pop) typedef struct { esp_ext_part_sector_size_t sector_size; // Sector size hint, pulled from a storage device driver query bool (*esp_mbr_parse_custom_supported_partition_types)(uint8_t, uint8_t *); // Custom function for parsing supported MBR partition types, optional } esp_mbr_parse_extra_args_t; typedef struct { esp_ext_part_sector_size_t sector_size; // Sector size hint for correct LBA alignment esp_ext_part_align_t alignment; // Alignment hint for correct LBA alignment bool keep_signature; // If true, the disk signature will be preserved in the generated MBR and not overwritten with a random value uint8_t (*esp_mbr_generate_custom_supported_partition_types)(uint8_t); // Custom function for generating supported MBR partition types, optional } esp_mbr_generate_extra_args_t; /** * @brief Parses a Master Boot Record (MBR) buffer and extracts partition information. * * This function reads the provided MBR buffer, validates its signature, and populates * the given partition list structure with the partition entries found in the MBR. * Additional parsing options can be provided via the extra_args parameter. * * @note This function is not thread-safe. * * @param[in] mbr_buf Pointer to a buffer containing the raw MBR data (must be at least `MBR_SIZE` bytes and start of the MBR must align with start of the buffer). * @param[out] part_list Pointer to the partition list structure to be filled with parsed entries. * @param[in] extra_args Optional extra arguments for parsing (can be NULL for defaults). * * @return * - ESP_OK: Parsing was successful. * - ESP_ERR_INVALID_ARG: Invalid arguments were provided. * - ESP_ERR_NOT_FOUND: MBR signature not found or invalid MBR. * - ESP_ERR_NO_MEM: Memory allocation failed during parsing. * - Other error codes from `esp_ext_part_list_insert`. */ esp_err_t esp_mbr_parse(void *mbr_buf, esp_ext_part_list_t *part_list, esp_mbr_parse_extra_args_t *extra_args); /** * @brief Generates a Master Boot Record (MBR) from a partition list. * * This function fills the provided MBR structure based on the given partition list. * It sets up the partition table, disk signature, and other MBR fields. Generation * options such as sector size, alignment, and signature preservation can be specified * via the extra_args parameter. * * @note This function is not thread-safe. * * @param[out] mbr Pointer to the blank MBR structure to be filled (must already be allocated and be at least `MBR_SIZE` bytes). * @param[in] part_list Pointer to the partition list structure containing partition entries to encode. * @param[in] extra_args Optional extra arguments for generation (can be NULL for defaults). * * @return * - ESP_OK: Generation was successful. * - ESP_ERR_INVALID_ARG: Invalid arguments were provided. * - ESP_ERR_INVALID_STATE: Error filling partition entry. * - ESP_ERR_NOT_SUPPORTED: Partition address or size (sector count) exceeds 32-bit limit of MBR. * - Other error codes from `esp_ext_part_list_signature_get` or `esp_mbr_partition_set`. */ esp_err_t esp_mbr_generate(mbr_t *mbr, esp_ext_part_list_t *part_list, esp_mbr_generate_extra_args_t *extra_args); /** * @brief Sets a partition entry in the MBR (Master Boot Record). * * This function updates the specified partition entry in the provided MBR structure * with the information from the given partition list item. Additional arguments for * partition generation must be supplied via the extra_args parameter. * * @note This function is not thread-safe. * * @warning If the partition entry is empty (i.e., `item->info.type` is `ESP_EXT_PART_TYPE_NONE`), it will be cleared in the MBR. * If there is an empty gap between partition entries, partition entries after the gap will most likely be ignored when the MBR is parsed (MBR does not allow gaps in the partition table). * To avoid this, you can use `esp_mbr_remove_gaps_between_partiton_entries()` function to remove gaps in the MBR partition table. * * @param[in,out] mbr Pointer to the MBR structure to be updated. * @param[in] partition_index Index of the partition entry to set (0-3). * @param[in] item Pointer to the partition list item structure containing partition information. * @param[in] extra_args Extra arguments for partition entry setting (required). * * @return * - ESP_OK: Success. * - ESP_ERR_INVALID_ARG: Invalid arguments were provided. * - ESP_ERR_INVALID_STATE: Error filling partition entry. * - ESP_ERR_NOT_SUPPORTED: Partition address or size (sector count) exceeds 32-bit limit of MBR. */ esp_err_t esp_mbr_partition_set(mbr_t *mbr, uint8_t partition_index, esp_ext_part_list_item_t *item, esp_mbr_generate_extra_args_t *extra_args); /** * @brief Removes gaps in the MBR partition table by shifting partitions. * * @note This function is not thread-safe. * * @param[in,out] mbr Pointer to the MBR structure to be updated. * @return * - ESP_OK: Success. * - ESP_ERR_INVALID_ARG: Invalid pointer to MBR structure. */ esp_err_t esp_mbr_remove_gaps_between_partiton_entries(mbr_t *mbr); #ifdef __cplusplus } #endif ================================================ FILE: esp_ext_part_tables/include/esp_mbr_utils.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_ext_part_tables.h" #ifdef __cplusplus extern "C" { #endif #define MBR_CHS_HEADS 255 #define MBR_CHS_SECTORS_PER_TRACK 63 #define MBR_CHS_MAX_CYLINDER 1023 #define MBR_CHS_MAX_HEAD 254 #define MBR_CHS_MAX_SECTOR 63 // Helper functions for MBR CHS conversion and LBA alignment /** * @brief Set a 3-byte CHS array from a 24-bit value. * * @param[out] chs 3-byte array to store the CHS value. * @param[in] val 24-bit value representing CHS. */ void esp_mbr_chs_arr_val_set(uint8_t chs[3], uint32_t val); /** * @brief Get a 24-bit value from a 3-byte CHS array. * * @param[in] chs 3-byte array containing the CHS value. * @return 24-bit value representing CHS stored in `uint32_t`. */ uint32_t esp_mbr_chs_arr_val_get(const uint8_t chs[3]); /** * @brief Convert an LBA value to a 3-byte CHS array. * * @param[out] chs 3-byte array to store the CHS value. * @param[in] lba Logical Block Address to convert. */ void esp_mbr_lba_to_chs_arr(uint8_t chs[3], uint32_t lba); /** * @brief Align an LBA value according to sector size and alignment requirements. * * @param[in] lba Logical Block Address to align. * @param[in] sector_size Sector size enumeration. * @param[in] alignment Alignment requirement enumeration. * @return Aligned LBA value. */ uint32_t esp_mbr_lba_align(uint32_t lba, esp_ext_part_sector_size_t sector_size, esp_ext_part_align_t alignment); /** * @brief Generate default supported MBR partition types for a given internal type. * * @param[in] type Internal artition type to generate supported types from internal `esp_ext_part_type_known_t` enum. * @return MBR partition type code. */ uint8_t esp_mbr_generate_default_supported_partition_types(uint8_t type); /** * @brief Parse default supported MBR partition types into internal types (`esp_ext_part_type_known_t`). * * @param[in] type MBR partition type code to parse. * @param[out] out_type_parsed Pointer to store the parsed internal partition type (from `esp_ext_part_type_known_t`). * @return true if the partition type is known and supported, false otherwise. */ bool esp_mbr_parse_default_supported_partition_types(uint8_t type, uint8_t *out_type_parsed); #ifdef __cplusplus } #endif ================================================ FILE: esp_ext_part_tables/src/esp_ext_part_tables.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "esp_err.h" #include "esp_log.h" #include "esp_idf_version.h" #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #include "esp_blockdev.h" #endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #include "esp_ext_part_tables.h" #include "esp_mbr.h" #if __has_include() #include #else #include "sys/queue.h" #endif uint64_t esp_ext_part_bytes_to_sector_count(uint64_t total_bytes, esp_ext_part_sector_size_t sector_size) { if (sector_size == ESP_EXT_PART_SECTOR_SIZE_UNKNOWN) { return 0; // Avoid division by zero } // Ceiling division for integers: (a + b - 1) / b return ((total_bytes + (uint64_t) sector_size - 1) / (uint64_t) sector_size); } uint64_t esp_ext_part_sector_count_to_bytes(uint64_t sector_count, esp_ext_part_sector_size_t sector_size) { return sector_count * (uint64_t) sector_size; } esp_err_t esp_ext_part_list_deinit(esp_ext_part_list_t *part_list) { if (part_list == NULL) { return ESP_ERR_INVALID_ARG; } esp_ext_part_list_item_t *it = NULL; esp_ext_part_list_item_t *tmp = NULL; SLIST_FOREACH_SAFE(it, &part_list->head, next, tmp) { SLIST_REMOVE(&part_list->head, it, esp_ext_part_list_item_, next); free(it->info.label); // Deep free the label if it was allocated free(it); // Free the item itself } memset(part_list, 0, sizeof(esp_ext_part_list_t)); // Reset the part_list structure return ESP_OK; } esp_err_t esp_ext_part_list_insert(esp_ext_part_list_t *part_list, esp_ext_part_list_item_t *item) { if (part_list == NULL || item == NULL) { return ESP_ERR_INVALID_ARG; } esp_ext_part_list_item_t *_item = (esp_ext_part_list_item_t *) malloc(sizeof(esp_ext_part_list_item_t)); if (_item == NULL) { return ESP_ERR_NO_MEM; } memcpy(_item, item, sizeof(esp_ext_part_list_item_t)); // Copy the item if (_item->info.label != NULL) { _item->info.label = strdup(item->info.label); // Deep copy the label if (_item->info.label == NULL) { free(_item); return ESP_ERR_NO_MEM; } } esp_ext_part_list_item_t *it = NULL; esp_ext_part_list_item_t *last = NULL; SLIST_FOREACH(it, &part_list->head, next) { last = it; } if (last == NULL) { SLIST_INSERT_HEAD(&part_list->head, _item, next); } else { SLIST_INSERT_AFTER(last, _item, next); } return ESP_OK; } esp_err_t esp_ext_part_list_deep_copy(esp_ext_part_list_t *dst, esp_ext_part_list_t *src) { if (dst == NULL || src == NULL) { return ESP_ERR_INVALID_ARG; } memcpy(dst, src, sizeof(esp_ext_part_list_t)); // Copy the structure memset(&dst->head, 0, sizeof(dst->head)); // Reset the head of the destination list esp_err_t err; esp_ext_part_list_item_t *it = NULL; SLIST_FOREACH(it, &src->head, next) { err = esp_ext_part_list_insert(dst, it); // Insert copies the item from src to dst if (err != ESP_OK) { esp_ext_part_list_deinit(dst); return err; } } return ESP_OK; } esp_ext_part_list_item_t *esp_ext_part_list_item_head(esp_ext_part_list_t *part_list) { if (part_list == NULL) { return NULL; } return SLIST_FIRST(&part_list->head); } esp_ext_part_list_item_t *esp_ext_part_list_item_next(esp_ext_part_list_item_t *item) { if (item == NULL) { return NULL; } return SLIST_NEXT(item, next); } esp_err_t esp_ext_part_list_signature_get(esp_ext_part_list_t *part_list, void *signature) { if (part_list == NULL || signature == NULL) { return ESP_ERR_INVALID_ARG; } uint32_t out = 0; switch (part_list->signature.type) { case ESP_EXT_PART_LIST_SIGNATURE_MBR: out = (uint32_t) part_list->signature.data[0]; memcpy(signature, &out, sizeof(uint32_t)); break; default: return ESP_ERR_NOT_SUPPORTED; // Unsupported signature type } return ESP_OK; } esp_err_t esp_ext_part_list_signature_set(esp_ext_part_list_t *part_list, const void *signature, esp_ext_part_signature_type_t type) { if (part_list == NULL || signature == NULL) { return ESP_ERR_INVALID_ARG; } part_list->signature.type = type; switch (type) { case ESP_EXT_PART_LIST_SIGNATURE_MBR: part_list->signature.data[0] = *((const uint32_t *) signature); break; default: return ESP_ERR_NOT_SUPPORTED; // Unsupported signature type } return ESP_OK; } #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) esp_err_t esp_ext_part_list_bdl_read(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args) { if (handle == NULL || part_list == NULL) { return ESP_ERR_INVALID_ARG; } esp_err_t err = ESP_OK; uint8_t *buf = NULL; switch (type) { case ESP_EXT_PART_LIST_SIGNATURE_MBR: buf = malloc(MBR_SIZE); if (buf == NULL) { return ESP_ERR_NO_MEM; } err = handle->ops->read(handle, buf, MBR_SIZE, 0, MBR_SIZE); if (err != ESP_OK) { free(buf); return err; } err = esp_mbr_parse(buf, part_list, (esp_mbr_parse_extra_args_t *) extra_args); free(buf); break; default: err = ESP_ERR_NOT_SUPPORTED; // Unsupported signature type break; } return err; } esp_err_t esp_ext_part_list_bdl_write(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args) { if (handle == NULL || part_list == NULL) { return ESP_ERR_INVALID_ARG; } esp_err_t err = ESP_OK; uint8_t *buf = NULL; switch (type) { case ESP_EXT_PART_LIST_SIGNATURE_MBR: buf = malloc(MBR_SIZE); if (buf == NULL) { return ESP_ERR_NO_MEM; } err = esp_mbr_generate((mbr_t *) buf, part_list, (esp_mbr_generate_extra_args_t *) extra_args); if (err != ESP_OK) { free(buf); return err; } err = handle->ops->write(handle, buf, 0, MBR_SIZE); free(buf); break; default: err = ESP_ERR_NOT_SUPPORTED; // Unsupported signature type break; } return err; } #endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) ================================================ FILE: esp_ext_part_tables/src/esp_mbr.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_err.h" #include "esp_log.h" #include "esp_random.h" #include "esp_ext_part_tables.h" #include "esp_mbr.h" #include "esp_mbr_utils.h" static const char *TAG = "esp_mbr"; static void ext_part_list_item_do_extra(esp_ext_part_list_item_t *item, mbr_partition_t *partition) { // This function is for any extra actions that might be needed for specific partition types. // It can be used to set flags, perform additional operations or checks if needed. switch (item->info.type) { // Parsed type case ESP_EXT_PART_TYPE_LITTLEFS: item->info.flags |= ESP_EXT_PART_FLAG_EXTRA; // Set the extra flag to indicate that this partition has extra information item->info.extra = (uint64_t) esp_mbr_chs_arr_val_get(partition->chs_start); // Put LittleFS block size which was stored in `chs_start` to `extra` field break; default: break; } } esp_err_t esp_mbr_parse(void *mbr_buf, esp_ext_part_list_t *part_list, esp_mbr_parse_extra_args_t *extra_args) { if (mbr_buf == NULL || part_list == NULL) { return ESP_ERR_INVALID_ARG; } mbr_t *mbr = (mbr_t *) mbr_buf; // Check MBR signature if (mbr->boot_signature != MBR_SIGNATURE) { ESP_LOGE(TAG, "MBR signature not found"); return ESP_ERR_NOT_FOUND; } // Set defaults part_list->sector_size = ESP_EXT_PART_SECTOR_SIZE_512B; // Default sector size bool (*f_parse_supported_partition_types)(uint8_t, uint8_t *) = esp_mbr_parse_default_supported_partition_types; // Load extra arguments if provided if (extra_args) { if (extra_args->sector_size != ESP_EXT_PART_SECTOR_SIZE_UNKNOWN) { part_list->sector_size = extra_args->sector_size; // Use the sector size hint from extra_args } if (extra_args->esp_mbr_parse_custom_supported_partition_types) { f_parse_supported_partition_types = extra_args->esp_mbr_parse_custom_supported_partition_types; // Use a custom function for supported partition types } } esp_err_t err = ESP_OK; if (mbr->copy_protected == MBR_COPY_PROTECTED) { part_list->flags |= ESP_EXT_PART_LIST_FLAG_READ_ONLY; } err = esp_ext_part_list_signature_set(part_list, &mbr->disk_signature, ESP_EXT_PART_LIST_SIGNATURE_MBR); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set partition list (disk) signature"); return err; } mbr_partition_t *partition; for (int i = 0; i < 4; i++) { partition = (mbr_partition_t *) &mbr->partition_table[i]; // Check if the partition entry is empty and if so, skip it if (partition->type == 0x00) { break; // No more partitions, exit the loop (MBR partition table cannot have holes in it) } // If the partition entry is not supported, skip it as well uint8_t parsed_type = ESP_EXT_PART_TYPE_NONE; bool is_supported = f_parse_supported_partition_types(partition->type, &parsed_type); if (!is_supported) { continue; } // Create a new partition item and populate it with the partition info esp_ext_part_list_item_t item = { .info = { .address = esp_ext_part_sector_count_to_bytes((uint64_t) partition->lba_start, part_list->sector_size), .size = esp_ext_part_sector_count_to_bytes((uint64_t) partition->sector_count, part_list->sector_size), .extra = 0, .label = NULL, // MBR does not have labels .flags = ESP_EXT_PART_FLAG_NONE, .type = parsed_type, } }; if (partition->status == MBR_PARTITION_STATUS_ACTIVE) { item.info.flags |= ESP_EXT_PART_FLAG_ACTIVE; } // Set the flags or extra field based on the partition type or do any extra actions needed ext_part_list_item_do_extra(&item, partition); // Add the partition info to the output table err = esp_ext_part_list_insert(part_list, &item); if (err != ESP_OK) { ESP_LOGD(TAG, "Failed to add partition info to list"); return err; } } return ESP_OK; } static bool mbr_partition_fill(mbr_partition_t *partition, esp_ext_part_list_item_t *item) { uint32_t lba_start = partition->lba_start; uint32_t lba_end = lba_start - 1 + partition->sector_count; switch (item->info.type) { case ESP_EXT_PART_TYPE_FAT12: case ESP_EXT_PART_TYPE_FAT16: case ESP_EXT_PART_TYPE_FAT32: // Set CHS values based on LBA start and end esp_mbr_lba_to_chs_arr(partition->chs_start, lba_start); esp_mbr_lba_to_chs_arr(partition->chs_end, lba_end); break; case ESP_EXT_PART_TYPE_LITTLEFS: // Use `chs_start` to store LittleFS block size (if stored in `extra` field) if (item->info.extra != 0) { // If the extra flag is set, use the extra field to store the LittleFS block size esp_mbr_chs_arr_val_set(partition->chs_start, (uint32_t) item->info.extra); if (!(item->info.flags & ESP_EXT_PART_FLAG_EXTRA)) { // If the extra flag is not set but the extra field is set, log a warning ESP_LOGW(TAG, "LittleFS partition with extra field set but extra flag was not set"); } } else { ESP_LOGE(TAG, "LittleFS partition with 0xC3 type without any block size value in `extra` field"); return false; // Error } break; default: break; } return true; // OK } esp_err_t esp_mbr_partition_set(mbr_t *mbr, uint8_t partition_index, esp_ext_part_list_item_t *item, esp_mbr_generate_extra_args_t *extra_args) { if (mbr == NULL || partition_index >= 4 || item == NULL || extra_args == NULL) { return ESP_ERR_INVALID_ARG; } // Set defaults mbr_partition_t *partition = &mbr->partition_table[partition_index]; uint8_t (*f_generate_supported_partition_types)(uint8_t) = esp_mbr_generate_default_supported_partition_types; // Load extra arguments if provided if (extra_args->esp_mbr_generate_custom_supported_partition_types) { f_generate_supported_partition_types = extra_args->esp_mbr_generate_custom_supported_partition_types; // Use a custom function for supported partition types } // Check if the partition entry is empty and if so, skip it if (item->info.type == ESP_EXT_PART_TYPE_NONE) { memset(partition, 0, sizeof(mbr_partition_t)); return ESP_OK; // No partition to set } // Check if we have enough space in the MBR partition table uint64_t first_sector_address = esp_ext_part_bytes_to_sector_count(item->info.address, extra_args->sector_size); uint64_t sector_count = esp_ext_part_bytes_to_sector_count(item->info.size, extra_args->sector_size); if (first_sector_address > UINT32_MAX || sector_count > UINT32_MAX) { ESP_LOGE(TAG, "Partition address or size exceeds 32-bit limit of MBR"); return ESP_ERR_NOT_SUPPORTED; // Address or size too large for MBR } // Set the partition info if (item->info.flags & ESP_EXT_PART_FLAG_ACTIVE) { partition->status = MBR_PARTITION_STATUS_ACTIVE; } partition->lba_start = esp_mbr_lba_align((uint32_t) first_sector_address, extra_args->sector_size, extra_args->alignment); partition->sector_count = (uint32_t) sector_count; partition->type = f_generate_supported_partition_types(item->info.type); if (mbr_partition_fill(partition, item) == false) { return ESP_ERR_INVALID_STATE; // Error filling partition } return ESP_OK; } esp_err_t esp_mbr_generate(mbr_t *mbr, esp_ext_part_list_t *part_list, esp_mbr_generate_extra_args_t *extra_args) { if (mbr == NULL || part_list == NULL) { return ESP_ERR_INVALID_ARG; } esp_err_t err = ESP_OK; // Set default arguments for MBR generation esp_mbr_generate_extra_args_t args = { .sector_size = part_list->sector_size != ESP_EXT_PART_SECTOR_SIZE_UNKNOWN ? part_list->sector_size : ESP_EXT_PART_SECTOR_SIZE_512B, // Default sector size .alignment = ESP_EXT_PART_ALIGN_1MiB, // Default alignment .keep_signature = false, // Default is to generate a new disk signature }; // Load extra arguments if provided if (extra_args) { if (extra_args->sector_size != ESP_EXT_PART_SECTOR_SIZE_UNKNOWN) { args.sector_size = extra_args->sector_size; } if (extra_args->alignment != ESP_EXT_PART_ALIGN_NONE) { args.alignment = extra_args->alignment; } args.keep_signature = extra_args->keep_signature; if (extra_args->esp_mbr_generate_custom_supported_partition_types) { args.esp_mbr_generate_custom_supported_partition_types = extra_args->esp_mbr_generate_custom_supported_partition_types; } } mbr->boot_signature = MBR_SIGNATURE; if (args.keep_signature) { // Use the disk signature from the partition list err = esp_ext_part_list_signature_get(part_list, &mbr->disk_signature); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to get disk signature from partition list"); return err; } } else { mbr->disk_signature = esp_random(); } if (part_list->flags & ESP_EXT_PART_LIST_FLAG_READ_ONLY) { mbr->copy_protected = MBR_COPY_PROTECTED; } esp_ext_part_list_item_t *it = NULL; int i = 0; SLIST_FOREACH(it, &part_list->head, next) { if (i >= MBR_MAX_PARTITION_COUNT) { ESP_LOGW(TAG, "More than %d partitions in the list, only the first %d will be added to the MBR", MBR_MAX_PARTITION_COUNT, MBR_MAX_PARTITION_COUNT); break; // MBR can only hold 4 partitions } err = esp_mbr_partition_set(mbr, i, it, &args); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set partition %d: %s", i, esp_err_to_name(err)); return err; // Error setting partition } i += 1; } return ESP_OK; } esp_err_t esp_mbr_remove_gaps_between_partiton_entries(mbr_t *mbr) { if (mbr == NULL) { return ESP_ERR_INVALID_ARG; // Invalid MBR pointer } // Iterate through the partition table and remove gaps mbr_partition_t *partition; uint8_t gap_index = 0; // Next index to fill for (int i = 0; i < 4; i++) { partition = &mbr->partition_table[i]; if (partition->type == 0x00) { continue; // Skip empty entries } if (gap_index != i) { // Move the partition to the next available index memcpy(&mbr->partition_table[gap_index], partition, sizeof(mbr_partition_t)); memset(partition, 0, sizeof(mbr_partition_t)); // Clear the old entry } gap_index++; } return ESP_OK; } ================================================ FILE: esp_ext_part_tables/src/esp_mbr_utils.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "esp_ext_part_tables.h" #include "esp_mbr_utils.h" static const char *TAG = "esp_mbr_utils"; void esp_mbr_chs_arr_val_set(uint8_t chs[3], uint32_t val) { chs[0] = val & 0xFF; chs[1] = (val >> 8) & 0xFF; chs[2] = (val >> 16) & 0xFF; } uint32_t esp_mbr_chs_arr_val_get(const uint8_t chs[3]) { return chs[0] | (chs[1] << 8) | (chs[2] << 16); } void esp_mbr_lba_to_chs_arr(uint8_t chs[3], uint32_t lba) { uint16_t cylinder; uint8_t head; uint8_t sector; uint32_t sectors_per_cylinder = MBR_CHS_HEADS * MBR_CHS_SECTORS_PER_TRACK; uint32_t temp; cylinder = lba / sectors_per_cylinder; temp = lba % sectors_per_cylinder; head = temp / MBR_CHS_SECTORS_PER_TRACK; sector = (temp % MBR_CHS_SECTORS_PER_TRACK) + 1; // Clamp to BIOS CHS limits if (cylinder > MBR_CHS_MAX_CYLINDER) { cylinder = MBR_CHS_MAX_CYLINDER; } if (head > MBR_CHS_MAX_HEAD) { head = MBR_CHS_MAX_HEAD; } if (sector > MBR_CHS_MAX_SECTOR) { sector = MBR_CHS_MAX_SECTOR; } uint8_t chs_bytes[3]; chs_bytes[0] = head & 0xFF; chs_bytes[1] = ((cylinder >> 2) & 0xC0) | (sector & 0x3F); // high 2 bits of cylinder + 6-bit sector chs_bytes[2] = cylinder & 0xFF; if (chs != NULL) { memcpy(chs, chs_bytes, 3); } } uint32_t esp_mbr_lba_align(uint32_t lba, esp_ext_part_sector_size_t sector_size, esp_ext_part_align_t alignment) { if (sector_size == 0 || alignment == 0) { return lba; // No alignment } uint32_t alignment_sectors = alignment / sector_size; return (lba + alignment_sectors - 1) & ~(alignment_sectors - 1); } static bool default_known_supported_partition_types(uint8_t type, esp_ext_part_type_known_t *out_type_parsed) { bool supported = true; esp_ext_part_type_known_t parsed_type = ESP_EXT_PART_TYPE_NONE; switch (type) { // Supported types: case 0x01: // FAT12 parsed_type = ESP_EXT_PART_TYPE_FAT12; break; case 0x04: __attribute__((fallthrough)); case 0x06: __attribute__((fallthrough)); case 0x0E: // FAT16B with LBA addressing (also uses 0x04 and 0x06 but on modern system shouldn't matter) parsed_type = ESP_EXT_PART_TYPE_FAT16; break; case 0x0B: __attribute__((fallthrough)); case 0x0C: // FAT32 with LBA addressing (0x0B is for CHS addressing) parsed_type = ESP_EXT_PART_TYPE_FAT32; break; case 0xC3: // Possibly LittleFS (MBR CHS field => LittleFS block size hack) parsed_type = ESP_EXT_PART_TYPE_LITTLEFS; break; // Unsupported types: case 0x07: // exFAT or NTFS parsed_type = ESP_EXT_PART_TYPE_EXFAT_OR_NTFS; supported = false; // Not supported break; case 0x83: // Linux partition (any type) parsed_type = ESP_EXT_PART_TYPE_LINUX_ANY; supported = false; // Not supported break; case 0xEE: // GPT protective MBR parsed_type = ESP_EXT_PART_TYPE_GPT_PROTECTIVE_MBR; supported = false; // Not supported break; case 0x05: __attribute__((fallthrough)); // Extended partition with CHS addressing case 0x0F: __attribute__((fallthrough)); // Extended partition with LBA addressing default: supported = false; break; } if (out_type_parsed != NULL) { *out_type_parsed = parsed_type; } if (supported == false) { ESP_LOGD(TAG, "Unknown or unsupported partition type: 0x%02X", type); } return supported; } bool esp_mbr_parse_default_supported_partition_types(uint8_t type, uint8_t *out_type_parsed) { esp_ext_part_type_known_t parsed_type = ESP_EXT_PART_TYPE_NONE; bool is_supported = default_known_supported_partition_types(type, &parsed_type); if (out_type_parsed != NULL) { *out_type_parsed = (uint8_t) parsed_type; } return is_supported; } uint8_t esp_mbr_generate_default_supported_partition_types(uint8_t type) { switch ((esp_ext_part_type_known_t) type) { case ESP_EXT_PART_TYPE_FAT12: return 0x01; // FAT12 case ESP_EXT_PART_TYPE_FAT16: return 0x0E; // FAT16B with LBA addressing case ESP_EXT_PART_TYPE_FAT32: return 0x0C; // FAT32 with LBA addressing /* LittleFS is not a standard MBR partition type, but we can use a custom type `0xC3`, which is not usually used nowadays. This allows us to identify LittleFS partitions in the MBR. Explanation why `0xC3` was chosen: 0xC 3 1100 0011 ↑↑ ↑ ↑↑↑↑ └│─│─┴┴┴┴── 0x83 => a modern filesystem (e.g. Linux) └─│─────── 0x40 => CHS used as LittleFS block size └─────── 0x10 => a hidden filesystem */ case ESP_EXT_PART_TYPE_LITTLEFS: return 0xC3; // Possibly LittleFS (MBR CHS field => LittleFS block size hack) case ESP_EXT_PART_TYPE_EXFAT_OR_NTFS: // Not supported, but we can return a type for it return 0x07; // exFAT or NTFS case ESP_EXT_PART_TYPE_LINUX_ANY: // Not supported, but we can return a type for it return 0x83; // Linux partition (any type) case ESP_EXT_PART_TYPE_GPT_PROTECTIVE_MBR: // Not supported, but we can return a type for it return 0xEE; // GPT protective MBR default: return 0x00; // Unknown type } } ================================================ FILE: esp_ext_part_tables/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_ext_part_tables_parse_test) ================================================ FILE: esp_ext_part_tables/test_apps/main/CMakeLists.txt ================================================ set(priv_requires "unity") if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0") list(APPEND priv_requires "esp_blockdev") endif() idf_component_register(SRCS "test_esp_ext_part.c" INCLUDE_DIRS "." PRIV_REQUIRES ${priv_requires} WHOLE_ARCHIVE) ================================================ FILE: esp_ext_part_tables/test_apps/main/idf_component.yml ================================================ dependencies: idf: ">=5.1" espressif/esp_ext_part_tables: version: "*" override_path: "../.." ================================================ FILE: esp_ext_part_tables/test_apps/main/test_esp_ext_part.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "esp_err.h" #include "esp_heap_caps.h" #include "esp_idf_version.h" #if !CONFIG_IDF_TARGET_LINUX #include "esp_newlib.h" #endif // !CONFIG_IDF_TARGET_LINUX #include "unity.h" #include "unity_test_runner.h" #include "unity_test_utils_memory.h" #include "esp_ext_part_tables.h" #include "esp_mbr.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { #if !CONFIG_IDF_TARGET_LINUX esp_reent_cleanup(); //clean up some of the newlib's lazy allocations #endif // !CONFIG_IDF_TARGET_LINUX unity_utils_evaluate_leaks_direct(0); } // MBR with 2 FAT12 entries uint8_t mbr_bin[512] = { [440] = 0xc4, 0x9d, 0x92, 0x4d, 0x00, 0x00, 0x00, 0x20, 0x21, 0x00, 0x01, 0x9e, 0x2f, 0x00, 0x00, 0x08, 0x00, 0x00, 0x11, 0x1f, 0x00, 0x00, 0x00, 0xa2, 0x23, 0x00, 0x01, 0x46, 0x05, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa }; unsigned int mbr_bin_len = 512; static void print_esp_ext_part_list_items(esp_ext_part_list_item_t *head) { esp_ext_part_list_item_t *it = head; int i = 0; do { printf("Partition %d:\n\tLBA start sector: %" PRIu64 ", address: %" PRIu64 ",\n\tsector count: %" PRIu64 ", size: %" PRIu64 ",\n\ttype: %" PRIu32 "\n\n", i, esp_ext_part_bytes_to_sector_count(it->info.address, ESP_EXT_PART_SECTOR_SIZE_512B), it->info.address, esp_ext_part_bytes_to_sector_count(it->info.size, ESP_EXT_PART_SECTOR_SIZE_512B), it->info.size, (uint32_t) (it->info.type)); i++; } while ((it = esp_ext_part_list_item_next(it)) != NULL); } TEST_CASE("Test mbr_bin struct", "[esp_ext_part_table]") { mbr_t *mbr = (mbr_t *) mbr_bin; TEST_ASSERT_NOT_NULL(mbr); TEST_ASSERT_EQUAL(MBR_SIGNATURE, mbr->boot_signature); printf("MBR boot signature: 0x%" PRIX16 "\n", mbr->boot_signature); printf("MBR disk signature: 0x%" PRIX32 "\n", mbr->disk_signature); } TEST_CASE("Test esp_mbr_parse", "[esp_ext_part_table]") { esp_ext_part_list_t part_list = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr_bin, &part_list, NULL)); esp_ext_part_list_item_t *it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); print_esp_ext_part_list_items(it); fflush(stdout); do { TEST_ASSERT_NOT_EQUAL(0, it->info.address); TEST_ASSERT_NOT_EQUAL(0, it->info.size); TEST_ASSERT_NOT_EQUAL(0, it->info.type); } while ((it = esp_ext_part_list_item_next(it)) != NULL); esp_ext_part_list_deinit(&part_list); TEST_ASSERT_EQUAL(0, part_list.head.slh_first); } void generate_original_mbr(mbr_t *mbr) { esp_mbr_generate_extra_args_t mbr_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, .alignment = ESP_EXT_PART_ALIGN_1MiB }; esp_ext_part_list_t part_list = {0}; // 2 FAT12 partitions with same parameters as in the original MBR in the array esp_ext_part_list_item_t item1 = { .info = { // Original MBR starts at 2048, but we use 8 for testing -> .address = esp_ext_part_sector_count_to_bytes(8, mbr_args.sector_size), // Should be round up to 2048 sectors (aligned to 1MiB) due to defined sector size and alignment in `esp_mbr_generate_extra_args_t args` below .size = esp_ext_part_sector_count_to_bytes(7953, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_FAT12, .label = NULL, } }; esp_ext_part_list_item_t item2 = { .info = { .address = esp_ext_part_sector_count_to_bytes(10240, mbr_args.sector_size), .size = esp_ext_part_sector_count_to_bytes(10240, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_FAT12, .label = NULL, } }; TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &item1)); TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &item2)); // Generate the MBR TEST_ESP_OK(esp_mbr_generate(mbr, &part_list, &mbr_args)); // Deinitialize the part list TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); } TEST_CASE("Test esp_mbr_generate generates the (almost) same MBR as the original", "[esp_ext_part_table]") { mbr_t *mbr = (mbr_t *) calloc(1, sizeof(mbr_t)); TEST_ASSERT_NOT_NULL(mbr); generate_original_mbr(mbr); esp_ext_part_list_t part_list1 = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr, &part_list1, NULL)); esp_ext_part_list_item_t *it1 = esp_ext_part_list_item_head(&part_list1); TEST_ASSERT_NOT_NULL(it1); esp_ext_part_list_t part_list2 = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr_bin, &part_list2, NULL)); esp_ext_part_list_item_t *it2 = esp_ext_part_list_item_head(&part_list2); TEST_ASSERT_NOT_NULL(it2); print_esp_ext_part_list_items(it1); print_esp_ext_part_list_items(it2); fflush(stdout); uint8_t *mbr_bin_from_part_table = (uint8_t *) mbr_bin + MBR_PARTITION_TABLE_OFFSET; uint8_t *mbr_from_part_table = (uint8_t *) mbr + MBR_PARTITION_TABLE_OFFSET; uint8_t compare_size = mbr_bin_len - MBR_PARTITION_TABLE_OFFSET; // Test if the generated MBR is the same as the original MBR - only from partition table part // Disk signature is randomly generated, so we don't compare it TEST_ASSERT_EQUAL_MEMORY(mbr_bin_from_part_table, mbr_from_part_table, compare_size); free(mbr); esp_ext_part_list_deinit(&part_list1); esp_ext_part_list_deinit(&part_list2); } TEST_CASE("Test esp_mbr_generate with esp_mbr_parse", "[esp_ext_part_table]") { mbr_t *mbr; mbr = (mbr_t *) calloc(1, sizeof(mbr_t)); TEST_ASSERT_NOT_NULL(mbr); generate_original_mbr(mbr); esp_ext_part_list_t part_list = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr, &part_list, NULL)); free(mbr); esp_ext_part_list_item_t *it; it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); // Print the partition list print_esp_ext_part_list_items(it); fflush(stdout); do { TEST_ASSERT_NOT_EQUAL(0, it->info.address); TEST_ASSERT_NOT_EQUAL(0, it->info.size); TEST_ASSERT_NOT_EQUAL(0, it->info.type); } while ((it = esp_ext_part_list_item_next(it)) != NULL); // Deinitialize the part list esp_ext_part_list_deinit(&part_list); it = NULL; TEST_ASSERT_EQUAL(0, part_list.head.slh_first); // Another MBR mbr = (mbr_t *) calloc(1, sizeof(mbr_t)); TEST_ASSERT_NOT_NULL(mbr); esp_mbr_generate_extra_args_t mbr_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, .alignment = ESP_EXT_PART_ALIGN_1MiB }; // 2 FAT12 partitions with same parameters as in the original MBR in the array esp_ext_part_list_item_t item1 = { .info = { .address = 8, // Should be round up to 2048 (aligned to 1MiB) due to defined sector size and alignment in `esp_mbr_generate_extra_args_t args` below .size = esp_ext_part_sector_count_to_bytes(7953, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_FAT12, .label = NULL, } }; esp_ext_part_list_item_t item2 = { .info = { .address = 10000, // Should be round up to 10240 (aligned to 1MiB) due to defined sector size and alignment in `esp_mbr_generate_extra_args_t args` below .size = esp_ext_part_sector_count_to_bytes(2 * 10240, mbr_args.sector_size), .type = ESP_EXT_PART_TYPE_LITTLEFS, .label = NULL, .extra = 4096, // LittleFS block size stored in CHS hack .flags = ESP_EXT_PART_FLAG_EXTRA, // Extra flag set to indicate that the extra field is used } }; TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &item1)); TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &item2)); // Generate the MBR TEST_ESP_OK(esp_mbr_generate(mbr, &part_list, &mbr_args)); // Deinitialize the part list esp_ext_part_list_deinit(&part_list); // Parse the MBR TEST_ESP_OK(esp_mbr_parse((void *) mbr, &part_list, NULL)); free(mbr); // Print the partition list it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); print_esp_ext_part_list_items(it); fflush(stdout); // Deinitialize the part list esp_ext_part_list_deinit(&part_list); it = NULL; } TEST_CASE("Test esp_ext_part_list_signature_t get and set", "[esp_ext_part_table]") { esp_ext_part_list_t part_list = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr_bin, &part_list, NULL)); TEST_ASSERT_EQUAL(part_list.signature.type, ESP_EXT_PART_LIST_SIGNATURE_MBR); uint32_t disk_signature = 0; uint32_t new_signature = 0x12345678; TEST_ESP_OK(esp_ext_part_list_signature_get(&part_list, &disk_signature)); TEST_ASSERT_NOT_EQUAL(disk_signature, new_signature); TEST_ESP_OK(esp_ext_part_list_signature_set(&part_list, &new_signature, ESP_EXT_PART_LIST_SIGNATURE_MBR)); TEST_ESP_OK(esp_ext_part_list_signature_get(&part_list, &disk_signature)); TEST_ASSERT_EQUAL(disk_signature, new_signature); // Deinitialize the part list TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); } TEST_CASE("Test esp_mbr_partition_set and esp_mbr_remove_gaps_between_partiton_entries", "[esp_ext_part_table]") { esp_ext_part_list_t part_list = {0}; esp_mbr_generate_extra_args_t mbr_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, .alignment = ESP_EXT_PART_ALIGN_1MiB }; // 4 FAT12 partitions esp_ext_part_list_item_t item = { .info = { .size = 10 * 1024 * 1024, // 10 MiB .type = ESP_EXT_PART_TYPE_FAT12, } }; for (int i = 0; i < 4; i++) { item.info.address = 1024 * 1024 + i * item.info.size; // First partition starts at 1 MiB offset, next partitions are 10 MiB apart TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &item)); } printf("Partition list after creation:\n"); esp_ext_part_list_item_t *it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); print_esp_ext_part_list_items(it); fflush(stdout); // Generate the MBR mbr_t *mbr = (mbr_t *) calloc(1, sizeof(mbr_t)); TEST_ASSERT_NOT_NULL(mbr); TEST_ESP_OK(esp_mbr_generate(mbr, &part_list, &mbr_args)); // Deinitialize the part list TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); // Create gaps in MBR at index 1 and 2 // This will remove the second and third partitions from the MBR esp_ext_part_list_item_t empty_item = { .info = { .type = ESP_EXT_PART_TYPE_NONE, // No type } }; esp_mbr_partition_set(mbr, 1, &empty_item, &mbr_args); esp_mbr_partition_set(mbr, 2, &empty_item, &mbr_args); printf("Partition 1 and 2 removed, 0 and 3 remained, gaps created\n\n"); // Parse the MBR to get the partition list without removing the gaps esp_ext_part_list_t part_list_from_mbr = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr, &part_list_from_mbr, NULL)); it = esp_ext_part_list_item_head(&part_list_from_mbr); TEST_ASSERT_NOT_NULL(it); int partition_count = 1; while ((it = esp_ext_part_list_item_next(it)) != NULL) { partition_count++; } TEST_ASSERT_EQUAL(partition_count, 1); // Print the partition list printf("Partition list after creating gaps (partition 3 is missing because the gaps were created and not shifted out):\n"); it = esp_ext_part_list_item_head(&part_list_from_mbr); TEST_ASSERT_NOT_NULL(it); print_esp_ext_part_list_items(it); fflush(stdout); // Deinitialize the part list TEST_ESP_OK(esp_ext_part_list_deinit(&part_list_from_mbr)); // Now remove the gaps between partition entries esp_mbr_remove_gaps_between_partiton_entries(mbr); // Parse the MBR to get the partition list with gaps removed esp_ext_part_list_t part_list_from_mbr_correct = {0}; TEST_ESP_OK(esp_mbr_parse((void *) mbr, &part_list_from_mbr_correct, NULL)); free(mbr); // Now the partition list should contain 2 partitions (originally partition 0 and 3, now partition 0 and 1) it = esp_ext_part_list_item_head(&part_list_from_mbr_correct); TEST_ASSERT_NOT_NULL(it); partition_count = 1; while ((it = esp_ext_part_list_item_next(it)) != NULL) { partition_count++; } TEST_ASSERT_EQUAL(partition_count, 2); // Print the partition list printf("Partition list after removing gaps (partition 0 stayed the same, partition 3 was shifted and now is partition 1):\n"); it = esp_ext_part_list_item_head(&part_list_from_mbr_correct); TEST_ASSERT_NOT_NULL(it); print_esp_ext_part_list_items(it); fflush(stdout); // Deinitialize the part list TEST_ESP_OK(esp_ext_part_list_deinit(&part_list_from_mbr_correct)); } #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #include "esp_blockdev.h" // BDL simulated block device implementation for testing static esp_err_t bdl_simulated_read(esp_blockdev_handle_t handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) { if (handle == NULL || dst_buf == NULL) { return ESP_ERR_INVALID_ARG; } uint8_t *buffer = (uint8_t *) handle->ctx; if (src_addr + data_read_len > handle->geometry.disk_size || data_read_len > dst_buf_size) { return ESP_ERR_INVALID_SIZE; } memcpy(dst_buf, buffer + src_addr, data_read_len); return ESP_OK; } static esp_err_t bdl_simulated_write(esp_blockdev_handle_t handle, const uint8_t *src_buf, uint64_t dst_addr, size_t data_write_len) { if (handle == NULL || src_buf == NULL) { return ESP_ERR_INVALID_ARG; } uint8_t *buffer = (uint8_t *) handle->ctx; if (dst_addr + data_write_len > handle->geometry.disk_size) { return ESP_ERR_INVALID_SIZE; } memcpy(buffer + dst_addr, src_buf, data_write_len); return ESP_OK; } static esp_err_t bdl_simulated_release_blockdev(esp_blockdev_handle_t handle) { if (handle != NULL) { free(handle); } return ESP_OK; } static const esp_blockdev_ops_t bdl_simulated_blockdev_ops = { .read = bdl_simulated_read, .write = bdl_simulated_write, .erase = NULL, // Not recommended to leave as NULL; just for test purposes .ioctl = NULL, .sync = NULL, .release = bdl_simulated_release_blockdev, }; static esp_err_t bdl_simulated_get_blockdev(uint8_t *buffer, size_t buffer_size, esp_blockdev_handle_t *out_handle) { if (buffer == NULL || out_handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_blockdev_handle_t out = (esp_blockdev_handle_t) calloc(1, sizeof(esp_blockdev_t)); if (out == NULL) { return ESP_ERR_NO_MEM; } out->ctx = (void *) buffer; out->device_flags.default_val_after_erase = 0; out->geometry.disk_size = buffer_size; out->geometry.read_size = 1; out->geometry.write_size = 1; out->geometry.erase_size = 1; out->ops = &bdl_simulated_blockdev_ops; *out_handle = out; return ESP_OK; } TEST_CASE("Test with BDL (simulated in RAM) - basic operations", "[esp_ext_part_table]") { size_t buffer_size = 3 * 1024; uint8_t *buffer = (uint8_t *) malloc(buffer_size); TEST_ASSERT_NOT_NULL(buffer); esp_blockdev_handle_t handle = NULL; esp_err_t err = bdl_simulated_get_blockdev(buffer, buffer_size, &handle); TEST_ESP_OK(err); TEST_ASSERT_NOT_NULL(handle); size_t sector_size = 512; // Write As to the first sector uint8_t buf[] = {[0 ... 511] = 'A'}; // Fill buffer with 'A' err = handle->ops->write(handle, buf, 0, sector_size); TEST_ESP_OK(err); uint8_t read_buf[512] = {0}; err = handle->ops->read(handle, read_buf, sector_size, 0, sector_size); TEST_ESP_OK(err); TEST_ASSERT_EQUAL_MEMORY(buf, read_buf, sizeof(buf)); // Write Bs to the emulated "first sector" (0 + start sector offset (2) == sector size (512) * 2) { uint8_t buf2[] = {[0 ... 511] = 'B'}; // Fill buffer with 'B' err = handle->ops->write(handle, buf2, sector_size * 2, sector_size); TEST_ESP_OK(err); err = handle->ops->read(handle, read_buf, sector_size, sector_size * 2, sector_size); TEST_ESP_OK(err); TEST_ASSERT_EQUAL_MEMORY(buf2, read_buf, sizeof(buf2)); } // Read the first sector again, it should be 'A's err = handle->ops->read(handle, read_buf, sector_size, 0, sector_size); TEST_ESP_OK(err); TEST_ASSERT_EQUAL_MEMORY(buf, read_buf, sizeof(buf)); // Visualize the first 5 sectors for (int i = 0; i < 5; i++) { // Read the first sector, it should be 'A's err = handle->ops->read(handle, read_buf, sector_size, sector_size * i, sector_size); TEST_ESP_OK(err); for (int j = 0; j < sizeof(read_buf); j++) { printf("%c", read_buf[j]); } printf("\n"); fflush(stdout); } handle->ops->release(handle); handle = NULL; free(buffer); buffer = NULL; } TEST_CASE("Test with BDL (simulated in RAM) - MBR related", "[esp_ext_part_table]") { size_t buffer_size = 512; uint8_t *buffer = (uint8_t *) malloc(buffer_size); TEST_ASSERT_NOT_NULL(buffer); esp_blockdev_handle_t handle = NULL; esp_err_t err = bdl_simulated_get_blockdev(buffer, buffer_size, &handle); TEST_ESP_OK(err); TEST_ASSERT_NOT_NULL(handle); err = handle->ops->write(handle, mbr_bin, 0, mbr_bin_len); TEST_ESP_OK(err); esp_mbr_parse_extra_args_t mbr_parse_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B }; esp_ext_part_list_t part_list = {0}; esp_ext_part_list_item_t *it = NULL; err = esp_ext_part_list_bdl_read(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_parse_args); TEST_ESP_OK(err); it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); printf("Partition list read from BDL simulated MBR:\n"); print_esp_ext_part_list_items(it); fflush(stdout); esp_mbr_generate_extra_args_t mbr_gen_args = { .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, .alignment = ESP_EXT_PART_ALIGN_1MiB }; esp_ext_part_list_item_t partition_for_insertion = { .info = { .address = esp_ext_part_sector_count_to_bytes(20480, mbr_gen_args.sector_size), // 10 MiB offset .size = 10 * 1024 * 1024, // 10 MiB .type = ESP_EXT_PART_TYPE_LITTLEFS, .extra = 4096, // LittleFS block size stored in CHS hack .flags = ESP_EXT_PART_FLAG_EXTRA, // Extra flag set to indicate that the extra field is used } }; TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &partition_for_insertion)); err = esp_ext_part_list_bdl_write(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_gen_args); TEST_ESP_OK(err); TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); err = esp_ext_part_list_bdl_read(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_parse_args); TEST_ESP_OK(err); it = esp_ext_part_list_item_head(&part_list); TEST_ASSERT_NOT_NULL(it); printf("Partition list after writing new partition to BDL simulated MBR:\n"); print_esp_ext_part_list_items(it); fflush(stdout); TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); } #endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) void app_main(void) { printf("Running esp_ext_part_tables component tests\n"); unity_run_menu(); } ================================================ FILE: esp_ext_part_tables/test_apps/pytest_esp_ext_part_tables.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.host_test @idf_parametrize('target', ['linux'], indirect=['target']) def test_esp_ext_part_tables(dut: Dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_ext_part_tables/test_apps/sdkconfig.defaults ================================================ # ignore task watchdog triggered by unity_run_menu CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: esp_flash_dispatcher/.build-test-rules.yml ================================================ esp_flash_dispatcher/test_apps: disable: - if: SOC_SPIRAM_XIP_SUPPORTED != 1 reason: "This feature is only necessary on chips which have PSRAM, and can XIP from PSRAM" - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 2 reason: IDF version below v5.2 is not supported ================================================ FILE: esp_flash_dispatcher/CMakeLists.txt ================================================ set(includes "include") idf_component_register(SRCS "esp_flash_dispatcher.c" INCLUDE_DIRS ${includes} PRIV_REQUIRES "spi_flash" "freertos" "esp_system" ) set(WRAP_FUNCTIONS esp_flash_write esp_flash_erase_region esp_flash_read esp_flash_erase_chip esp_flash_write_encrypted) foreach(wrap ${WRAP_FUNCTIONS}) target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") endforeach() ================================================ FILE: esp_flash_dispatcher/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_flash_dispatcher/README.md ================================================ ## Overview When a task stack resides in PSRAM, invoking `esp_flash_*` directly in that task can cause crashes during moments when the Flash driver temporarily disables the CPU cache (resulting in the PSRAM stack being inaccessible). Typical failures include Cache errors and Guru Meditation. The `esp_flash_dispatcher` component intercepts common Flash APIs and executes the real Flash operations in a dedicated background task whose stack lives in internal RAM. This guarantees that even while cache is disabled, Flash operations run on an always-accessible stack. Therefore, application tasks can keep their stacks in PSRAM without worrying about Flash operations breaking them. ## Usage 1. Add esp_flash_dispatcher component to your project: `idf.py add-dependency espressif/esp_flash_dispatcher` 2. Initialize esp_flash_dispatcher in app_main: ```c #include "esp_flash_dispatcher.h" const esp_flash_dispatcher_config_t cfg = { .task_stack_size = 2048, .task_priority = 10, .task_core_id = tskNO_AFFINITY, .queue_size = 5, }; ESP_ERROR_CHECK(esp_flash_dispatcher_init(&cfg)); ``` 3. Now you can call any API which writes or reads SPI Flash from a task with the stack in PSRAM, no other changes are required. ================================================ FILE: esp_flash_dispatcher/esp_flash_dispatcher.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "esp_log.h" #include "esp_heap_caps.h" #include "esp_flash_spi_init.h" #include "esp_private/startup_internal.h" #include "esp_check.h" #include "esp_flash_dispatcher.h" static const char *TAG = "flash_dispatcher"; extern esp_err_t __real_esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint32_t size); extern esp_err_t __real_esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t address, uint32_t size); extern esp_err_t __real_esp_flash_erase_region(esp_flash_t *chip, uint32_t start_address, uint32_t size); extern esp_err_t __real_esp_flash_erase_chip(esp_flash_t *chip); extern esp_err_t __real_esp_flash_write_encrypted(esp_flash_t *chip, uint32_t address, const void *buffer, uint32_t length); // Operation type for flash requests typedef enum { FLASH_OP_READ = 0, FLASH_OP_WRITE, FLASH_OP_WRITE_ENCRYPTED, FLASH_OP_ERASE_REGION, FLASH_OP_ERASE_CHIP, } flash_operation_t; // Structure to hold flash operation requests typedef struct { esp_flash_t *chip; flash_operation_t op; // Operation type to execute union { struct { void *buffer; uint32_t address; size_t size; } read; // for FLASH_OP_READ struct { const void *buffer; uint32_t address; size_t size; } write; // for FLASH_OP_WRITE struct { uint32_t address; const void *buffer; size_t size; } write_encrypted;// for FLASH_OP_WRITE_ENCRYPTED struct { uint32_t start_address; size_t size; } erase_region; // for FLASH_OP_ERASE_REGION } args; } flash_operation_request_t; typedef struct { QueueHandle_t queue; TaskHandle_t task; bool dispatcher_initialized; QueueHandle_t result_queue; } flash_dispatcher_context_t; // Configuration struct is declared in public header static flash_dispatcher_context_t s_flash_dispatcher_ctx; static void flash_dispatcher_task(void *arg) { flash_operation_request_t request; esp_err_t result; while (true) { if (xQueueReceive(s_flash_dispatcher_ctx.queue, &request, portMAX_DELAY) == pdTRUE) { // Execute the actual flash operation based on the operation type and arguments switch (request.op) { case FLASH_OP_READ: result = __real_esp_flash_read(request.chip, request.args.read.buffer, request.args.read.address, request.args.read.size); break; case FLASH_OP_WRITE: result = __real_esp_flash_write(request.chip, request.args.write.buffer, request.args.write.address, request.args.write.size); break; case FLASH_OP_WRITE_ENCRYPTED: result = __real_esp_flash_write_encrypted(request.chip, request.args.write_encrypted.address, request.args.write_encrypted.buffer, request.args.write_encrypted.size); break; case FLASH_OP_ERASE_REGION: result = __real_esp_flash_erase_region(request.chip, request.args.erase_region.start_address, request.args.erase_region.size); break; case FLASH_OP_ERASE_CHIP: result = __real_esp_flash_erase_chip(request.chip); break; default: ESP_EARLY_LOGE(TAG, "Unsupported flash operation type: %d", (int)request.op); result = ESP_FAIL; break; } // Publish result and signal completion to the waiting caller if (xQueueSend(s_flash_dispatcher_ctx.result_queue, &result, portMAX_DELAY) != pdTRUE) { ESP_EARLY_LOGE(TAG, "Failed to send result to queue"); } } } } esp_err_t esp_flash_dispatcher_init(const esp_flash_dispatcher_config_t *cfg) { if (s_flash_dispatcher_ctx.queue != NULL || s_flash_dispatcher_ctx.task != NULL) { ESP_EARLY_LOGE(TAG, "flash dispatcher already initialized"); return ESP_ERR_INVALID_STATE; } s_flash_dispatcher_ctx.queue = xQueueCreateWithCaps(cfg->queue_size, sizeof(flash_operation_request_t), MALLOC_CAP_INTERNAL); ESP_RETURN_ON_FALSE(s_flash_dispatcher_ctx.queue, ESP_ERR_NO_MEM, TAG, "create flash operation queue failed"); s_flash_dispatcher_ctx.result_queue = xQueueCreateWithCaps(cfg->queue_size, sizeof(esp_err_t), MALLOC_CAP_INTERNAL); if (s_flash_dispatcher_ctx.result_queue == NULL) { vQueueDeleteWithCaps(s_flash_dispatcher_ctx.queue); ESP_EARLY_LOGE(TAG, "Failed to create completion semaphore"); return ESP_ERR_NO_MEM; } BaseType_t rc = xTaskCreatePinnedToCoreWithCaps(flash_dispatcher_task, "flash_dispatcher", cfg->task_stack_size, NULL, cfg->task_priority, &s_flash_dispatcher_ctx.task, cfg->task_core_id, MALLOC_CAP_INTERNAL); if (rc != pdPASS) { // Cleanup resources if task creation failed vQueueDeleteWithCaps(s_flash_dispatcher_ctx.queue); s_flash_dispatcher_ctx.queue = NULL; vQueueDeleteWithCaps(s_flash_dispatcher_ctx.result_queue); s_flash_dispatcher_ctx.result_queue = NULL; ESP_EARLY_LOGE(TAG, "create flash dispatcher task failed"); return ESP_ERR_INVALID_STATE; } s_flash_dispatcher_ctx.dispatcher_initialized = true; return ESP_OK; } /** * Send a flash operation request to the dispatcher queue and wait for the result. * The meaning and order of arg1/arg2/arg3 must match what the dispatcher task expects * for the given operation type. This helper centralizes the common send/wait logic. */ static esp_err_t flash_dispatcher_execute(flash_operation_t op, esp_flash_t *chip, void *arg1, void *arg2, void *arg3, const char *op_name) { ESP_RETURN_ON_FALSE(s_flash_dispatcher_ctx.dispatcher_initialized, ESP_ERR_INVALID_STATE, TAG, "flash dispatcher is not initialized"); esp_err_t operation_result = ESP_FAIL; flash_operation_request_t request = { 0 }; request.chip = chip; request.op = op; switch (op) { case FLASH_OP_READ: request.args.read.buffer = arg1; request.args.read.address = (uint32_t)(uintptr_t)arg2; request.args.read.size = (size_t)(uintptr_t)arg3; break; case FLASH_OP_WRITE: request.args.write.buffer = arg1; request.args.write.address = (uint32_t)(uintptr_t)arg2; request.args.write.size = (size_t)(uintptr_t)arg3; break; case FLASH_OP_WRITE_ENCRYPTED: request.args.write_encrypted.address = (uint32_t)(uintptr_t)arg1; request.args.write_encrypted.buffer = arg2; request.args.write_encrypted.size = (size_t)(uintptr_t)arg3; break; case FLASH_OP_ERASE_REGION: request.args.erase_region.start_address = (uint32_t)(uintptr_t)arg1; request.args.erase_region.size = (size_t)(uintptr_t)arg2; break; case FLASH_OP_ERASE_CHIP: default: break; } if (xQueueSend(s_flash_dispatcher_ctx.queue, &request, portMAX_DELAY) != pdTRUE) { ESP_EARLY_LOGE(TAG, "Failed to send %s request to queue", op_name ? op_name : "flash"); return ESP_ERR_TIMEOUT; } if (xQueueReceive(s_flash_dispatcher_ctx.result_queue, &operation_result, portMAX_DELAY) != pdTRUE) { ESP_EARLY_LOGE(TAG, "Failed to receive %s result from queue", op_name ? op_name : "flash"); return ESP_ERR_TIMEOUT; } return operation_result; } esp_err_t __wrap_esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint32_t size) { return flash_dispatcher_execute(FLASH_OP_READ, chip, (void *)buffer, (void *)address, (void *)size, "flash read"); } esp_err_t __wrap_esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t address, uint32_t size) { return flash_dispatcher_execute(FLASH_OP_WRITE, chip, (void *)buffer, (void *)address, (void *)size, "flash write"); } esp_err_t __wrap_esp_flash_write_encrypted(esp_flash_t *chip, uint32_t address, const void *buffer, uint32_t size) { return flash_dispatcher_execute(FLASH_OP_WRITE_ENCRYPTED, chip, (void *)address, (void *)buffer, (void *)size, "flash write_encrypted"); } esp_err_t __wrap_esp_flash_erase_region(esp_flash_t *chip, uint32_t start_address, uint32_t size) { return flash_dispatcher_execute(FLASH_OP_ERASE_REGION, chip, (void *)start_address, (void *)size, NULL, "flash erase_region"); } esp_err_t __wrap_esp_flash_erase_chip(esp_flash_t *chip) { return flash_dispatcher_execute(FLASH_OP_ERASE_CHIP, chip, NULL, NULL, NULL, "flash erase_chip"); } ================================================ FILE: esp_flash_dispatcher/idf_component.yml ================================================ version: "1.0.0" description: This component allows flash operations to be performed by tasks with stacks in PSRAM. url: https://github.com/espressif/idf-extra-components/tree/master/esp_flash_dispatcher repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.2" ================================================ FILE: esp_flash_dispatcher/include/esp_flash_dispatcher.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "freertos/FreeRTOS.h" #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /* Configuration for the flash dispatcher task. * The dispatcher serializes flash operations to a dedicated task. */ typedef struct { uint32_t task_stack_size; // Stack size for the dedicated flash task (in bytes) uint32_t task_priority; // Priority for the dedicated flash task BaseType_t task_core_id; // Core affinity (PRO_CPU_NUM, APP_CPU_NUM, or tskNO_AFFINITY) uint32_t queue_size; // Length of the request queue } esp_flash_dispatcher_config_t; /** * @brief Default configuration to init flash dispatcher */ #define ESP_FLASH_DISPATCHER_DEFAULT_CONFIG { \ .task_stack_size = 2048, \ .task_priority = 10, \ .task_core_id = tskNO_AFFINITY, \ .queue_size = 1, \ } /** * @brief Initialize flash dispatcher. * * @param[in] cfg: Configuration structure * * @return * - ESP_OK on success * - ESP_ERR_NO_MEM if there is no memory for allocating main structure * - ESP_ERR_INVALID_STATE if the dispatcher is already initialized */ esp_err_t esp_flash_dispatcher_init(const esp_flash_dispatcher_config_t *cfg); #ifdef __cplusplus } #endif ================================================ FILE: esp_flash_dispatcher/test_apps/CMakeLists.txt ================================================ # This is the project CMakeLists.txt file for the test subproject cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(test_esp_flash_dispatcher) ================================================ FILE: esp_flash_dispatcher/test_apps/README.md ================================================ | Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H4 | ESP32-P4 | ESP32-S3 | | ----------------- | -------- | --------- | -------- | -------- | -------- | ================================================ FILE: esp_flash_dispatcher/test_apps/main/CMakeLists.txt ================================================ set(srcs "test_app_main.c" "test_esp_flash_dispatcher.c") # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register(SRCS ${srcs} PRIV_REQUIRES unity spi_flash esp_psram esp_partition esp_flash_dispatcher INCLUDE_DIRS "." WHOLE_ARCHIVE) ================================================ FILE: esp_flash_dispatcher/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_flash_dispatcher: version: "*" override_path: "../.." ================================================ FILE: esp_flash_dispatcher/test_apps/main/test_app_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_utils.h" #include "esp_heap_caps.h" // The source allocated in esp_flash_dispatcher test is large, and these memory should never be freed. #define TEST_MEMORY_LEAK_THRESHOLD (3300) void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); } void app_main(void) { unity_run_menu(); } ================================================ FILE: esp_flash_dispatcher/test_apps/main/test_esp_flash_dispatcher.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include #include #include #include "esp_flash.h" #include #include "esp_log.h" #include "unity.h" #include "sdkconfig.h" #include "esp_flash_dispatcher.h" #include "esp_partition.h" // Buffer for PSRAM task stack static StackType_t *s_psram_task_stack = NULL; #define TEST_FLASH_ADDRESS 0x110000 // default address in flash for testing #define TEST_FLASH_SIZE 4096 #define TEST_PSRAM_TASK_STACK_SIZE 4096 static const esp_partition_t *get_test_data_partition(void) { /* This finds "flash_test" partition defined in partition_table_unit_test_app.csv */ const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "flash_test"); TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */ return result; } // The PSRAM task will take the creating task's handle as argument to notify it upon completion static void psram_flash_test_task(void *arg) { const esp_partition_t *test_part = get_test_data_partition(); TaskHandle_t creating_task_handle = (TaskHandle_t)arg; ESP_LOGI("PSRAM_TASK", "PSRAM task started on core %d", xPortGetCoreID()); esp_err_t ret; uint8_t *write_buf = (uint8_t *)heap_caps_malloc(TEST_FLASH_SIZE, MALLOC_CAP_INTERNAL); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(TEST_FLASH_SIZE, MALLOC_CAP_INTERNAL); TEST_ASSERT_NOT_NULL(write_buf); TEST_ASSERT_NOT_NULL(read_buf); // Fill write buffer with some pattern for (int i = 0; i < TEST_FLASH_SIZE; i++) { write_buf[i] = (uint8_t)(i % 256); } ESP_LOGI("PSRAM_TASK", "Performing flash erase at 0x%lx, size 0x%x", test_part->address, TEST_FLASH_SIZE); ret = esp_flash_erase_region(NULL, test_part->address, 4096); TEST_ASSERT_EQUAL(ESP_OK, ret); ESP_LOGI("PSRAM_TASK", "Flash erase successful."); ESP_LOGI("PSRAM_TASK", "Performing flash write to 0x%lx, size 0x%x", test_part->address, TEST_FLASH_SIZE); ret = esp_flash_write(NULL, write_buf, test_part->address, TEST_FLASH_SIZE); TEST_ASSERT_EQUAL(ESP_OK, ret); ESP_LOGI("PSRAM_TASK", "Flash write successful."); ESP_LOGI("PSRAM_TASK", "Performing flash read from 0x%lx, size 0x%x", test_part->address, TEST_FLASH_SIZE); ret = esp_flash_read(NULL, read_buf, test_part->address, TEST_FLASH_SIZE); TEST_ASSERT_EQUAL(ESP_OK, ret); ESP_LOGI("PSRAM_TASK", "Flash read successful."); // Verify data TEST_ASSERT_EQUAL_HEX8_ARRAY(write_buf, read_buf, TEST_FLASH_SIZE); ESP_LOGI("PSRAM_TASK", "Flash read data verified successfully."); heap_caps_free(write_buf); heap_caps_free(read_buf); // Notify the creating task that this task has completed xTaskNotifyGive(creating_task_handle); vTaskDelete(NULL); } TEST_CASE("Flash operations from PSRAM task", "[flash_dispatcher]") { // Use default configuration when cfg is NULL const esp_flash_dispatcher_config_t cfg = ESP_FLASH_DISPATCHER_DEFAULT_CONFIG; esp_flash_dispatcher_init(&cfg); ESP_LOGI("TEST", "Creating PSRAM task"); s_psram_task_stack = (StackType_t *)heap_caps_malloc(TEST_PSRAM_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); TEST_ASSERT_NOT_NULL(s_psram_task_stack); TaskHandle_t psram_task_handle; xTaskCreatePinnedToCore( psram_flash_test_task, "psram_flash_test", TEST_PSRAM_TASK_STACK_SIZE, // The size here is in bytes for dynamic allocation (void *)xTaskGetCurrentTaskHandle(), // Pass the current task handle as argument 5, // Priority &psram_task_handle, 0 // Run on APP_CPU if available, otherwise PRO_CPU ); TEST_ASSERT_NOT_NULL(psram_task_handle); // Wait for the PSRAM task to complete ulTaskNotifyTake(pdTRUE, portMAX_DELAY); vTaskDelay(2); heap_caps_free(s_psram_task_stack); s_psram_task_stack = NULL; } ================================================ FILE: esp_flash_dispatcher/test_apps/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0x9000, 0x6000, factory, 0, 0, 0x10000, 1M flash_test, data, fat, , 700K ================================================ FILE: esp_flash_dispatcher/test_apps/pytest_flash_dispatcher.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.quad_psram @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_esp_flash_dispatcher(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_flash_dispatcher/test_apps/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_INIT=n CONFIG_SPIRAM=y CONFIG_SPIRAM_XIP_FROM_PSRAM=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" ================================================ FILE: esp_gcov/.build-test-rules.yml ================================================ ================================================ FILE: esp_gcov/CMakeLists.txt ================================================ if(CONFIG_ESP_GCOV_ENABLE) if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU") # TODO: LLVM-214 message(FATAL_ERROR "Coverage info is supported only when building with GNU!") endif() # Set a name for Gcov library set(GCOV_LIB libgcov_rtio) # Set include directory of Gcov internal headers execute_process(COMMAND ${CMAKE_C_COMPILER} -print-file-name=plugin OUTPUT_VARIABLE gcc_plugin_dir OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE gcc_plugin_result) set_source_files_properties(gcov_rtio.c PROPERTIES COMPILE_FLAGS "-I${gcc_plugin_dir}/include") # Copy libgcov.a with symbols redefinition find_library(GCOV_LIBRARY_PATH gcov ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) # Check if libgcov.a was found if(NOT GCOV_LIBRARY_PATH) message(FATAL_ERROR "GCOV: libgcov.a not found in system directories: ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}") endif() # Verify libgcov.a file exists and is readable if(NOT EXISTS "${GCOV_LIBRARY_PATH}") message(FATAL_ERROR "GCOV: libgcov.a file does not exist") endif() # Check if symbol map file exists set(SYMBOL_MAP_FILE "${CMAKE_CURRENT_LIST_DIR}/io_sym.map") # Check if objcopy tool exists find_program(OBJCOPY_TOOL "${_CMAKE_TOOLCHAIN_PREFIX}objcopy") if(NOT OBJCOPY_TOOL) message(FATAL_ERROR "GCOV: objcopy tool not found: ${_CMAKE_TOOLCHAIN_PREFIX}objcopy. " "Make sure your toolchain is properly installed.") endif() add_custom_command(OUTPUT ${GCOV_LIB}.a COMMAND ${OBJCOPY_TOOL} --redefine-syms ${SYMBOL_MAP_FILE} ${GCOV_LIBRARY_PATH} ${GCOV_LIB}.a COMMAND ${CMAKE_COMMAND} -E echo "GCOV: Successfully created modified library: ${GCOV_LIB}.a" MAIN_DEPENDENCY ${GCOV_LIBRARY_PATH} DEPENDS ${SYMBOL_MAP_FILE} VERBATIM COMMENT "Creating GCOV library with symbol redefinition") add_custom_target(${GCOV_LIB}_target DEPENDS ${GCOV_LIB}.a) add_library(${GCOV_LIB} STATIC IMPORTED) set_target_properties(${GCOV_LIB} PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${GCOV_LIB}.a) add_dependencies(${GCOV_LIB} ${GCOV_LIB}_target) # Register component with app_trace dependency idf_component_register(SRCS "gcov_rtio.c" REQUIRES "esp_trace" INCLUDE_DIRS ".") # Configure gcov-specific flags target_compile_options(${COMPONENT_LIB} PRIVATE "-fno-profile-arcs" "-fno-test-coverage") target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=__gcov_init") target_link_libraries(${COMPONENT_LIB} INTERFACE ${GCOV_LIB} c) message(STATUS "GCOV: Component configured successfully") message(STATUS "GCOV: GCC Plugin Dir: ${gcc_plugin_dir}") message(STATUS "GCOV: Original Library: ${GCOV_LIBRARY_PATH}") message(STATUS "GCOV: Modified Library: ${CMAKE_CURRENT_BINARY_DIR}/${GCOV_LIB}.a") else() # Register empty component when GCOV is disabled idf_component_register(REQUIRES "esp_trace") message(STATUS "GCOV: Component registered but GCOV support is disabled") endif() ================================================ FILE: esp_gcov/Kconfig ================================================ menu "GNU Code Coverage" config ESP_GCOV_ENABLE bool "GCOV to Host Enable" depends on ESP_TRACE_TRANSPORT_APPTRACE && ESP_TRACE_LIB_NONE select ESP_DEBUG_STUBS_ENABLE select ESP_IPC_ENABLE default n help Enables support for GCOV data transfer to host. config ESP_GCOV_DUMP_TASK_STACK_SIZE int "Gcov dump task stack size" depends on ESP_GCOV_ENABLE default 2048 help Configures stack size of Gcov dump task endmenu ================================================ FILE: esp_gcov/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_gcov/README.md ================================================ # Gcov (Source Code Coverage) ## Basics of Gcov and Gcovr Source code coverage is data indicating the count and frequency of every program execution path that has been taken within a program's runtime. [Gcov](https://en.wikipedia.org/wiki/Gcov) is a GCC tool that, when used in concert with the compiler, can generate log files indicating the execution count of each line of a source file. The [Gcovr](https://gcovr.com/) tool is a utility for managing Gcov and generating summarized code coverage results. Generally, using Gcov to compile and run programs on the host will undergo these steps: 1. Compile the source code using GCC with the `--coverage` option enabled. This will cause the compiler to generate a `.gcno` notes files during compilation. The notes files contain information to reconstruct execution path block graphs and map each block to source code line numbers. Each source file compiled with the `--coverage` option should have their own `.gcno` file of the same name (e.g., a `main.c` will generate a `main.gcno` when compiled). 2. Execute the program. During execution, the program should generate `.gcda` data files. These data files contain the counts of the number of times an execution path was taken. The program will generate a `.gcda` file for each source file compiled with the `--coverage` option (e.g., `main.c` will generate a `main.gcda`). 3. Gcov or Gcovr can be used to generate a code coverage based on the `.gcno`, `.gcda`, and source files. Gcov will generate a text-based coverage report for each source file in the form of a `.gcov` file, whilst Gcovr will generate a coverage report in HTML format. ## Gcov and Gcovr in ESP-IDF Using Gcov in ESP-IDF is complicated due to the fact that the program is running remotely from the host (i.e., on the target). The code coverage data (i.e., the `.gcda` files) is initially stored on the target itself. OpenOCD is then used to dump the code coverage data from the target to the host via JTAG during runtime. Using Gcov in ESP-IDF can be split into the following steps. 1. [Setting Up a Project for Gcov](#setting-up-a-project-for-gcov) 2. [Dumping Code Coverage Data](#dumping-code-coverage-data) 3. [Generating Coverage Report](#generating-coverage-report) ## Setting Up a Project for Gcov ### Compiler Option In order to obtain code coverage data in a project, one or more source files within the project must be compiled with the `--coverage` option. In ESP-IDF, this can be achieved at the component level or the individual source file level: - To cause all source files in a component to be compiled with the `--coverage` option, you can add `target_compile_options(${COMPONENT_LIB} PRIVATE --coverage)` to the `CMakeLists.txt` file of the component. - To cause a select number of source files (e.g., `source1.c` and `source2.c`) in the same component to be compiled with the `--coverage` option, you can add `set_source_files_properties(source1.c source2.c PROPERTIES COMPILE_FLAGS --coverage)` to the `CMakeLists.txt` file of the component. When a source file is compiled with the `--coverage` option (e.g., `gcov_example.c`), the compiler will generate the `gcov_example.gcno` file in the project's build directory. ### Project Configuration Before building a project with source code coverage, make sure that the following project configuration options are enabled by running `idf.py menuconfig`. - Enable the application tracing module by selecting `Trace Memory` for the `CONFIG_APPTRACE_DESTINATION1` option. - Enable Gcov to the host via the `CONFIG_APPTRACE_GCOV_ENABLE`. ## Dumping Code Coverage Data Once a project has been complied with the `--coverage` option and flashed onto the target, code coverage data will be stored internally on the target (i.e., in trace memory) whilst the application runs. The process of transferring code coverage data from the target to the host is known as dumping. The dumping of coverage data is done via OpenOCD (see JTAG Debugging documentation on how to setup and run OpenOCD). A dump is triggered by issuing commands to OpenOCD, therefore a telnet session to OpenOCD must be opened to issue such commands (run `telnet localhost 4444`). Note that GDB could be used instead of telnet to issue commands to OpenOCD, however all commands issued from GDB will need to be prefixed as `mon `. When the target dumps code coverage data, the `.gcda` files are stored in the project's build directory. For example, if `gcov_example_main.c` of the `main` component is compiled with the `--coverage` option, then dumping the code coverage data would generate a `gcov_example_main.gcda` in `build/esp-idf/main/CMakeFiles/__idf_main.dir/gcov_example_main.c.gcda`. Note that the `.gcno` files produced during compilation are also placed in the same directory. The dumping of code coverage data can be done multiple times throughout an application's lifetime. Each dump will simply update the `.gcda` file with the newest code coverage information. Code coverage data is accumulative, thus the newest data will contain the total execution count of each code path over the application's entire lifetime. ESP-IDF supports two methods of dumping code coverage data form the target to the host: * Instant Run-Time Dump * Hard-coded Dump ### Instant Run-Time Dump An Instant Run-Time Dump is triggered by calling the `esp gcov` OpenOCD command (via a telnet session). Once called, OpenOCD will immediately preempt the {IDF_TARGET_NAME}'s current state and execute a built-in ESP-IDF Gcov debug stub function. The debug stub function will handle the dumping of data to the host. Upon completion, the {IDF_TARGET_NAME} will resume its current state. ### Hard-coded Dump A Hard-coded Dump is triggered by the application itself by calling `esp_gcov_dump()` from somewhere within the application. When called, the application will halt and wait for OpenOCD to connect and retrieve the code coverage data. Once `esp_gcov_dump()` is called, the host must execute the `esp gcov dump` OpenOCD command (via a telnet session). The `esp gcov dump` command will cause OpenOCD to connect to the {IDF_TARGET_NAME}, retrieve the code coverage data, then disconnect from the {IDF_TARGET_NAME}, thus allowing the application to resume. Hard-coded Dumps can also be triggered multiple times throughout an application's lifetime. Hard-coded dumps are useful if code coverage data is required at certain points of an application's lifetime by placing `esp_gcov_dump()` where necessary (e.g., after application initialization, during each iteration of an application's main loop). GDB can be used to set a breakpoint on `esp_gcov_dump()`, then call `mon esp gcov dump` automatically via the use a `gdbinit` script (see Using GDB from JTAG debugging documentation). The following GDB script will add a breakpoint at `esp_gcov_dump()`, then call the `mon esp gcov dump` OpenOCD command. ``` b esp_gcov_dump commands mon esp gcov dump end ``` > **Note:** All OpenOCD commands should be invoked in GDB as: `mon `. ## Generating Coverage Report Once the code coverage data has been dumped, the `.gcno`, `.gcda` and the source files can be used to generate a code coverage report. A code coverage report is simply a report indicating the number of times each line in a source file has been executed. Both Gcov and Gcovr can be used to generate code coverage reports. Gcov is provided along with the Xtensa toolchain, whilst Gcovr may need to be installed separately. For details on how to use Gcov or Gcovr, refer to [Gcov documentation](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html) and [Gcovr documentation](https://gcovr.com/). ### Adding Gcovr Build Target to Project To make report generation more convenient, users can define additional build targets in their projects such that the report generation can be done with a single build command. Add the following lines to the `CMakeLists.txt` file of your project. ```cmake idf_create_coverage_report(${CMAKE_CURRENT_BINARY_DIR}/coverage_report) idf_clean_coverage_report(${CMAKE_CURRENT_BINARY_DIR}/coverage_report) ``` The following commands can now be used: * `cmake --build build/ --target gcovr-report` will generate an HTML coverage report in `$(BUILD_DIR_BASE)/coverage_report/html` directory. * `cmake --build build/ --target cov-data-clean` will remove all coverage data files. ================================================ FILE: esp_gcov/esp_gcov.h ================================================ /* * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once /** * @brief Triggers gcov info dump. * This function waits for the host to connect to target before dumping data. */ void esp_gcov_dump(void); ================================================ FILE: esp_gcov/gcov_rtio.c ================================================ /* * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ // This module implements runtime file I/O API for GCOV. #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_app_trace.h" #include "esp_freertos_hooks.h" #include "esp_dbg_stubs.h" #include "esp_private/esp_ipc.h" #include "esp_log.h" #define ESP_GCOV_DOWN_BUF_SIZE 4200 static const char *TAG = "esp_gcov_rtio"; static volatile bool s_create_gcov_task = false; static volatile bool s_gcov_task_running = false; extern void __gcov_dump(void); extern void __gcov_reset(void); void gcov_dump_task(void *pvParameter) { int dump_result = 0; bool *running = (bool *)pvParameter; ESP_EARLY_LOGV(TAG, "%s stack use in %d", __FUNCTION__, uxTaskGetStackHighWaterMark(NULL)); ESP_EARLY_LOGV(TAG, "Alloc apptrace down buf %d bytes", ESP_GCOV_DOWN_BUF_SIZE); void *down_buf = malloc(ESP_GCOV_DOWN_BUF_SIZE); if (down_buf == NULL) { ESP_EARLY_LOGE(TAG, "Could not allocate memory for the buffer"); dump_result = ESP_ERR_NO_MEM; goto gcov_exit; } ESP_EARLY_LOGV(TAG, "Config apptrace down buf"); esp_err_t res = esp_apptrace_down_buffer_config(down_buf, ESP_GCOV_DOWN_BUF_SIZE); if (res != ESP_OK) { ESP_EARLY_LOGE(TAG, "Failed to config apptrace down buf (%d)!", res); dump_result = res; goto gcov_exit; } ESP_EARLY_LOGV(TAG, "Dump data..."); __gcov_dump(); // reset dump status to allow incremental data accumulation __gcov_reset(); free(down_buf); ESP_EARLY_LOGV(TAG, "Finish file transfer session"); dump_result = esp_apptrace_fstop(); if (dump_result != ESP_OK) { ESP_EARLY_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!", dump_result); } gcov_exit: ESP_EARLY_LOGV(TAG, "dump_result %d", dump_result); if (running) { *running = false; } ESP_EARLY_LOGV(TAG, "%s stack use out %d", __FUNCTION__, uxTaskGetStackHighWaterMark(NULL)); vTaskDelete(NULL); } void gcov_create_task(void *arg) { ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__); xTaskCreatePinnedToCore(&gcov_dump_task, "gcov_dump_task", CONFIG_ESP_GCOV_DUMP_TASK_STACK_SIZE, (void *)&s_gcov_task_running, configMAX_PRIORITIES - 1, NULL, 0); } static IRAM_ATTR void gcov_create_task_tick_hook(void) { if (s_create_gcov_task) { if (esp_ipc_call_nonblocking(xPortGetCoreID(), &gcov_create_task, NULL) == ESP_OK) { s_create_gcov_task = false; } } } /** * @brief Triggers gcov info dump task * This function is to be called by OpenOCD, not by normal user code. * TODO: what about interrupted flash access (when cache disabled) * * @return ESP_OK on success, otherwise see esp_err_t */ static int esp_dbg_stub_gcov_entry(void) { /* we are in isr context here */ s_create_gcov_task = true; return ESP_OK; } void gcov_rtio_init(void) { uint32_t stub_entry = 0; ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__); assert(esp_dbg_stub_entry_get(ESP_DBG_STUB_ENTRY_GCOV, &stub_entry) == ESP_OK); if (stub_entry != 0) { /* "__gcov_init()" can be called several times. We must avoid multiple tick hook registration */ return; } esp_dbg_stub_entry_set(ESP_DBG_STUB_ENTRY_GCOV, (uint32_t)&esp_dbg_stub_gcov_entry); assert(esp_dbg_stub_entry_get(ESP_DBG_STUB_ENTRY_CAPABILITIES, &stub_entry) == ESP_OK); esp_dbg_stub_entry_set(ESP_DBG_STUB_ENTRY_CAPABILITIES, stub_entry | ESP_DBG_STUB_CAP_GCOV_TASK); esp_register_freertos_tick_hook(gcov_create_task_tick_hook); } void esp_gcov_dump(void) { ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__); while (!esp_apptrace_host_is_connected()) { vTaskDelay(pdMS_TO_TICKS(10)); } /* We are not in isr context here. Waiting for the completion is safe */ s_gcov_task_running = true; s_create_gcov_task = true; while (s_gcov_task_running) { vTaskDelay(pdMS_TO_TICKS(10)); } } void *gcov_rtio_fopen(const char *path, const char *mode) { ESP_EARLY_LOGV(TAG, "%s '%s' '%s'", __FUNCTION__, path, mode); void *f = esp_apptrace_fopen(path, mode); ESP_EARLY_LOGV(TAG, "%s ret %p", __FUNCTION__, f); return f; } int gcov_rtio_fclose(void *stream) { ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__); return esp_apptrace_fclose(stream); } size_t gcov_rtio_fread(void *ptr, size_t size, size_t nmemb, void *stream) { ESP_EARLY_LOGV(TAG, "%s read %u", __FUNCTION__, size * nmemb); size_t sz = esp_apptrace_fread(ptr, size, nmemb, stream); ESP_EARLY_LOGV(TAG, "%s actually read %u", __FUNCTION__, sz); return sz; } size_t gcov_rtio_fwrite(const void *ptr, size_t size, size_t nmemb, void *stream) { ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__); return esp_apptrace_fwrite(ptr, size, nmemb, stream); } int gcov_rtio_fseek(void *stream, long offset, int whence) { int ret = esp_apptrace_fseek(stream, offset, whence); ESP_EARLY_LOGV(TAG, "%s(%p %ld %d) = %d", __FUNCTION__, stream, offset, whence, ret); return ret; } long gcov_rtio_ftell(void *stream) { long ret = esp_apptrace_ftell(stream); ESP_EARLY_LOGV(TAG, "%s(%p) = %ld", __FUNCTION__, stream, ret); return ret; } int gcov_rtio_feof(void *stream) { int ret = esp_apptrace_feof(stream); ESP_EARLY_LOGV(TAG, "%s(%p) = %d", __FUNCTION__, stream, ret); return ret; } void gcov_rtio_setbuf(void *arg1 __attribute__((unused)), void *arg2 __attribute__((unused))) { return; } /* Wrappers for Gcov functions */ extern void __real___gcov_init(void *info); void __wrap___gcov_init(void *info) { __real___gcov_init(info); gcov_rtio_init(); } ================================================ FILE: esp_gcov/idf_component.yml ================================================ version: 1.0.5 description: Gcov (Source Code Coverage) component for ESP-IDF url: https://github.com/espressif/idf-extra-components/tree/master/esp_gcov issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git dependencies: idf: ">=6.0" ================================================ FILE: esp_gcov/project_include.cmake ================================================ # idf_create_coverage_report # # Create coverage report. # # Arguments: # report_dir - Directory where the coverage report will be generated # SOURCE_DIR - (Optional) Source directory to use as root for gcovr. # If not provided, defaults to PROJECT_DIR. # GCOV_OPTIONS - (Optional) Additional options to pass to gcovr. # # Example: # idf_create_coverage_report(${report_dir}) # Uses PROJECT_DIR # idf_create_coverage_report(${report_dir} SOURCE_DIR ${component_dir}) # Uses custom source dir # idf_create_coverage_report(${report_dir} GCOV_OPTIONS "--gcov-ignore-parse-errors=negative_hits.warn") # function(idf_create_coverage_report report_dir) cmake_parse_arguments(ARG "" "SOURCE_DIR" "GCOV_OPTIONS" ${ARGN}) set(gcov_tool ${_CMAKE_TOOLCHAIN_PREFIX}gcov) # Use provided SOURCE_DIR or default to PROJECT_DIR if(ARG_SOURCE_DIR) set(source_root_dir "${ARG_SOURCE_DIR}") else() idf_build_get_property(source_root_dir PROJECT_DIR) endif() file(TO_NATIVE_PATH "${report_dir}" _report_dir) file(TO_NATIVE_PATH "${source_root_dir}" _source_root_dir) file(TO_NATIVE_PATH "${report_dir}/html/index.html" _index_path) add_custom_target(pre-cov-report COMMENT "Generating coverage report in: ${_report_dir}" COMMAND ${CMAKE_COMMAND} -E echo "Using gcov: ${gcov_tool}" COMMAND ${CMAKE_COMMAND} -E echo "Source root: ${_source_root_dir}" COMMAND ${CMAKE_COMMAND} -E make_directory ${_report_dir}/html ) add_custom_target(gcovr-report COMMAND gcovr -r ${_source_root_dir} --gcov-executable ${gcov_tool} ${ARG_GCOV_OPTIONS} -s --html-details ${_index_path} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS pre-cov-report ) endfunction() # idf_clean_coverage_report # # Clean coverage report. function(idf_clean_coverage_report report_dir) file(TO_CMAKE_PATH "${report_dir}" _report_dir) add_custom_target(cov-data-clean COMMENT "Clean coverage report in: ${_report_dir}" COMMAND ${CMAKE_COMMAND} -E remove_directory ${_report_dir}) endfunction() ================================================ FILE: esp_gcov/sdkconfig.rename ================================================ # sdkconfig replacement configurations for deprecated options formatted as # CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION CONFIG_ESP32_GCOV_ENABLE CONFIG_ESP_GCOV_ENABLE CONFIG_APPTRACE_GCOV_DUMP_TASK_STACK_SIZE CONFIG_ESP_GCOV_DUMP_TASK_STACK_SIZE ================================================ FILE: esp_isotp/.build-test-rules.yml ================================================ ================================================ FILE: esp_isotp/CMakeLists.txt ================================================ idf_component_register( SRCS "src/esp_isotp.c" "isotp-c/isotp.c" INCLUDE_DIRS "inc" PRIV_INCLUDE_DIRS "isotp-c" REQUIRES esp_driver_twai PRIV_REQUIRES esp_timer ) # Force include our custom isotp_config.h before any source file compilation # This ensures our configuration is loaded first, preventing macro redefinitions target_compile_options(${COMPONENT_LIB} PRIVATE "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/src/isotp_config.h" ) ================================================ FILE: esp_isotp/Kconfig ================================================ menu "ISO-TP Protocol" config ISO_TP_DEFAULT_BLOCK_SIZE int "Default Block Size" default 8 range 1 32 help Maximum number of consecutive frames the receiver can receive at one time. This value is affected by CAN driver queue length. Higher values improve throughput but require more buffer space. config ISO_TP_DEFAULT_ST_MIN_US int "Default STmin (microseconds)" default 1000 range 0 50000 help The STmin parameter specifies the minimum time gap allowed between the transmission of consecutive frame network protocol data units. 0 = no delay, 1-50000 microseconds for timing control. config ISO_TP_MAX_WFT_NUMBER int "Maximum Wait Frame Number" default 1 range 1 10 help This parameter indicates how many FC N_PDU WTs (Wait frames) can be transmitted by the receiver in a row before giving up. config ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US int "Default Response Timeout (microseconds)" default 100000 range 10000 5000000 help The default timeout to use when waiting for a response during a multi-frame send or receive operation. Common values: 100000 (100ms), 1000000 (1s). config ISO_TP_FRAME_PADDING bool "Enable Frame Padding" default n help Enable padding of ISO-TP message frames to full CAN frame size. When enabled, frames are padded with ISO_TP_FRAME_PADDING_VALUE. config ISO_TP_FRAME_PADDING_VALUE hex "Frame Padding Value" default 0xAA range 0x00 0xFF depends on ISO_TP_FRAME_PADDING help Value used for padding when ISO_TP_FRAME_PADDING is enabled. Common values: 0x00, 0xAA, 0xCC, 0xFF. endmenu ================================================ FILE: esp_isotp/LICENSE ================================================ MIT License Copyright (c) 2019-2024 Li Shen & Co-Operators Copyright (c) 2024 Simon Cahill & Contributors. 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: esp_isotp/README.md ================================================ # ESP-IDF ISO-TP Component [![Component Registry](https://components.espressif.com/components/espressif/esp_isotp/badge.svg)](https://components.espressif.com/components/espressif/esp_isotp) ISO 15765-2 (ISO-TP) for ESP-IDF. Sends/receives large payloads (≤4095 B) over TWAI with segmentation and reassembly. ## Key Features - Segmentation + flow control for >7 B - Non-blocking API, ISR-backed - Multiple links in parallel - UDS/OBD-II friendly - Timeouts + sequence checks - 11-bit and 29-bit IDs > [!NOTE] > TWAI-FD (Flexible Data-rate) is not supported in this version. ## Installation To add this component to your project, run the following command from your project's root directory: ```bash idf.py add-dependency espressif/esp_isotp ``` ## Configuration You can configure protocol parameters like timing through the project configuration menu: ```bash idf.py menuconfig ``` Navigate to `Component config` → `ISO-TP Protocol Configuration`. ## Quick Start Guide Here's a simple example that initializes the TWAI driver and an ISO-TP link, then echoes back any received data. ### Example Code ```c #include "esp_isotp.h" #include "esp_twai_onchip.h" void app_main(void) { // 1) Init TWAI twai_onchip_node_config_t twai_cfg = { .io_cfg = {.tx = GPIO_NUM_5, .rx = GPIO_NUM_4}, .bit_timing = {.bitrate = 500000}, .tx_queue_depth = 16, }; twai_node_handle_t twai_node; ESP_ERROR_CHECK(twai_new_node_onchip(&twai_cfg, &twai_node)); // 2) Create ISO-TP (11-bit IDs, auto-detected) esp_isotp_config_t config = { .tx_id = 0x7E0, // request ID (11-bit, auto-detected) .rx_id = 0x7E8, // response ID (11-bit, auto-detected) .tx_buffer_size = 4096, .rx_buffer_size = 4096, }; esp_isotp_handle_t isotp_handle; ESP_ERROR_CHECK(esp_isotp_new_transport(twai_node, &config, &isotp_handle)); // 3) Loop uint8_t buffer[4096]; uint32_t received_size; while (1) { // This is the engine of the component. Call it frequently! esp_isotp_poll(isotp_handle); // Check if a full message has been received if (esp_isotp_receive(isotp_handle, buffer, sizeof(buffer), &received_size) == ESP_OK) { printf("Received %lu bytes\n", received_size); // Echo the message back esp_isotp_send(isotp_handle, buffer, received_size); } vTaskDelay(pdMS_TO_TICKS(5)); // A small delay is good practice } } ``` ## Extended (29-bit) IDs - ID format is auto-detected: IDs > 0x7FF use 29-bit extended format, others use 11-bit standard format. - `tx_id` and `rx_id` must differ and match peer's expected format. ```c esp_isotp_config_t cfg = { .tx_id = 0x18DAF110, // 29-bit ID (auto-detected as extended) .rx_id = 0x18DA10F1, // 29-bit ID (auto-detected as extended) .tx_buffer_size = 4096, .rx_buffer_size = 4096, }; ``` ## Errors - Common: ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_SIZE, ESP_ERR_NO_MEM, ESP_ERR_TIMEOUT - Send: ESP_ERR_NOT_FINISHED when previous TX in progress - Receive: ESP_ERR_NOT_FOUND when no complete message; ESP_ERR_INVALID_RESPONSE on bad sequence - Full list: see `esp_isotp.h` ## Checklist - IDs valid and different; 11-bit vs 29-bit matches `use_extended_id` - Buffers > 0; size gates max single-message length (≤4095 B) - Call `esp_isotp_poll()` every 1–10 ms - TWAI node created and enabled before use ================================================ FILE: esp_isotp/idf_component.yml ================================================ version: "0.1.1" description: ISO-TP (ISO 15765-2) protocol implementation for ESP-IDF url: https://github.com/espressif/idf-extra-components/tree/master/esp_isotp repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.5.0" ================================================ FILE: esp_isotp/inc/esp_isotp.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once /** * @file esp_isotp.h * @brief ISO-TP (ISO 15765-2) Transport Protocol Implementation * * ISO-TP enables transmission of data larger than 8 bytes over TWAI networks * through automatic fragmentation and reassembly. * * ## How it Works * * **Small packets (≤7 bytes)**: Sent in a single TWAI frame immediately. * **Large packets (>7 bytes)**: Split into multiple frames - first frame sent immediately, * remaining frames sent during esp_isotp_poll() calls. * */ #include "esp_err.h" #include "esp_twai.h" #include "esp_twai_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief ISO-TP link handle */ typedef struct esp_isotp_link_t *esp_isotp_handle_t; /** * @brief ISO-TP receive callback function type * * Called when a complete message has been received successfully. * * @warning This callback executes in the same context as isotp_on_can_message(), * which is typically ISR context. Keep callback execution time minimal * and avoid blocking operations. * @note The data pointer is only valid during the callback execution. * Copy data immediately if needed beyond the callback scope. * * @param[in] handle ISO-TP handle that received the message * @param[in] data Pointer to received data (valid only during callback) * @param[in] size Size of received data in bytes * @param[in] user_arg User argument provided during configuration */ typedef void (*esp_isotp_rx_callback_t)(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size, void *user_arg); /** * @brief ISO-TP transmit callback function type * * Called when a complete message has been transmitted successfully. * * @note Execution context depends on message type: * - Single-frame: Called immediately from isotp_send() in caller's context * - Multi-frame: Called from isotp_poll() in task context * @note Keep callback execution time minimal to avoid affecting system performance. * * @param[in] handle ISO-TP handle that transmitted the message * @param[in] tx_size Size of transmitted data in bytes * @param[in] user_arg User argument provided during configuration */ typedef void (*esp_isotp_tx_callback_t)(esp_isotp_handle_t handle, uint32_t tx_size, void *user_arg); /** * @brief Configuration structure for creating a new ISO-TP link */ typedef struct { uint32_t tx_id; /*!< TWAI ID for transmitting ISO-TP frames (11-bit or 29-bit, auto-detected from value) */ uint32_t rx_id; /*!< TWAI ID for receiving ISO-TP frames (11-bit or 29-bit, auto-detected from value) */ uint32_t tx_buffer_size; /*!< Size of the transmit buffer (max message size to send) */ uint32_t rx_buffer_size; /*!< Size of the receive buffer (max message size to receive) */ uint32_t tx_frame_pool_size; /*!< Size of TX frame pool */ esp_isotp_rx_callback_t rx_callback; /*!< Receive completion callback (NULL for polling mode) */ esp_isotp_tx_callback_t tx_callback; /*!< Transmit completion callback (NULL to disable) */ void *callback_arg; /*!< User argument passed to callbacks */ } esp_isotp_config_t; /** * @brief Create a new ISO-TP transport bound to a TWAI node. * * Allocates internal buffers, creates TX frame pool, registers TWAI callbacks * and enables the provided TWAI node. * * @param twai_node TWAI node handle to bind. * @param config Transport configuration. * @param[out] out_handle Returned ISO-TP transport handle. * @return esp_err_t * - ESP_OK on success * - ESP_ERR_INVALID_ARG for invalid parameters * - ESP_ERR_INVALID_SIZE for invalid buffer sizes * - ESP_ERR_NO_MEM when allocation fails * - Other error codes from TWAI functions */ esp_err_t esp_isotp_new_transport(twai_node_handle_t twai_node, const esp_isotp_config_t *config, esp_isotp_handle_t *out_handle); /** * @brief Send data over an ISO-TP link (non-blocking, ISR-safe) * * Immediately sends first/single frame and returns. For multi-frame messages, * remaining frames are sent during subsequent esp_isotp_poll() calls. * * @note This function is ISR-safe and can be called from interrupt context. * @note TX completion callback timing: * - Single-frame: Called immediately from isotp_send() * - Multi-frame: Called from isotp_poll() when last frame is sent * * @param handle ISO-TP handle * @param data Data to send * @param size Data length in bytes * @return * - ESP_OK: Send initiated successfully * - ESP_ERR_NOT_FINISHED: Previous send still in progress * - ESP_ERR_NO_MEM: Data too large for buffer or no space available * - ESP_ERR_INVALID_SIZE: Invalid data size * - ESP_ERR_TIMEOUT: Send operation timed out * - ESP_ERR_INVALID_ARG: Invalid parameters * - ESP_FAIL: Other send errors */ esp_err_t esp_isotp_send(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size); /** * @brief Send data over an ISO-TP link with specified TWAI ID (non-blocking, ISR-safe) * * Similar to esp_isotp_send(), but allows specifying a different TWAI ID for transmission. * This function is primarily used for functional addressing where multiple nodes * may respond to the same request. * * @note This function is ISR-safe and can be called from interrupt context. * @note TX completion callback timing: * - Single-frame: Called immediately from isotp_send_with_id() * - Multi-frame: Called from isotp_poll() when last frame is sent * * @param handle ISO-TP handle * @param id TWAI identifier to use for transmission (overrides configured tx_id) * @param data Data to send * @param size Data length in bytes * @return * - ESP_OK: Send initiated successfully * - ESP_ERR_NOT_FINISHED: Previous send still in progress * - ESP_ERR_NO_MEM: Data too large for buffer or no space available * - ESP_ERR_INVALID_SIZE: Invalid data size * - ESP_ERR_TIMEOUT: Send operation timed out * - ESP_ERR_INVALID_ARG: Invalid parameters or ID exceeds maximum value * - ESP_FAIL: Other send errors */ esp_err_t esp_isotp_send_with_id(esp_isotp_handle_t handle, uint32_t id, const uint8_t *data, uint32_t size); /** * @brief Extract a complete received message (non-blocking, task context only) * * This function only extracts data that has already been assembled by esp_isotp_poll(). * It does NOT process incoming TWAI frames - that happens in esp_isotp_poll(). * * Process: TWAI frames → esp_isotp_poll() assembles → esp_isotp_receive() extracts * * @warning This function is NOT ISR-safe and must only be called from task context. * @note When using callback mode (rx_callback != NULL), received messages are * automatically delivered via callback. This function should only be used * in polling mode (rx_callback == NULL). * * @param handle ISO-TP handle * @param data Buffer to store received data * @param size Buffer size in bytes * @param[out] received_size Actual received data length * @return * - ESP_OK: Complete message extracted and internal buffer cleared * - ESP_ERR_NOT_FOUND: No complete message ready for extraction * - ESP_ERR_INVALID_SIZE: Receive buffer overflow or invalid size * - ESP_ERR_INVALID_RESPONSE: Invalid sequence number or protocol error * - ESP_ERR_TIMEOUT: Receive operation timed out * - ESP_ERR_INVALID_ARG: Invalid parameters * - ESP_FAIL: Other receive errors */ esp_err_t esp_isotp_receive(esp_isotp_handle_t handle, uint8_t *data, uint32_t size, uint32_t *received_size); /** * @brief Poll the ISO-TP link to process messages (CRITICAL - call regularly, task context only) * * This function drives the ISO-TP state machine. Call every 1-10ms for proper operation. * * What it does: * - Sends remaining frames for multi-frame messages * - Processes incoming TWAI frames and assembles complete messages * - Handles flow control and timeouts * - Updates internal state machine * - Triggers TX completion callbacks for multi-frame messages * * Without regular polling: multi-frame sends will stall and receives won't complete. * * @warning This function is NOT ISR-safe and must only be called from task context. * @note TX completion callbacks for multi-frame messages are triggered from this function. * * @param handle ISO-TP handle * @return * - ESP_OK: Processing successful * - ESP_ERR_INVALID_ARG: Invalid parameters */ esp_err_t esp_isotp_poll(esp_isotp_handle_t handle); /** * @brief Delete an ISO-TP link * * @param handle The handle of the ISO-TP link to delete * @return * - ESP_OK: Success (or TWAI disable warning logged) * - ESP_ERR_INVALID_ARG: Invalid argument * - Other ESP error codes: TWAI node disable failed */ esp_err_t esp_isotp_delete(esp_isotp_handle_t handle); #ifdef __cplusplus } #endif ================================================ FILE: esp_isotp/src/esp_isotp.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "esp_check.h" #include "esp_log.h" #include "esp_timer.h" #include "esp_twai.h" #include "esp_isotp.h" // Include isotp-c library from submodule #include "isotp.h" static const char *TAG = "esp_isotp"; /** * @brief Determine if the given ID requires extended (29-bit) format * * @param id TWAI identifier to check * @return true if ID requires 29-bit extended format, false for 11-bit standard format */ static inline bool is_extended_id(uint32_t id) { return (id > TWAI_STD_ID_MASK); } /** * @brief TWAI frame container with embedded data buffer. * * This structure wraps the TWAI frame with an embedded 8-byte data buffer * to ensure memory safety during asynchronous operations. * * Used for: * - TX frames: Pre-allocated in SLIST pool, recycled after transmission * - RX frames: Pre-allocated in link structure for ISR-safe reception */ typedef struct esp_isotp_frame_t { twai_frame_t frame; ///< TWAI driver frame structure uint8_t data_payload[8]; ///< Embedded 8-byte TWAI frame data buffer SLIST_ENTRY(esp_isotp_frame_t) link; ///< Single-linked list entry for frame pool } esp_isotp_frame_t; SLIST_HEAD(frame_pool_head, esp_isotp_frame_t); /** * @brief ISO-TP link context structure. * * Contains all state and buffers needed for an ISO-TP transport session. * This structure bridges the isotp-c library with ESP-IDF TWAI driver. */ typedef struct esp_isotp_link_t { IsoTpLink link; ///< isotp-c library link state twai_node_handle_t twai_node; ///< Associated TWAI driver node handle uint8_t *isotp_tx_buffer; ///< ISO-TP TX reassembly buffer (for multi-frame messages) uint8_t *isotp_rx_buffer; ///< ISO-TP RX reassembly buffer (for multi-frame messages) esp_isotp_frame_t isr_rx_frame_buffer; ///< Pre-allocated frame buffer for ISR-safe RX operations struct frame_pool_head tx_frame_pool; ///< Single-linked list of available TX frames esp_isotp_frame_t *tx_frame_array; ///< Pre-allocated array of TX frames size_t tx_frame_pool_size; ///< Size of TX frame pool esp_isotp_rx_callback_t rx_callback; ///< User RX callback function esp_isotp_tx_callback_t tx_callback; ///< User TX callback function void *callback_arg; ///< User argument for callbacks } esp_isotp_link_t; /** * @brief Wrapper callback for isotp-c RX completion. * * This function wraps the user's callback to hide isotp-c internal link parameter. * * @note Execution context: This callback runs in the same context as isotp_on_can_message(), * which is typically called from ISR context (esp_isotp_rx_callback). * Therefore, user RX callbacks should avoid blocking operations and keep execution minimal. * * @param link ISO-TP link handle (unused). * @param data Pointer to received data. * @param size Size of received data in bytes. * @param user_arg User context pointer (esp_isotp_handle_t). */ #ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK static void esp_isotp_rx_wrapper(void *link, const uint8_t *data, uint32_t size, void *user_arg) { esp_isotp_handle_t handle = (esp_isotp_handle_t)user_arg; if (handle && handle->rx_callback) { handle->rx_callback(handle, data, size, handle->callback_arg); } } #endif /** * @brief Wrapper callback for isotp-c TX completion. * * This function wraps the user's callback to hide isotp-c internal link parameter. * * @note Execution context depends on transmission type: * - Single-frame: Called from isotp_send() in the same context as the caller * (may be ISR context if esp_isotp_send() was called from ISR) * - Multi-frame: Called from isotp_poll() in task context when the last frame is sent * * @param link ISO-TP link handle (unused). * @param tx_size Size of transmitted data in bytes. * @param user_arg User context pointer (esp_isotp_handle_t). */ #ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK static void esp_isotp_tx_wrapper(void *link, uint32_t tx_size, void *user_arg) { esp_isotp_handle_t handle = (esp_isotp_handle_t)user_arg; if (handle && handle->tx_callback) { handle->tx_callback(handle, tx_size, handle->callback_arg); } } #endif /** * @brief TWAI transmit done callback. * * Called when a TWAI frame transmission is complete. Returns the used * frame back to the SLIST pool for reuse. * * @note Runs in ISR context. * @param handle TWAI node handle invoking the callback. * @param edata Transmit event data from TWAI driver. * @param user_ctx User context pointer (esp_isotp_handle_t). * @return Always returns false (no context switch needed). */ static IRAM_ATTR bool esp_isotp_tx_callback(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx) { esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t) user_ctx; // Return the used frame back to the SLIST pool. if (isotp_handle && edata->done_tx_frame) { esp_isotp_frame_t *tx_frame = (esp_isotp_frame_t *)edata->done_tx_frame; // Return frame to SLIST pool for reuse SLIST_INSERT_HEAD(&isotp_handle->tx_frame_pool, tx_frame, link); } return false; } /** * @brief TWAI receive done callback. * * Processes a received TWAI frame and feeds it to the ISO-TP state machine. * * @note Runs in ISR context. * @param handle TWAI node handle invoking the callback. * @param edata Receive event data from TWAI driver (unused). * @param user_ctx User context pointer (esp_isotp_handle_t). * @return true to request a context switch to a higher-priority task, false otherwise. */ static IRAM_ATTR bool esp_isotp_rx_callback(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx) { esp_isotp_handle_t link_handle = (esp_isotp_handle_t)user_ctx; if (!link_handle) { return false; // No valid context, nothing to do } esp_isotp_frame_t *rx_frame = &link_handle->isr_rx_frame_buffer; if (twai_node_receive_from_isr(handle, &rx_frame->frame) != ESP_OK) { return false; } // ID match check if (rx_frame->frame.header.id != link_handle->link.receive_arbitration_id) { return false; } // Feed received TWAI frame to isotp-c state machine for reassembly. // isotp-c will handle single/multi-frame logic and send flow control frames as needed. isotp_on_can_message(&link_handle->link, rx_frame->frame.buffer, rx_frame->frame.buffer_len); return false; } /** * @brief Get monotonic timestamp in microseconds. * * Returns the current time in microseconds as a 32-bit, monotonically * increasing value. Wrap-around is expected; the library compares * timestamps using IsoTpTimeAfter(). * * @return 32-bit timestamp in microseconds. */ uint32_t isotp_user_get_us(void) { return (uint32_t)esp_timer_get_time(); } /** * @brief isotp-c library stub function: send twai message * * Queues a TWAI frame for transmission using the configured TWAI node. * * @param arbitration_id TWAI identifier (11-bit or 29-bit). * @param data Pointer to frame payload. * @param size Payload length in bytes (0–8). * @param user_data Optional ISO-TP link handle. * @retval ISOTP_RET_OK Frame queued successfully. * @retval ISOTP_RET_ERROR Transmission failed or invalid context. */ int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size, void *user_data) { esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t) user_data; ESP_RETURN_ON_FALSE_ISR(isotp_handle != NULL, ISOTP_RET_ERROR, TAG, "Invalid ISO-TP handle"); twai_node_handle_t twai_node = isotp_handle->twai_node; // Get a pre-allocated frame from the SLIST pool. // This avoids dynamic allocation overhead completely. esp_isotp_frame_t *tx_frame = SLIST_FIRST(&isotp_handle->tx_frame_pool); ESP_RETURN_ON_FALSE_ISR(tx_frame != NULL, ISOTP_RET_ERROR, TAG, "No available frames in pool"); // Remove frame from pool SLIST_REMOVE_HEAD(&isotp_handle->tx_frame_pool, link); // Initialize TWAI frame header and copy payload data into embedded buffer. memset(&tx_frame->frame, 0, sizeof(twai_frame_t)); tx_frame->frame.header.id = arbitration_id; tx_frame->frame.header.ide = is_extended_id(arbitration_id); // Extended (29-bit) vs Standard (11-bit) ID // Size validation - TWAI frames are max 8 bytes by protocol ESP_RETURN_ON_FALSE_ISR(size <= 8, ISOTP_RET_ERROR, TAG, "Invalid TWAI frame size"); // Copy payload into the embedded buffer to ensure data lifetime during async transmission. memcpy(tx_frame->data_payload, data, size); tx_frame->frame.buffer = tx_frame->data_payload; tx_frame->frame.buffer_len = size; // Send the frame; TX callback will return frame to pool on completion. esp_err_t ret = twai_node_transmit(twai_node, &tx_frame->frame, 0); if (ret != ESP_OK) { // Return frame to SLIST pool if sending failed immediately. SLIST_INSERT_HEAD(&isotp_handle->tx_frame_pool, tx_frame, link); ESP_EARLY_LOGE(TAG, "Failed to send TWAI frame: %s", esp_err_to_name(ret)); return ISOTP_RET_ERROR; } return ISOTP_RET_OK; } /** * @brief Print a formatted debug message from isotp-c. * * @param message Format string. * @param ... Variadic arguments for the format string. */ void isotp_user_debug(const char *message, ...) { va_list args; va_start(args, message); esp_log_writev(ESP_LOG_DEBUG, "isotp_c", message, args); va_end(args); } esp_err_t esp_isotp_new_transport(twai_node_handle_t twai_node, const esp_isotp_config_t *config, esp_isotp_handle_t *out_handle) { esp_err_t ret = ESP_OK; esp_isotp_handle_t isotp = NULL; ESP_RETURN_ON_FALSE(twai_node && config && out_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); ESP_RETURN_ON_FALSE(config->tx_buffer_size > 0 && config->rx_buffer_size > 0, ESP_ERR_INVALID_SIZE, TAG, "Buffer sizes must be greater than 0"); ESP_RETURN_ON_FALSE(config->tx_frame_pool_size != 0, ESP_ERR_INVALID_SIZE, TAG, "TX frame pool size cannot be zero"); // Validate ID ranges - each ID is validated against its own required format ESP_RETURN_ON_FALSE((config->tx_id & ~TWAI_EXT_ID_MASK) == 0, ESP_ERR_INVALID_ARG, TAG, "TX ID exceeds maximum value"); ESP_RETURN_ON_FALSE((config->rx_id & ~TWAI_EXT_ID_MASK) == 0, ESP_ERR_INVALID_ARG, TAG, "RX ID exceeds maximum value"); // Allocate memory for handle. isotp = calloc(1, sizeof(esp_isotp_link_t)); ESP_RETURN_ON_FALSE(isotp, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for ISO-TP link"); // Allocate ISO-TP reassembly buffers for multi-frame message handling. // These buffers are used by isotp-c to reassemble/fragment large payloads. isotp->isotp_tx_buffer = calloc(config->tx_buffer_size, sizeof(uint8_t)); isotp->isotp_rx_buffer = calloc(config->rx_buffer_size, sizeof(uint8_t)); ESP_GOTO_ON_FALSE(isotp->isotp_rx_buffer && isotp->isotp_tx_buffer, ESP_ERR_NO_MEM, err, TAG, "Failed to allocate ISO-TP reassembly buffers"); // Initialize TX frame pool with user-specified size // Using simple single-linked list for maximum efficiency isotp->tx_frame_pool_size = config->tx_frame_pool_size; SLIST_INIT(&isotp->tx_frame_pool); // Allocate array of TX frames isotp->tx_frame_array = calloc(isotp->tx_frame_pool_size, sizeof(esp_isotp_frame_t)); ESP_GOTO_ON_FALSE(isotp->tx_frame_array, ESP_ERR_NO_MEM, err, TAG, "Failed to allocate TX frame array"); // Initialize each frame and add to SLIST pool for (size_t i = 0; i < isotp->tx_frame_pool_size; i++) { esp_isotp_frame_t *frame = &isotp->tx_frame_array[i]; frame->frame.buffer = frame->data_payload; frame->frame.buffer_len = sizeof(frame->data_payload); SLIST_INSERT_HEAD(&isotp->tx_frame_pool, frame, link); } // Initialize the isotp-c library link with our allocated buffers. isotp_init_link(&isotp->link, config->tx_id, isotp->isotp_tx_buffer, config->tx_buffer_size, isotp->isotp_rx_buffer, config->rx_buffer_size); isotp->link.receive_arbitration_id = config->rx_id; // Pre-allocate ISR-safe receive frame buffer to avoid dynamic allocation in interrupt context. // This buffer is reused for each incoming TWAI frame received in the ISR. memset(&isotp->isr_rx_frame_buffer, 0, sizeof(esp_isotp_frame_t)); isotp->isr_rx_frame_buffer.frame.buffer = isotp->isr_rx_frame_buffer.data_payload; isotp->isr_rx_frame_buffer.frame.buffer_len = sizeof(isotp->isr_rx_frame_buffer.data_payload); // Set user argument for TWAI operations. isotp->link.user_send_can_arg = isotp; // Save user callback functions isotp->rx_callback = config->rx_callback; isotp->tx_callback = config->tx_callback; isotp->callback_arg = config->callback_arg; // Set isotp-c wrapper callbacks if user callbacks provided. #ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK if (config->tx_callback) { isotp_set_tx_done_cb(&isotp->link, esp_isotp_tx_wrapper, isotp); } #endif #ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK if (config->rx_callback) { isotp_set_rx_done_cb(&isotp->link, esp_isotp_rx_wrapper, isotp); } #endif // Register TWAI callbacks. twai_event_callbacks_t cbs = { .on_rx_done = esp_isotp_rx_callback, .on_tx_done = esp_isotp_tx_callback, }; ret = twai_node_register_event_callbacks(twai_node, &cbs, isotp); ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to register event callbacks"); // Enable TWAI node. ret = twai_node_enable(twai_node); ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to enable TWAI node"); isotp->twai_node = twai_node; *out_handle = isotp; return ESP_OK; err: if (isotp) { if (isotp->isotp_rx_buffer) { free(isotp->isotp_rx_buffer); } if (isotp->isotp_tx_buffer) { free(isotp->isotp_tx_buffer); } if (isotp->tx_frame_array) { free(isotp->tx_frame_array); } free(isotp); } return ret; } /** * @brief Delete an ISO-TP transport and free resources. * * Disables the TWAI node, cleans up TX frame pool and frees allocated * memory. Continues cleanup even if disabling TWAI fails. * * @param handle ISO-TP transport handle. * @return ESP_OK on success or the error from TWAI disable. */ esp_err_t esp_isotp_delete(esp_isotp_handle_t handle) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); esp_err_t ret = ESP_OK; // Disable TWAI node after unregistering callbacks esp_err_t twai_ret = twai_node_disable(handle->twai_node); if (twai_ret != ESP_OK) { ESP_LOGW(TAG, "Failed to disable TWAI node: %s", esp_err_to_name(twai_ret)); if (ret == ESP_OK) { ret = twai_ret; } } // Unregister TWAI callbacks first to prevent use-after-free during disable twai_event_callbacks_t cbs = { 0 }; esp_err_t unreg_ret = twai_node_register_event_callbacks(handle->twai_node, &cbs, NULL); if (unreg_ret != ESP_OK) { ESP_LOGW(TAG, "Failed to unregister TWAI callbacks: %s", esp_err_to_name(unreg_ret)); ret = unreg_ret; } // Clean up ISO-TP link. isotp_destroy_link(&handle->link); // Clean up TX frame array (SLIST pool is automatically cleaned when frames are freed). if (handle->tx_frame_array) { free(handle->tx_frame_array); } // Free ISO-TP reassembly buffers and handle. if (handle->isotp_tx_buffer) { free(handle->isotp_tx_buffer); } if (handle->isotp_rx_buffer) { free(handle->isotp_rx_buffer); } free(handle); return ret; } /** * @brief Poll the ISO-TP link. Call this periodically from a task. * * @param handle ISO-TP transport handle. * @return ESP_OK on success, or an error code on failure. */ esp_err_t esp_isotp_poll(esp_isotp_handle_t handle) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); // Run ISO-TP state machine to check timeouts and send consecutive frames. isotp_poll(&handle->link); return ESP_OK; } /** * @brief Send a payload using ISO-TP. * * @param handle ISO-TP transport handle. * @param data Pointer to payload buffer. * @param size Payload size in bytes. * @return * - ESP_OK on success * - ESP_ERR_NOT_FINISHED when the send is still in progress * - ESP_ERR_NO_MEM for buffer overflow conditions * - ESP_ERR_INVALID_SIZE for invalid sizes * - ESP_ERR_TIMEOUT on timeout * - ESP_FAIL for other errors */ esp_err_t esp_isotp_send(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size) { if (!(handle && data && size)) { return ESP_ERR_INVALID_ARG; } int ret = isotp_send(&handle->link, data, size); switch (ret) { case ISOTP_RET_OK: return ESP_OK; case ISOTP_RET_INPROGRESS: return ESP_ERR_NOT_FINISHED; case ISOTP_RET_OVERFLOW: // Buffer overflow case ISOTP_RET_NOSPACE: // Not enough space in internal buffers return ESP_ERR_NO_MEM; case ISOTP_RET_LENGTH: // Payload size exceeds buffer size return ESP_ERR_INVALID_SIZE; case ISOTP_RET_TIMEOUT: return ESP_ERR_TIMEOUT; case ISOTP_RET_ERROR: default: ESP_EARLY_LOGE(TAG, "ISO-TP send failed with error code: %d", ret); return ESP_FAIL; } } /** * @brief Send a payload with a specific TWAI ID. * * Uses the provided TWAI ID for this transmission instead of the default. * * @param handle ISO-TP transport handle. * @param id TWAI identifier (subject to STD/EXT mask based on configuration). * @param data Pointer to payload buffer. * @param size Payload size in bytes. * @return Same as esp_isotp_send(). */ esp_err_t esp_isotp_send_with_id(esp_isotp_handle_t handle, uint32_t id, const uint8_t *data, uint32_t size) { if (!(handle && data && size)) { return ESP_ERR_INVALID_ARG; } if ((id & ~TWAI_EXT_ID_MASK) != 0) { return ESP_ERR_INVALID_ARG; } int ret = isotp_send_with_id(&handle->link, id, data, size); switch (ret) { case ISOTP_RET_OK: return ESP_OK; case ISOTP_RET_INPROGRESS: return ESP_ERR_NOT_FINISHED; case ISOTP_RET_OVERFLOW: // Buffer overflow case ISOTP_RET_NOSPACE: // Not enough space in internal buffers return ESP_ERR_NO_MEM; case ISOTP_RET_LENGTH: // Payload size exceeds buffer size return ESP_ERR_INVALID_SIZE; case ISOTP_RET_TIMEOUT: return ESP_ERR_TIMEOUT; case ISOTP_RET_ERROR: default: ESP_EARLY_LOGE(TAG, "ISO-TP send with ID failed with error code: %d", ret); return ESP_FAIL; } } /** * @brief Receive a payload using ISO-TP. * * @param handle ISO-TP transport handle. * @param data Output buffer for received data. * @param size Size of the output buffer in bytes. * @param received_size Actual number of bytes written to the buffer. * @return * - ESP_OK when data is received * - ESP_ERR_NOT_FOUND when no data is available * - ESP_ERR_INVALID_SIZE when the buffer is too small or size invalid * - ESP_ERR_INVALID_RESPONSE on sequence errors * - ESP_ERR_TIMEOUT on timeout * - ESP_FAIL for other errors */ esp_err_t esp_isotp_receive(esp_isotp_handle_t handle, uint8_t *data, uint32_t size, uint32_t *received_size) { ESP_RETURN_ON_FALSE(handle && data && size && received_size, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); *received_size = 0; int ret = isotp_receive(&handle->link, data, size, received_size); switch (ret) { case ISOTP_RET_OK: return ESP_OK; case ISOTP_RET_NO_DATA: return ESP_ERR_NOT_FOUND; case ISOTP_RET_OVERFLOW: // Receive buffer too small for the message case ISOTP_RET_LENGTH: // Invalid length in message return ESP_ERR_INVALID_SIZE; case ISOTP_RET_NOSPACE: // Not enough space in internal buffers (rare if configured correctly) return ESP_ERR_NO_MEM; case ISOTP_RET_WRONG_SN: // Sequence number error return ESP_ERR_INVALID_RESPONSE; case ISOTP_RET_INPROGRESS: // Should not happen in receive, but handle for robustness return ESP_ERR_INVALID_STATE; case ISOTP_RET_TIMEOUT: return ESP_ERR_TIMEOUT; case ISOTP_RET_ERROR: default: ESP_LOGE(TAG, "ISO-TP receive failed with error code: %d", ret); return ESP_FAIL; } } ================================================ FILE: esp_isotp/src/isotp_config.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef ISOTPC_CONFIG_H #define ISOTPC_CONFIG_H // This file overrides the default isotp_config.h from isotp-c submodule // Values are taken from ESP-IDF Kconfig system #include "sdkconfig.h" /* Max number of messages the receiver can receive at one time, this value * is affected by can driver queue length */ #define ISO_TP_DEFAULT_BLOCK_SIZE CONFIG_ISO_TP_DEFAULT_BLOCK_SIZE /* The STmin parameter value specifies the minimum time gap allowed between * the transmission of consecutive frame network protocol data units */ #define ISO_TP_DEFAULT_ST_MIN_US CONFIG_ISO_TP_DEFAULT_ST_MIN_US /* This parameter indicate how many FC N_PDU WTs can be transmitted by the * receiver in a row. */ #define ISO_TP_MAX_WFT_NUMBER CONFIG_ISO_TP_MAX_WFT_NUMBER /* The default timeout to use when waiting for a response during a multi-frame send or receive. */ #define ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US CONFIG_ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US /* Determines if by default, padding is added to ISO-TP message frames */ #ifdef CONFIG_ISO_TP_FRAME_PADDING #define ISO_TP_FRAME_PADDING #endif /* The value to use when padding frames if enabled by ISO_TP_FRAME_PADDING */ #define ISO_TP_FRAME_PADDING_VALUE CONFIG_ISO_TP_FRAME_PADDING_VALUE /* Always enable the additional user_data argument in isotp_user_send_can function */ #define ISO_TP_USER_SEND_CAN_ARG /* Always enable the transmission complete callback */ #define ISO_TP_TRANSMIT_COMPLETE_CALLBACK /* Always enable the receive complete callback */ #define ISO_TP_RECEIVE_COMPLETE_CALLBACK #endif // ISOTPC_CONFIG_H ================================================ FILE: esp_jpeg/.build-test-rules.yml ================================================ ================================================ FILE: esp_jpeg/CHANGELOG.md ================================================ ## 1.3.1 - Fixed the format of Kconfig file ## 1.3.0 - Added option to get image size without decoding it ## 1.2.1 - Fixed decoding of non-conforming 0xFFFF marker ## 1.2.0 - Added option to for passing user defined working buffer ## 1.1.0 - Added support for decoding images without Huffman tables - Fixed undefined configuration options from Kconfig ## 1.0.5~3 - Added option to swap output color bytes regardless of JD_FORMAT ## 1.0.4 - Added ROM implementation support for ESP32-C6 ## 1.0.2 - Fixed compiler warnings ## 1.0.1 - Fixed: exclude ESP32-C2 from list of ROM implementations ## 1.0.0 - Initial version ================================================ FILE: esp_jpeg/CMakeLists.txt ================================================ set(sources "jpeg_decoder.c") set(includes "include") # Compile only when cannot use ROM code if(NOT CONFIG_JD_USE_ROM) list(APPEND sources "tjpgd/tjpgd.c") list(APPEND includes "tjpgd") endif() if(CONFIG_JD_DEFAULT_HUFFMAN) list(APPEND sources "jpeg_default_huffman_table.c") endif() idf_component_register(SRCS ${sources} INCLUDE_DIRS ${includes}) ================================================ FILE: esp_jpeg/Kconfig ================================================ menu "JPEG Decoder" config JD_USE_ROM bool "Use TinyJPG Decoder from ROM" depends on ESP_ROM_HAS_JPEG_DECODE default y help By default, Espressif SoCs use TJpg decoder implemented in ROM code. If this feature is disabled, new configuration of TJpg decoder can be used. Refer to README.md for more details. config JD_SZBUF int "Size of stream input buffer" depends on !JD_USE_ROM default 512 config JD_FORMAT int depends on !JD_USE_ROM default 0 if JD_FORMAT_RGB888 default 1 if JD_FORMAT_RGB565 choice prompt "Output pixel format" depends on !JD_USE_ROM default JD_FORMAT_RGB888 help Output format is selected at runtime. config JD_FORMAT_RGB888 bool "Support RGB565 and RGB888 output (16-bit/pix and 24-bit/pix)" config JD_FORMAT_RGB565 bool "Support RGB565 output (16-bit/pix)" endchoice config JD_USE_SCALE bool "Enable descaling" depends on !JD_USE_ROM default y help If scaling is enabled, size of output image can be lowered during decoding. config JD_TBLCLIP bool "Use table conversion for saturation arithmetic" depends on !JD_USE_ROM default y help Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size. config JD_FASTDECODE int depends on !JD_USE_ROM default 0 if JD_FASTDECODE_BASIC default 1 if JD_FASTDECODE_32BIT default 2 if JD_FASTDECODE_TABLE choice prompt "Optimization level" depends on !JD_USE_ROM default JD_FASTDECODE_32BIT config JD_FASTDECODE_BASIC bool "Basic optimization. Suitable for 8/16-bit MCUs" config JD_FASTDECODE_32BIT bool "+ 32-bit barrel shifter. Suitable for 32-bit MCUs" config JD_FASTDECODE_TABLE bool "+ Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM)" endchoice config JD_DEFAULT_HUFFMAN bool "Support images without Huffman table" depends on !JD_USE_ROM default n help Enable this option to support decoding JPEG images that lack an embedded Huffman table. When enabled, a default Huffman table is used during decoding, allowing the JPEG decoder to handle images without explicitly provided Huffman tables. Note: Enabling this option increases ROM usage due to the inclusion of default Huffman tables. endmenu ================================================ FILE: esp_jpeg/README.md ================================================ # JPEG Decoder: TJpgDec - Tiny JPEG Decompressor [![Component Registry](https://components.espressif.com/components/espressif/esp_jpeg/badge.svg)](https://components.espressif.com/components/espressif/esp_jpeg) ![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) TJpgDec is a lightweight JPEG image decompressor optimized for embedded systems with minimal memory consumption. On some microcontrollers, TJpgDec is available in ROM and will be used by default, though this can be disabled in menuconfig if desired[^1]. [^1]: **_NOTE:_** When the ROM decoder is used, the configuration can't be changed. The configuration is fixed. ## Features **Compilation configuration:** - Stream input buffer size (default: 512 bytes) - Output pixel format (default: RGB888; options: RGB888/RGB565) - Enable/disable output descaling (default: enabled) - Use table-based saturation for arithmetic operations (default: enabled) - Use default Huffman tables: Useful from decoding frames from cameras, that do not provide Huffman tables (default: disabled to save ROM) - Three optimization levels (default: 32-bit MCUs) for different CPU types: - 8/16-bit MCUs - 32-bit MCUs - Table-based Huffman decoding **Runtime configuration:** - Pixel format options: RGB888, RGB565 - Selectable scaling ratios: 1/1, 1/2, 1/4, or 1/8 (chosen at decompression) - Option to swap the first and last bytes of color values ## TJpgDec in ROM On certain microcontrollers, TJpgDec is available in ROM and used by default. This can be disabled in menuconfig if you prefer to use the library code provided in this component. ### List of MCUs, which have TJpgDec in ROM - ESP32 - ESP32-S3 - ESP32-C3 - ESP32-C6 - ESP32-C5 - ESP32-C61 ### Fixed compilation configuration of the ROM code The ROM version uses the following fixed settings: - Stream input buffer: 512 bytes - Output pixel format: RGB888 - Output descaling: enabled - Saturation table: enabled - Optimization level: Basic (JD_FASTDECODE = 0) ### Pros and cons using ROM code **Advantages:** - Saves approximately 5 KB of flash memory with the same configuration **Disadvantages:** - Compilation configuration cannot be changed - Certain configurations may provide faster performance ## Speed comparison The table below shows example decoding times for a JPEG image using various configurations: * Image size: 320 x 180 px * Output format: RGB565 * CPU: ESP32-S3 * CPU frequency: 240 MHz * SPI mode: DIO * Internal RAM used * Measured in 1000 retries | ROM used | JD_SZBUF | JD_FORMAT | JD_USE_SCALE | JD_TBLCLIP | JD_FASTDECODE | RAM buffer | Flash size | Approx. time | | :------: | :------: | :-------: | :----------: | :--------: | :-----------: | :--------: | :--------: | :----------: | | YES | 512 | RGB888 | 1 | 1 | 0 | 3.1 kB | 0 kB | 52 ms | | NO | 512 | RGB888 | 1 | 1 | 0 | 3.1 kB | 5 kB | 50 ms | | NO | 512 | RGB888 | 1 | 0 | 0 | 3.1 kB | 4 kB | 68 ms | | NO | 512 | RGB888 | 1 | 1 | 1 | 3.1 kB | 5 kB | 50 ms | | NO | 512 | RGB888 | 1 | 0 | 1 | 3.1 kB | 4 kB | 62 ms | | NO | 512 | RGB888 | 1 | 1 | 2 | 65.5 kB | 5.5 kB | 46 ms | | NO | 512 | RGB888 | 1 | 0 | 2 | 65.5 kB | 4.5 kB | 59 ms | | NO | 512 | RGB565 | 1 | 1 | 0 | 5 kB | 5 kB | 60 ms | | NO | 512 | RGB565 | 1 | 1 | 1 | 5 kB | 5 kB | 59 ms | | NO | 512 | RGB565 | 1 | 1 | 2 | 65.5 kB | 5.5 kB | 56 ms | ## Add to project Packages from this repository are uploaded to [Espressif's component service](https://components.espressif.com/). You can add them to your project via `idf.py add-dependancy`, e.g. ``` idf.py add-dependency esp_jpeg==1.0.0 ``` Alternatively, you can create `idf_component.yml`. More is in [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). ## Example use Here is example of usage. This calling is **blocking**. ``` esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)jpeg_img_buf, .indata_size = jpeg_img_buf_size, .outbuf = out_img_buf, .outbuf_size = out_img_buf_size, .out_format = JPEG_IMAGE_OUT_FORMAT_RGB565, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 1, } }; esp_jpeg_image_output_t outimg; esp_jpeg_decode(&jpeg_cfg, &outimg); ``` ================================================ FILE: esp_jpeg/examples/get_started/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(lcd_tjpgd) ================================================ FILE: esp_jpeg/examples/get_started/README.md ================================================ # LCD tjpgd example ## Overview This example shows how to decode a jpeg image and display it on an SPI-interfaced LCD, and rotates the image periodically. If you want to adapt this example to another type of display or pinout, check [lcd_tjpgd_example_main.c](main/lcd_tjpgd_example_main.c) for comments with some implementation details. ## How to Use Example ### Hardware Required * An ESP development board * An SPI-interfaced LCD * An USB cable for power supply and programming ### Hardware Connection The connection between ESP Board and the LCD is as follows: ```text ESP Board LCD Screen +---------+ +---------------------------------+ | | | | | 3V3 +--------------+ VCC +----------------------+ | | | | | | | | GND +--------------+ GND | | | | | | | | | | DATA0 +--------------+ MOSI | | | | | | | | | | PCLK +--------------+ SCK | | | | | | | | | | CS +--------------+ CS | | | | | | | | | | D/C +--------------+ D/C | | | | | | | | | | RST +--------------+ RST | | | | | | | | | |BK_LIGHT +--------------+ BCKL +----------------------+ | | | | | +---------+ +---------------------------------+ ``` The GPIO number used by this example can be changed in [lcd_tjpgd_example_main.c](main/lcd_tjpgd_example_main.c), where: | GPIO number | LCD pin | | ------------------------ | ------- | | EXAMPLE_PIN_NUM_PCLK | SCK | | EXAMPLE_PIN_NUM_CS | CS | | EXAMPLE_PIN_NUM_DC | DC | | EXAMPLE_PIN_NUM_RST | RST | | EXAMPLE_PIN_NUM_DATA0 | MOSI | | EXAMPLE_PIN_NUM_BK_LIGHT | BCKL | Especially, please pay attention to the level used to turn on the LCD backlight, some LCD module needs a low level to turn it on, while others take a high level. You can change the backlight level macro `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` in [lcd_tjpgd_example_main.c](main/lcd_tjpgd_example_main.c). ### Build and Flash Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. A flowing picture will be shown on the LCD screen. (To exit the serial monitor, type ``Ctrl-]``.) See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. ## Troubleshooting For any technical queries, please open an [issue] (https://github.com/espressif/idf-extra-components/issues) on GitHub. We will get back to you soon. ================================================ FILE: esp_jpeg/examples/get_started/main/CMakeLists.txt ================================================ set(srcs "pretty_effect.c" "lcd_tjpgd_example_main.c" "decode_image.c" ) idf_component_register(SRCS ${srcs} INCLUDE_DIRS "." EMBED_FILES image.jpg PRIV_REQUIRES esp_lcd driver) ================================================ FILE: esp_jpeg/examples/get_started/main/Kconfig.projbuild ================================================ menu "Example Configuration" config EXAMPLE_LCD_FLUSH_PARALLEL_LINES int "LCD flush parallel lines" default 12 if IDF_TARGET_ESP32C2 default 16 help To speed up transfers, every SPI transfer sends a bunch of lines. endmenu ================================================ FILE: esp_jpeg/examples/get_started/main/decode_image.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ /* The image used for the effect on the LCD in the SPI master example is stored in flash as a jpeg file. This file contains the decode_image routine, which uses the tiny JPEG decoder library to decode this JPEG into a format that can be sent to the display. Keep in mind that the decoder library cannot handle progressive files (will give ``Image decoder: jd_prepare failed (8)`` as an error) so make sure to save in the correct format if you want to use a different image file. */ #include #include "decode_image.h" #include "jpeg_decoder.h" #include "esp_log.h" #include "esp_check.h" #include "freertos/FreeRTOS.h" //Reference the binary-included jpeg file extern const uint8_t image_jpg_start[] asm("_binary_image_jpg_start"); extern const uint8_t image_jpg_end[] asm("_binary_image_jpg_end"); //Define the height and width of the jpeg file. Make sure this matches the actual jpeg //dimensions. const char *TAG = "ImageDec"; //Decode the embedded image into pixel lines that can be used with the rest of the logic. esp_err_t decode_image(uint16_t **pixels) { *pixels = NULL; esp_err_t ret = ESP_OK; //Allocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines. *pixels = calloc(IMAGE_H * IMAGE_W, sizeof(uint16_t)); ESP_GOTO_ON_FALSE((*pixels), ESP_ERR_NO_MEM, err, TAG, "Error allocating memory for lines"); //JPEG decode config esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)image_jpg_start, .indata_size = image_jpg_end - image_jpg_start, .outbuf = (uint8_t *)(*pixels), .outbuf_size = IMAGE_W * IMAGE_H * sizeof(uint16_t), .out_format = JPEG_IMAGE_FORMAT_RGB565, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 1, } }; //JPEG decode esp_jpeg_image_output_t outimg; esp_jpeg_decode(&jpeg_cfg, &outimg); ESP_LOGI(TAG, "JPEG image decoded! Size of the decoded image is: %dpx x %dpx", outimg.width, outimg.height); return ret; err: //Something went wrong! Exit cleanly, de-allocating everything we allocated. if (*pixels != NULL) { free(*pixels); } return ret; } ================================================ FILE: esp_jpeg/examples/get_started/main/decode_image.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #pragma once #include #include "esp_err.h" #define IMAGE_W 320 #define IMAGE_H 240 #ifdef __cplusplus extern "C" { #endif /** * @brief Decode the jpeg ``image.jpg`` embedded into the program file into pixel data. * * @param pixels A pointer to a pointer for an array of rows, which themselves are an array of pixels. * Effectively, you can get the pixel data by doing ``decode_image(&myPixels); pixelval=myPixels[ypos][xpos];`` * @return - ESP_ERR_NOT_SUPPORTED if image is malformed or a progressive jpeg file * - ESP_ERR_NO_MEM if out of memory * - ESP_OK on successful decode */ esp_err_t decode_image(uint16_t **pixels); #ifdef __cplusplus } #endif ================================================ FILE: esp_jpeg/examples/get_started/main/idf_component.yml ================================================ dependencies: idf: ">=5.0" esp_jpeg: version: "*" override_path: "../../../" ================================================ FILE: esp_jpeg/examples/get_started/main/lcd_tjpgd_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_vendor.h" #include "esp_lcd_panel_ops.h" #include "esp_heap_caps.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "pretty_effect.h" // Using SPI2 in the example, as it also supports octal modes on some targets #define LCD_HOST SPI2_HOST // To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. // More means more memory use, but less overhead for setting up / finishing transfers. Make sure 240 // is dividable by this. #define PARALLEL_LINES CONFIG_EXAMPLE_LCD_FLUSH_PARALLEL_LINES //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define EXAMPLE_LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000) #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 0 #define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL #define EXAMPLE_PIN_NUM_DATA0 23 /*!< for 1-line SPI, this also refereed as MOSI */ #define EXAMPLE_PIN_NUM_PCLK 19 #define EXAMPLE_PIN_NUM_CS 22 #define EXAMPLE_PIN_NUM_DC 21 #define EXAMPLE_PIN_NUM_RST 18 #define EXAMPLE_PIN_NUM_BK_LIGHT 5 // The pixel number in horizontal and vertical #define EXAMPLE_LCD_H_RES 320 #define EXAMPLE_LCD_V_RES 240 // Bit number used to represent command and parameter #define EXAMPLE_LCD_CMD_BITS 8 #define EXAMPLE_LCD_PARAM_BITS 8 // Simple routine to generate some patterns and send them to the LCD. Because the // SPI driver handles transactions in the background, we can calculate the next line // while the previous one is being sent. static uint16_t *s_lines[2]; static void display_pretty_colors(esp_lcd_panel_handle_t panel_handle) { int frame = 0; // Indexes of the line currently being sent to the LCD and the line we're calculating int sending_line = 0; int calc_line = 0; while (1) { frame++; for (int y = 0; y < EXAMPLE_LCD_V_RES; y += PARALLEL_LINES) { // Calculate a line pretty_effect_calc_lines(s_lines[calc_line], y, frame, PARALLEL_LINES); sending_line = calc_line; calc_line = !calc_line; // Send the calculated data esp_lcd_panel_draw_bitmap(panel_handle, 0, y, 0 + EXAMPLE_LCD_H_RES, y + PARALLEL_LINES, s_lines[sending_line]); } } } void app_main(void) { gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT }; // Initialize the GPIO of backlight ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); spi_bus_config_t buscfg = { .sclk_io_num = EXAMPLE_PIN_NUM_PCLK, .mosi_io_num = EXAMPLE_PIN_NUM_DATA0, .miso_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = PARALLEL_LINES * EXAMPLE_LCD_H_RES * 2 + 8 }; // Initialize the SPI bus ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); esp_lcd_panel_io_handle_t io_handle = NULL; esp_lcd_panel_io_spi_config_t io_config = { .dc_gpio_num = EXAMPLE_PIN_NUM_DC, .cs_gpio_num = EXAMPLE_PIN_NUM_CS, .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, .spi_mode = 0, .trans_queue_depth = 10, }; // Attach the LCD to the SPI bus ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle)); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = EXAMPLE_PIN_NUM_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, .bits_per_pixel = 16, }; // Initialize the LCD configuration ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); // Turn off backlight to avoid unpredictable display on the LCD screen while initializing // the LCD panel driver. (Different LCD screens may need different levels) ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL)); // Reset the display ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); // Initialize LCD panel ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); // Swap x and y axis (Different LCD screens may need different options) ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true)); // Turn on the screen ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); // Turn on backlight (Different LCD screens may need different levels) ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL)); // Initialize the effect displayed ESP_ERROR_CHECK(pretty_effect_init()); // Allocate memory for the pixel buffers for (int i = 0; i < 2; i++) { s_lines[i] = heap_caps_malloc(EXAMPLE_LCD_H_RES * PARALLEL_LINES * sizeof(uint16_t), MALLOC_CAP_DMA); assert(s_lines[i] != NULL); } //Go do nice stuff. display_pretty_colors(panel_handle); } ================================================ FILE: esp_jpeg/examples/get_started/main/pretty_effect.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "pretty_effect.h" #include "decode_image.h" uint16_t *pixels; //Grab a rgb16 pixel from the esp32_tiles image static inline uint16_t get_bgnd_pixel(int x, int y) { //Get color of the pixel on x,y coords return (uint16_t) * (pixels + (y * IMAGE_W) + x); } //This variable is used to detect the next frame. static int prev_frame = -1; //Instead of calculating the offsets for each pixel we grab, we pre-calculate the valueswhenever a frame changes, then reuse //these as we go through all the pixels in the frame. This is much, much faster. static int8_t xofs[320], yofs[240]; static int8_t xcomp[320], ycomp[240]; //Calculate the pixel data for a set of lines (with implied line size of 320). Pixels go in dest, line is the Y-coordinate of the //first line to be calculated, linect is the amount of lines to calculate. Frame increases by one every time the entire image //is displayed; this is used to go to the next frame of animation. void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect) { if (frame != prev_frame) { //We need to calculate a new set of offset coefficients. Take some random sines as offsets to make everything //look pretty and fluid-y. for (int x = 0; x < 320; x++) { xofs[x] = sin(frame * 0.15 + x * 0.06) * 4; } for (int y = 0; y < 240; y++) { yofs[y] = sin(frame * 0.1 + y * 0.05) * 4; } for (int x = 0; x < 320; x++) { xcomp[x] = sin(frame * 0.11 + x * 0.12) * 4; } for (int y = 0; y < 240; y++) { ycomp[y] = sin(frame * 0.07 + y * 0.15) * 4; } prev_frame = frame; } for (int y = line; y < line + linect; y++) { for (int x = 0; x < 320; x++) { *dest++ = get_bgnd_pixel(x + yofs[y] + xcomp[x], y + xofs[x] + ycomp[y]); } } } esp_err_t pretty_effect_init(void) { return decode_image(&pixels); } ================================================ FILE: esp_jpeg/examples/get_started/main/pretty_effect.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Calculate the effect for a bunch of lines. * * @param dest Destination for the pixels. Assumed to be LINECT * 320 16-bit pixel values. * @param line Starting line of the chunk of lines. * @param frame Current frame, used for animation * @param linect Amount of lines to calculate */ void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect); /** * @brief Initialize the effect * * @return ESP_OK on success, an error from the jpeg decoder otherwise. */ esp_err_t pretty_effect_init(void); #ifdef __cplusplus } #endif ================================================ FILE: esp_jpeg/idf_component.yml ================================================ version: "1.3.1" description: "JPEG Decoder: TJpgDec" url: https://github.com/espressif/idf-extra-components/tree/master/esp_jpeg/ dependencies: idf: ">=5.0" ================================================ FILE: esp_jpeg/include/jpeg_decoder.h ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Scale of output image * */ typedef enum { JPEG_IMAGE_SCALE_0 = 0, /*!< No scale */ JPEG_IMAGE_SCALE_1_2, /*!< Scale 1:2 */ JPEG_IMAGE_SCALE_1_4, /*!< Scale 1:4 */ JPEG_IMAGE_SCALE_1_8, /*!< Scale 1:8 */ } esp_jpeg_image_scale_t; /** * @brief Format of output image * */ typedef enum { JPEG_IMAGE_FORMAT_RGB888 = 0, /*!< Format RGB888 */ JPEG_IMAGE_FORMAT_RGB565, /*!< Format RGB565 */ } esp_jpeg_image_format_t; /** * @brief JPEG Configuration Type * */ typedef struct esp_jpeg_image_cfg_s { uint8_t *indata; /*!< Input JPEG image */ uint32_t indata_size; /*!< Size of input image */ uint8_t *outbuf; /*!< Output buffer */ uint32_t outbuf_size; /*!< Output buffer size */ esp_jpeg_image_format_t out_format; /*!< Output image format */ esp_jpeg_image_scale_t out_scale; /*!< Output scale */ struct { uint8_t swap_color_bytes: 1; /*!< Swap first and last color bytes */ } flags; struct { void *working_buffer; /*!< If set to NULL, a working buffer will be allocated in esp_jpeg_decode(). Tjpgd does not use dynamic allocation, se we pass this buffer to Tjpgd that uses it as scratchpad */ size_t working_buffer_size; /*!< Size of the working buffer. Must be set it working_buffer != NULL. Default size is 3.1kB or 65kB if JD_FASTDECODE == 2 */ } advanced; struct { uint32_t read; /*!< Internal count of read bytes */ } priv; } esp_jpeg_image_cfg_t; /** * @brief JPEG output info */ typedef struct esp_jpeg_image_output_s { uint16_t width; /*!< Width of the output image */ uint16_t height; /*!< Height of the output image */ size_t output_len; /*!< Length of the output image in bytes */ } esp_jpeg_image_output_t; /** * @brief Decode JPEG image * * @note This function is blocking. * * @param[in] cfg: Configuration structure * @param[out] img: Output image info * * @return * - ESP_OK on success * - ESP_ERR_NO_MEM if there is no memory for allocating main structure * - ESP_FAIL if there is an error in decoding JPEG */ esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img); /** * @brief Get information about the JPEG image * * Use this function to get the size of the JPEG image without decoding it. * Allocate a buffer of size img->output_len to store the decoded image. * * @note cfg->outbuf and cfg->outbuf_size are not used in this function. * @param[in] cfg: Configuration structure * @param[out] img: Output image info * * @return * - ESP_OK on success * - ESP_ERR_INVALID_ARG if cfg or img is NULL * - ESP_FAIL if there is an error in decoding JPEG */ esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img); #ifdef __cplusplus } #endif ================================================ FILE: esp_jpeg/jpeg_decoder.c ================================================ /* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "freertos/FreeRTOS.h" #include "esp_system.h" #include "esp_rom_caps.h" #include "esp_log.h" #include "esp_err.h" #include "esp_check.h" #include "jpeg_decoder.h" #if CONFIG_JD_USE_ROM /* When supported in ROM, use ROM functions */ #if defined(ESP_ROM_HAS_JPEG_DECODE) #include "rom/tjpgd.h" #else #error Using JPEG decoder from ROM is not supported for selected target. Please select external code in menuconfig. #endif /* The ROM code of TJPGD is older and has different return type in decode callback */ typedef unsigned int jpeg_decode_out_t; #else /* When Tiny JPG Decoder is not in ROM or selected external code */ #include "tjpgd.h" /* The TJPGD outside the ROM code is newer and has different return type in decode callback */ typedef int jpeg_decode_out_t; #endif static const char *TAG = "JPEG"; #define LOBYTE(u16) ((uint8_t)(((uint16_t)(u16)) & 0xff)) #define HIBYTE(u16) ((uint8_t)((((uint16_t)(u16))>>8) & 0xff)) #if defined(JD_FASTDECODE) && (JD_FASTDECODE == 2) #define JPEG_WORK_BUF_SIZE 65472 #else #define JPEG_WORK_BUF_SIZE 3100 /* Recommended buffer size; Independent on the size of the image */ #endif /* If not set JD_FORMAT, it is set in ROM to RGB888, otherwise, it can be set in config */ #ifndef JD_FORMAT #define JD_FORMAT 0 #endif /* Output color bytes from tjpgd (depends on JD_FORMAT) */ #if (JD_FORMAT==0) #define ESP_JPEG_COLOR_BYTES 3 #elif (JD_FORMAT==1) #define ESP_JPEG_COLOR_BYTES 2 #elif (JD_FORMAT==2) #error Grayscale image output format is not supported #define ESP_JPEG_COLOR_BYTES 1 #endif /******************************************************************************* * Function definitions *******************************************************************************/ static uint8_t jpeg_get_div_by_scale(esp_jpeg_image_scale_t scale); static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format); static unsigned int jpeg_decode_in_cb(JDEC *jd, uint8_t *buff, unsigned int nbyte); static jpeg_decode_out_t jpeg_decode_out_cb(JDEC *jd, void *bitmap, JRECT *rect); static inline uint16_t ldb_word(const void *ptr); /******************************************************************************* * Public API functions *******************************************************************************/ esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img) { esp_err_t ret = ESP_OK; uint8_t *workbuf = NULL; JRESULT res; JDEC JDEC; assert(cfg != NULL); assert(img != NULL); const bool allocate_buffer = (cfg->advanced.working_buffer == NULL); const size_t workbuf_size = allocate_buffer ? JPEG_WORK_BUF_SIZE : cfg->advanced.working_buffer_size; if (allocate_buffer) { workbuf = heap_caps_malloc(JPEG_WORK_BUF_SIZE, MALLOC_CAP_DEFAULT); ESP_GOTO_ON_FALSE(workbuf, ESP_ERR_NO_MEM, err, TAG, "no mem for JPEG work buffer"); } else { workbuf = cfg->advanced.working_buffer; ESP_RETURN_ON_FALSE(workbuf_size != 0, ESP_ERR_INVALID_ARG, TAG, "Working buffer size not defined!"); } cfg->priv.read = 0; /* Prepare image */ res = jd_prepare(&JDEC, jpeg_decode_in_cb, workbuf, workbuf_size, cfg); ESP_GOTO_ON_FALSE((res == JDR_OK), ESP_FAIL, err, TAG, "Error in preparing JPEG image! %d", res); const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale); const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format); /* Size of output image */ const uint32_t outsize = (JDEC.height / scale_div) * (JDEC.width / scale_div) * out_color_bytes; ESP_GOTO_ON_FALSE((outsize <= cfg->outbuf_size), ESP_ERR_NO_MEM, err, TAG, "Not enough size in output buffer!"); /* Size of output image */ img->height = JDEC.height / scale_div; img->width = JDEC.width / scale_div; img->output_len = outsize; /* Decode JPEG */ res = jd_decomp(&JDEC, jpeg_decode_out_cb, cfg->out_scale); ESP_GOTO_ON_FALSE((res == JDR_OK), ESP_FAIL, err, TAG, "Error in decoding JPEG image! %d", res); err: if (workbuf && allocate_buffer) { free(workbuf); } return ret; } esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img) { if (cfg == NULL || img == NULL) { return ESP_ERR_INVALID_ARG; } else if (cfg->indata == NULL || cfg->indata_size < 5) { return ESP_ERR_INVALID_ARG; } esp_err_t ret = ESP_FAIL; if (ldb_word(cfg->indata) != 0xFFD8) { return ESP_FAIL; /* Err: SOI is not detected */ } unsigned ofs = 2; // Start after SOI marker while (true) { /* Get a JPEG marker */ uint8_t *seg = cfg->indata + ofs; /* Segment pointer */ unsigned short marker = ldb_word(seg); /* Marker */ unsigned int len = ldb_word(seg + 2); /* Length field */ if (len <= 2 || (marker >> 8) != 0xFF) { return ESP_FAIL; } ofs += 2 + len; /* Number of bytes loaded */ if (ofs > cfg->indata_size) { return ESP_FAIL; // No more data } if ((marker & 0xFF) == 0xC0) { /* SOF0 (baseline JPEG) */ seg += 4; /* Skip marker and length field */ /* Size of output image */ img->height = ldb_word(seg + 1); img->width = ldb_word(seg + 3); const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale); const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format); img->output_len = (img->height / scale_div) * (img->width / scale_div) * out_color_bytes; ret = ESP_OK; break; } } return ret; } /******************************************************************************* * Private API functions *******************************************************************************/ static unsigned int jpeg_decode_in_cb(JDEC *dec, uint8_t *buff, unsigned int nbyte) { assert(dec != NULL); uint32_t to_read = nbyte; esp_jpeg_image_cfg_t *cfg = (esp_jpeg_image_cfg_t *)dec->device; assert(cfg != NULL); if (buff) { if (cfg->priv.read + to_read > cfg->indata_size) { to_read = cfg->indata_size - cfg->priv.read; } /* Copy data from JPEG image */ memcpy(buff, &cfg->indata[cfg->priv.read], to_read); cfg->priv.read += to_read; } else if (buff == NULL) { /* Skip data */ cfg->priv.read += to_read; } return to_read; } static jpeg_decode_out_t jpeg_decode_out_cb(JDEC *dec, void *bitmap, JRECT *rect) { uint16_t color = 0; assert(dec != NULL); esp_jpeg_image_cfg_t *cfg = (esp_jpeg_image_cfg_t *)dec->device; assert(cfg != NULL); assert(bitmap != NULL); assert(rect != NULL); uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale); uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format); /* Copy decoded image data to output buffer */ uint8_t *in = (uint8_t *)bitmap; uint32_t line = dec->width / scale_div; uint8_t *dst = (uint8_t *)cfg->outbuf; for (int y = rect->top; y <= rect->bottom; y++) { for (int x = rect->left; x <= rect->right; x++) { if ( (JD_FORMAT == 0 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB888) || (JD_FORMAT == 1 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB565) ) { /* Output image format is same as set in TJPGD */ for (int b = 0; b < ESP_JPEG_COLOR_BYTES; b++) { if (cfg->flags.swap_color_bytes) { dst[(y * line * out_color_bytes) + x * out_color_bytes + b] = in[out_color_bytes - b - 1]; } else { dst[(y * line * out_color_bytes) + x * out_color_bytes + b] = in[b]; } } } else if (JD_FORMAT == 0 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB565) { /* Output image format is not same as set in TJPGD */ /* We need to convert the 3 bytes in `in` to a rgb565 value */ color = ((in[0] & 0xF8) << 8); color |= ((in[1] & 0xFC) << 3); color |= (in[2] >> 3); if (cfg->flags.swap_color_bytes) { dst[(y * line * out_color_bytes) + (x * out_color_bytes)] = HIBYTE(color); dst[(y * line * out_color_bytes) + (x * out_color_bytes) + 1] = LOBYTE(color); } else { dst[(y * line * out_color_bytes) + (x * out_color_bytes) + 1] = HIBYTE(color); dst[(y * line * out_color_bytes) + (x * out_color_bytes)] = LOBYTE(color); } } else { ESP_LOGE(TAG, "Selected output format is not supported!"); assert(0); } in += ESP_JPEG_COLOR_BYTES; } } return 1; } static uint8_t jpeg_get_div_by_scale(esp_jpeg_image_scale_t scale) { switch (scale) { /* Not scaled */ case JPEG_IMAGE_SCALE_0: return 1; /* Scaled 1:2 */ case JPEG_IMAGE_SCALE_1_2: return 2; /* Scaled 1:4 */ case JPEG_IMAGE_SCALE_1_4: return 4; /* Scaled 1:8 */ case JPEG_IMAGE_SCALE_1_8: return 8; } return 1; } static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format) { switch (format) { /* RGB888 (24-bit/pix) */ case JPEG_IMAGE_FORMAT_RGB888: return 3; /* RGB565 (16-bit/pix) */ case JPEG_IMAGE_FORMAT_RGB565: return 2; } return 1; } static inline uint16_t ldb_word(const void *ptr) { const uint8_t *p = (const uint8_t *)ptr; return ((uint16_t)p[0] << 8) | p[1]; } ================================================ FILE: esp_jpeg/jpeg_default_huffman_table.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ // Default Huffman tables for baseline JPEG // These values are taken directly from CCITT Rec. T.81 (1992 E) Appendix K.3.3 // The *_num_bits array always contains exactly 16 elements. // Each element represents the number of Huffman codes of a specific length: // - The first element corresponds to codes of length 1 bit, // - The second element to codes of length 2 bits, and so forth up to 16 bits. // // The *_values array has a length equal to the sum of all elements in the *_num_bits array, // representing the actual values associated with each Huffman code in order. // Luminance DC Table const unsigned char esp_jpeg_lum_dc_num_bits[16] = {0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}; const unsigned esp_jpeg_lum_dc_codes_total = 12; const unsigned char esp_jpeg_lum_dc_values[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Chrominance DC Table const unsigned char esp_jpeg_chrom_dc_num_bits[16] = {0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}; const unsigned esp_jpeg_chrom_dc_codes_total = 12; const unsigned char esp_jpeg_chrom_dc_values[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Luminance AC Table const unsigned char esp_jpeg_lum_ac_num_bits[16] = {0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125}; const unsigned esp_jpeg_lum_ac_codes_total = 162; const unsigned char esp_jpeg_lum_ac_values[162] = { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA }; // Chrominance AC Table const unsigned char esp_jpeg_chrom_ac_num_bits[16] = {0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119}; const unsigned esp_jpeg_chrom_ac_codes_total = 162; const unsigned char esp_jpeg_chrom_ac_values[162] = { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA }; ================================================ FILE: esp_jpeg/license.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_jpeg/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_jpeg_test) ================================================ FILE: esp_jpeg/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "tjpgd_test.c" "test_tjpgd_main.c" INCLUDE_DIRS "." PRIV_REQUIRES "unity" WHOLE_ARCHIVE EMBED_FILES "logo.jpg" "usb_camera.jpg" "usb_camera_2.jpg") ================================================ FILE: esp_jpeg/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_jpeg: version: "*" override_path: "../../" ================================================ FILE: esp_jpeg/test_apps/main/jpg_to_rgb888_hex.py ================================================ from PIL import Image def jpg_to_rgb888_hex_c_array(input_filename: str, output_filename: str) -> str: """ Convert a .jpg file to RGB888 hex data and format it as a C-style array. Parameters: input_filename (str): The path to the JPEG file. Returns: str: A string representing the RGB888 hex data formatted as a C array. """ # Open the image file with Image.open(input_filename) as img: # Ensure the image is in RGB mode rgb_img = img.convert("RGB") # Get image dimensions width, height = rgb_img.size # List to store hex values as C-style entries hex_data = [] # Iterate over each pixel to get RGB values for y in range(height): for x in range(width): r, g, b = rgb_img.getpixel((x, y)) # Format each RGB value as C-style hex (e.g., 0xRRGGBB) hex_data.append(f"0x{r:02X}{g:02X}{b:02X}") # Format as a C-style array with line breaks for readability hex_array = ",\n ".join(hex_data) c_array = f"unsigned int image_data[{width * height}] = {{\n {hex_array}\n}};" # Write the C array to the output file with open(output_filename, "w") as file: file.write(c_array) print(f"C-style RGB888 hex array saved to {output_filename}") return c_array def main(): """ Main function to convert a JPEG file to an RGB888 C-style hex array. Instructions: 1. Replace 'input.jpg' with the path to your JPEG file. 2. Run the script to get the C-style array output. """ # Input JPEG file path input_filename = "usb_camera.jpg" # Replace with your JPEG file path # Output file path for the C array output_filename = "output_array.c" # Specify your desired output filename # Convert JPEG to C-style RGB888 hex array jpg_to_rgb888_hex_c_array(input_filename, output_filename) if __name__ == "__main__": main() ================================================ FILE: esp_jpeg/test_apps/main/test_logo_jpg.h ================================================ // JPEG encoded image 46x46, 7561 bytes extern const unsigned char logo_jpg[] asm("_binary_logo_jpg_start"); extern char _binary_logo_jpg_start; extern char _binary_logo_jpg_end; // Must be defined as macro because extern variables are not known at compile time (but at link time) #define logo_jpg_len (&_binary_logo_jpg_end - &_binary_logo_jpg_start) ================================================ FILE: esp_jpeg/test_apps/main/test_logo_rgb888.h ================================================ unsigned char logo_rgb888[] = { 0xe1, 0x3b, 0x1f, 0xdf, 0x33, 0x16, 0xe1, 0x2f, 0x14, 0xe2, 0x30, 0x16, 0xdf, 0x2e, 0x14, 0xe1, 0x32, 0x16, 0xdf, 0x30, 0x14, 0xe1, 0x30, 0x16, 0xe3, 0x2e, 0x17, 0xdd, 0x33, 0x15, 0xe1, 0x30, 0x16, 0xe1, 0x2f, 0x12, 0xdf, 0x30, 0x14, 0xdc, 0x32, 0x14, 0xdf, 0x30, 0x15, 0xe2, 0x30, 0x15, 0xe0, 0x2f, 0x15, 0xe1, 0x31, 0x15, 0xe0, 0x2f, 0x15, 0xe2, 0x32, 0x16, 0xde, 0x32, 0x13, 0xdf, 0x30, 0x14, 0xe0, 0x31, 0x18, 0xe1, 0x31, 0x15, 0xe2, 0x30, 0x13, 0xdd, 0x30, 0x17, 0xdf, 0x30, 0x17, 0xe2, 0x30, 0x13, 0xe2, 0x30, 0x15, 0xdf, 0x30, 0x14, 0xe1, 0x33, 0x14, 0xe0, 0x30, 0x14, 0xdd, 0x31, 0x15, 0xdf, 0x30, 0x15, 0xe0, 0x30, 0x14, 0xe2, 0x30, 0x15, 0xdf, 0x30, 0x15, 0xdd, 0x31, 0x12, 0xdf, 0x2f, 0x13, 0xe1, 0x30, 0x18, 0xe2, 0x30, 0x16, 0xe1, 0x2f, 0x14, 0xe0, 0x31, 0x16, 0xe1, 0x30, 0x16, 0xe0, 0x30, 0x12, 0xe3, 0x3e, 0x26, 0xe3, 0x24, 0x0d, 0xdf, 0x29, 0x0d, 0xdd, 0x29, 0x0a, 0xdf, 0x26, 0x0b, 0xdf, 0x29, 0x0d, 0xdd, 0x27, 0x0b, 0xdd, 0x24, 0x07, 0xdd, 0x29, 0x0c, 0xdc, 0x28, 0x09, 0xdf, 0x27, 0x07, 0xdc, 0x26, 0x08, 0xdf, 0x29, 0x0b, 0xde, 0x28, 0x0c, 0xdf, 0x24, 0x0d, 0xdf, 0x29, 0x0b, 0xde, 0x25, 0x08, 0xdd, 0x28, 0x07, 0xde, 0x28, 0x0c, 0xdc, 0x27, 0x0d, 0xdb, 0x25, 0x0b, 0xe0, 0x27, 0x0e, 0xe0, 0x27, 0x0a, 0xe1, 0x25, 0x09, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x0d, 0xe0, 0x28, 0x08, 0xdd, 0x29, 0x0a, 0xde, 0x28, 0x0e, 0xdf, 0x26, 0x0b, 0xde, 0x28, 0x0a, 0xdf, 0x26, 0x0b, 0xde, 0x28, 0x0c, 0xe0, 0x28, 0x08, 0xe0, 0x25, 0x0b, 0xe0, 0x28, 0x08, 0xdd, 0x28, 0x0e, 0xdf, 0x26, 0x0d, 0xe0, 0x27, 0x0e, 0xda, 0x28, 0x0b, 0xdf, 0x26, 0x09, 0xdd, 0x29, 0x08, 0xdc, 0x28, 0x07, 0xdc, 0x28, 0x09, 0xe0, 0x27, 0x0a, 0xdf, 0x24, 0x03, 0xe0, 0x2e, 0x13, 0xe1, 0x31, 0x15, 0xde, 0x28, 0x0a, 0xdf, 0x27, 0x07, 0xdd, 0x27, 0x09, 0xdc, 0x28, 0x09, 0xdf, 0x26, 0x09, 0xe3, 0x27, 0x09, 0xdf, 0x26, 0x09, 0xdf, 0x26, 0x09, 0xdd, 0x27, 0x0d, 0xe0, 0x27, 0x0e, 0xde, 0x25, 0x0a, 0xde, 0x28, 0x0c, 0xdf, 0x28, 0x04, 0xe0, 0x25, 0x0b, 0xe1, 0x28, 0x0d, 0xdd, 0x27, 0x09, 0xe0, 0x26, 0x09, 0xe3, 0x27, 0x0d, 0xe0, 0x27, 0x0c, 0xe0, 0x27, 0x0e, 0xe0, 0x27, 0x0c, 0xdd, 0x28, 0x07, 0xdd, 0x28, 0x0f, 0xdd, 0x27, 0x09, 0xe0, 0x28, 0x08, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x0c, 0xe0, 0x27, 0x0e, 0xdc, 0x28, 0x09, 0xe0, 0x26, 0x09, 0xe0, 0x26, 0x0f, 0xdd, 0x28, 0x07, 0xde, 0x28, 0x0e, 0xdc, 0x26, 0x0c, 0xe0, 0x28, 0x08, 0xe0, 0x27, 0x0a, 0xdf, 0x26, 0x09, 0xdd, 0x29, 0x0c, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x09, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xde, 0x26, 0x06, 0xe0, 0x35, 0x1b, 0xdc, 0x31, 0x15, 0xe0, 0x25, 0x0b, 0xe2, 0x26, 0x0a, 0xdd, 0x29, 0x0c, 0xde, 0x28, 0x0c, 0xe1, 0x27, 0x0a, 0xdf, 0x26, 0x09, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x0c, 0xdd, 0x27, 0x0b, 0xe1, 0x27, 0x08, 0xe0, 0x25, 0x0d, 0xdd, 0x29, 0x0a, 0xe0, 0x27, 0x0c, 0xdc, 0x26, 0x08, 0xdd, 0x27, 0x09, 0xdf, 0x26, 0x0d, 0xdd, 0x27, 0x09, 0xda, 0x29, 0x09, 0xdc, 0x28, 0x07, 0xdf, 0x2a, 0x09, 0xdc, 0x27, 0x0d, 0xdc, 0x09, 0x00, 0xd8, 0x05, 0x00, 0xd9, 0x06, 0x00, 0xd9, 0x09, 0x00, 0xdb, 0x14, 0x00, 0xde, 0x20, 0x00, 0xdd, 0x29, 0x08, 0xdb, 0x25, 0x09, 0xdd, 0x27, 0x09, 0xe0, 0x28, 0x08, 0xde, 0x26, 0x06, 0xe1, 0x26, 0x0c, 0xde, 0x28, 0x0a, 0xdd, 0x29, 0x0a, 0xdf, 0x27, 0x07, 0xdf, 0x26, 0x0b, 0xde, 0x28, 0x0a, 0xdd, 0x28, 0x07, 0xe1, 0x26, 0x0c, 0xe1, 0x26, 0x0c, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x0b, 0xdf, 0x25, 0x06, 0xe2, 0x35, 0x1c, 0xdf, 0x30, 0x15, 0xe1, 0x26, 0x0c, 0xe1, 0x27, 0x0a, 0xdc, 0x26, 0x0a, 0xde, 0x25, 0x0a, 0xde, 0x28, 0x0a, 0xdc, 0x28, 0x09, 0xe2, 0x27, 0x0d, 0xe0, 0x25, 0x0e, 0xdd, 0x27, 0x0b, 0xe0, 0x28, 0x06, 0xdb, 0x27, 0x0a, 0xe1, 0x28, 0x0d, 0xda, 0x29, 0x09, 0xdf, 0x26, 0x0b, 0xdf, 0x29, 0x0d, 0xdf, 0x27, 0x07, 0xde, 0x2a, 0x0d, 0xe0, 0x28, 0x06, 0xe1, 0x26, 0x0c, 0xdf, 0x25, 0x06, 0xdf, 0x20, 0x09, 0xe6, 0x59, 0x3f, 0xe6, 0x6a, 0x57, 0xe8, 0x64, 0x4f, 0xe5, 0x53, 0x3e, 0xe2, 0x39, 0x24, 0xdd, 0x19, 0x00, 0xd8, 0x02, 0x00, 0xde, 0x03, 0x03, 0xdd, 0x1d, 0x00, 0xdb, 0x29, 0x0f, 0xde, 0x28, 0x0e, 0xdc, 0x27, 0x06, 0xdc, 0x26, 0x0a, 0xe2, 0x28, 0x09, 0xe0, 0x25, 0x0b, 0xdf, 0x26, 0x0d, 0xe3, 0x28, 0x10, 0xdf, 0x26, 0x09, 0xdd, 0x27, 0x09, 0xdc, 0x26, 0x08, 0xdb, 0x27, 0x0a, 0xde, 0x28, 0x0a, 0xde, 0x26, 0x04, 0xe0, 0x35, 0x19, 0xde, 0x32, 0x15, 0xdc, 0x26, 0x0a, 0xe0, 0x26, 0x09, 0xe1, 0x28, 0x0b, 0xde, 0x28, 0x0a, 0xdd, 0x27, 0x09, 0xdc, 0x28, 0x09, 0xdf, 0x29, 0x0d, 0xe0, 0x28, 0x08, 0xda, 0x29, 0x07, 0xdf, 0x27, 0x07, 0xdd, 0x29, 0x0c, 0xdd, 0x25, 0x05, 0xe2, 0x29, 0x0c, 0xdb, 0x1f, 0x03, 0xda, 0x09, 0x00, 0xda, 0x0d, 0x01, 0xd9, 0x18, 0x00, 0xe1, 0x21, 0x0c, 0xdc, 0x28, 0x07, 0xdc, 0x10, 0x01, 0xe0, 0x3b, 0x21, 0xfd, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, 0xfe, 0xff, 0xfe, 0xfe, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xd2, 0xce, 0xed, 0xa2, 0x99, 0xe5, 0x5f, 0x4f, 0xda, 0x1c, 0x00, 0xda, 0x00, 0x00, 0xde, 0x1c, 0x00, 0xdd, 0x2d, 0x0f, 0xde, 0x28, 0x0c, 0xe0, 0x27, 0x0a, 0xdd, 0x27, 0x0b, 0xde, 0x28, 0x0c, 0xd9, 0x27, 0x0c, 0xe0, 0x27, 0x0a, 0xe0, 0x27, 0x0c, 0xe0, 0x27, 0x0a, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x0c, 0xdd, 0x25, 0x05, 0xe0, 0x35, 0x1b, 0xe1, 0x2f, 0x14, 0xe2, 0x28, 0x0b, 0xe0, 0x26, 0x07, 0xdc, 0x26, 0x08, 0xe2, 0x28, 0x0b, 0xe0, 0x26, 0x09, 0xde, 0x28, 0x0a, 0xde, 0x2a, 0x0d, 0xda, 0x12, 0x02, 0xdd, 0x16, 0x00, 0xdf, 0x2b, 0x0e, 0xe0, 0x25, 0x0e, 0xdd, 0x2a, 0x06, 0xdb, 0x08, 0x00, 0xdb, 0x17, 0x02, 0xe6, 0x56, 0x43, 0xe3, 0x4b, 0x30, 0xe3, 0x28, 0x0e, 0xd9, 0x09, 0x00, 0xd8, 0x02, 0x00, 0xd6, 0x05, 0x00, 0xdb, 0x26, 0x0d, 0xe6, 0x64, 0x53, 0xf2, 0xae, 0xa7, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xfe, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xf6, 0xd9, 0xd9, 0xea, 0x77, 0x6a, 0xdd, 0x17, 0x00, 0xd7, 0x00, 0x04, 0xe0, 0x26, 0x05, 0xda, 0x2a, 0x0a, 0xe0, 0x27, 0x0a, 0xde, 0x28, 0x0e, 0xe1, 0x27, 0x0a, 0xde, 0x29, 0x08, 0xdb, 0x25, 0x07, 0xde, 0x28, 0x0a, 0xdd, 0x27, 0x09, 0xdd, 0x27, 0x09, 0xdd, 0x25, 0x03, 0xe3, 0x37, 0x1b, 0xdd, 0x31, 0x15, 0xdd, 0x27, 0x0b, 0xde, 0x28, 0x0a, 0xdd, 0x29, 0x0c, 0xdf, 0x26, 0x0b, 0xdd, 0x29, 0x0c, 0xe2, 0x29, 0x0e, 0xda, 0x0e, 0x00, 0xe0, 0x3e, 0x23, 0xe1, 0x3b, 0x1f, 0xdd, 0x1c, 0x06, 0xdf, 0x27, 0x07, 0xd9, 0x01, 0x00, 0xe0, 0x34, 0x20, 0xf3, 0xbf, 0xb9, 0xff, 0xff, 0xfc, 0xfe, 0xff, 0xfe, 0xf6, 0xe3, 0xe1, 0xf4, 0xbe, 0xb5, 0xec, 0x87, 0x7c, 0xe4, 0x4c, 0x39, 0xdc, 0x10, 0x00, 0xd3, 0x02, 0x02, 0xd8, 0x01, 0x00, 0xdf, 0x3b, 0x29, 0xee, 0xa0, 0x98, 0xfd, 0xff, 0xfb, 0xfd, 0xfe, 0xff, 0xfc, 0xff, 0xfd, 0xfc, 0xfe, 0xfc, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xf8, 0xd8, 0xd5, 0xe1, 0x55, 0x43, 0xd9, 0x00, 0x00, 0xe0, 0x1b, 0x02, 0xde, 0x2a, 0x0b, 0xe0, 0x27, 0x0a, 0xdd, 0x27, 0x09, 0xdf, 0x26, 0x0d, 0xe0, 0x27, 0x0c, 0xe1, 0x27, 0x0a, 0xdf, 0x26, 0x0b, 0xe0, 0x27, 0x0c, 0xde, 0x24, 0x05, 0xe0, 0x35, 0x1b, 0xe0, 0x31, 0x18, 0xe0, 0x27, 0x0c, 0xe0, 0x24, 0x08, 0xe1, 0x26, 0x0e, 0xe0, 0x27, 0x0c, 0xde, 0x28, 0x0e, 0xd9, 0x0d, 0x00, 0xde, 0x1e, 0x09, 0xfd, 0xf7, 0xfa, 0xfb, 0xeb, 0xe7, 0xd9, 0x13, 0x00, 0xda, 0x0d, 0x03, 0xe8, 0x67, 0x59, 0xfe, 0xf9, 0xf7, 0xff, 0xff, 0xfe, 0xfd, 0xfe, 0xff, 0xfc, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfd, 0xfd, 0xfe, 0xff, 0xf7, 0xcf, 0xc7, 0xec, 0x7f, 0x7a, 0xdd, 0x2e, 0x13, 0xd4, 0x00, 0x00, 0xd3, 0x01, 0x00, 0xe1, 0x3d, 0x2b, 0xf6, 0xb8, 0xb3, 0xfd, 0xfe, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfd, 0xfd, 0xfc, 0xfe, 0xfc, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfc, 0xec, 0x8e, 0x84, 0xda, 0x04, 0x00, 0xdc, 0x15, 0x01, 0xde, 0x2a, 0x0b, 0xdd, 0x27, 0x09, 0xdd, 0x29, 0x0a, 0xe1, 0x26, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x28, 0x0c, 0xdc, 0x28, 0x0b, 0xe0, 0x26, 0x05, 0xe1, 0x35, 0x19, 0xdd, 0x31, 0x14, 0xdf, 0x27, 0x07, 0xdd, 0x27, 0x09, 0xdf, 0x29, 0x0b, 0xda, 0x2a, 0x0e, 0xdb, 0x1a, 0x04, 0xdc, 0x04, 0x00, 0xf4, 0xd2, 0xcc, 0xfb, 0xfe, 0xfc, 0xe9, 0x83, 0x71, 0xdc, 0x0a, 0x05, 0xdf, 0x36, 0x21, 0xfe, 0xfd, 0xf8, 0xfe, 0xff, 0xfe, 0xfb, 0xfd, 0xfb, 0xfd, 0xfc, 0xfb, 0xfc, 0xfe, 0xfc, 0xfb, 0xfd, 0xfb, 0xfe, 0xfd, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xff, 0xfe, 0xfd, 0xff, 0xf9, 0xfd, 0xff, 0xfd, 0xfd, 0xf0, 0xf3, 0xef, 0x94, 0x8b, 0xdc, 0x2c, 0x16, 0xd3, 0x01, 0x00, 0xd6, 0x00, 0x00, 0xe8, 0x6d, 0x61, 0xf8, 0xf8, 0xef, 0xfe, 0xff, 0xff, 0xfb, 0xfe, 0xfd, 0xfa, 0xfa, 0xf9, 0xfc, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xf0, 0xb0, 0xa6, 0xdc, 0x04, 0x00, 0xdd, 0x14, 0x00, 0xe0, 0x2a, 0x10, 0xe0, 0x25, 0x0b, 0xdc, 0x28, 0x07, 0xdf, 0x27, 0x07, 0xe1, 0x26, 0x0c, 0xde, 0x28, 0x0c, 0xdc, 0x24, 0x04, 0xdf, 0x37, 0x1c, 0xdf, 0x33, 0x16, 0xdc, 0x26, 0x0a, 0xdf, 0x2a, 0x07, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x04, 0xd5, 0x02, 0x00, 0xf2, 0x9a, 0x8a, 0xff, 0xfe, 0xfd, 0xeb, 0x97, 0x86, 0xd9, 0x01, 0x00, 0xda, 0x0b, 0x00, 0xe2, 0x41, 0x28, 0xfe, 0xff, 0xff, 0xfc, 0xff, 0xfe, 0xf9, 0xff, 0xfd, 0xfa, 0xff, 0xfd, 0xfb, 0xfc, 0xfc, 0xfb, 0xfe, 0xfc, 0xfd, 0xfc, 0xfb, 0xfb, 0xfc, 0xfc, 0xfb, 0xfd, 0xfb, 0xfb, 0xfc, 0xfd, 0xfb, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfd, 0xf9, 0xf2, 0xf1, 0xeb, 0x7c, 0x74, 0xda, 0x04, 0x00, 0xd4, 0x02, 0x00, 0xe3, 0x31, 0x1e, 0xf6, 0xd0, 0xc7, 0xfd, 0xfe, 0xff, 0xfa, 0xff, 0xff, 0xfd, 0xfc, 0xfb, 0xfe, 0xfe, 0xfb, 0xff, 0xff, 0xff, 0xf1, 0xb6, 0xac, 0xd9, 0x00, 0x02, 0xdc, 0x1d, 0x04, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x0a, 0xdf, 0x2b, 0x0c, 0xdb, 0x25, 0x09, 0xe0, 0x27, 0x0c, 0xe1, 0x25, 0x07, 0xdd, 0x35, 0x1a, 0xe0, 0x2f, 0x17, 0xe0, 0x26, 0x0f, 0xe0, 0x25, 0x0b, 0xdd, 0x29, 0x08, 0xd9, 0x06, 0x00, 0xe2, 0x44, 0x28, 0xff, 0xff, 0xff, 0xf5, 0xd9, 0xd1, 0xdd, 0x00, 0x01, 0xdb, 0x1a, 0x02, 0xda, 0x14, 0x01, 0xe3, 0x3d, 0x21, 0xfb, 0xfc, 0xfc, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xff, 0xff, 0xfc, 0xff, 0xfe, 0xfb, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xf9, 0xfc, 0xfc, 0xfb, 0xfd, 0xfe, 0xfe, 0xfd, 0xfc, 0xf9, 0xfa, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xfd, 0xf8, 0xc4, 0xc0, 0xdd, 0x34, 0x1f, 0xd2, 0x01, 0x01, 0xda, 0x0b, 0x00, 0xf6, 0xb1, 0xa6, 0xfe, 0xfe, 0xff, 0xfd, 0xff, 0xfe, 0xfa, 0xfc, 0xfa, 0xfa, 0xfe, 0xf9, 0xfd, 0xfd, 0xfe, 0xef, 0x97, 0x83, 0xd7, 0x00, 0x00, 0xdb, 0x27, 0x06, 0xde, 0x25, 0x0a, 0xdd, 0x29, 0x0a, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x0b, 0xe0, 0x24, 0x06, 0xe0, 0x35, 0x1b, 0xe1, 0x2f, 0x14, 0xdd, 0x28, 0x07, 0xdd, 0x27, 0x09, 0xdc, 0x24, 0x04, 0xdb, 0x04, 0x03, 0xfb, 0xd7, 0xd3, 0xfc, 0xfe, 0xfa, 0xe1, 0x3b, 0x1d, 0xd7, 0x05, 0x00, 0xdf, 0x29, 0x0b, 0xdf, 0x23, 0x05, 0xdc, 0x26, 0x0a, 0xe3, 0x46, 0x2d, 0xe3, 0x4f, 0x3b, 0xe6, 0x60, 0x50, 0xea, 0x79, 0x6d, 0xed, 0x9e, 0x93, 0xf4, 0xcb, 0xc9, 0xfa, 0xfa, 0xfa, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfd, 0xfd, 0xfc, 0xfb, 0xfd, 0xfd, 0xfc, 0xfc, 0xfe, 0xff, 0xfb, 0xfc, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfb, 0xfa, 0xf7, 0xe9, 0x61, 0x4f, 0xd2, 0x01, 0x00, 0xda, 0x00, 0x01, 0xee, 0x9d, 0x91, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xfe, 0xfb, 0xfb, 0xfb, 0xfd, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xe7, 0x52, 0x3d, 0xd7, 0x00, 0x01, 0xe2, 0x29, 0x0c, 0xde, 0x26, 0x06, 0xe2, 0x28, 0x0b, 0xe0, 0x27, 0x0a, 0xdc, 0x24, 0x04, 0xe0, 0x37, 0x1f, 0xe1, 0x31, 0x15, 0xdb, 0x26, 0x0c, 0xe3, 0x29, 0x0a, 0xdb, 0x05, 0x00, 0xe6, 0x55, 0x3c, 0xfd, 0xfd, 0xfe, 0xf3, 0xac, 0xa1, 0xd7, 0x03, 0x01, 0xe0, 0x2d, 0x08, 0xe0, 0x16, 0x03, 0xd8, 0x06, 0x00, 0xd9, 0x07, 0x00, 0xd9, 0x01, 0x00, 0xdb, 0x00, 0x00, 0xd5, 0x00, 0x01, 0xd4, 0x01, 0x01, 0xd7, 0x00, 0x01, 0xd7, 0x03, 0x01, 0xe0, 0x37, 0x1f, 0xe9, 0x78, 0x6a, 0xf7, 0xc9, 0xc5, 0xfb, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfc, 0xff, 0xfe, 0xfa, 0xfc, 0xfa, 0xfd, 0xfd, 0xfa, 0xff, 0xfc, 0xfd, 0xfa, 0xfd, 0xfc, 0xfb, 0xff, 0xfe, 0xfc, 0xfe, 0xfc, 0xea, 0x80, 0x6e, 0xd6, 0x02, 0x00, 0xd8, 0x01, 0x00, 0xf3, 0xa1, 0x8f, 0xfc, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xfa, 0xfb, 0xfb, 0xfe, 0xff, 0xff, 0xf6, 0xe6, 0xe2, 0xdc, 0x12, 0x00, 0xde, 0x1a, 0x05, 0xdf, 0x29, 0x0b, 0xdd, 0x27, 0x09, 0xe1, 0x27, 0x0a, 0xde, 0x24, 0x05, 0xdf, 0x37, 0x1c, 0xe0, 0x30, 0x12, 0xde, 0x28, 0x0c, 0xdf, 0x25, 0x08, 0xd9, 0x02, 0x00, 0xf3, 0xcc, 0xbf, 0xfe, 0xfe, 0xfb, 0xe3, 0x36, 0x1d, 0xd6, 0x0b, 0x00, 0xda, 0x04, 0x00, 0xdd, 0x23, 0x0c, 0xe7, 0x75, 0x62, 0xf2, 0xae, 0xa5, 0xf0, 0xc7, 0xbf, 0xf3, 0xbd, 0xb5, 0xf1, 0xa0, 0x96, 0xeb, 0x86, 0x7b, 0xe7, 0x5d, 0x4c, 0xde, 0x2a, 0x0b, 0xd8, 0x02, 0x00, 0xd3, 0x02, 0x00, 0xdc, 0x01, 0x01, 0xe5, 0x52, 0x42, 0xf3, 0xbf, 0xb9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, 0xfc, 0xfc, 0xf8, 0xfe, 0xf9, 0xff, 0xfe, 0xfd, 0xf9, 0xfc, 0xfc, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xfc, 0xea, 0x88, 0x77, 0xd7, 0x00, 0x00, 0xd9, 0x00, 0x00, 0xf3, 0xb4, 0xa6, 0xff, 0xfd, 0xfa, 0xfc, 0xff, 0xff, 0xfb, 0xff, 0xfe, 0xff, 0xff, 0xfa, 0xeb, 0x7b, 0x65, 0xd4, 0x03, 0x00, 0xdd, 0x27, 0x0d, 0xd9, 0x29, 0x0b, 0xe1, 0x27, 0x0a, 0xe1, 0x23, 0x05, 0xe0, 0x35, 0x1b, 0xe1, 0x31, 0x15, 0xdc, 0x28, 0x0b, 0xdd, 0x13, 0x01, 0xe5, 0x36, 0x1a, 0xfe, 0xfe, 0xff, 0xf3, 0xcb, 0xc3, 0xd8, 0x01, 0x00, 0xda, 0x02, 0x00, 0xe7, 0x5c, 0x4e, 0xfa, 0xe4, 0xe2, 0xfe, 0xfe, 0xfb, 0xfe, 0xfe, 0xfe, 0xfd, 0xff, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xfe, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff, 0xfd, 0xfa, 0xef, 0xef, 0xf2, 0xb6, 0xb0, 0xe9, 0x68, 0x5e, 0xde, 0x14, 0x01, 0xd3, 0x02, 0x00, 0xd8, 0x02, 0x00, 0xe5, 0x62, 0x57, 0xf8, 0xec, 0xe7, 0xfd, 0xff, 0xfd, 0xfd, 0xff, 0xfd, 0xfd, 0xfc, 0xfc, 0xfd, 0xfc, 0xfb, 0xfd, 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfe, 0xff, 0xff, 0xe9, 0x81, 0x6f, 0xd2, 0x01, 0x00, 0xdb, 0x0b, 0x02, 0xf7, 0xd8, 0xcd, 0xfd, 0xfe, 0xfe, 0xfb, 0xfe, 0xfc, 0xfe, 0xfd, 0xfd, 0xf7, 0xe3, 0xde, 0xe0, 0x11, 0x03, 0xdc, 0x20, 0x06, 0xdd, 0x27, 0x09, 0xde, 0x28, 0x0a, 0xdd, 0x25, 0x05, 0xe2, 0x35, 0x1e, 0xe1, 0x30, 0x16, 0xda, 0x2b, 0x08, 0xd8, 0x00, 0x00, 0xeb, 0x79, 0x68, 0xff, 0xfe, 0xff, 0xe7, 0x6e, 0x55, 0xd5, 0x01, 0x00, 0xe6, 0x65, 0x57, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfe, 0xfc, 0xfd, 0xfe, 0xfe, 0xfb, 0xfc, 0xfd, 0xfc, 0xfc, 0xfc, 0xfb, 0xfe, 0xfc, 0xfb, 0xfc, 0xfc, 0xfd, 0xff, 0xfd, 0xfa, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xfe, 0xf5, 0xd9, 0xd2, 0xea, 0x73, 0x67, 0xd7, 0x0a, 0x00, 0xd5, 0x00, 0x01, 0xde, 0x21, 0x0d, 0xf3, 0xb9, 0xb1, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfc, 0xfc, 0xfc, 0xfd, 0xff, 0xf9, 0xfe, 0xfe, 0xfe, 0xe6, 0x5f, 0x46, 0xd2, 0x01, 0x00, 0xdf, 0x32, 0x19, 0xfb, 0xfa, 0xf9, 0xfd, 0xff, 0xff, 0xfc, 0xff, 0xfd, 0xfd, 0xff, 0xfd, 0xe5, 0x58, 0x40, 0xdc, 0x06, 0x00, 0xdf, 0x26, 0x09, 0xdf, 0x26, 0x0b, 0xe0, 0x24, 0x08, 0xe1, 0x36, 0x1c, 0xe1, 0x31, 0x15, 0xe0, 0x24, 0x0a, 0xda, 0x08, 0x01, 0xf0, 0xbf, 0xb4, 0xff, 0xfe, 0xfe, 0xde, 0x1a, 0x05, 0xde, 0x26, 0x15, 0xfe, 0xff, 0xfe, 0xfc, 0xff, 0xfd, 0xfc, 0xfc, 0xfb, 0xfc, 0xfd, 0xfd, 0xfa, 0xfc, 0xfa, 0xfe, 0xfe, 0xfd, 0xfc, 0xfe, 0xfc, 0xfc, 0xf9, 0xf9, 0xfc, 0xfd, 0xfd, 0xff, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfa, 0xfd, 0xfd, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xca, 0xca, 0xe1, 0x45, 0x32, 0xd3, 0x02, 0x00, 0xd7, 0x01, 0x00, 0xee, 0x95, 0x89, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xfb, 0xf9, 0xfc, 0xfb, 0xfc, 0xff, 0xfd, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xe2, 0x2f, 0x1c, 0xd3, 0x01, 0x00, 0xea, 0x75, 0x5f, 0xfe, 0xff, 0xfe, 0xfb, 0xfc, 0xfc, 0xff, 0xff, 0xfc, 0xed, 0xa0, 0x92, 0xd8, 0x02, 0x00, 0xde, 0x28, 0x0e, 0xe0, 0x27, 0x0c, 0xde, 0x24, 0x05, 0xdf, 0x37, 0x1a, 0xdf, 0x30, 0x15, 0xe2, 0x1c, 0x00, 0xdb, 0x1b, 0x00, 0xf9, 0xf6, 0xf7, 0xf7, 0xe8, 0xe7, 0xd8, 0x01, 0x00, 0xec, 0x9a, 0x84, 0xff, 0xff, 0xfe, 0xf9, 0xff, 0xfc, 0xfd, 0xfd, 0xfd, 0xfc, 0xfe, 0xfc, 0xfb, 0xff, 0xf8, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0xfe, 0xff, 0xfb, 0xfc, 0xfd, 0xfc, 0xfe, 0xfa, 0xfd, 0xfd, 0xfe, 0xfb, 0xfd, 0xfb, 0xfa, 0xfd, 0xfb, 0xfd, 0xfd, 0xfa, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xeb, 0x75, 0x65, 0xd8, 0x00, 0x03, 0xd4, 0x00, 0x00, 0xec, 0x90, 0x7d, 0xff, 0xff, 0xfe, 0xfd, 0xfe, 0xfe, 0xfa, 0xfd, 0xfc, 0xfc, 0xfe, 0xfc, 0xfb, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xf7, 0xd0, 0xc5, 0xd6, 0x06, 0x00, 0xd6, 0x00, 0x00, 0xf5, 0xc7, 0xbf, 0xff, 0xff, 0xfe, 0xfc, 0xff, 0xfd, 0xf6, 0xd0, 0xc9, 0xdc, 0x12, 0x00, 0xdd, 0x1f, 0x01, 0xdd, 0x29, 0x08, 0xe0, 0x26, 0x05, 0xe1, 0x35, 0x19, 0xe0, 0x31, 0x16, 0xde, 0x14, 0x01, 0xe0, 0x35, 0x19, 0xfd, 0xff, 0xfe, 0xf6, 0xb1, 0xa5, 0xd7, 0x00, 0x01, 0xf3, 0xc9, 0xc4, 0xfc, 0xff, 0xfd, 0xfc, 0xfc, 0xfc, 0xfe, 0xfd, 0xfd, 0xfb, 0xfc, 0xfc, 0xfe, 0xff, 0xff, 0xf7, 0xe2, 0xe0, 0xf5, 0xdb, 0xd6, 0xfe, 0xf8, 0xfd, 0xfd, 0xff, 0xff, 0xfb, 0xfe, 0xfe, 0xfd, 0xff, 0xfe, 0xfb, 0xfd, 0xfb, 0xfd, 0xff, 0xfd, 0xfc, 0xfe, 0xfa, 0xfb, 0xfc, 0xff, 0xfc, 0xfc, 0xf9, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xee, 0x91, 0x83, 0xd8, 0x01, 0x00, 0xd6, 0x02, 0x00, 0xf0, 0x9d, 0x8e, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfb, 0xfe, 0xfd, 0xfc, 0xfc, 0xf9, 0xfb, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xed, 0x7c, 0x66, 0xd6, 0x02, 0x00, 0xe3, 0x41, 0x26, 0xfd, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0xf6, 0xf2, 0xdf, 0x30, 0x15, 0xdf, 0x18, 0x02, 0xdc, 0x28, 0x09, 0xdc, 0x22, 0x05, 0xe3, 0x37, 0x1b, 0xe3, 0x31, 0x17, 0xd8, 0x0f, 0x00, 0xe3, 0x4a, 0x34, 0xfc, 0xfd, 0xfd, 0xed, 0x8a, 0x7e, 0xdc, 0x03, 0x03, 0xf7, 0xe1, 0xdd, 0xff, 0xff, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfe, 0xfc, 0xff, 0xff, 0xff, 0xf5, 0xb4, 0xaa, 0xd6, 0x02, 0x00, 0xda, 0x15, 0x00, 0xde, 0x33, 0x17, 0xe9, 0x5b, 0x45, 0xf0, 0x95, 0x8c, 0xfb, 0xde, 0xdc, 0xfd, 0xfd, 0xfa, 0xff, 0xff, 0xff, 0xfd, 0xfe, 0xfe, 0xfb, 0xfd, 0xfb, 0xfb, 0xfd, 0xf9, 0xff, 0xfc, 0xfe, 0xfb, 0xfe, 0xfd, 0xfe, 0xff, 0xff, 0xea, 0x96, 0x87, 0xd5, 0x01, 0x00, 0xdb, 0x00, 0x00, 0xf6, 0xc6, 0xbe, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xfa, 0xfe, 0xfd, 0xfd, 0xfb, 0xfb, 0xf8, 0xff, 0xfd, 0xff, 0xf8, 0xf5, 0xf5, 0xdc, 0x25, 0x0f, 0xd3, 0x00, 0x01, 0xf1, 0xb0, 0xa3, 0xff, 0xff, 0xfe, 0xfc, 0xff, 0xfd, 0xe7, 0x48, 0x30, 0xd8, 0x10, 0x00, 0xde, 0x25, 0x0a, 0xe0, 0x25, 0x0b, 0xe1, 0x35, 0x19, 0xe0, 0x31, 0x18, 0xda, 0x10, 0x00, 0xe5, 0x53, 0x40, 0xfe, 0xff, 0xff, 0xeb, 0x7b, 0x67, 0xd9, 0x0a, 0x00, 0xfa, 0xed, 0xf1, 0xff, 0xfe, 0xff, 0xfa, 0xfd, 0xfc, 0xfe, 0xfd, 0xfc, 0xfb, 0xfe, 0xfd, 0xf4, 0xc4, 0xba, 0xe0, 0x1d, 0x13, 0xdf, 0x14, 0x00, 0xd9, 0x02, 0x01, 0xd4, 0x01, 0x04, 0xd4, 0x00, 0x00, 0xda, 0x15, 0x00, 0xec, 0x6f, 0x64, 0xf9, 0xe5, 0xde, 0xfd, 0xfe, 0xff, 0xfd, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfc, 0xfc, 0xff, 0xfe, 0xfd, 0xfd, 0xfc, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xee, 0x75, 0x5e, 0xd3, 0x00, 0x00, 0xe0, 0x25, 0x0d, 0xf9, 0xf3, 0xed, 0xff, 0xff, 0xff, 0xfb, 0xfc, 0xfd, 0xfc, 0xfe, 0xfa, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xeb, 0x98, 0x82, 0xd3, 0x01, 0x00, 0xe4, 0x3f, 0x25, 0xfd, 0xff, 0xfd, 0xff, 0xfe, 0xfd, 0xe6, 0x54, 0x3f, 0xd9, 0x08, 0x00, 0xdf, 0x29, 0x0b, 0xdd, 0x24, 0x09, 0xe2, 0x36, 0x1a, 0xe2, 0x31, 0x17, 0xdc, 0x09, 0x01, 0xe2, 0x57, 0x3f, 0xff, 0xff, 0xfc, 0xe9, 0x7c, 0x6d, 0xd9, 0x03, 0x00, 0xf9, 0xdd, 0xd6, 0xfc, 0xff, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfd, 0xfa, 0xfe, 0xfd, 0xf8, 0xfb, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xf7, 0xe0, 0xe0, 0xf3, 0xc8, 0xbc, 0xee, 0x96, 0x86, 0xe3, 0x50, 0x36, 0xd9, 0x02, 0x03, 0xd2, 0x01, 0x00, 0xdb, 0x19, 0x0b, 0xf0, 0xa2, 0x99, 0xff, 0xff, 0xfe, 0xfc, 0xfe, 0xf8, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfc, 0xf9, 0xfd, 0xfe, 0xfe, 0xfd, 0xff, 0xfe, 0xe2, 0x3a, 0x27, 0xd4, 0x04, 0x00, 0xe6, 0x6f, 0x58, 0xfc, 0xff, 0xfd, 0xfb, 0xfe, 0xfd, 0xfc, 0xfe, 0xfc, 0xfa, 0xfd, 0xfc, 0xff, 0xfe, 0xfd, 0xfb, 0xf5, 0xf7, 0xdf, 0x24, 0x0a, 0xd6, 0x02, 0x00, 0xf2, 0xc4, 0xc0, 0xfd, 0xff, 0xff, 0xe8, 0x5e, 0x4d, 0xdb, 0x05, 0x00, 0xdf, 0x26, 0x0b, 0xdb, 0x25, 0x09, 0xde, 0x36, 0x1b, 0xe2, 0x32, 0x16, 0xd9, 0x0d, 0x00, 0xe5, 0x51, 0x3d, 0xff, 0xfe, 0xff, 0xec, 0x8e, 0x84, 0xd3, 0x02, 0x00, 0xec, 0x92, 0x81, 0xff, 0xff, 0xfe, 0xfb, 0xfe, 0xfc, 0xfb, 0xfd, 0xfb, 0xfb, 0xfe, 0xfd, 0xfb, 0xfe, 0xfd, 0xfe, 0xfe, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xf5, 0xbd, 0xb6, 0xe4, 0x4c, 0x39, 0xd3, 0x01, 0x00, 0xda, 0x00, 0x00, 0xe9, 0x83, 0x71, 0xff, 0xff, 0xfc, 0xfb, 0xff, 0xff, 0xfc, 0xfc, 0xfb, 0xf8, 0xfe, 0xfc, 0xfd, 0xfc, 0xfb, 0xff, 0xff, 0xff, 0xf6, 0xd4, 0xca, 0xd7, 0x06, 0x00, 0xdb, 0x04, 0x01, 0xf9, 0xd1, 0xcd, 0xff, 0xff, 0xfe, 0xfc, 0xfc, 0xfb, 0xfc, 0xfe, 0xfc, 0xfb, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xee, 0x82, 0x6d, 0xd6, 0x00, 0x00, 0xe7, 0x70, 0x5b, 0xf9, 0xef, 0xe9, 0xe5, 0x43, 0x2e, 0xdb, 0x11, 0x00, 0xe1, 0x29, 0x09, 0xdd, 0x25, 0x05, 0xe1, 0x36, 0x1e, 0xe3, 0x31, 0x16, 0xd8, 0x11, 0x00, 0xe3, 0x40, 0x2b, 0xfd, 0xff, 0xfc, 0xee, 0xa3, 0x99, 0xd5, 0x01, 0x00, 0xdd, 0x22, 0x13, 0xfd, 0xf3, 0xed, 0xfd, 0xfe, 0xfe, 0xfb, 0xfd, 0xfb, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfb, 0xfd, 0xfd, 0xfd, 0xfa, 0xfe, 0xf7, 0xfb, 0xfe, 0xfd, 0xfd, 0xfc, 0xfc, 0xfa, 0xff, 0xfd, 0xfc, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xec, 0x85, 0x7a, 0xda, 0x00, 0x00, 0xd4, 0x02, 0x00, 0xe9, 0x85, 0x73, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfb, 0xfd, 0xfb, 0xfb, 0xfe, 0xfc, 0xfd, 0xfe, 0xff, 0xfe, 0xfe, 0xfd, 0xe6, 0x70, 0x56, 0xd4, 0x02, 0x00, 0xe3, 0x5e, 0x45, 0xfd, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xfb, 0xfd, 0xfb, 0xfb, 0xff, 0xfa, 0xff, 0xff, 0xfe, 0xf6, 0xd3, 0xcf, 0xdd, 0x0e, 0x00, 0xdd, 0x23, 0x04, 0xe2, 0x2e, 0x0f, 0xde, 0x20, 0x02, 0xde, 0x28, 0x0a, 0xdd, 0x29, 0x0a, 0xdf, 0x25, 0x06, 0xdd, 0x37, 0x1b, 0xdd, 0x31, 0x14, 0xde, 0x1a, 0x00, 0xdf, 0x2d, 0x10, 0xfe, 0xfe, 0xfd, 0xf3, 0xcb, 0xc7, 0xdc, 0x0b, 0x00, 0xd7, 0x00, 0x00, 0xe4, 0x47, 0x38, 0xfe, 0xfd, 0xfc, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xf8, 0xfb, 0xfc, 0xfc, 0xfd, 0xfb, 0xfd, 0xfc, 0xfc, 0xfc, 0xfe, 0xfe, 0xfe, 0xfb, 0xfe, 0xfe, 0xff, 0xfe, 0xfd, 0xfb, 0xfb, 0xfa, 0xff, 0xff, 0xfc, 0xfe, 0xfe, 0xfe, 0xf1, 0x9e, 0x8f, 0xd4, 0x01, 0x00, 0xd5, 0x00, 0x01, 0xef, 0xb0, 0xa2, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0xfe, 0xf8, 0xfb, 0xfb, 0xfc, 0xff, 0xff, 0xfe, 0xf9, 0xe0, 0xdf, 0xdf, 0x0e, 0x01, 0xda, 0x09, 0x00, 0xf6, 0xdc, 0xd9, 0xfe, 0xfc, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfd, 0xf9, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xe2, 0x47, 0x2a, 0xdb, 0x05, 0x00, 0xda, 0x1b, 0x04, 0xdf, 0x26, 0x09, 0xdf, 0x29, 0x0d, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x37, 0x1b, 0xe2, 0x31, 0x17, 0xdf, 0x20, 0x07, 0xdb, 0x11, 0x00, 0xf7, 0xe4, 0xdf, 0xfc, 0xfc, 0xfb, 0xdf, 0x1f, 0x02, 0xdd, 0x1f, 0x01, 0xd8, 0x01, 0x00, 0xe1, 0x3d, 0x2b, 0xf5, 0xc4, 0xbf, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfc, 0xfe, 0xf8, 0xfc, 0xfe, 0xfa, 0xfa, 0xfb, 0xfb, 0xfe, 0xfe, 0xfe, 0xfb, 0xfb, 0xfb, 0xfa, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xed, 0x87, 0x72, 0xd3, 0x02, 0x00, 0xdf, 0x1a, 0x01, 0xfa, 0xea, 0xec, 0xfc, 0xfe, 0xfc, 0xfe, 0xfd, 0xfd, 0xfa, 0xfd, 0xfb, 0xfe, 0xfe, 0xfd, 0xfd, 0xff, 0xff, 0xe5, 0x62, 0x48, 0xd3, 0x03, 0x00, 0xe9, 0x85, 0x73, 0xff, 0xff, 0xff, 0xfa, 0xfd, 0xfc, 0xfb, 0xfc, 0xfd, 0xfb, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xeb, 0x82, 0x76, 0xd8, 0x00, 0x00, 0xe2, 0x23, 0x0c, 0xde, 0x28, 0x0a, 0xdc, 0x26, 0x0a, 0xe1, 0x28, 0x0d, 0xdf, 0x23, 0x05, 0xe0, 0x35, 0x1b, 0xdf, 0x2f, 0x13, 0xe0, 0x28, 0x06, 0xd8, 0x01, 0x00, 0xee, 0xa4, 0x93, 0xff, 0xfe, 0xfb, 0xe2, 0x47, 0x2d, 0xde, 0x10, 0x00, 0xde, 0x2a, 0x0d, 0xde, 0x08, 0x00, 0xd7, 0x0c, 0x00, 0xe7, 0x54, 0x44, 0xed, 0x85, 0x7d, 0xef, 0xa5, 0x9c, 0xf4, 0xbc, 0xb9, 0xfe, 0xf8, 0xfb, 0xfd, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xf9, 0xfc, 0xfb, 0xfe, 0xfe, 0xfe, 0xfb, 0xfc, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xe2, 0x41, 0x28, 0xd2, 0x01, 0x01, 0xef, 0x7b, 0x66, 0xfd, 0xfe, 0xfe, 0xfa, 0xfd, 0xfc, 0xfc, 0xfe, 0xfc, 0xfe, 0xfd, 0xfc, 0xfd, 0xff, 0xfd, 0xf2, 0xb2, 0xa8, 0xd5, 0x01, 0x00, 0xe4, 0x42, 0x23, 0xfb, 0xfe, 0xfd, 0xfe, 0xff, 0xff, 0xfe, 0xfd, 0xf6, 0xfd, 0xfd, 0xff, 0xfd, 0xff, 0xfb, 0xf3, 0xb9, 0xaf, 0xda, 0x08, 0x00, 0xdc, 0x28, 0x09, 0xe0, 0x28, 0x08, 0xdd, 0x27, 0x09, 0xdd, 0x27, 0x09, 0xe0, 0x24, 0x06, 0xde, 0x36, 0x1b, 0xdd, 0x30, 0x19, 0xe0, 0x27, 0x0a, 0xdd, 0x07, 0x01, 0xe1, 0x5e, 0x46, 0xff, 0xfe, 0xff, 0xeb, 0x8c, 0x7f, 0xd6, 0x00, 0x00, 0xdd, 0x2a, 0x06, 0xde, 0x28, 0x0e, 0xde, 0x22, 0x06, 0xd8, 0x09, 0x00, 0xd8, 0x02, 0x00, 0xdb, 0x00, 0x00, 0xd9, 0x08, 0x00, 0xde, 0x2a, 0x1d, 0xf0, 0x98, 0x8e, 0xfd, 0xff, 0xfd, 0xfe, 0xff, 0xfe, 0xfa, 0xfd, 0xfb, 0xfc, 0xfc, 0xf9, 0xfd, 0xfd, 0xfd, 0xfb, 0xfe, 0xfc, 0xf6, 0xc4, 0xbd, 0xd5, 0x01, 0x00, 0xdc, 0x19, 0x00, 0xf8, 0xee, 0xe8, 0xfe, 0xff, 0xfe, 0xff, 0xfc, 0xfd, 0xfc, 0xfc, 0xfc, 0xfd, 0xff, 0xfd, 0xfb, 0xee, 0xef, 0xdd, 0x1b, 0x09, 0xdd, 0x0a, 0x00, 0xf7, 0xda, 0xd6, 0xfd, 0xff, 0xf9, 0xfe, 0xfd, 0xfd, 0xfb, 0xfd, 0xfb, 0xfc, 0xfe, 0xf8, 0xf6, 0xe1, 0xe0, 0xdc, 0x22, 0x05, 0xd8, 0x1c, 0x00, 0xe0, 0x26, 0x07, 0xde, 0x28, 0x0a, 0xdd, 0x27, 0x09, 0xde, 0x24, 0x05, 0xdf, 0x37, 0x1c, 0xe1, 0x31, 0x15, 0xe0, 0x25, 0x0d, 0xdc, 0x1c, 0x00, 0xde, 0x1f, 0x04, 0xfc, 0xfd, 0xf7, 0xfc, 0xeb, 0xe9, 0xdf, 0x10, 0x00, 0xdd, 0x1f, 0x01, 0xe1, 0x28, 0x0b, 0xe1, 0x28, 0x0d, 0xda, 0x1a, 0x00, 0xdb, 0x02, 0x02, 0xd6, 0x02, 0x00, 0xd9, 0x0a, 0x00, 0xdb, 0x0c, 0x00, 0xd7, 0x00, 0x00, 0xe9, 0x78, 0x62, 0xfe, 0xfe, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfb, 0xfc, 0xfe, 0xfc, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xe3, 0x45, 0x2b, 0xd6, 0x01, 0x02, 0xf2, 0x9f, 0x93, 0xfe, 0xff, 0xff, 0xfa, 0xfb, 0xfb, 0xfd, 0xfe, 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xff, 0xfe, 0xe4, 0x53, 0x36, 0xd5, 0x00, 0x01, 0xef, 0xac, 0x9f, 0xfd, 0xfe, 0xfe, 0xf9, 0xfc, 0xfb, 0xfb, 0xfe, 0xfd, 0xff, 0xfe, 0xfe, 0xfc, 0xff, 0xfe, 0xe6, 0x47, 0x2f, 0xdf, 0x0f, 0x00, 0xdf, 0x26, 0x0b, 0xdf, 0x26, 0x0b, 0xe1, 0x28, 0x0d, 0xdd, 0x25, 0x05, 0xe0, 0x35, 0x1b, 0xe1, 0x31, 0x13, 0xe1, 0x27, 0x0a, 0xe0, 0x27, 0x0a, 0xd7, 0x01, 0x00, 0xef, 0x9a, 0x8f, 0xfb, 0xff, 0xff, 0xe4, 0x5b, 0x42, 0xd6, 0x02, 0x00, 0xde, 0x2d, 0x0b, 0xd6, 0x0e, 0x00, 0xdc, 0x26, 0x0a, 0xeb, 0x81, 0x6d, 0xef, 0x92, 0x86, 0xe4, 0x5b, 0x43, 0xdc, 0x0d, 0x01, 0xdd, 0x18, 0x01, 0xd8, 0x01, 0x02, 0xf3, 0xa7, 0xa1, 0xfe, 0xff, 0xfe, 0xf8, 0xfe, 0xfc, 0xfb, 0xfb, 0xfa, 0xfc, 0xff, 0xfd, 0xfd, 0xfe, 0xfe, 0xef, 0x8e, 0x7f, 0xd3, 0x00, 0x00, 0xe8, 0x67, 0x51, 0xfe, 0xfe, 0xfe, 0xfa, 0xff, 0xfd, 0xfc, 0xfc, 0xfd, 0xfa, 0xff, 0xff, 0xfd, 0xff, 0xf9, 0xec, 0x7b, 0x6c, 0xd5, 0x01, 0x00, 0xea, 0x86, 0x74, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xfb, 0xfb, 0xfc, 0xff, 0xfb, 0xfd, 0xfd, 0xfd, 0xe4, 0x4b, 0x33, 0xdc, 0x0c, 0x00, 0xde, 0x2a, 0x0d, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x26, 0x06, 0xe1, 0x36, 0x1c, 0xe1, 0x30, 0x16, 0xdd, 0x27, 0x0d, 0xdd, 0x29, 0x0a, 0xdb, 0x0f, 0x00, 0xde, 0x2e, 0x12, 0xff, 0xff, 0xfe, 0xfb, 0xdf, 0xd8, 0xd9, 0x09, 0x01, 0xdb, 0x0f, 0x00, 0xe1, 0x30, 0x1a, 0xfd, 0xdf, 0xe1, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0xfd, 0xfd, 0xfc, 0xec, 0x88, 0x7a, 0xd8, 0x08, 0x00, 0xd9, 0x08, 0x00, 0xe0, 0x38, 0x1d, 0xfb, 0xfc, 0xfc, 0xfe, 0xff, 0xfc, 0xfd, 0xfb, 0xff, 0xfc, 0xfe, 0xfc, 0xfe, 0xfe, 0xff, 0xf1, 0xc0, 0xb3, 0xd6, 0x01, 0x02, 0xe4, 0x3f, 0x25, 0xfd, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, 0xfe, 0xfe, 0xee, 0x96, 0x8c, 0xd4, 0x00, 0x00, 0xe9, 0x6a, 0x5b, 0xff, 0xff, 0xfe, 0xf8, 0xff, 0xfc, 0xfd, 0xfd, 0xfa, 0xfe, 0xff, 0xff, 0xef, 0xb2, 0xa9, 0xda, 0x08, 0x00, 0xdf, 0x23, 0x09, 0xdc, 0x28, 0x09, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x0b, 0xdf, 0x25, 0x06, 0xe1, 0x36, 0x1c, 0xdd, 0x30, 0x19, 0xde, 0x29, 0x08, 0xdf, 0x26, 0x09, 0xdf, 0x25, 0x08, 0xd8, 0x01, 0x00, 0xed, 0x9d, 0x8e, 0xfe, 0xfe, 0xfe, 0xe8, 0x77, 0x60, 0xd4, 0x00, 0x00, 0xf0, 0xb5, 0xa9, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfe, 0xfe, 0xfb, 0xff, 0xff, 0xff, 0xe3, 0x42, 0x29, 0xd8, 0x0b, 0x02, 0xdf, 0x11, 0x00, 0xf6, 0xcc, 0xc9, 0xfa, 0xff, 0xfd, 0xfc, 0xfd, 0xfe, 0xfc, 0xfc, 0xfb, 0xfe, 0xff, 0xfc, 0xf5, 0xd6, 0xd3, 0xdc, 0x0b, 0x00, 0xe1, 0x26, 0x0e, 0xfe, 0xed, 0xeb, 0xfe, 0xff, 0xff, 0xfb, 0xfc, 0xfd, 0xfa, 0xfe, 0xf9, 0xff, 0xff, 0xff, 0xf1, 0xa6, 0x9d, 0xd4, 0x02, 0x00, 0xe7, 0x65, 0x54, 0xfc, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0xfd, 0xff, 0xfd, 0xfd, 0xed, 0xef, 0xdb, 0x1b, 0x06, 0xdb, 0x11, 0x00, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe1, 0x31, 0x13, 0xdf, 0x26, 0x0b, 0xdd, 0x2a, 0x06, 0xde, 0x28, 0x0e, 0xdc, 0x18, 0x04, 0xdb, 0x16, 0x00, 0xfa, 0xf1, 0xee, 0xff, 0xff, 0xff, 0xe1, 0x20, 0x0b, 0xf4, 0xbd, 0xb0, 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xf8, 0xfa, 0xfd, 0xfb, 0xfd, 0xfe, 0xff, 0xfa, 0xff, 0xfe, 0xea, 0x73, 0x66, 0xd7, 0x01, 0x00, 0xd8, 0x09, 0x00, 0xf2, 0xb5, 0xac, 0xfd, 0xff, 0xfb, 0xfd, 0xfa, 0xfa, 0xfc, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0xf8, 0xe4, 0xdf, 0xd9, 0x1b, 0x00, 0xdd, 0x1b, 0x00, 0xf7, 0xe7, 0xdf, 0xfe, 0xfe, 0xfe, 0xfd, 0xfe, 0xfe, 0xff, 0xfe, 0xfb, 0xff, 0xfe, 0xfe, 0xef, 0xae, 0xa1, 0xd6, 0x02, 0x00, 0xe5, 0x54, 0x3b, 0xfe, 0xe9, 0xe8, 0xfb, 0xef, 0xed, 0xfa, 0xe6, 0xe7, 0xe2, 0x4e, 0x32, 0xd6, 0x00, 0x00, 0xdf, 0x2b, 0x0a, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe0, 0x31, 0x15, 0xe1, 0x26, 0x0c, 0xdc, 0x27, 0x0d, 0xde, 0x25, 0x0a, 0xdd, 0x2c, 0x0c, 0xd7, 0x01, 0x00, 0xe4, 0x52, 0x3d, 0xfe, 0xfe, 0xff, 0xf5, 0xc7, 0xc1, 0xef, 0xa2, 0x94, 0xfe, 0xff, 0xfe, 0xfb, 0xfc, 0xfd, 0xfa, 0xfd, 0xfb, 0xff, 0xfe, 0xff, 0xfb, 0xfe, 0xfd, 0xe5, 0x4d, 0x31, 0xdb, 0x0a, 0x00, 0xdd, 0x0e, 0x00, 0xf5, 0xc3, 0xbc, 0xff, 0xfe, 0xfe, 0xfe, 0xfc, 0xfe, 0xfc, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xdb, 0xd1, 0xde, 0x0f, 0x01, 0xdd, 0x24, 0x09, 0xfa, 0xeb, 0xea, 0xfd, 0xff, 0xff, 0xfb, 0xfc, 0xfc, 0xfa, 0xfd, 0xfc, 0xfd, 0xff, 0xfd, 0xf1, 0xa7, 0xa0, 0xdd, 0x05, 0x00, 0xde, 0x24, 0x03, 0xe0, 0x2b, 0x11, 0xde, 0x33, 0x17, 0xdc, 0x21, 0x07, 0xd9, 0x0a, 0x00, 0xe2, 0x2d, 0x0a, 0xdd, 0x27, 0x0b, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe2, 0x30, 0x16, 0xdd, 0x27, 0x0b, 0xde, 0x28, 0x0a, 0xe0, 0x27, 0x0c, 0xdd, 0x27, 0x0d, 0xdf, 0x27, 0x07, 0xdb, 0x00, 0x00, 0xed, 0x87, 0x74, 0xff, 0xff, 0xff, 0xf7, 0xe1, 0xdf, 0xf6, 0xdc, 0xd7, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0xfd, 0xfd, 0xf0, 0x9a, 0x94, 0xdb, 0x0a, 0x01, 0xd7, 0x0f, 0x01, 0xe0, 0x2b, 0x11, 0xfb, 0xf4, 0xf5, 0xfe, 0xff, 0xfc, 0xfb, 0xfc, 0xfc, 0xfb, 0xfd, 0xf9, 0xfd, 0xff, 0xfd, 0xf6, 0xc6, 0xbe, 0xd6, 0x00, 0x00, 0xe5, 0x3a, 0x20, 0xfb, 0xfa, 0xf7, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfb, 0xfd, 0xfb, 0xff, 0xff, 0xfc, 0xed, 0x9d, 0x8e, 0xda, 0x02, 0x00, 0xdd, 0x22, 0x0a, 0xda, 0x1a, 0x00, 0xdb, 0x0c, 0x00, 0xda, 0x09, 0x00, 0xde, 0x1f, 0x00, 0xde, 0x1b, 0x00, 0xde, 0x2a, 0x0d, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe0, 0x30, 0x14, 0xde, 0x2a, 0x0d, 0xdc, 0x26, 0x0a, 0xde, 0x29, 0x08, 0xe1, 0x27, 0x08, 0xe0, 0x27, 0x0a, 0xdf, 0x1f, 0x04, 0xd9, 0x02, 0x03, 0xef, 0xa1, 0x96, 0xfe, 0xff, 0xff, 0xf7, 0xd1, 0xc8, 0xec, 0x8a, 0x80, 0xed, 0x87, 0x82, 0xea, 0x73, 0x5f, 0xdb, 0x11, 0x00, 0xda, 0x19, 0x03, 0xdb, 0x0c, 0x00, 0xec, 0x99, 0x8d, 0xfd, 0xff, 0xff, 0xf8, 0xff, 0xfa, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xfc, 0xfd, 0xfd, 0xf0, 0x98, 0x8e, 0xd6, 0x01, 0x05, 0xe2, 0x5e, 0x43, 0xfe, 0xff, 0xfc, 0xfb, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfe, 0xfc, 0xff, 0xfe, 0xff, 0xe9, 0x81, 0x72, 0xd3, 0x01, 0x00, 0xe0, 0x2c, 0x0f, 0xdb, 0x11, 0x00, 0xd8, 0x01, 0x00, 0xf0, 0xa5, 0x9c, 0xf6, 0xed, 0xe8, 0xe1, 0x34, 0x1d, 0xdb, 0x18, 0x00, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe0, 0x31, 0x16, 0xdd, 0x29, 0x0c, 0xda, 0x28, 0x0b, 0xe0, 0x27, 0x0c, 0xe0, 0x25, 0x0e, 0xd9, 0x28, 0x08, 0xde, 0x2d, 0x0d, 0xde, 0x17, 0x01, 0xd8, 0x00, 0x01, 0xf0, 0x98, 0x90, 0xfd, 0xff, 0xfc, 0xf1, 0xba, 0xaf, 0xdd, 0x16, 0x02, 0xd4, 0x00, 0x00, 0xdd, 0x16, 0x02, 0xda, 0x1e, 0x02, 0xe1, 0x27, 0x14, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfc, 0xff, 0xfd, 0xfa, 0xfd, 0xfd, 0xfe, 0xff, 0xfa, 0xfd, 0xfd, 0xfe, 0xe4, 0x57, 0x3b, 0xd3, 0x00, 0x00, 0xf4, 0xb9, 0xaf, 0xfd, 0xfe, 0xff, 0xfc, 0xff, 0xf9, 0xff, 0xfe, 0xfe, 0xfc, 0xfe, 0xfc, 0xfe, 0xff, 0xff, 0xe3, 0x5c, 0x43, 0xdc, 0x04, 0x01, 0xd3, 0x00, 0x00, 0xdc, 0x17, 0x02, 0xf4, 0xb0, 0xa7, 0xff, 0xff, 0xfe, 0xf7, 0xe3, 0xdc, 0xe1, 0x2a, 0x1e, 0xdd, 0x17, 0x06, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xe2, 0x30, 0x13, 0xdd, 0x24, 0x07, 0xe1, 0x28, 0x0f, 0xe1, 0x27, 0x0a, 0xde, 0x28, 0x0e, 0xdd, 0x27, 0x09, 0xdf, 0x26, 0x0b, 0xdf, 0x2b, 0x0e, 0xdd, 0x1a, 0x00, 0xda, 0x00, 0x00, 0xeb, 0x78, 0x6d, 0xfd, 0xff, 0xff, 0xfe, 0xfd, 0xfa, 0xe7, 0x79, 0x71, 0xda, 0x0e, 0x00, 0xda, 0x00, 0x00, 0xd7, 0x0d, 0x00, 0xe8, 0x58, 0x49, 0xf9, 0xf0, 0xef, 0xfd, 0xfe, 0xfe, 0xff, 0xfe, 0xfb, 0xfe, 0xff, 0xff, 0xfa, 0xd7, 0xd3, 0xda, 0x0a, 0x01, 0xdd, 0x0e, 0x02, 0xeb, 0x8a, 0x7d, 0xf7, 0xe7, 0xe3, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xf9, 0xd8, 0xd8, 0xe8, 0x7d, 0x6f, 0xd6, 0x02, 0x00, 0xd9, 0x00, 0x00, 0xe6, 0x59, 0x4f, 0xfd, 0xf4, 0xf1, 0xff, 0xff, 0xff, 0xef, 0xa3, 0x9d, 0xd9, 0x09, 0x00, 0xd9, 0x14, 0x00, 0xdf, 0x29, 0x0b, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xdf, 0x30, 0x15, 0xdf, 0x29, 0x0d, 0xdd, 0x27, 0x09, 0xdc, 0x26, 0x0a, 0xde, 0x29, 0x08, 0xe0, 0x27, 0x0a, 0xde, 0x28, 0x0a, 0xe0, 0x27, 0x0a, 0xde, 0x2a, 0x0d, 0xdd, 0x1f, 0x01, 0xd9, 0x00, 0x02, 0xe4, 0x47, 0x38, 0xfb, 0xe5, 0xe1, 0xff, 0xff, 0xfc, 0xf8, 0xe5, 0xe2, 0xe7, 0x61, 0x59, 0xda, 0x1b, 0x00, 0xd4, 0x00, 0x00, 0xdc, 0x12, 0x00, 0xea, 0x6b, 0x5c, 0xf0, 0xa5, 0x99, 0xf3, 0xc7, 0xc0, 0xe6, 0x57, 0x40, 0xda, 0x08, 0x01, 0xdd, 0x2a, 0x06, 0xdb, 0x0a, 0x00, 0xdf, 0x29, 0x0d, 0xe7, 0x66, 0x52, 0xdf, 0x40, 0x28, 0xd4, 0x01, 0x00, 0xdb, 0x00, 0x03, 0xe2, 0x49, 0x3b, 0xf6, 0xc5, 0xc0, 0xfc, 0xff, 0xfd, 0xfe, 0xff, 0xfe, 0xe7, 0x6a, 0x5f, 0xda, 0x00, 0x00, 0xde, 0x15, 0x00, 0xdc, 0x2b, 0x0b, 0xe0, 0x27, 0x0a, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe2, 0x35, 0x1c, 0xdf, 0x30, 0x14, 0xdd, 0x28, 0x0e, 0xdd, 0x27, 0x0b, 0xde, 0x28, 0x0c, 0xe0, 0x27, 0x0c, 0xe0, 0x27, 0x0c, 0xde, 0x28, 0x0c, 0xdd, 0x27, 0x09, 0xe0, 0x26, 0x09, 0xe1, 0x26, 0x0e, 0xde, 0x29, 0x08, 0xd7, 0x04, 0x00, 0xdf, 0x14, 0x00, 0xeb, 0x80, 0x72, 0xfe, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xfc, 0xf2, 0x99, 0x8d, 0xe4, 0x42, 0x2d, 0xd4, 0x07, 0x00, 0xda, 0x01, 0x01, 0xd4, 0x02, 0x00, 0xd9, 0x01, 0x00, 0xd9, 0x06, 0x00, 0xd8, 0x05, 0x00, 0xd8, 0x08, 0x00, 0xda, 0x00, 0x00, 0xd8, 0x02, 0x00, 0xdf, 0x2a, 0x11, 0xeb, 0x7e, 0x73, 0xfa, 0xe4, 0xe2, 0xff, 0xff, 0xfe, 0xfd, 0xfe, 0xfe, 0xf0, 0xa1, 0x96, 0xde, 0x27, 0x11, 0xd8, 0x00, 0x01, 0xe0, 0x21, 0x08, 0xdf, 0x29, 0x0d, 0xdd, 0x28, 0x04, 0xdd, 0x28, 0x0e, 0xdd, 0x27, 0x09, 0xe0, 0x28, 0x08, 0xdd, 0x27, 0x09, 0xe0, 0x27, 0x0a, 0xde, 0x24, 0x05, 0xe2, 0x36, 0x1a, 0xe1, 0x31, 0x15, 0xe0, 0x27, 0x0c, 0xdf, 0x26, 0x09, 0xdf, 0x26, 0x09, 0xdf, 0x26, 0x09, 0xdf, 0x24, 0x0a, 0xdf, 0x26, 0x09, 0xdf, 0x26, 0x09, 0xdf, 0x25, 0x0e, 0xda, 0x29, 0x09, 0xdc, 0x28, 0x0b, 0xdf, 0x29, 0x0b, 0xde, 0x1b, 0x00, 0xd8, 0x02, 0x00, 0xdd, 0x25, 0x14, 0xea, 0x83, 0x78, 0xf7, 0xe6, 0xe4, 0xff, 0xff, 0xff, 0xfc, 0xfd, 0xff, 0xfe, 0xfe, 0xfd, 0xf6, 0xe0, 0xdc, 0xf4, 0xb8, 0xb2, 0xf0, 0xa5, 0x99, 0xef, 0x9f, 0x90, 0xf2, 0xa2, 0x9a, 0xf0, 0xb9, 0xae, 0xf3, 0xd9, 0xd6, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfa, 0xf7, 0xf8, 0xee, 0x95, 0x89, 0xdf, 0x38, 0x22, 0xd7, 0x00, 0x01, 0xda, 0x0f, 0x00, 0xe0, 0x2a, 0x0c, 0xdd, 0x27, 0x09, 0xdd, 0x29, 0x0a, 0xdf, 0x27, 0x07, 0xdf, 0x26, 0x0d, 0xe0, 0x27, 0x0c, 0xe0, 0x27, 0x0a, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x0b, 0xde, 0x24, 0x05, 0xe1, 0x34, 0x1b, 0xe0, 0x30, 0x12, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x09, 0xde, 0x28, 0x0a, 0xdf, 0x29, 0x0b, 0xde, 0x28, 0x0c, 0xde, 0x28, 0x0a, 0xdf, 0x26, 0x09, 0xdd, 0x28, 0x07, 0xe0, 0x28, 0x08, 0xde, 0x28, 0x0a, 0xdc, 0x27, 0x0e, 0xe0, 0x24, 0x0a, 0xdd, 0x2b, 0x10, 0xdd, 0x13, 0x00, 0xd7, 0x00, 0x00, 0xde, 0x12, 0x00, 0xe0, 0x49, 0x32, 0xef, 0x88, 0x79, 0xf5, 0xc5, 0xbd, 0xfb, 0xf5, 0xf1, 0xfe, 0xff, 0xfe, 0xfa, 0xff, 0xfb, 0xfc, 0xff, 0xfd, 0xfe, 0xfe, 0xfe, 0xfd, 0xff, 0xfe, 0xfa, 0xfd, 0xfb, 0xf7, 0xd1, 0xca, 0xef, 0x94, 0x89, 0xe3, 0x5a, 0x44, 0xdb, 0x1f, 0x05, 0xda, 0x01, 0x01, 0xdf, 0x0f, 0x00, 0xdd, 0x28, 0x05, 0xdf, 0x26, 0x0b, 0xdf, 0x27, 0x05, 0xdd, 0x29, 0x0a, 0xe0, 0x26, 0x07, 0xde, 0x28, 0x0e, 0xe0, 0x27, 0x0a, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xdc, 0x28, 0x0b, 0xde, 0x28, 0x0c, 0xdf, 0x27, 0x07, 0xe0, 0x35, 0x1b, 0xe1, 0x31, 0x15, 0xdf, 0x29, 0x0d, 0xe0, 0x27, 0x0a, 0xdc, 0x28, 0x09, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x0b, 0xdd, 0x28, 0x07, 0xe0, 0x27, 0x0c, 0xe0, 0x26, 0x09, 0xe0, 0x26, 0x09, 0xe1, 0x28, 0x0b, 0xdd, 0x27, 0x09, 0xdf, 0x29, 0x0d, 0xe2, 0x2a, 0x0a, 0xe0, 0x21, 0x00, 0xdd, 0x0e, 0x02, 0xd6, 0x02, 0x00, 0xda, 0x07, 0x00, 0xde, 0x19, 0x00, 0xdf, 0x2e, 0x14, 0xe1, 0x38, 0x23, 0xe5, 0x42, 0x2a, 0xe5, 0x40, 0x28, 0xe4, 0x2f, 0x16, 0xdd, 0x1d, 0x02, 0xdb, 0x0c, 0x00, 0xd8, 0x00, 0x00, 0xda, 0x07, 0x00, 0xde, 0x1f, 0x00, 0xde, 0x28, 0x0e, 0xdc, 0x25, 0x0d, 0xde, 0x29, 0x08, 0xdc, 0x28, 0x0b, 0xe0, 0x27, 0x0a, 0xe0, 0x27, 0x0c, 0xdd, 0x27, 0x0b, 0xdf, 0x26, 0x09, 0xe1, 0x28, 0x0b, 0xe2, 0x27, 0x0d, 0xe0, 0x27, 0x0a, 0xde, 0x28, 0x0c, 0xdf, 0x26, 0x0b, 0xde, 0x26, 0x04, 0xe0, 0x34, 0x18, 0xd9, 0x27, 0x0c, 0xdd, 0x24, 0x0b, 0xe1, 0x26, 0x0c, 0xdf, 0x29, 0x0d, 0xe0, 0x27, 0x0e, 0xdf, 0x26, 0x0d, 0xe0, 0x27, 0x0e, 0xe0, 0x25, 0x0d, 0xde, 0x28, 0x0e, 0xde, 0x24, 0x0f, 0xde, 0x28, 0x0e, 0xdd, 0x29, 0x0a, 0xd9, 0x27, 0x0d, 0xe2, 0x25, 0x0e, 0xdc, 0x27, 0x0d, 0xdf, 0x24, 0x0c, 0xdc, 0x28, 0x0b, 0xe1, 0x25, 0x0b, 0xdf, 0x26, 0x0d, 0xe0, 0x27, 0x0c, 0xdf, 0x1f, 0x00, 0xdc, 0x18, 0x00, 0xdc, 0x15, 0x01, 0xda, 0x0f, 0x00, 0xde, 0x10, 0x00, 0xdd, 0x18, 0x00, 0xdc, 0x1e, 0x00, 0xde, 0x26, 0x06, 0xe0, 0x27, 0x0c, 0xdf, 0x25, 0x04, 0xdd, 0x26, 0x10, 0xdd, 0x29, 0x0c, 0xdd, 0x28, 0x07, 0xdf, 0x29, 0x0d, 0xe0, 0x27, 0x0e, 0xdf, 0x24, 0x0a, 0xdf, 0x26, 0x0d, 0xdd, 0x28, 0x0f, 0xde, 0x28, 0x0a, 0xdf, 0x25, 0x0e, 0xdd, 0x27, 0x0b, 0xdd, 0x27, 0x09, 0xde, 0x28, 0x0c, 0xe1, 0x26, 0x0c, 0xe0, 0x22, 0x04, 0xdf, 0x2d, 0x13, 0xe1, 0x3b, 0x1d, 0xe3, 0x34, 0x19, 0xe2, 0x33, 0x17, 0xde, 0x32, 0x15, 0xdf, 0x30, 0x14, 0xe0, 0x31, 0x16, 0xe1, 0x32, 0x17, 0xe0, 0x31, 0x15, 0xe2, 0x31, 0x17, 0xe2, 0x30, 0x16, 0xe2, 0x32, 0x14, 0xe0, 0x31, 0x15, 0xe0, 0x31, 0x16, 0xe2, 0x32, 0x14, 0xde, 0x32, 0x16, 0xe3, 0x33, 0x15, 0xdf, 0x33, 0x17, 0xe1, 0x32, 0x17, 0xe1, 0x32, 0x16, 0xe1, 0x32, 0x16, 0xdf, 0x30, 0x15, 0xe2, 0x33, 0x18, 0xe0, 0x31, 0x15, 0xe1, 0x30, 0x18, 0xde, 0x31, 0x18, 0xde, 0x32, 0x16, 0xe3, 0x33, 0x17, 0xde, 0x32, 0x16, 0xde, 0x31, 0x1a, 0xe4, 0x32, 0x18, 0xe1, 0x30, 0x16, 0xe2, 0x31, 0x1b, 0xe1, 0x2f, 0x1c, 0xde, 0x2f, 0x13, 0xe1, 0x32, 0x17, 0xe1, 0x32, 0x16, 0xe1, 0x32, 0x17, 0xe1, 0x32, 0x16, 0xe2, 0x31, 0x17, 0xe1, 0x32, 0x17, 0xe2, 0x31, 0x17, 0xe1, 0x31, 0x15, 0xe0, 0x31, 0x16, 0xe0, 0x31, 0x16, 0xdf, 0x2f, 0x11, 0xe1, 0x3f, 0x24 }; ================================================ FILE: esp_jpeg/test_apps/main/test_tjpgd_main.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running esp_jpeg component tests\n"); unity_run_menu(); } ================================================ FILE: esp_jpeg/test_apps/main/test_usb_camera_2_jpg.h ================================================ /* Raw data from Logitech C170 USB camera was reconstructed to usb_camera_2.jpg It was converted to RGB888 array with jpg_to_rgb888_hex.py */ // JPEG encoded frame 160x120, 1384 bytes, has broken 0xFFFF marker extern const unsigned char camera_2_jpg[] asm("_binary_usb_camera_2_jpg_start"); extern char _binary_usb_camera_2_jpg_start; extern char _binary_usb_camera_2_jpg_end; // Must be defined as macro because extern variables are not known at compile time (but at link time) #define camera_2_jpg_len (&_binary_usb_camera_2_jpg_end - &_binary_usb_camera_2_jpg_start) ================================================ FILE: esp_jpeg/test_apps/main/test_usb_camera_2_rgb888.h ================================================ unsigned int usb_camera_2_rgb888[19200] = { 0x242422, 0x222220, 0x21221D, 0x252620, 0x31322A, 0x424437, 0x545648, 0x5F624F, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x232726, 0x242827, 0x242827, 0x252928, 0x262A29, 0x272B2A, 0x282C2B, 0x282C2B, 0x363B35, 0x363B35, 0x363B35, 0x383D37, 0x3D423C, 0x434842, 0x4A4F49, 0x4E534D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x4F544E, 0x4B504A, 0x444943, 0x3B403A, 0x313630, 0x282D27, 0x212620, 0x1F211C, 0x252722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x1D1E19, 0x1D1E19, 0x1D1E19, 0x1D1E19, 0x1D1E19, 0x1D1E19, 0x1D1E19, 0x1D1D1B, 0x1F2420, 0x1F2322, 0x1F2322, 0x1F2322, 0x1F2322, 0x1F2322, 0x1F2322, 0x1F2322, 0x1D2120, 0x1D2120, 0x1D2120, 0x1D2120, 0x1D2120, 0x1D2120, 0x1D2120, 0x1D2120, 0x232726, 0x232726, 0x232726, 0x232726, 0x232726, 0x232726, 0x232726, 0x232726, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x2A2A28, 0x262624, 0x22231E, 0x23241E, 0x2B2C24, 0x393B2E, 0x47493B, 0x515441, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x222625, 0x232726, 0x252928, 0x282C2B, 0x2B2F2E, 0x2E3231, 0x303433, 0x323635, 0x393E38, 0x393E38, 0x3A3F39, 0x3D423C, 0x424741, 0x484D47, 0x4D524C, 0x50554F, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x494E48, 0x474C46, 0x414640, 0x3B403A, 0x343933, 0x2D322C, 0x282D27, 0x272924, 0x2B2D28, 0x2C2D28, 0x2C2D28, 0x2C2D28, 0x2C2D28, 0x2C2D28, 0x2C2D28, 0x2C2D28, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x2B2C27, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x292A25, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272823, 0x272725, 0x282D29, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x242827, 0x242827, 0x242827, 0x242827, 0x242827, 0x242827, 0x242827, 0x242827, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x333331, 0x2F2F2D, 0x2A2B26, 0x282923, 0x2E2F27, 0x3A3C2F, 0x47493B, 0x505340, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x202423, 0x222625, 0x262A29, 0x2C302F, 0x323635, 0x383C3B, 0x3C403F, 0x3F4342, 0x3B403A, 0x3D423C, 0x414640, 0x454A44, 0x4A4F49, 0x4E534D, 0x515650, 0x535852, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x40453F, 0x3E433D, 0x3B403A, 0x383D37, 0x353A34, 0x333832, 0x343631, 0x343631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x2F302B, 0x2F302B, 0x2F302B, 0x2F302B, 0x2F302B, 0x2F302B, 0x2F302B, 0x2F302B, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30302E, 0x2F3430, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x262A29, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x31312F, 0x2D2D2B, 0x2A2B26, 0x2C2D27, 0x34352D, 0x434538, 0x525446, 0x5C5F4C, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x1E2221, 0x212524, 0x272B2A, 0x2F3332, 0x373B3A, 0x3F4342, 0x454948, 0x484C4B, 0x3C413B, 0x40453F, 0x464B45, 0x4D524C, 0x515650, 0x545953, 0x545953, 0x545953, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x3A3F39, 0x3B403A, 0x3B403A, 0x3C413B, 0x3C413B, 0x3D423C, 0x3D423C, 0x3F413C, 0x3E403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x343530, 0x343530, 0x343530, 0x343530, 0x343530, 0x343530, 0x343530, 0x343530, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x323230, 0x2F3430, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2D3130, 0x2D3130, 0x2D3130, 0x2D3130, 0x2D3130, 0x2D3130, 0x2D3130, 0x2D3130, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x1B1B19, 0x1A1A18, 0x1A1B16, 0x1F201A, 0x2C2D25, 0x3E4033, 0x515345, 0x5C5F4C, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x1E2221, 0x212524, 0x272B2A, 0x2F3332, 0x373B3A, 0x3F4342, 0x454948, 0x484C4B, 0x383D37, 0x3E433D, 0x484D47, 0x50554F, 0x555A54, 0x555A54, 0x525751, 0x50554F, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x4B504A, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x393E38, 0x3A3F39, 0x3C413B, 0x3E433D, 0x40453F, 0x424741, 0x434842, 0x464843, 0x434540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30302E, 0x2A2F2B, 0x2A2E2D, 0x2A2E2D, 0x2A2E2D, 0x2A2E2D, 0x2A2E2D, 0x2A2E2D, 0x2A2E2D, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x282C2B, 0x2C302F, 0x2C302F, 0x2C302F, 0x2C302F, 0x2C302F, 0x2C302F, 0x2C302F, 0x2C302F, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x181816, 0x161614, 0x151611, 0x191A14, 0x25261E, 0x36382B, 0x47493B, 0x525542, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x202423, 0x222625, 0x262A29, 0x2C302F, 0x323635, 0x383C3B, 0x3C403F, 0x3F4342, 0x313630, 0x393E38, 0x454A44, 0x50554F, 0x555A54, 0x535852, 0x4D524C, 0x494E48, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x484D47, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x3E433D, 0x3E433D, 0x3F443E, 0x414640, 0x424741, 0x434842, 0x444943, 0x474944, 0x444641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x323230, 0x292E2A, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x484846, 0x424240, 0x3B3C37, 0x383933, 0x3B3C34, 0x444639, 0x505244, 0x585B48, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x222625, 0x232726, 0x252928, 0x282C2B, 0x2B2F2E, 0x2E3231, 0x303433, 0x323635, 0x292E28, 0x323731, 0x414640, 0x4D524C, 0x525751, 0x4E534D, 0x464B45, 0x40453F, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x424741, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x454A44, 0x444943, 0x444943, 0x444943, 0x434842, 0x424741, 0x424741, 0x444641, 0x434540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x3C3D38, 0x3C3D38, 0x3C3D38, 0x3C3D38, 0x3C3D38, 0x3C3D38, 0x3C3D38, 0x3C3C3A, 0x313632, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x2F3332, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x313534, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x828280, 0x7A7A78, 0x6D6E69, 0x62635D, 0x5D5E56, 0x5F6154, 0x656759, 0x6A6D5A, 0x555942, 0x55593E, 0x555A3C, 0x555B39, 0x555B37, 0x555B35, 0x555C33, 0x595839, 0x655345, 0x624642, 0x543834, 0x492A27, 0x422320, 0x452220, 0x4C2424, 0x532728, 0x512325, 0x542225, 0x572025, 0x5A1F25, 0x5A1F25, 0x5C1D25, 0x5C1D25, 0x532226, 0x42332C, 0x484A3F, 0x5B5D52, 0x626459, 0x5E6055, 0x585A4F, 0x595B50, 0x5C5E53, 0x5B5D52, 0x57594E, 0x56584D, 0x5C5E53, 0x606257, 0x595B50, 0x46483D, 0x35362E, 0x212622, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x232726, 0x242827, 0x242827, 0x252928, 0x262A29, 0x272B2A, 0x282C2B, 0x282C2B, 0x232822, 0x2E332D, 0x3E433D, 0x4B504A, 0x4F544E, 0x4B504A, 0x424741, 0x3B403A, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x3E433D, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x414640, 0x4A4F49, 0x494E48, 0x474C46, 0x454A44, 0x434842, 0x414640, 0x40453F, 0x41433E, 0x41433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x454641, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464644, 0x3A3F3B, 0x3A3E3D, 0x3A3E3D, 0x3A3E3D, 0x3A3E3D, 0x3A3E3D, 0x3A3E3D, 0x3A3E3D, 0x383C3B, 0x383C3B, 0x383C3B, 0x383C3B, 0x383C3B, 0x383C3B, 0x383C3B, 0x383C3B, 0x323635, 0x323635, 0x323635, 0x323635, 0x323635, 0x323635, 0x323635, 0x323635, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x292D2C, 0x63B1BD, 0x58A5AF, 0x4D969F, 0x4B8E96, 0x508B8F, 0x518384, 0x4B7372, 0x46635E, 0x465A4F, 0x4F5748, 0x545543, 0x5B533E, 0x615138, 0x655035, 0x684E33, 0x684D38, 0x6F594E, 0x634E4B, 0x563E3C, 0x492F2E, 0x422625, 0x432324, 0x482627, 0x4F282B, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x4C2128, 0x463E3C, 0x424B46, 0x4E5752, 0x57605B, 0x5A635E, 0x57605B, 0x505954, 0x4B544F, 0x4F5853, 0x515A55, 0x545D58, 0x555E59, 0x535C57, 0x4D5651, 0x47504B, 0x454B47, 0x242621, 0x252621, 0x252621, 0x252621, 0x252621, 0x252621, 0x252621, 0x252621, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x353630, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x67B3C0, 0x5CA6B1, 0x5296A1, 0x508F98, 0x548B90, 0x558285, 0x4C7372, 0x48635E, 0x47584E, 0x4F5547, 0x555442, 0x5C513D, 0x614F37, 0x664E34, 0x6A4C32, 0x684C37, 0x664E44, 0x5B4643, 0x4F3735, 0x442A29, 0x3F2322, 0x422223, 0x492728, 0x50292C, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x4E2128, 0x3E3331, 0x3D3F3A, 0x4A4C47, 0x565853, 0x5C5E59, 0x5A5C57, 0x555752, 0x50524D, 0x535550, 0x555752, 0x585A55, 0x595B56, 0x575954, 0x51534E, 0x4B4D48, 0x474944, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x262722, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x363731, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x71B6C5, 0x64A8B5, 0x5999A5, 0x558F9A, 0x598B92, 0x588185, 0x506F71, 0x4B5F5D, 0x49544C, 0x515145, 0x575040, 0x5E4D3B, 0x644B35, 0x684A32, 0x6B4930, 0x694835, 0x594137, 0x4E3936, 0x452D2B, 0x3D2322, 0x3B1F1E, 0x412122, 0x4A2829, 0x522B2E, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x4F2026, 0x34211D, 0x362F27, 0x463F37, 0x544D45, 0x5D564E, 0x5E574F, 0x5B544C, 0x585149, 0x585149, 0x5A534B, 0x5D564E, 0x5E574F, 0x5C554D, 0x564F47, 0x504941, 0x4A453F, 0x292823, 0x282924, 0x282924, 0x282924, 0x282924, 0x282924, 0x282924, 0x282924, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x383933, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x7CB9CB, 0x70ABBB, 0x629AA9, 0x5F909E, 0x5F8A93, 0x5E7F86, 0x546D71, 0x4E5C5C, 0x4A4F49, 0x534C42, 0x5A4A3D, 0x614738, 0x654632, 0x69452F, 0x6D432D, 0x6A4332, 0x52372E, 0x46312E, 0x3E2624, 0x381E1D, 0x391D1C, 0x402021, 0x4B292A, 0x542D30, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x521F24, 0x30130F, 0x342017, 0x453128, 0x554138, 0x604C43, 0x634F46, 0x624E45, 0x5F4B42, 0x5E4A41, 0x604C43, 0x634F46, 0x655148, 0x624E45, 0x5D4940, 0x564239, 0x4E3F38, 0x2E2923, 0x2A2B26, 0x2A2B26, 0x2A2B26, 0x2A2B26, 0x2A2B26, 0x2A2B26, 0x2A2B26, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x3B3C36, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x373833, 0x373833, 0x373833, 0x373833, 0x373833, 0x373833, 0x373833, 0x373833, 0x87BED2, 0x7AAFC1, 0x6D9CAE, 0x6791A1, 0x658995, 0x627D86, 0x586970, 0x53585B, 0x4D4946, 0x55463F, 0x5B453A, 0x624235, 0x67402F, 0x6C3F2C, 0x6E3E2A, 0x6A3F2F, 0x53362E, 0x46312E, 0x3E2624, 0x381E1D, 0x391D1C, 0x402021, 0x4B292A, 0x542D30, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x551E23, 0x350C06, 0x3D190D, 0x4E2A1E, 0x5E3A2E, 0x694539, 0x6D493D, 0x6B473B, 0x694539, 0x674337, 0x6A463A, 0x6D493D, 0x6E4A3E, 0x6B473B, 0x664236, 0x603C30, 0x553A31, 0x342B24, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2D2E29, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x3E3F39, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x3A3B36, 0x93C1D8, 0x85B2C7, 0x769FB3, 0x6E91A4, 0x6D8997, 0x697B87, 0x5D6770, 0x56555A, 0x4E4443, 0x56413C, 0x5D3F37, 0x643C32, 0x6A3A2C, 0x6E3929, 0x713827, 0x6B3A2C, 0x5D3F37, 0x4E3936, 0x452D2B, 0x3D2322, 0x3B1F1E, 0x412122, 0x4A2829, 0x522B2E, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x561D23, 0x440F07, 0x4C1B0D, 0x5B2A1C, 0x6A392B, 0x724133, 0x744335, 0x714032, 0x6E3D2F, 0x6E3D2F, 0x703F31, 0x734234, 0x744335, 0x724133, 0x6C3B2D, 0x663527, 0x5A3429, 0x382D27, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x30312C, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x41423C, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x3D3E39, 0x9BC5DD, 0x8CB5CB, 0x7EA1B7, 0x7692A7, 0x738899, 0x6D7987, 0x60646F, 0x595259, 0x504041, 0x593D3A, 0x5E3B35, 0x653930, 0x6B372A, 0x6F3527, 0x733425, 0x6C362A, 0x6C4B44, 0x5B4643, 0x4F3735, 0x442A29, 0x3F2322, 0x422223, 0x492728, 0x50292C, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x591C21, 0x541A0F, 0x5D2212, 0x6A2F1F, 0x763B2B, 0x7B4030, 0x7A3F2F, 0x753A2A, 0x703525, 0x733828, 0x753A2A, 0x783D2D, 0x793E2E, 0x773C2C, 0x713626, 0x6B3020, 0x5C3023, 0x3D2E27, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x32332E, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x43443E, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0x3F403B, 0xA0C5DF, 0x92B6CE, 0x81A1B8, 0x7893A8, 0x76899A, 0x707988, 0x61636F, 0x5B5159, 0x513E40, 0x593B39, 0x603934, 0x67362F, 0x6D3429, 0x713326, 0x743224, 0x6D3429, 0x76554E, 0x634E4B, 0x563E3C, 0x492F2E, 0x422625, 0x432324, 0x482627, 0x4F282B, 0x4C2226, 0x4F2026, 0x512026, 0x531E26, 0x551D26, 0x561D26, 0x581C26, 0x591C21, 0x602016, 0x6A2818, 0x753323, 0x7F3D2D, 0x824030, 0x7F3D2D, 0x783626, 0x733121, 0x773525, 0x793727, 0x7C3A2A, 0x7D3B2B, 0x7B3929, 0x753323, 0x6F2D1D, 0x5F2E20, 0x3E2F28, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x33342F, 0x2E2F2A, 0x2D2E29, 0x2B2C27, 0x292A25, 0x272823, 0x252621, 0x23241F, 0x23221D, 0x232019, 0x2D2920, 0x3B372E, 0x48443B, 0x4F4B42, 0x4F4B42, 0x4B473E, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49463D, 0x45453B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x44463B, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404139, 0x44453F, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x96C1E3, 0x8DB6D6, 0x83A6C6, 0x7F9CBA, 0x7B93AD, 0x74839A, 0x64697D, 0x5B5464, 0x554550, 0x5D4249, 0x634044, 0x6A3E3F, 0x6F3C39, 0x743A36, 0x763934, 0x793832, 0x76312C, 0x752E2A, 0x722B27, 0x6E2723, 0x69221E, 0x641D19, 0x611A16, 0x5F1814, 0x67201C, 0x67201C, 0x67201C, 0x67201C, 0x67201C, 0x67201C, 0x67201C, 0x65221C, 0x5D2318, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x2E3025, 0x2B2D22, 0x27291E, 0x24261B, 0x25271C, 0x292B20, 0x2E3025, 0x333329, 0x38352C, 0x3A362D, 0x3D3930, 0x413D34, 0x454138, 0x48443B, 0x4B473E, 0x4D4940, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x96C3E4, 0x8CB7D7, 0x81A8C7, 0x7D9FBB, 0x7C96AF, 0x74849B, 0x636B7E, 0x595765, 0x50434D, 0x594046, 0x5E3E41, 0x653B3C, 0x6B3A36, 0x6F3833, 0x723731, 0x75362F, 0x73342F, 0x72312D, 0x6F2E2A, 0x6B2A26, 0x662521, 0x62211D, 0x5E1D19, 0x5C1B17, 0x601F1B, 0x601F1B, 0x601F1B, 0x601F1B, 0x601F1B, 0x601F1B, 0x601F1B, 0x5F201B, 0x5C2318, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x2D2F24, 0x2B2D22, 0x27291E, 0x25271C, 0x26281D, 0x2B2D22, 0x303227, 0x35352B, 0x3A372E, 0x3D3930, 0x3F3B32, 0x423E35, 0x464239, 0x49453C, 0x4B473E, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x94C7E6, 0x8ABBD9, 0x80ACC9, 0x7DA2BD, 0x7A9AB1, 0x72889D, 0x626F80, 0x585A67, 0x49424A, 0x523F43, 0x573D3E, 0x5E3B39, 0x643933, 0x683730, 0x6B362E, 0x6C352E, 0x6F3835, 0x6D3534, 0x6A3231, 0x662E2D, 0x612928, 0x5D2524, 0x592120, 0x571F1E, 0x551D1C, 0x551D1C, 0x551D1C, 0x551D1C, 0x551D1C, 0x551D1C, 0x551D1C, 0x551E1B, 0x5A241A, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x2D2F24, 0x2A2C21, 0x27291E, 0x26281D, 0x292B20, 0x2E3025, 0x35372C, 0x3A3A30, 0x3F3C33, 0x413D34, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4B473E, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x93CCE9, 0x88C0DB, 0x7EB1CC, 0x79A7BF, 0x789EB3, 0x708D9F, 0x5F7583, 0x555F69, 0x424348, 0x4A4041, 0x513D3C, 0x583B37, 0x5E3931, 0x62372E, 0x65362C, 0x65362E, 0x6A3E3B, 0x663C3D, 0x63393A, 0x5E3435, 0x592F30, 0x552B2C, 0x522829, 0x502627, 0x4A2021, 0x4A2021, 0x4A2021, 0x4A2021, 0x4A2021, 0x4A2021, 0x4A2021, 0x4D201D, 0x57251C, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x2C2E23, 0x2A2C21, 0x282A1F, 0x282A1F, 0x2C2E23, 0x33352A, 0x3A3C31, 0x404036, 0x46433A, 0x47433A, 0x47433A, 0x48443B, 0x49453C, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x90D2EC, 0x85C5DE, 0x7BB7CF, 0x76ADC2, 0x75A4B6, 0x6D92A2, 0x5D7985, 0x53646C, 0x40484B, 0x494544, 0x4E433F, 0x55413A, 0x5B3F34, 0x5F3D31, 0x623C2F, 0x613C33, 0x634442, 0x604347, 0x5C3F43, 0x583B3F, 0x53363A, 0x4F3236, 0x4C2F33, 0x4A2D31, 0x44272B, 0x44272B, 0x44272B, 0x44272B, 0x44272B, 0x44272B, 0x44272B, 0x492525, 0x55261E, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x2A2C21, 0x292B20, 0x292B20, 0x2A2C21, 0x303227, 0x383A2F, 0x414338, 0x47473D, 0x4C4940, 0x4D4940, 0x4C483F, 0x4C483F, 0x4B473E, 0x4A463D, 0x4A463D, 0x49453C, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x8ED6EE, 0x84CBE1, 0x79BBD1, 0x75B2C5, 0x74A9B9, 0x6C98A5, 0x5A7F88, 0x516A6F, 0x425252, 0x4A4F4B, 0x514C46, 0x584A41, 0x5C493B, 0x614738, 0x654536, 0x62463B, 0x5E4A49, 0x58494E, 0x55464B, 0x514247, 0x4C3D42, 0x48393E, 0x44353A, 0x423338, 0x403136, 0x403136, 0x403136, 0x403136, 0x403136, 0x403136, 0x403136, 0x472F2F, 0x532620, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x292B20, 0x292B20, 0x292B20, 0x2C2E23, 0x33352A, 0x3D3F34, 0x46483D, 0x4D4D43, 0x524F46, 0x534F46, 0x514D44, 0x4F4B42, 0x4D4940, 0x4B473E, 0x49453C, 0x48443B, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x8CDAF0, 0x82CFE3, 0x77C0D3, 0x73B6C7, 0x72ADBB, 0x6A9CA7, 0x58838A, 0x4F6E71, 0x465A59, 0x4E5752, 0x54554D, 0x5B5348, 0x615142, 0x65503F, 0x684E3D, 0x654F42, 0x594F50, 0x534E55, 0x504B52, 0x4C474E, 0x474249, 0x433E45, 0x3F3A41, 0x3D383F, 0x413C43, 0x413C43, 0x413C43, 0x413C43, 0x413C43, 0x413C43, 0x413C43, 0x4B383C, 0x502820, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x292B20, 0x282A1F, 0x2A2C21, 0x2E3025, 0x36382D, 0x404237, 0x4B4D42, 0x525248, 0x57544B, 0x57534A, 0x555148, 0x524E45, 0x4E4A41, 0x4B473E, 0x49453C, 0x48443B, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x8BDCF1, 0x82D0E4, 0x76C1D4, 0x73B8C8, 0x71AFBC, 0x699DA8, 0x58858B, 0x4E7072, 0x48615E, 0x515E57, 0x585B52, 0x5F594D, 0x635747, 0x675644, 0x6B5442, 0x675549, 0x575151, 0x505058, 0x4D4D55, 0x494951, 0x44444C, 0x404048, 0x3C3C44, 0x3B3B43, 0x42424A, 0x42424A, 0x42424A, 0x42424A, 0x42424A, 0x42424A, 0x42424A, 0x4B3F41, 0x502721, 0x5F291D, 0x673125, 0x6E382C, 0x733D31, 0x743E32, 0x733D31, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x713B2F, 0x673F35, 0x3D3125, 0x323429, 0x313328, 0x2F3126, 0x2C2E23, 0x2A2C21, 0x292B20, 0x282A1F, 0x282A1F, 0x282A1F, 0x2A2C21, 0x2E3025, 0x37392E, 0x424439, 0x4D4F44, 0x55554B, 0x5A574E, 0x59554C, 0x57534A, 0x534F46, 0x4F4B42, 0x4B473E, 0x49453C, 0x47433A, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x474539, 0x434337, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x424439, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x404237, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x3E4035, 0x5B9193, 0x578A8D, 0x508386, 0x4D7D81, 0x4D7D81, 0x568187, 0x60878E, 0x698B94, 0x496A73, 0x4A6671, 0x49616D, 0x465967, 0x405361, 0x3C4C5B, 0x384857, 0x354552, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x324A4C, 0x324A4C, 0x324A4C, 0x324A4C, 0x324A4C, 0x324A4C, 0x324A4C, 0x384848, 0x444643, 0x4D4844, 0x544D47, 0x595049, 0x5B5148, 0x5C4E43, 0x5A4A3D, 0x594637, 0x5B4432, 0x5E432E, 0x5F432D, 0x624229, 0x644227, 0x654226, 0x664124, 0x62422D, 0x49322C, 0x422F31, 0x3B2B2C, 0x362828, 0x332727, 0x332B29, 0x35302D, 0x353430, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x71A7A9, 0x6C9FA2, 0x619497, 0x59898D, 0x538387, 0x558086, 0x5A8188, 0x5F818A, 0x45666F, 0x47636E, 0x475F6B, 0x475A68, 0x435664, 0x425261, 0x40505F, 0x3E4E5B, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x2D4547, 0x2D4547, 0x2D4547, 0x2D4547, 0x2D4547, 0x2D4547, 0x2D4547, 0x314345, 0x3B3F3E, 0x464241, 0x4D4A45, 0x564E4B, 0x59524A, 0x5B5148, 0x5B4D42, 0x5C4A3C, 0x5B4839, 0x5E4735, 0x614631, 0x644530, 0x64462C, 0x67452C, 0x674529, 0x644531, 0x48312B, 0x402D2F, 0x39292A, 0x332525, 0x302424, 0x302826, 0x312C29, 0x31302C, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x80B6B8, 0x7BAEB1, 0x6FA2A5, 0x649498, 0x58888C, 0x547F85, 0x547B82, 0x557780, 0x496A73, 0x4B6772, 0x4B636F, 0x4B5E6C, 0x475A68, 0x465665, 0x445463, 0x42525F, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x253D3F, 0x253D3F, 0x253D3F, 0x253D3F, 0x253D3F, 0x253D3F, 0x253D3F, 0x293B3D, 0x2E383A, 0x393A3E, 0x424443, 0x4D4C4A, 0x56524F, 0x5B544E, 0x5C534C, 0x5E5148, 0x5F4F42, 0x614E3F, 0x634E3D, 0x664D39, 0x674C37, 0x684C36, 0x6A4C34, 0x674C3B, 0x45302B, 0x3F2C2E, 0x372728, 0x302222, 0x2C2020, 0x2B2321, 0x2B2623, 0x2B2A26, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x73A9AB, 0x70A3A6, 0x699C9F, 0x629296, 0x5A8A8E, 0x578288, 0x557C83, 0x567881, 0x5D7E87, 0x5C7883, 0x59717D, 0x536674, 0x4A5D6B, 0x435362, 0x3C4C5B, 0x394956, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x1F3739, 0x1F3739, 0x1F3739, 0x1F3739, 0x1F3739, 0x1F3739, 0x1F3739, 0x223539, 0x233036, 0x2C353C, 0x383F45, 0x454A4E, 0x4E5255, 0x565656, 0x5A5653, 0x5D5650, 0x5D544D, 0x60534A, 0x635346, 0x655244, 0x655241, 0x685141, 0x68523D, 0x685143, 0x473430, 0x412E30, 0x372728, 0x302222, 0x2A1E1E, 0x271F1D, 0x27221F, 0x262521, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x508688, 0x54878A, 0x56898C, 0x5A8A8E, 0x5A8A8E, 0x5D888E, 0x5F868D, 0x61838C, 0x75969F, 0x728E99, 0x69818D, 0x5C6F7D, 0x4C5F6D, 0x3E4E5D, 0x324251, 0x2C3C49, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x1C3436, 0x1C3436, 0x1C3436, 0x1C3436, 0x1C3436, 0x1C3436, 0x1C3436, 0x1E3338, 0x1C2E38, 0x253340, 0x303E47, 0x3E4852, 0x465157, 0x4D545A, 0x515558, 0x545454, 0x575654, 0x5A5551, 0x5D544D, 0x60534A, 0x615348, 0x635346, 0x645244, 0x645248, 0x4C3A38, 0x473436, 0x3C2C2D, 0x332525, 0x2C2020, 0x28201E, 0x27221F, 0x252420, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x376D6F, 0x407376, 0x4A7D80, 0x56868A, 0x5E8E92, 0x669197, 0x6A9198, 0x6D8F98, 0x7E9FA8, 0x7A96A1, 0x708894, 0x627583, 0x506371, 0x415160, 0x344453, 0x2D3D4A, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x1D3537, 0x1D3537, 0x1D3537, 0x1D3537, 0x1D3537, 0x1D3537, 0x1D3537, 0x1D343A, 0x1C323F, 0x213647, 0x2C3F4E, 0x364856, 0x3E4E5B, 0x435059, 0x454F58, 0x464D53, 0x4F5356, 0x525252, 0x535250, 0x56514D, 0x58514B, 0x5A4F49, 0x5A5046, 0x5D4E47, 0x544444, 0x513E40, 0x453536, 0x3A2C2C, 0x312525, 0x2C2422, 0x2A2522, 0x282723, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x386E70, 0x427578, 0x4F8285, 0x5E8E92, 0x67979B, 0x6E999F, 0x71989F, 0x73959E, 0x73949D, 0x718D98, 0x6C8490, 0x637684, 0x576A78, 0x4E5E6D, 0x455564, 0x40505D, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x21393B, 0x21393B, 0x21393B, 0x21393B, 0x21393B, 0x21393B, 0x21393B, 0x203940, 0x1E3847, 0x233C52, 0x2A4455, 0x33485B, 0x364C5A, 0x394B59, 0x394752, 0x3A444D, 0x454E55, 0x484D51, 0x4B4C4E, 0x4D4B4C, 0x4D4C48, 0x504B48, 0x504B45, 0x534947, 0x5F4F4F, 0x5A4749, 0x4E3E3F, 0x433535, 0x392D2D, 0x322A28, 0x2F2A27, 0x2C2B27, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x43797B, 0x4D8083, 0x5A8D90, 0x68989C, 0x6F9FA3, 0x739EA4, 0x749BA2, 0x74969F, 0x63848D, 0x64808B, 0x647C88, 0x627583, 0x5D707E, 0x5A6A79, 0x566675, 0x546471, 0x354A4F, 0x324A4C, 0x31494B, 0x2F4749, 0x2C4446, 0x2A4244, 0x284042, 0x273F41, 0x243C3E, 0x243C3E, 0x243C3E, 0x243C3E, 0x243C3E, 0x243C3E, 0x243C3E, 0x233C43, 0x223D4E, 0x244056, 0x2B465B, 0x30495D, 0x334A5C, 0x344756, 0x314351, 0x313F4A, 0x414B54, 0x434A50, 0x454A4E, 0x48494B, 0x494949, 0x4A4947, 0x4C4845, 0x4F4745, 0x645656, 0x614E50, 0x544445, 0x483A3A, 0x3D3131, 0x372F2D, 0x332E2B, 0x302F2B, 0x2D2F2A, 0x272E27, 0x202A22, 0x1E2B22, 0x233329, 0x2F4036, 0x3C5045, 0x4A574D, 0x505245, 0x534F43, 0x514D41, 0x4F4B3F, 0x4C483C, 0x4A463A, 0x494539, 0x484438, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x4A463A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444035, 0x423E33, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x4B6B7A, 0x496978, 0x496978, 0x4F6F7E, 0x5A7A89, 0x688897, 0x7393A2, 0x7999A8, 0x648493, 0x5C7C8B, 0x577786, 0x597988, 0x5E7E8D, 0x597988, 0x4B6B7A, 0x405C68, 0x3D545A, 0x3D5054, 0x394C50, 0x33464A, 0x2D4044, 0x273A3E, 0x23363A, 0x203337, 0x203337, 0x203337, 0x203337, 0x203337, 0x203337, 0x203337, 0x203337, 0x213337, 0x26353A, 0x28353B, 0x2A373D, 0x2C393F, 0x2E3B41, 0x313E44, 0x323F45, 0x334046, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3D484C, 0x3C4846, 0x3D4844, 0x3D4844, 0x3D4844, 0x3D4844, 0x3D4844, 0x3D4844, 0x3D4844, 0x2B3632, 0x27322E, 0x232E2A, 0x212C28, 0x26312D, 0x2F3A36, 0x3A4541, 0x444A46, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x454138, 0x454138, 0x454138, 0x454138, 0x454138, 0x454138, 0x454138, 0x474038, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x60808F, 0x557584, 0x496978, 0x446473, 0x486877, 0x50707F, 0x567685, 0x587887, 0x648493, 0x5D7D8C, 0x567685, 0x577786, 0x5A7A89, 0x547483, 0x446473, 0x395561, 0x3A5157, 0x3B4E52, 0x374A4E, 0x324549, 0x2C3F43, 0x273A3E, 0x23363A, 0x213438, 0x213438, 0x213438, 0x213438, 0x213438, 0x213438, 0x213438, 0x213438, 0x223438, 0x27363B, 0x29363C, 0x2B383E, 0x2D3A40, 0x303D43, 0x323F45, 0x334046, 0x344147, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x394448, 0x3B4745, 0x3C4743, 0x3C4743, 0x3C4743, 0x3C4743, 0x3C4743, 0x3C4743, 0x3C4743, 0x3B4642, 0x36413D, 0x2F3A36, 0x293430, 0x28332F, 0x2C3733, 0x323D39, 0x393F3B, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x464239, 0x464239, 0x464239, 0x464239, 0x464239, 0x464239, 0x464239, 0x484139, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x88A8B7, 0x7696A5, 0x628291, 0x567685, 0x537382, 0x50707F, 0x476776, 0x3E5E6D, 0x638392, 0x5B7B8A, 0x537382, 0x527281, 0x537382, 0x4B6B7A, 0x3A5A69, 0x2E4A56, 0x354C52, 0x374A4E, 0x33464A, 0x2F4246, 0x2B3E42, 0x26393D, 0x23363A, 0x213438, 0x23363A, 0x23363A, 0x23363A, 0x23363A, 0x23363A, 0x23363A, 0x23363A, 0x24363A, 0x29383D, 0x2B383E, 0x2D3A40, 0x2F3C42, 0x323F45, 0x344147, 0x354248, 0x364349, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x343F43, 0x394543, 0x3A4541, 0x3A4541, 0x3A4541, 0x3A4541, 0x3A4541, 0x3A4541, 0x3A4541, 0x495450, 0x444F4B, 0x3C4743, 0x343F3B, 0x2F3A36, 0x2D3834, 0x2E3935, 0x323834, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x48443B, 0x4A433B, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xB7D7E6, 0xA9C9D8, 0x9BBBCA, 0x97B7C6, 0x95B5C4, 0x84A4B3, 0x638392, 0x486877, 0x5D7D8C, 0x557584, 0x4C6C7B, 0x4A6A79, 0x4A6A79, 0x426271, 0x315160, 0x24404C, 0x2F464C, 0x314448, 0x2F4246, 0x2C3F43, 0x293C40, 0x26393D, 0x24373B, 0x223539, 0x26393D, 0x26393D, 0x26393D, 0x26393D, 0x26393D, 0x26393D, 0x26393D, 0x27393D, 0x2C3B40, 0x2E3B41, 0x303D43, 0x323F45, 0x344147, 0x364349, 0x38454B, 0x39464C, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x303B3F, 0x364240, 0x37423E, 0x37423E, 0x37423E, 0x37423E, 0x37423E, 0x37423E, 0x37423E, 0x444F4B, 0x414C48, 0x3E4945, 0x3A4541, 0x37423E, 0x35403C, 0x35403C, 0x383E3A, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4C453D, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xDCFCFF, 0xD2F2FF, 0xCEEEFD, 0xD6F6FF, 0xDAFAFF, 0xC1E1F0, 0x8DADBC, 0x618190, 0x517180, 0x496978, 0x416170, 0x416170, 0x436372, 0x3C5C6B, 0x2B4B5A, 0x203C48, 0x283F45, 0x2B3E42, 0x2A3D41, 0x283B3F, 0x273A3E, 0x25383C, 0x24373B, 0x24373B, 0x283B3F, 0x283B3F, 0x283B3F, 0x283B3F, 0x283B3F, 0x283B3F, 0x283B3F, 0x293B3F, 0x2F3E43, 0x313E44, 0x323F45, 0x354248, 0x37444A, 0x39464C, 0x3B484E, 0x3C494F, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x2E3B41, 0x303B3F, 0x333F3D, 0x343F3B, 0x343F3B, 0x343F3B, 0x343F3B, 0x343F3B, 0x343F3B, 0x343F3B, 0x313C38, 0x333E3A, 0x37423E, 0x3B4642, 0x3E4945, 0x3F4A46, 0x404B47, 0x434945, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x4D4940, 0x4D4940, 0x4D4940, 0x4D4940, 0x4D4940, 0x4D4940, 0x4D4940, 0x4F4840, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE7FFFF, 0xD5F5FF, 0xC8E8F7, 0xCEEEFD, 0xD7F7FF, 0xC2E2F1, 0x8DADBC, 0x5E7E8D, 0x40606F, 0x395968, 0x345463, 0x375766, 0x3D5D6C, 0x395968, 0x2B4B5A, 0x213D49, 0x22393F, 0x25383C, 0x25383C, 0x25383C, 0x25383C, 0x25383C, 0x25383C, 0x25383C, 0x2B3E42, 0x2B3E42, 0x2B3E42, 0x2B3E42, 0x2B3E42, 0x2B3E42, 0x2B3E42, 0x2C3E42, 0x324146, 0x334046, 0x354248, 0x37444A, 0x3A474D, 0x3C494F, 0x3D4A50, 0x3E4B51, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x323F45, 0x343F43, 0x303C3A, 0x313C38, 0x313C38, 0x313C38, 0x313C38, 0x313C38, 0x313C38, 0x313C38, 0x242F2B, 0x293430, 0x313C38, 0x394440, 0x3E4945, 0x404B47, 0x3F4A46, 0x414743, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x504C43, 0x504C43, 0x504C43, 0x504C43, 0x504C43, 0x504C43, 0x504C43, 0x524B43, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xDFFFFF, 0xB5D5E4, 0x88A8B7, 0x7A9AA9, 0x82A2B1, 0x7A9AA9, 0x567685, 0x325261, 0x2F4F5E, 0x2A4A59, 0x284857, 0x2F4F5E, 0x395968, 0x395968, 0x2E4E5D, 0x26424E, 0x1D343A, 0x213438, 0x213438, 0x223539, 0x23363A, 0x24373B, 0x25383C, 0x25383C, 0x2D4044, 0x2D4044, 0x2D4044, 0x2D4044, 0x2D4044, 0x2D4044, 0x2D4044, 0x2E4044, 0x344348, 0x354248, 0x37444A, 0x39464C, 0x3C494F, 0x3E4B51, 0x3F4C52, 0x404D53, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x37444A, 0x394448, 0x2E3A38, 0x2F3A36, 0x2F3A36, 0x2F3A36, 0x2F3A36, 0x2F3A36, 0x2F3A36, 0x2F3A36, 0x26312D, 0x2B3632, 0x333E3A, 0x38433F, 0x394440, 0x36413D, 0x303B37, 0x2F3531, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x524E45, 0x524E45, 0x524E45, 0x524E45, 0x524E45, 0x524E45, 0x524E45, 0x544D45, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xD2F2FF, 0x92B2C1, 0x486877, 0x264655, 0x2B4B5A, 0x30505F, 0x1D3D4C, 0x042433, 0x254554, 0x214150, 0x214150, 0x2B4B5A, 0x375766, 0x3A5A69, 0x315160, 0x294551, 0x1B3238, 0x1E3135, 0x203337, 0x213438, 0x23363A, 0x24373B, 0x25383C, 0x26393D, 0x2E4145, 0x2E4145, 0x2E4145, 0x2E4145, 0x2E4145, 0x2E4145, 0x2E4145, 0x2F4145, 0x354449, 0x364349, 0x38454B, 0x3A474D, 0x3D4A50, 0x3F4C52, 0x404D53, 0x414E54, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3B484E, 0x3D484C, 0x2D3937, 0x2E3935, 0x2E3935, 0x2E3935, 0x2E3935, 0x2E3935, 0x2E3935, 0x2E3935, 0x2F3A36, 0x323D39, 0x37423E, 0x38433F, 0x343F3B, 0x2B3632, 0x202B27, 0x1C221E, 0x3F3F37, 0x433F36, 0x454138, 0x47433A, 0x49453C, 0x4B473E, 0x4D4940, 0x4E4A41, 0x534F46, 0x534F46, 0x534F46, 0x534F46, 0x534F46, 0x534F46, 0x534F46, 0x554E46, 0x4C463A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x4D453A, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x484438, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x464236, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xD7F3FF, 0x96B2D9, 0x4B678C, 0x294668, 0x304D6D, 0x395773, 0x2B4963, 0x153449, 0x2B4A5C, 0x2C4C59, 0x2E4E59, 0x305158, 0x325358, 0x345658, 0x365859, 0x39585B, 0x2B4449, 0x2B424A, 0x2A4149, 0x273E46, 0x253C44, 0x233A42, 0x213840, 0x20373F, 0x283F47, 0x283F47, 0x283F47, 0x283F47, 0x283F47, 0x283F47, 0x283F47, 0x2A3E45, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x364646, 0x364445, 0x344243, 0x324041, 0x303E3F, 0x2E3C3D, 0x2C3A3B, 0x2B393A, 0x2F3D3E, 0x2F3D3E, 0x2D3B3C, 0x2B393A, 0x283637, 0x263435, 0x253334, 0x263232, 0x373D3D, 0x393D3C, 0x3C403F, 0x404443, 0x444847, 0x474B4A, 0x4A4E4D, 0x4C504F, 0x464A49, 0x464A49, 0x484C4B, 0x4A4E4D, 0x4D5150, 0x4F5352, 0x505453, 0x535552, 0x55584F, 0x57594E, 0x585A4F, 0x5A5C51, 0x5D5F54, 0x5F6156, 0x616358, 0x616358, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE3FFFF, 0xA5C1E8, 0x5C789D, 0x3A5779, 0x3E5B7B, 0x466480, 0x385670, 0x244358, 0x304F61, 0x31515E, 0x33535E, 0x35565D, 0x37585D, 0x395B5D, 0x3B5D5E, 0x3E5D60, 0x2C454A, 0x2C434B, 0x2B424A, 0x294048, 0x263D45, 0x243B43, 0x223941, 0x213840, 0x294048, 0x294048, 0x294048, 0x294048, 0x294048, 0x294048, 0x294048, 0x2B3F46, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x374747, 0x374546, 0x354344, 0x334142, 0x313F40, 0x2F3D3E, 0x2D3B3C, 0x2C3A3B, 0x303E3F, 0x303E3F, 0x2E3C3D, 0x2C3A3B, 0x293738, 0x273536, 0x263435, 0x273333, 0x333939, 0x363A39, 0x383C3B, 0x3B3F3E, 0x3E4241, 0x414544, 0x444847, 0x454948, 0x434746, 0x444847, 0x464A49, 0x484C4B, 0x4A4E4D, 0x4D5150, 0x4E5251, 0x515350, 0x53564D, 0x54564B, 0x56584D, 0x585A4F, 0x5B5D52, 0x5D5F54, 0x5E6055, 0x5F6156, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE7FFFF, 0xB6D2F9, 0x738FB4, 0x506D8F, 0x506D8D, 0x54728E, 0x486680, 0x36556A, 0x385769, 0x395966, 0x3A5A65, 0x3C5D64, 0x3F6065, 0x416365, 0x426465, 0x456467, 0x2E474C, 0x2E454D, 0x2D444C, 0x2B424A, 0x283F47, 0x263D45, 0x243B43, 0x233A42, 0x2B424A, 0x2B424A, 0x2B424A, 0x2B424A, 0x2B424A, 0x2B424A, 0x2B424A, 0x2D4148, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x394949, 0x394748, 0x374546, 0x354344, 0x334142, 0x313F40, 0x2F3D3E, 0x2E3C3D, 0x324041, 0x324041, 0x303E3F, 0x2E3C3D, 0x2B393A, 0x293738, 0x283637, 0x293535, 0x2F3535, 0x303433, 0x323635, 0x343837, 0x363A39, 0x383C3B, 0x3A3E3D, 0x3B3F3E, 0x3F4342, 0x404443, 0x424645, 0x444847, 0x464A49, 0x494D4C, 0x4A4E4D, 0x4D4F4C, 0x4F5249, 0x505247, 0x525449, 0x54564B, 0x57594E, 0x595B50, 0x5A5C51, 0x5B5D52, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE7FFFF, 0xBEDAFF, 0x829EC3, 0x5F7C9E, 0x597696, 0x597793, 0x4E6C86, 0x405F74, 0x3E5D6F, 0x3F5F6C, 0x41616C, 0x43646B, 0x45666B, 0x47696B, 0x496B6C, 0x4C6B6E, 0x314A4F, 0x314850, 0x2F464E, 0x2D444C, 0x2B424A, 0x294048, 0x273E46, 0x263D45, 0x2E454D, 0x2E454D, 0x2E454D, 0x2E454D, 0x2E454D, 0x2E454D, 0x2E454D, 0x30444B, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x3C4C4C, 0x3C4A4B, 0x3A4849, 0x384647, 0x364445, 0x334142, 0x324041, 0x313F40, 0x354344, 0x344243, 0x334142, 0x303E3F, 0x2E3C3D, 0x2C3A3B, 0x2A3839, 0x2B3737, 0x2C3232, 0x2D3130, 0x2D3130, 0x2E3231, 0x2F3332, 0x303433, 0x303433, 0x313534, 0x3A3E3D, 0x3B3F3E, 0x3D4140, 0x3F4342, 0x414544, 0x434746, 0x454948, 0x484A47, 0x494C43, 0x4B4D42, 0x4D4F44, 0x4F5146, 0x515348, 0x54564B, 0x55574C, 0x56584D, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE6FFFF, 0xBEDAFF, 0x8AA6CB, 0x6784A6, 0x5A7797, 0x55738F, 0x4C6A84, 0x426176, 0x416072, 0x42626F, 0x44646F, 0x46676E, 0x48696E, 0x4A6C6E, 0x4C6E6F, 0x4F6E71, 0x344D52, 0x344B53, 0x324951, 0x30474F, 0x2E454D, 0x2B424A, 0x2A4149, 0x294048, 0x314850, 0x314850, 0x314850, 0x314850, 0x314850, 0x314850, 0x314850, 0x33474E, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x3E4E4E, 0x3F4D4E, 0x3D4B4C, 0x3B494A, 0x384647, 0x364445, 0x354344, 0x344243, 0x384647, 0x374546, 0x354344, 0x334142, 0x313F40, 0x2F3D3E, 0x2D3B3C, 0x2E3A3A, 0x2D3333, 0x2D3130, 0x2D3130, 0x2C302F, 0x2B2F2E, 0x2B2F2E, 0x2A2E2D, 0x2A2E2D, 0x353938, 0x353938, 0x373B3A, 0x393D3C, 0x3C403F, 0x3E4241, 0x3F4342, 0x424441, 0x44473E, 0x46483D, 0x47493E, 0x494B40, 0x4C4E43, 0x4E5045, 0x505247, 0x505247, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xDFFBFF, 0xBFDBFF, 0x92AED3, 0x6E8BAD, 0x5B7898, 0x52708C, 0x4A6882, 0x446378, 0x405F71, 0x41616E, 0x42626D, 0x44656C, 0x47686D, 0x496B6D, 0x4B6D6E, 0x4D6C6F, 0x364F54, 0x364D55, 0x354C54, 0x334A52, 0x30474F, 0x2E454D, 0x2C434B, 0x2C434B, 0x334A52, 0x334A52, 0x334A52, 0x334A52, 0x334A52, 0x334A52, 0x334A52, 0x354950, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x415151, 0x414F50, 0x3F4D4E, 0x3D4B4C, 0x3B494A, 0x394748, 0x374546, 0x364445, 0x3B494A, 0x3A4849, 0x384647, 0x364445, 0x334142, 0x313F40, 0x303E3F, 0x313D3D, 0x323838, 0x323635, 0x303433, 0x2E3231, 0x2C302F, 0x2A2E2D, 0x282C2B, 0x272B2A, 0x2F3332, 0x303433, 0x323635, 0x343837, 0x363A39, 0x393D3C, 0x3A3E3D, 0x3D3F3C, 0x3E4138, 0x404237, 0x424439, 0x44463B, 0x46483D, 0x494B40, 0x4A4C41, 0x4B4D42, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xDFFBFF, 0xC5E1FF, 0x9DB9DE, 0x7996B8, 0x617E9E, 0x55738F, 0x4E6C86, 0x4B6A7F, 0x3C5B6D, 0x3D5D6A, 0x3F5F6A, 0x416269, 0x436469, 0x456769, 0x47696A, 0x4A696C, 0x385156, 0x384F57, 0x374E56, 0x354C54, 0x324951, 0x30474F, 0x2E454D, 0x2E454D, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x435353, 0x435152, 0x414F50, 0x3F4D4E, 0x3D4B4C, 0x3B494A, 0x394748, 0x384647, 0x3D4B4C, 0x3C4A4B, 0x3A4849, 0x384647, 0x354344, 0x334142, 0x324041, 0x333F3F, 0x383E3E, 0x383C3B, 0x353938, 0x323635, 0x2F3332, 0x2C302F, 0x2A2E2D, 0x282C2B, 0x2B2F2E, 0x2C302F, 0x2E3231, 0x303433, 0x323635, 0x353938, 0x363A39, 0x393B38, 0x3A3D34, 0x3C3E33, 0x3E4035, 0x404237, 0x424439, 0x45473C, 0x46483D, 0x47493E, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE3FFFF, 0xCBE7FF, 0xA7C3E8, 0x83A0C2, 0x6885A5, 0x5A7894, 0x54728C, 0x527186, 0x39586A, 0x3A5A67, 0x3C5C67, 0x3E5F66, 0x406166, 0x426466, 0x446667, 0x476669, 0x395257, 0x395058, 0x384F57, 0x364D55, 0x334A52, 0x314850, 0x2F464E, 0x2F464E, 0x364D55, 0x364D55, 0x364D55, 0x364D55, 0x364D55, 0x364D55, 0x364D55, 0x384C53, 0x334849, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x344847, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394B4B, 0x445454, 0x445253, 0x435152, 0x404E4F, 0x3E4C4D, 0x3C4A4B, 0x3A4849, 0x394748, 0x3E4C4D, 0x3D4B4C, 0x3B494A, 0x394748, 0x374546, 0x344243, 0x334142, 0x344040, 0x3C4242, 0x3C403F, 0x393D3C, 0x353938, 0x323635, 0x2E3231, 0x2B2F2E, 0x2A2E2D, 0x292D2C, 0x2A2E2D, 0x2C302F, 0x2E3231, 0x303433, 0x323635, 0x343837, 0x373936, 0x383B32, 0x3A3C31, 0x3C3E33, 0x3E4035, 0x404237, 0x424439, 0x44463B, 0x45473C, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4C4E43, 0x4D4D43, 0x504A3E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x51493E, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4F473C, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x4B4338, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x444034, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x423E32, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x413D31, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0xE0FFFF, 0xC2E7F9, 0x98BDCF, 0x759AAC, 0x608597, 0x557A8C, 0x4D7284, 0x476C7E, 0x3C6173, 0x3A5F71, 0x395E70, 0x395E70, 0x3D6274, 0x44697B, 0x4B7082, 0x537483, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x384C4B, 0x394C4A, 0x334340, 0x33423D, 0x31403B, 0x2F3E39, 0x2C3B36, 0x2A3934, 0x293833, 0x283732, 0x2C3B36, 0x2D3C37, 0x2E3D38, 0x2F3E39, 0x31403B, 0x32413C, 0x34433E, 0x36423E, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x434746, 0x444847, 0x454948, 0x484C4B, 0x4A4E4D, 0x4C504F, 0x4E5251, 0x515350, 0x43463D, 0x45473C, 0x46483D, 0x484A3F, 0x4B4D42, 0x4D4F44, 0x4F5146, 0x505247, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x413D34, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xDFFFFF, 0xC4E9FB, 0x9DC2D4, 0x7A9FB1, 0x63889A, 0x567B8D, 0x4E7385, 0x4A6F81, 0x3E6375, 0x3C6173, 0x395E70, 0x395E70, 0x3C6173, 0x43687A, 0x4A6F81, 0x517281, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x394D4C, 0x394D4C, 0x394D4C, 0x394D4C, 0x394D4C, 0x394D4C, 0x394D4C, 0x3A4D4B, 0x354542, 0x35443F, 0x33423D, 0x31403B, 0x2F3E39, 0x2C3B36, 0x2B3A35, 0x2A3934, 0x2B3A35, 0x2B3A35, 0x2C3B36, 0x2D3C37, 0x2E3D38, 0x2F3E39, 0x2F3E39, 0x323E3A, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x3D4140, 0x3E4241, 0x3F4342, 0x424645, 0x444847, 0x464A49, 0x484C4B, 0x4B4D4A, 0x45483F, 0x46483D, 0x484A3F, 0x4A4C41, 0x4D4F44, 0x4F5146, 0x505247, 0x515348, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x423E35, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xDCFFFF, 0xC8EDFF, 0xA6CBDD, 0x83A8BA, 0x678C9E, 0x577C8E, 0x507587, 0x4F7486, 0x406577, 0x3E6375, 0x3B6072, 0x3A5F71, 0x3C6173, 0x416678, 0x476C7E, 0x4F707F, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3B4F4E, 0x3B4F4E, 0x3B4F4E, 0x3B4F4E, 0x3B4F4E, 0x3B4F4E, 0x3B4F4E, 0x3C4F4D, 0x394946, 0x394843, 0x374641, 0x35443F, 0x33423D, 0x303F3A, 0x2F3E39, 0x2E3D38, 0x2A3934, 0x2A3934, 0x2A3934, 0x2A3934, 0x2A3934, 0x2A3934, 0x2A3934, 0x2C3834, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x333736, 0x343837, 0x363A39, 0x383C3B, 0x3A3E3D, 0x3C403F, 0x3E4241, 0x414340, 0x464940, 0x484A3F, 0x4A4C41, 0x4C4E43, 0x4E5045, 0x505247, 0x525449, 0x53554A, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x444037, 0x444037, 0x444037, 0x444037, 0x444037, 0x444037, 0x444037, 0x444037, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xDAFFFF, 0xCDF2FF, 0xB2D7E9, 0x8FB4C6, 0x6C91A3, 0x587D8F, 0x53788A, 0x557A8C, 0x44697B, 0x416678, 0x3D6274, 0x3A5F71, 0x3B6072, 0x3F6476, 0x44697B, 0x4B6C7B, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3F5250, 0x3E4E4B, 0x3E4D48, 0x3C4B46, 0x3A4944, 0x384742, 0x364540, 0x34433E, 0x33423D, 0x2C3B36, 0x2C3B36, 0x2B3A35, 0x293833, 0x283732, 0x263530, 0x25342F, 0x27332F, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2A2E2D, 0x2B2F2E, 0x2D3130, 0x2F3332, 0x313534, 0x333736, 0x353938, 0x383A37, 0x45483F, 0x47493E, 0x484A3F, 0x4A4C41, 0x4D4F44, 0x4F5146, 0x515348, 0x525449, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x47433A, 0x47433A, 0x47433A, 0x47433A, 0x47433A, 0x47433A, 0x47433A, 0x47433A, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xD7FCFF, 0xD2F7FF, 0xBFE4F6, 0x9BC0D2, 0x7297A9, 0x597E90, 0x567B8D, 0x5C8193, 0x486D7F, 0x44697B, 0x3F6476, 0x3B6072, 0x3A5F71, 0x3D6274, 0x416678, 0x476877, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x415554, 0x415554, 0x415554, 0x415554, 0x415554, 0x415554, 0x415554, 0x425553, 0x445451, 0x44534E, 0x42514C, 0x404F4A, 0x3E4D48, 0x3B4A45, 0x3A4944, 0x394843, 0x33423D, 0x32413C, 0x303F3A, 0x2D3C37, 0x2A3934, 0x273631, 0x25342F, 0x25312D, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x242827, 0x252928, 0x272B2A, 0x292D2C, 0x2C302F, 0x2E3231, 0x2F3332, 0x323431, 0x3F4239, 0x414338, 0x43453A, 0x45473C, 0x47493E, 0x494B40, 0x4B4D42, 0x4C4E43, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x49453C, 0x49453C, 0x49453C, 0x49453C, 0x49453C, 0x49453C, 0x49453C, 0x49453C, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xD4F9FF, 0xD7FCFF, 0xCBF0FF, 0xA7CCDE, 0x789DAF, 0x5A7F91, 0x587D8F, 0x628799, 0x4C7183, 0x476C7E, 0x416678, 0x3C6173, 0x3A5F71, 0x3B6072, 0x3E6375, 0x436473, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x435756, 0x435756, 0x435756, 0x435756, 0x435756, 0x435756, 0x435756, 0x445755, 0x495956, 0x495853, 0x475651, 0x45544F, 0x43524D, 0x41504B, 0x3F4E49, 0x3E4D48, 0x3D4C47, 0x3C4B46, 0x384742, 0x34433E, 0x303F3A, 0x2B3A35, 0x283732, 0x283430, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x232726, 0x242827, 0x262A29, 0x282C2B, 0x2A2E2D, 0x2C302F, 0x2E3231, 0x313330, 0x363930, 0x383A2F, 0x3A3C31, 0x3C3E33, 0x3E4035, 0x404237, 0x424439, 0x43453A, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xD2F7FF, 0xDBFFFF, 0xD5FAFF, 0xB0D5E7, 0x7CA1B3, 0x5B8092, 0x5B8092, 0x678C9E, 0x4E7385, 0x4A6F81, 0x43687A, 0x3C6173, 0x395E70, 0x395E70, 0x3C6173, 0x416271, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x455958, 0x455958, 0x455958, 0x455958, 0x455958, 0x455958, 0x455958, 0x465957, 0x4D5D5A, 0x4D5C57, 0x4B5A55, 0x495853, 0x475651, 0x45544F, 0x43524D, 0x42514C, 0x485752, 0x465550, 0x42514C, 0x3C4B46, 0x374641, 0x31403B, 0x2D3C37, 0x2D3935, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x252928, 0x262A29, 0x272B2A, 0x292D2C, 0x2C302F, 0x2E3231, 0x303433, 0x323431, 0x2C2F26, 0x2E3025, 0x303227, 0x323429, 0x34362B, 0x37392E, 0x383A2F, 0x393B30, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x4E4A41, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xD1F6FF, 0xDDFFFF, 0xDAFFFF, 0xB5DAEC, 0x7FA4B6, 0x5C8193, 0x5C8193, 0x6A8FA1, 0x507587, 0x4B7082, 0x44697B, 0x3D6274, 0x395E70, 0x395E70, 0x3A5F71, 0x3F606F, 0x415C67, 0x40575F, 0x3A5159, 0x344B53, 0x324951, 0x334A52, 0x364D55, 0x384F57, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x354C54, 0x374B52, 0x394E4F, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3A4E4D, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3C504F, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x3E5251, 0x465A59, 0x465A59, 0x465A59, 0x465A59, 0x465A59, 0x465A59, 0x465A59, 0x475A58, 0x4F5F5C, 0x4F5E59, 0x4D5C57, 0x4B5A55, 0x495853, 0x475651, 0x45544F, 0x44534E, 0x4E5D58, 0x4C5B56, 0x475651, 0x42514C, 0x3B4A45, 0x364540, 0x31403B, 0x313D39, 0x2D3331, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x2E3231, 0x262A29, 0x272B2A, 0x292D2C, 0x2B2F2E, 0x2E3231, 0x303433, 0x313534, 0x343633, 0x262920, 0x282A1F, 0x2A2C21, 0x2C2E23, 0x2E3025, 0x313328, 0x323429, 0x33352A, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4A4C41, 0x4B4B41, 0x4B483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4C483F, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4A463D, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x4F4B42, 0x48443B, 0x47433A, 0x464239, 0x444037, 0x413D34, 0x3F3B32, 0x3D3930, 0x3C382F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3F3B2F, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0x3D392D, 0xC5EAFC, 0xCEF3FF, 0xCBF0FF, 0xAED3E5, 0x83A8BA, 0x658A9C, 0x608597, 0x678C9E, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x3F5950, 0x3F5950, 0x3F5950, 0x3F5950, 0x3F5950, 0x3F5950, 0x3F5950, 0x455650, 0x3E4241, 0x423E3F, 0x3D393A, 0x373334, 0x312D2E, 0x2B2728, 0x272324, 0x252122, 0x292526, 0x2A2627, 0x2B2728, 0x2C2829, 0x2E2A2B, 0x2F2B2C, 0x302C2D, 0x2F2D2E, 0x2D2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2E3231, 0x2E3231, 0x303433, 0x323635, 0x353938, 0x373B3A, 0x383C3B, 0x393D3C, 0x3E433D, 0x3F443E, 0x414640, 0x434842, 0x454A44, 0x474C46, 0x494E48, 0x4A4F49, 0x434842, 0x434842, 0x424741, 0x40453F, 0x3F443E, 0x3D423C, 0x3C413B, 0x3E403B, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0x353631, 0xC5EAFC, 0xD1F6FF, 0xD2F7FF, 0xB8DDEF, 0x8DB2C4, 0x6B90A2, 0x608597, 0x64899B, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x405A51, 0x405A51, 0x405A51, 0x405A51, 0x405A51, 0x405A51, 0x405A51, 0x445751, 0x434947, 0x454545, 0x414141, 0x3B3B3B, 0x363636, 0x303030, 0x2C2C2C, 0x2A2A2A, 0x272727, 0x272727, 0x282828, 0x292929, 0x2A2A2A, 0x2B2B2B, 0x2C2C2C, 0x2C2C2C, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2D3130, 0x2D3130, 0x2F3332, 0x313534, 0x343837, 0x363A39, 0x373B3A, 0x383C3B, 0x3C413B, 0x3D423C, 0x3E433D, 0x414640, 0x434842, 0x454A44, 0x474C46, 0x484D47, 0x454A44, 0x444943, 0x444943, 0x434842, 0x424741, 0x414640, 0x40453F, 0x42443F, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0x363732, 0xC5EAFC, 0xD5FAFF, 0xDCFFFF, 0xC6EBFD, 0x9ABFD1, 0x7297A9, 0x608597, 0x5E8395, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x425C53, 0x425C53, 0x425C53, 0x425C53, 0x425C53, 0x425C53, 0x425C53, 0x465953, 0x495450, 0x4B4F4E, 0x484C4B, 0x434746, 0x3F4342, 0x3B3F3E, 0x373B3A, 0x363A39, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x272B2A, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2D3130, 0x2F3332, 0x323635, 0x343837, 0x353938, 0x363A39, 0x383D37, 0x393E38, 0x3A3F39, 0x3D423C, 0x3F443E, 0x414640, 0x434842, 0x444943, 0x464B45, 0x464B45, 0x464B45, 0x464B45, 0x464B45, 0x464B45, 0x464B45, 0x484A45, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0x383934, 0xC7ECFE, 0xDAFFFF, 0xE3FFFF, 0xD1F6FF, 0xA3C8DA, 0x769BAD, 0x5E8395, 0x597E90, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x445E55, 0x445E55, 0x445E55, 0x445E55, 0x445E55, 0x445E55, 0x445E55, 0x475C55, 0x4D5E58, 0x4E5A56, 0x4C5854, 0x495551, 0x46524E, 0x434F4B, 0x414D49, 0x404C48, 0x2A3632, 0x293531, 0x283430, 0x27332F, 0x25312D, 0x24302C, 0x222E2A, 0x232E2A, 0x2A302E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x282C2B, 0x292D2C, 0x2A2E2D, 0x2D3130, 0x2F3332, 0x313534, 0x333736, 0x343837, 0x333832, 0x343933, 0x353A34, 0x373C36, 0x3A3F39, 0x3C413B, 0x3E433D, 0x3E433D, 0x434842, 0x444943, 0x454A44, 0x464B45, 0x484D47, 0x494E48, 0x4B504A, 0x4D4F4A, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0x3B3C37, 0xCBF0FF, 0xDDFFFF, 0xE3FFFF, 0xD2F7FF, 0xA2C7D9, 0x7499AB, 0x5B8092, 0x557A8C, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x476158, 0x476158, 0x476158, 0x476158, 0x476158, 0x476158, 0x476158, 0x496058, 0x4E635C, 0x4F625C, 0x4E615B, 0x4C5F59, 0x4B5E58, 0x495C56, 0x485B55, 0x475A54, 0x31443E, 0x30433D, 0x2E413B, 0x2B3E38, 0x283B35, 0x253832, 0x233630, 0x24332E, 0x28312E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x252928, 0x262A29, 0x282C2B, 0x2A2E2D, 0x2C302F, 0x2E3231, 0x303433, 0x313534, 0x2D322C, 0x2E332D, 0x30352F, 0x323731, 0x343933, 0x363B35, 0x383D37, 0x393E38, 0x3D423C, 0x3E433D, 0x40453F, 0x434842, 0x464B45, 0x494E48, 0x4B504A, 0x4E504B, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0x3E3F3A, 0xD0F5FF, 0xDFFFFF, 0xE3FFFF, 0xC9EEFF, 0x98BDCF, 0x6C91A3, 0x577C8E, 0x53788A, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x4A645B, 0x4A645B, 0x4A645B, 0x4A645B, 0x4A645B, 0x4A645B, 0x4A645B, 0x4A645B, 0x4B665D, 0x4A655C, 0x4A655C, 0x4A655C, 0x4A655C, 0x4A655C, 0x4A655C, 0x4A655C, 0x3C574E, 0x3A554C, 0x375249, 0x324D44, 0x2E4940, 0x2A453C, 0x264138, 0x293E37, 0x27322E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x222625, 0x232726, 0x252928, 0x272B2A, 0x2A2E2D, 0x2C302F, 0x2D3130, 0x2E3231, 0x282D27, 0x292E28, 0x2A2F29, 0x2D322C, 0x2F342E, 0x313630, 0x333832, 0x343933, 0x323731, 0x343933, 0x373C36, 0x3C413B, 0x40453F, 0x444943, 0x484D47, 0x4B4D48, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0x41423D, 0xD6FBFF, 0xDFFFFF, 0xDBFFFF, 0xBBE0F2, 0x89AEC0, 0x618698, 0x527789, 0x53788A, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x4C665D, 0x4C665D, 0x4C665D, 0x4C665D, 0x4C665D, 0x4C665D, 0x4C665D, 0x4B665D, 0x46645A, 0x46665B, 0x46665B, 0x47675C, 0x48685D, 0x49695E, 0x4A6A5F, 0x4A6A5F, 0x47675C, 0x45655A, 0x416156, 0x3B5B50, 0x36564B, 0x315146, 0x2D4D42, 0x2E483F, 0x27322E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x202423, 0x212524, 0x232726, 0x252928, 0x282C2B, 0x2A2E2D, 0x2B2F2E, 0x2C302F, 0x242923, 0x252A24, 0x262B25, 0x292E28, 0x2B302A, 0x2D322C, 0x2F342E, 0x30352F, 0x282D27, 0x2A2F29, 0x2E332D, 0x333832, 0x393E38, 0x3E433D, 0x424741, 0x474944, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0x43443F, 0xD9FEFF, 0xDFFFFF, 0xD6FBFF, 0xB2D7E9, 0x7FA4B6, 0x5A7F91, 0x4F7486, 0x54798B, 0x4A6F81, 0x466B7D, 0x406577, 0x3A5F71, 0x385D6F, 0x395E70, 0x3C6173, 0x416271, 0x3D5863, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E56, 0x374E54, 0x395155, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x395153, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x3C5456, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x405956, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D5A, 0x445D59, 0x465F59, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x466057, 0x4D675E, 0x4D675E, 0x4D675E, 0x4D675E, 0x4D675E, 0x4D675E, 0x4D675E, 0x4C675E, 0x426358, 0x416559, 0x42665A, 0x43675B, 0x45695D, 0x466A5E, 0x476B5F, 0x486C60, 0x4D7165, 0x4A6E62, 0x466A5E, 0x406458, 0x3A5E52, 0x34584C, 0x2F5347, 0x334E45, 0x26322E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x2B2F2E, 0x1F2322, 0x202423, 0x222625, 0x242827, 0x262A29, 0x292D2C, 0x2A2E2D, 0x2B2F2E, 0x222721, 0x232822, 0x242923, 0x262B25, 0x292E28, 0x2B302A, 0x2D322C, 0x2D322C, 0x222721, 0x242923, 0x282D27, 0x2E332D, 0x343933, 0x3A3F39, 0x3F443E, 0x434540, 0x454742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x464742, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x42433E, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x40413C, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x444540, 0x96D4F9, 0x90CEF3, 0x85C1E5, 0x77B2D4, 0x679FC0, 0x598EAD, 0x4F829F, 0x4C7B95, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x446459, 0x446459, 0x446459, 0x446459, 0x446459, 0x446459, 0x446459, 0x456359, 0x3A5149, 0x384D46, 0x354842, 0x31423C, 0x2C3B36, 0x293430, 0x282E2C, 0x282A29, 0x303030, 0x332F30, 0x352C2F, 0x362A2E, 0x35292D, 0x38272D, 0x37262C, 0x34282C, 0x262425, 0x232726, 0x242827, 0x262A29, 0x272B2A, 0x292D2C, 0x2A2E2D, 0x2A2E2D, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x3B3F3E, 0x3C403F, 0x3E4241, 0x404443, 0x424645, 0x454948, 0x464A49, 0x474B4A, 0x3C403F, 0x3D4140, 0x3F4342, 0x414544, 0x434746, 0x454948, 0x474B4A, 0x484C4B, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x90CEF3, 0x8AC8ED, 0x81BDE1, 0x73AED0, 0x659DBE, 0x588DAC, 0x4F829F, 0x4B7A94, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x45655A, 0x45655A, 0x45655A, 0x45655A, 0x45655A, 0x45655A, 0x45655A, 0x46645A, 0x3D574E, 0x3D544C, 0x3A4F48, 0x364943, 0x32433D, 0x303C38, 0x2D3834, 0x2E3432, 0x303231, 0x312F30, 0x322E2F, 0x342B2E, 0x34292D, 0x33272B, 0x34252A, 0x31262A, 0x242424, 0x212524, 0x222625, 0x232726, 0x242827, 0x252928, 0x262A29, 0x262A29, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x353938, 0x363A39, 0x383C3B, 0x3A3E3D, 0x3C403F, 0x3F4342, 0x404443, 0x414544, 0x3A3E3D, 0x3B3F3E, 0x3D4140, 0x3F4342, 0x414544, 0x434746, 0x454948, 0x464A49, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x84C2E7, 0x7FBDE2, 0x77B3D7, 0x6CA7C9, 0x6098B9, 0x558AA9, 0x4D809D, 0x4B7A94, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x47675C, 0x47675C, 0x47675C, 0x47675C, 0x47675C, 0x47675C, 0x47675C, 0x47675C, 0x436157, 0x415F55, 0x405B52, 0x3D574E, 0x3A5149, 0x384B45, 0x374842, 0x384440, 0x2E3734, 0x303433, 0x313131, 0x322E2F, 0x2F2B2C, 0x30272A, 0x2E2528, 0x2B2527, 0x232323, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x2C302F, 0x2D3130, 0x2E3231, 0x303433, 0x333736, 0x353938, 0x373B3A, 0x383C3B, 0x363A39, 0x373B3A, 0x393D3C, 0x3B3F3E, 0x3D4140, 0x3F4342, 0x414544, 0x424645, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x75B3D8, 0x71AFD4, 0x6BA7CB, 0x629DBF, 0x5991B2, 0x5186A5, 0x4C7F9C, 0x4A7993, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x4A6A5F, 0x476B5F, 0x466A5E, 0x46675C, 0x446459, 0x426056, 0x425C53, 0x425951, 0x42554F, 0x31403B, 0x333E3A, 0x313A37, 0x313534, 0x2F3130, 0x2C2C2C, 0x2A2829, 0x282828, 0x252726, 0x222625, 0x212524, 0x1F2322, 0x1E2221, 0x1C201F, 0x1B1F1E, 0x1B1F1E, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x232726, 0x242827, 0x252928, 0x272B2A, 0x2A2E2D, 0x2C302F, 0x2D3130, 0x2E3231, 0x313534, 0x323635, 0x333736, 0x363A39, 0x383C3B, 0x3A3E3D, 0x3C403F, 0x3D4140, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x65A3C8, 0x62A0C5, 0x5F9BBF, 0x5893B5, 0x528AAB, 0x4D82A1, 0x4A7D9A, 0x4A7993, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4D6D62, 0x4D6D62, 0x4D6D62, 0x4D6D62, 0x4D6D62, 0x4D6D62, 0x4D6D62, 0x4C6D62, 0x487264, 0x467263, 0x467062, 0x466F61, 0x466C5F, 0x48695E, 0x47675C, 0x4A655C, 0x395048, 0x394C46, 0x374842, 0x35413D, 0x303B37, 0x2C3532, 0x2A302E, 0x282E2C, 0x292D2C, 0x282C2B, 0x262A29, 0x232726, 0x202423, 0x1D2120, 0x1B1F1E, 0x1A1E1D, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x1D2120, 0x1E2221, 0x202423, 0x222625, 0x242827, 0x262A29, 0x282C2B, 0x292D2C, 0x2B2F2E, 0x2C302F, 0x2E3231, 0x303433, 0x323635, 0x343837, 0x363A39, 0x373B3A, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x5694B9, 0x5492B7, 0x538FB3, 0x4F8AAC, 0x4C84A5, 0x4A7F9E, 0x487B98, 0x497892, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4F6F64, 0x4F6F64, 0x4F6F64, 0x4F6F64, 0x4F6F64, 0x4F6F64, 0x4F6F64, 0x4C7064, 0x447464, 0x427564, 0x427564, 0x457464, 0x457464, 0x487264, 0x4A7063, 0x4D6E63, 0x436157, 0x445E55, 0x415850, 0x3C4F49, 0x374842, 0x32413C, 0x2F3B37, 0x2D3834, 0x323836, 0x323635, 0x2F3332, 0x2A2E2D, 0x262A29, 0x212524, 0x1E2221, 0x1D2120, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x1C201F, 0x1D2120, 0x1E2221, 0x202423, 0x232726, 0x252928, 0x272B2A, 0x272B2A, 0x262A29, 0x272B2A, 0x282C2B, 0x2B2F2E, 0x2D3130, 0x2F3332, 0x313534, 0x323635, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x4A88AD, 0x4987AC, 0x4985A9, 0x4883A5, 0x477FA0, 0x477C9B, 0x477A97, 0x497892, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x517166, 0x517166, 0x517166, 0x517166, 0x517166, 0x517166, 0x517166, 0x4D7366, 0x407362, 0x3C7562, 0x3F7663, 0x417664, 0x437665, 0x477666, 0x497566, 0x4C7567, 0x4D7165, 0x4D6D62, 0x49675D, 0x445E55, 0x3E554D, 0x384D46, 0x334640, 0x33423D, 0x3B4441, 0x3C403F, 0x383C3B, 0x323635, 0x2D3130, 0x272B2A, 0x232726, 0x212524, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x1D2120, 0x1E2221, 0x202423, 0x222625, 0x242827, 0x262A29, 0x282C2B, 0x292D2C, 0x222625, 0x232726, 0x242827, 0x272B2A, 0x292D2C, 0x2B2F2E, 0x2D3130, 0x2E3231, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x4482A7, 0x4482A7, 0x4480A4, 0x447FA1, 0x457D9E, 0x457A99, 0x467996, 0x487791, 0x3D6A81, 0x3F687C, 0x3F6578, 0x3F6373, 0x3E5F6E, 0x3D5D6A, 0x3E5C67, 0x3E5A65, 0x3F5761, 0x3F565E, 0x3E555D, 0x3C535B, 0x395058, 0x374E56, 0x364D55, 0x354C54, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395058, 0x395056, 0x3B5357, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3B5355, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3E5658, 0x3D5758, 0x3F5C58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x3E5D58, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x42615C, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476661, 0x476660, 0x4B6A62, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x4B6B60, 0x527267, 0x527267, 0x527267, 0x527267, 0x527267, 0x527267, 0x527267, 0x4E7467, 0x3C7360, 0x397460, 0x3A7561, 0x3F7663, 0x407764, 0x447766, 0x487767, 0x4B7567, 0x537C6E, 0x53776B, 0x507065, 0x4B665D, 0x435D54, 0x3D544C, 0x384D46, 0x384943, 0x414A47, 0x424645, 0x3D4140, 0x383C3B, 0x313534, 0x2C302F, 0x272B2A, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x252928, 0x1F2322, 0x202423, 0x222625, 0x242827, 0x262A29, 0x282C2B, 0x2A2E2D, 0x2B2F2E, 0x202423, 0x212524, 0x222625, 0x242827, 0x272B2A, 0x292D2C, 0x2B2F2E, 0x2C302F, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x3A3F39, 0x4A7EA3, 0x497DA2, 0x497B9C, 0x48799A, 0x487795, 0x487491, 0x48728B, 0x4A7087, 0x45687E, 0x47667A, 0x476474, 0x476170, 0x475F6B, 0x465C69, 0x465A63, 0x455962, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x436C64, 0x436C64, 0x436C64, 0x436C64, 0x436C64, 0x436C64, 0x436C64, 0x466B64, 0x3A5753, 0x3B5451, 0x384E4C, 0x344847, 0x2E4040, 0x2C3A3B, 0x293436, 0x293134, 0x202328, 0x242329, 0x27242B, 0x2D262E, 0x322831, 0x362A34, 0x392C36, 0x382E36, 0x252527, 0x232726, 0x242827, 0x262A29, 0x272B2A, 0x292D2C, 0x2A2E2D, 0x2A2E2D, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x2C3033, 0x2C2F34, 0x2C2F34, 0x2C2F34, 0x2C2F34, 0x2C2F34, 0x2C2F34, 0x2C2F34, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x3B3E43, 0x497DA2, 0x487CA1, 0x487A9B, 0x477899, 0x477694, 0x46728F, 0x47718A, 0x496F86, 0x44677D, 0x466579, 0x466373, 0x46606F, 0x465E6A, 0x455B68, 0x455962, 0x445861, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x446D65, 0x446D65, 0x446D65, 0x446D65, 0x446D65, 0x446D65, 0x446D65, 0x476C65, 0x3E5E59, 0x405A57, 0x3C5653, 0x394F4D, 0x344A48, 0x314343, 0x303E3F, 0x303A3C, 0x242C2F, 0x282B30, 0x2B2A30, 0x2E2930, 0x2E2930, 0x312730, 0x312730, 0x2F282F, 0x242426, 0x212524, 0x222625, 0x232726, 0x242827, 0x252928, 0x262A29, 0x262A29, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x2B2F32, 0x2B2E33, 0x2B2E33, 0x2B2E33, 0x2B2E33, 0x2B2E33, 0x2B2E33, 0x2B2E33, 0x383B40, 0x383B40, 0x383B40, 0x383B40, 0x383B40, 0x383B40, 0x383B40, 0x383B40, 0x477BA0, 0x467A9F, 0x467899, 0x457697, 0x457492, 0x44708D, 0x456F88, 0x476D84, 0x42657B, 0x446377, 0x446171, 0x445E6D, 0x445C68, 0x435966, 0x435760, 0x42565F, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x476F67, 0x446962, 0x456560, 0x42625D, 0x3F5C58, 0x3D5754, 0x3C5250, 0x394D4C, 0x3B4B4B, 0x2F3A3C, 0x30383B, 0x2F3438, 0x2E2F34, 0x2B2A30, 0x29262D, 0x28232A, 0x242227, 0x222325, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x202423, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x292D30, 0x292C31, 0x292C31, 0x292C31, 0x292C31, 0x292C31, 0x292C31, 0x292C31, 0x34373C, 0x34373C, 0x34373C, 0x34373C, 0x34373C, 0x34373C, 0x34373C, 0x34373C, 0x44789D, 0x43779C, 0x437596, 0x427394, 0x42718F, 0x426E8B, 0x426C85, 0x446A81, 0x3F6278, 0x416074, 0x425F6F, 0x425C6B, 0x415965, 0x405663, 0x40545D, 0x40545D, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x49726A, 0x487169, 0x466E66, 0x466B64, 0x436660, 0x44635E, 0x445E5B, 0x465C5A, 0x394D4C, 0x394949, 0x374244, 0x323A3D, 0x2A3235, 0x26292E, 0x212429, 0x202125, 0x232728, 0x222625, 0x212524, 0x1F2322, 0x1E2221, 0x1C201F, 0x1B1F1E, 0x1B1F1E, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x262A2D, 0x26292E, 0x26292E, 0x26292E, 0x26292E, 0x26292E, 0x26292E, 0x26292E, 0x2F3237, 0x2F3237, 0x2F3237, 0x2F3237, 0x2F3237, 0x2F3237, 0x2F3237, 0x2F3237, 0x41759A, 0x407499, 0x417394, 0x407192, 0x3F6E8C, 0x3F6B88, 0x3F6982, 0x41677E, 0x3C5F75, 0x3F5E72, 0x3F5C6C, 0x3F5968, 0x3E5662, 0x3D5360, 0x3E525B, 0x3D515A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4B746C, 0x4B746C, 0x4B746C, 0x4B746C, 0x4B746C, 0x4B746C, 0x4B746C, 0x4A756C, 0x49786E, 0x49786E, 0x48776D, 0x49746B, 0x48736A, 0x487068, 0x4A6D67, 0x4C6B66, 0x425F5B, 0x425B58, 0x3E5251, 0x394949, 0x313F40, 0x2A3537, 0x252F31, 0x222A2C, 0x282E2E, 0x282C2B, 0x262A29, 0x232726, 0x202423, 0x1D2120, 0x1B1F1E, 0x1A1E1D, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x23272A, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x2A2D32, 0x3F7398, 0x3E7297, 0x3E7091, 0x3D6E8F, 0x3D6C8A, 0x3C6885, 0x3D6780, 0x3F657C, 0x3A5D73, 0x3C5B6F, 0x3C5969, 0x3C5665, 0x3C5460, 0x3B515E, 0x3B4F58, 0x3A4E57, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4B796F, 0x477B6F, 0x447C6F, 0x467C6F, 0x467A6E, 0x47796E, 0x4A786E, 0x4C776E, 0x4E766E, 0x4B6E68, 0x4B6A65, 0x46635F, 0x415A57, 0x3A504E, 0x354747, 0x2F4141, 0x303C3C, 0x303938, 0x323635, 0x2F3332, 0x2A2E2D, 0x262A29, 0x212524, 0x1E2221, 0x1D2120, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x212528, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x24272C, 0x24272C, 0x24272C, 0x24272C, 0x24272C, 0x24272C, 0x24272C, 0x24272C, 0x3D7196, 0x3C7095, 0x3C6E8F, 0x3B6C8D, 0x3B6A88, 0x3A6683, 0x3B657E, 0x3D637A, 0x385B71, 0x3A596D, 0x3A5767, 0x3A5463, 0x3A525E, 0x394F5C, 0x394D56, 0x384C55, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x507971, 0x507971, 0x507971, 0x507971, 0x507971, 0x507971, 0x507971, 0x4D7B71, 0x407A6C, 0x3F7C6D, 0x3F7C6D, 0x437B6E, 0x447C6F, 0x487C70, 0x4C7B71, 0x4F7A71, 0x4F7870, 0x50756E, 0x4E6E69, 0x4A6763, 0x44615D, 0x415A57, 0x3C5552, 0x3E514F, 0x3B4443, 0x3C403F, 0x383C3B, 0x323635, 0x2D3130, 0x272B2A, 0x232726, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x1F2326, 0x1F2227, 0x1F2227, 0x1F2227, 0x1F2227, 0x1F2227, 0x1F2227, 0x1F2227, 0x202328, 0x202328, 0x202328, 0x202328, 0x202328, 0x202328, 0x202328, 0x202328, 0x3C7095, 0x3B6F94, 0x3B6D8E, 0x3A6B8C, 0x396886, 0x396582, 0x3A647D, 0x3C6279, 0x375A70, 0x39586C, 0x395666, 0x395362, 0x39515D, 0x384E5B, 0x384C55, 0x374B54, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3A5358, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x3C5A5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x405F5A, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x466560, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B64, 0x4C7066, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4C7064, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4E7266, 0x4D7368, 0x4B736A, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x517A72, 0x517A72, 0x517A72, 0x517A72, 0x517A72, 0x517A72, 0x517A72, 0x4D7C72, 0x3E786A, 0x3B7B6B, 0x3D7B6C, 0x3F7C6D, 0x437D6F, 0x477D70, 0x497D71, 0x4D7C72, 0x537E75, 0x537B73, 0x51766F, 0x50706B, 0x4C6B66, 0x486561, 0x47615E, 0x495D5B, 0x404A49, 0x424645, 0x3D4140, 0x383C3B, 0x313534, 0x2C302F, 0x272B2A, 0x252928, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212524, 0x212526, 0x1E2225, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x1E2126, 0x435E6F, 0x435E6F, 0x435E6F, 0x435E6F, 0x435E6F, 0x435E6F, 0x435E6F, 0x435E6F, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x517573, 0x496566, 0x496163, 0x455A5D, 0x3E5155, 0x36484C, 0x313E44, 0x2D383E, 0x2C333B, 0x181B24, 0x1C1A25, 0x1F1C27, 0x241C29, 0x291E2C, 0x2D212F, 0x302231, 0x2F2432, 0x1F1E24, 0x1C1F24, 0x1D2025, 0x1F2227, 0x202328, 0x22252A, 0x23262B, 0x24272C, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x425D6E, 0x425D6E, 0x425D6E, 0x425D6E, 0x425D6E, 0x425D6E, 0x425D6E, 0x425D6E, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x517573, 0x4C6C6B, 0x4D6768, 0x476162, 0x43585B, 0x3B5053, 0x35474B, 0x323F45, 0x323B42, 0x20272F, 0x23262F, 0x26242F, 0x29232F, 0x29232F, 0x2D2230, 0x2D2230, 0x2B2330, 0x212026, 0x1F2227, 0x1F2227, 0x202328, 0x212429, 0x22252A, 0x23262B, 0x23262B, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x405B6C, 0x405B6C, 0x405B6C, 0x405B6C, 0x405B6C, 0x405B6C, 0x405B6C, 0x405B6C, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4F7673, 0x4F7371, 0x50706F, 0x4B6B6A, 0x476364, 0x425C5D, 0x3E5356, 0x3A4D51, 0x3B4A4F, 0x303B41, 0x313840, 0x30343D, 0x2F2F39, 0x2C2A35, 0x2A2732, 0x29232F, 0x25222D, 0x25262B, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x23262B, 0x22252A, 0x22252A, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x3E596A, 0x3E596A, 0x3E596A, 0x3E596A, 0x3E596A, 0x3E596A, 0x3E596A, 0x3E596A, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x527B77, 0x507975, 0x4D7471, 0x4A6E6C, 0x466867, 0x436161, 0x435D5E, 0x44595C, 0x3E5155, 0x3E4D52, 0x3C474D, 0x373E46, 0x2F363E, 0x2B2E37, 0x262932, 0x25252F, 0x292C31, 0x292C31, 0x282B30, 0x26292E, 0x25282D, 0x23262B, 0x22252A, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x3B5667, 0x3B5667, 0x3B5667, 0x3B5667, 0x3B5667, 0x3B5667, 0x3B5667, 0x3B5667, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4D7773, 0x507F79, 0x4E7D77, 0x4B7A74, 0x4B7571, 0x47716D, 0x456C69, 0x466867, 0x476565, 0x476364, 0x475F61, 0x43565A, 0x3E4D52, 0x354248, 0x2F3A40, 0x293239, 0x272E36, 0x2F3438, 0x2F3237, 0x2D3035, 0x2A2D32, 0x272A2F, 0x24272C, 0x22252A, 0x202328, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4B7873, 0x4A7D76, 0x467E75, 0x477C74, 0x457871, 0x44756F, 0x45726D, 0x456F6B, 0x466D6A, 0x4C6E6D, 0x4B6969, 0x476364, 0x425A5C, 0x3B5053, 0x35474B, 0x2F4145, 0x313C42, 0x333B3E, 0x35383D, 0x313439, 0x2D3035, 0x282B30, 0x24272C, 0x212429, 0x1F2227, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x365162, 0x365162, 0x365162, 0x365162, 0x365162, 0x365162, 0x365162, 0x365162, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4B7873, 0x427B72, 0x407C72, 0x3F7B71, 0x40786F, 0x3F776E, 0x41746D, 0x43726C, 0x456F6B, 0x4B7470, 0x4B6F6D, 0x4A6A69, 0x466263, 0x3F5B5C, 0x3C5456, 0x385052, 0x394B4F, 0x384043, 0x393C41, 0x35383D, 0x303338, 0x2A2D32, 0x25282D, 0x212429, 0x1F2227, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x355061, 0x355061, 0x355061, 0x355061, 0x355061, 0x355061, 0x355061, 0x355061, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x385364, 0x395362, 0x3A525C, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B5459, 0x3B575A, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3A5858, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x3E5C5C, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x41645E, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x446761, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x486B65, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4C6F69, 0x4B7069, 0x4B736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4A736B, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4E776F, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7870, 0x4F7872, 0x4F7872, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4F7874, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4A7973, 0x3F786F, 0x3B7A6F, 0x3B796E, 0x3B776D, 0x3D766D, 0x3F746C, 0x3F726B, 0x42716B, 0x4A7470, 0x4A716E, 0x496D6B, 0x476766, 0x436161, 0x405C5D, 0x3E5859, 0x405357, 0x3A4446, 0x3B3E43, 0x373A3F, 0x313439, 0x2B2E33, 0x25282D, 0x212429, 0x1E2126, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x212429, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x45686C, 0x3E5960, 0x3E555D, 0x3C5059, 0x374953, 0x32434D, 0x2F3B47, 0x2D3743, 0x2C3240, 0x272938, 0x292637, 0x2A2637, 0x2C2336, 0x2D2135, 0x2D2034, 0x2D1E33, 0x2D1E33, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x45686C, 0x405F64, 0x425B62, 0x3E575E, 0x3C5059, 0x364A53, 0x33444E, 0x323E4A, 0x333B48, 0x2C3240, 0x2E303F, 0x2F2C3D, 0x2F283A, 0x2C2537, 0x2C2034, 0x2A1E32, 0x291C30, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x43696C, 0x44676B, 0x456469, 0x426166, 0x405B62, 0x3D565D, 0x3C5059, 0x3A4C56, 0x3B4954, 0x38424E, 0x383E4C, 0x353847, 0x333242, 0x2D2A3B, 0x292536, 0x261F31, 0x241D2F, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x426A6C, 0x466E70, 0x456D6F, 0x446A6D, 0x43666A, 0x416267, 0x415E64, 0x425B62, 0x445861, 0x3F515B, 0x3F4D58, 0x3C4652, 0x363C4A, 0x2E3442, 0x292B3A, 0x232534, 0x222131, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x416A6C, 0x447272, 0x447272, 0x437171, 0x446D6F, 0x426B6D, 0x43696C, 0x45666B, 0x47646A, 0x425D64, 0x425961, 0x3F515B, 0x3A4853, 0x333F4B, 0x2D3743, 0x28303D, 0x252D3A, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x3F6B6C, 0x3F7170, 0x3C7370, 0x3E7270, 0x3F7170, 0x407070, 0x426E6F, 0x446D6F, 0x466C6F, 0x406166, 0x415E64, 0x3D585F, 0x3B525A, 0x364A53, 0x33444E, 0x2E3F49, 0x2E3C47, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x3F6B6C, 0x376F6C, 0x35706C, 0x36716D, 0x3A716E, 0x3B726F, 0x3E706F, 0x427070, 0x466F71, 0x3B6365, 0x3C5F63, 0x3D5C61, 0x3C575E, 0x39545B, 0x395058, 0x374E56, 0x384C55, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x35515C, 0x36515C, 0x3A535A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3B525A, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3C535B, 0x3B545B, 0x3D585F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x3C595F, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D63, 0x405D61, 0x416364, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x416362, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x446665, 0x436765, 0x476F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x466F67, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x487169, 0x48716B, 0x4C756F, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4C7571, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4E7773, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4C7974, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B76, 0x4E7B78, 0x4E7A79, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4E7A7B, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4E7779, 0x4D7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4E7678, 0x4D7577, 0x4B7375, 0x497173, 0x476F71, 0x456D6F, 0x436B6D, 0x3E6C6C, 0x336B68, 0x306E69, 0x326F6A, 0x346F6B, 0x38706D, 0x3D716F, 0x3F7170, 0x427070, 0x376062, 0x395F62, 0x3A5D61, 0x3C5B60, 0x3C595F, 0x3C575E, 0x3E575E, 0x3D565D, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6163, 0x406766, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x3F6864, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x426B67, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x466F6B, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x48716D, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x497671, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7873, 0x4B7875, 0x4C7877, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C7879, 0x4C787B, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x497278, 0x436C72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x446B72, 0x436A71, 0x426970, 0x40676E, 0x3E656C, 0x3B6269, 0x396067, 0x385F66, 0x375E65, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2B484E, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x2F4C52, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x355258, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x38555B, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x395A5F, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3F6065, 0x3E6165, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x466E70, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x487072, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x497576, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B7778, 0x4B777A, 0x4B777A, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x4B767C, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x4B747A, 0x497278, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x4A7178, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x3F666D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x26424D, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x2D4954, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x335056, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545A, 0x37545C, 0x395A61, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x395A63, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3D5E67, 0x3C5E67, 0x406669, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x3F6769, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x426A6C, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457172, 0x457174, 0x477376, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x49747A, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x477278, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x457076, 0x477076, 0x456E74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x466D74, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x426970, 0x436871, 0x436571, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x446473, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E, 0x3F5F6E }; ================================================ FILE: esp_jpeg/test_apps/main/test_usb_camera_jpg.h ================================================ /* Raw data from Logitech C270 USB camera was reconstructed to usb_camera.jpg It was converted to RGB888 array with jpg_to_rgb888_hex.py */ // JPEG encoded frame 160x120, 2632 bytes, no huffman tables, double block size (16x8 pixels) extern const unsigned char jpeg_no_huffman[] asm("_binary_usb_camera_jpg_start"); extern char _binary_usb_camera_jpg_start; extern char _binary_usb_camera_jpg_end; // Must be defined as macro because extern variables are not known at compile time (but at link time) #define jpeg_no_huffman_len (&_binary_usb_camera_jpg_end - &_binary_usb_camera_jpg_start) ================================================ FILE: esp_jpeg/test_apps/main/test_usb_camera_rgb888.h ================================================ unsigned int jpeg_no_huffman_rgb888[19200] = { 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000E00, 0x000900, 0x000800, 0x000600, 0x131E10, 0x767E71, 0x787E72, 0x74776C, 0x787B70, 0x7F8176, 0x84867B, 0x86887A, 0x87897B, 0x888A7C, 0x888A7C, 0x888A7C, 0x898B7D, 0x898B7D, 0x8A8C7E, 0x8A8C7E, 0x898B7D, 0x888A7C, 0x888A7C, 0x848678, 0x87897B, 0x8A8C7E, 0x8C8E80, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8C8E80, 0x8B8D7F, 0x8B8D7F, 0x8B8D7F, 0x8B8D7F, 0x8A8C7E, 0x8A8C7E, 0x898B7D, 0x898B7D, 0x878B7C, 0x818A79, 0x7F8A79, 0x7F8A79, 0x808B7A, 0x818C7B, 0x808B7A, 0x7D8A78, 0x7B8876, 0x788573, 0x788573, 0x788774, 0x788774, 0x778673, 0x768572, 0x748370, 0x73826F, 0x74816F, 0x73806E, 0x73806E, 0x727F6D, 0x707F6C, 0x6F7E6B, 0x6E7D6A, 0x6E7D6A, 0x6A7B68, 0x6A7B68, 0x687B67, 0x677A66, 0x667965, 0x657864, 0x647763, 0x657664, 0x667463, 0x667264, 0x657362, 0x657362, 0x647563, 0x647563, 0x617461, 0x5F7161, 0x627363, 0x5D6D62, 0x5E6B61, 0x5D6A63, 0x626D67, 0x495450, 0x313A39, 0x000802, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000D00, 0x000900, 0x000900, 0x000800, 0x182315, 0x788073, 0x797F73, 0x7A7D72, 0x7D8075, 0x818378, 0x83857A, 0x848678, 0x858779, 0x86887A, 0x87897B, 0x87897B, 0x888A7C, 0x898B7D, 0x8A8C7E, 0x8B8D7F, 0x8C8E80, 0x8D8F81, 0x8D8F81, 0x858779, 0x888A7C, 0x8B8D7F, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8F9183, 0x8F9183, 0x8F9183, 0x8F9183, 0x8F9183, 0x8F9183, 0x8F9183, 0x8F9183, 0x8E9082, 0x8B8D7F, 0x8B8D7F, 0x8A8C7E, 0x8A8C7E, 0x898B7D, 0x898B7D, 0x888A7C, 0x878B7C, 0x888F7F, 0x868F7E, 0x848D7C, 0x838C7B, 0x828B7A, 0x7F8877, 0x798473, 0x768170, 0x768170, 0x768170, 0x768170, 0x768170, 0x74816F, 0x73806E, 0x727F6D, 0x717E6C, 0x727D6C, 0x727D6C, 0x727D6C, 0x727D6C, 0x707D6B, 0x707D6B, 0x707D6B, 0x707D6B, 0x6C7B68, 0x6C7B68, 0x6B7A67, 0x6A7966, 0x687966, 0x677865, 0x677865, 0x687665, 0x6D7B6A, 0x6D796B, 0x6A7867, 0x697766, 0x677866, 0x657664, 0x627562, 0x607262, 0x637464, 0x5F6F64, 0x616E64, 0x5E6B64, 0x636E68, 0x4E5955, 0x363F3E, 0x000802, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000D00, 0x000A00, 0x000B00, 0x000A00, 0x202B1D, 0x798174, 0x787E72, 0x797C71, 0x7A7D72, 0x7C7E73, 0x7C7E73, 0x7D7F71, 0x7E8072, 0x808274, 0x828476, 0x808274, 0x808274, 0x818375, 0x818375, 0x838577, 0x858779, 0x87897B, 0x888A7C, 0x86887A, 0x898B7D, 0x8D8F81, 0x8F9183, 0x8F9183, 0x8F9183, 0x909284, 0x909284, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8F9183, 0x8F9183, 0x8F9183, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8C8E80, 0x8C8E80, 0x888C7D, 0x878B7C, 0x868A7B, 0x868A7B, 0x868A7B, 0x848879, 0x808777, 0x7D8474, 0x707767, 0x707767, 0x6F7867, 0x6E7766, 0x6D7665, 0x6C7564, 0x6B7463, 0x6A7362, 0x6B7463, 0x6A7362, 0x6A7362, 0x697261, 0x687160, 0x67705F, 0x67705F, 0x666F5E, 0x687362, 0x687362, 0x677261, 0x677261, 0x667160, 0x65705F, 0x63705E, 0x63705E, 0x646F5F, 0x646F5F, 0x63705F, 0x657261, 0x657664, 0x677866, 0x667765, 0x667765, 0x647565, 0x627265, 0x627265, 0x5E6B62, 0x626F66, 0x56615D, 0x3E4945, 0x000903, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000D00, 0x000B00, 0x000C00, 0x000A00, 0x293426, 0x798174, 0x767C70, 0x7B7E73, 0x7C7F74, 0x7F8176, 0x808277, 0x818375, 0x828476, 0x858779, 0x87897B, 0x87897B, 0x86887A, 0x858779, 0x858779, 0x858779, 0x87897B, 0x898B7D, 0x8A8C7E, 0x87897B, 0x898B7D, 0x8D8F81, 0x8F9183, 0x909284, 0x909284, 0x909284, 0x919385, 0x8E9082, 0x8F9183, 0x8F9183, 0x8E9082, 0x8E9082, 0x8E9082, 0x8F9183, 0x909284, 0x8C8E80, 0x8C8E80, 0x8C8E80, 0x8B8D7F, 0x8B8D7F, 0x8A8C7E, 0x8A8C7E, 0x8A8C7E, 0x898A7C, 0x898A7C, 0x87897B, 0x888A7C, 0x898B7D, 0x898B7D, 0x888A7C, 0x86887A, 0x7D8172, 0x7D8172, 0x7C8071, 0x7C8071, 0x7B7F70, 0x797D6E, 0x787C6D, 0x787C6D, 0x757C6C, 0x747B6B, 0x737A6A, 0x717868, 0x6E7565, 0x6C7363, 0x6A7161, 0x697060, 0x6A7161, 0x697060, 0x686F5F, 0x676E5E, 0x666D5D, 0x656C5C, 0x646B5B, 0x636A5A, 0x5B6656, 0x5C6757, 0x5D6A59, 0x616E5D, 0x647360, 0x687764, 0x677866, 0x677866, 0x647666, 0x627466, 0x657568, 0x5D6D63, 0x626F66, 0x5B6861, 0x48534F, 0x010C04, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000D00, 0x000B00, 0x000C00, 0x000A00, 0x333E30, 0x798174, 0x757B6F, 0x76796E, 0x787B70, 0x7C7E73, 0x7E8075, 0x7E8072, 0x7E8072, 0x7E8072, 0x7F8173, 0x848678, 0x838577, 0x828476, 0x828476, 0x828476, 0x848678, 0x86887A, 0x87897B, 0x86887A, 0x898B7D, 0x8D8F81, 0x8F9183, 0x909284, 0x909284, 0x909284, 0x919385, 0x919385, 0x929486, 0x929486, 0x909284, 0x8F9183, 0x8F9183, 0x919385, 0x939587, 0x919385, 0x909284, 0x909284, 0x909284, 0x8F9183, 0x8F9183, 0x8E9082, 0x8E9082, 0x909183, 0x8E8F81, 0x8C8D7F, 0x8C8D7F, 0x8B8C7E, 0x8A8B7D, 0x88897B, 0x87887A, 0x888A7C, 0x888A7C, 0x87897B, 0x86887A, 0x838778, 0x838778, 0x828677, 0x828677, 0x808475, 0x7F8374, 0x7D8172, 0x7B7F70, 0x797D6E, 0x767A6B, 0x75796A, 0x747869, 0x767A6B, 0x75796A, 0x747869, 0x727667, 0x707465, 0x6E7263, 0x6D7162, 0x6C7061, 0x6C7465, 0x6B7364, 0x687362, 0x697463, 0x687764, 0x687764, 0x657663, 0x637461, 0x657865, 0x617363, 0x677868, 0x5F6F64, 0x5E6E64, 0x5D6C65, 0x4F5B57, 0x07140B, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000D00, 0x000B00, 0x000D00, 0x000A00, 0x3D483A, 0x7C8477, 0x777D71, 0x777A6F, 0x797C71, 0x7C7E73, 0x7D7F74, 0x7C7E70, 0x7A7C6E, 0x7A7C6E, 0x7A7C6E, 0x7B7D6F, 0x7B7D6F, 0x7B7D6F, 0x7C7E70, 0x7E8072, 0x808274, 0x828476, 0x838577, 0x87897B, 0x8A8C7E, 0x8D8F81, 0x909284, 0x909284, 0x909284, 0x909284, 0x919385, 0x8F9183, 0x909284, 0x909284, 0x8E9082, 0x8C8E80, 0x8B8D7F, 0x8E9082, 0x919385, 0x8F9183, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8C8E80, 0x8C8E80, 0x8C8E80, 0x8E8F81, 0x8C8D7F, 0x8B8C7E, 0x8A8B7D, 0x8B8C7E, 0x8B8C7E, 0x898B7D, 0x888A7C, 0x888A7C, 0x87897B, 0x858779, 0x848678, 0x818576, 0x818576, 0x808475, 0x808475, 0x7C8373, 0x7C8373, 0x7C8373, 0x7B8272, 0x7A8171, 0x798070, 0x7B7F70, 0x7A7E6F, 0x7C8071, 0x7B7F70, 0x7A7E6F, 0x797D6E, 0x787A6C, 0x77796B, 0x76786A, 0x747869, 0x767D6D, 0x747D6C, 0x707B6A, 0x6F7A69, 0x6D7A68, 0x6C7967, 0x677865, 0x657663, 0x677A67, 0x607262, 0x677969, 0x617367, 0x5E6E63, 0x5E6E64, 0x55645D, 0x0F1F15, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000A00, 0x000D00, 0x000C00, 0x485345, 0x828A7D, 0x7D8377, 0x878A7F, 0x888B80, 0x8A8C81, 0x8A8C81, 0x888A7C, 0x888A7C, 0x898B7D, 0x8A8C7E, 0x86887A, 0x87897B, 0x888A7C, 0x898B7D, 0x8A8C7E, 0x8B8D7F, 0x8C8E80, 0x8C8E80, 0x888A7C, 0x8B8D7F, 0x8F9183, 0x919385, 0x919385, 0x919385, 0x929486, 0x929486, 0x8F9183, 0x919385, 0x909284, 0x8D8F81, 0x8A8C7E, 0x898B7D, 0x8C8E80, 0x909284, 0x848678, 0x848678, 0x848678, 0x838577, 0x838577, 0x828476, 0x828476, 0x818375, 0x898A7C, 0x87887A, 0x868779, 0x87887A, 0x87897B, 0x888A7C, 0x87897B, 0x87897B, 0x888C7D, 0x878B7C, 0x85897A, 0x838778, 0x828677, 0x818576, 0x818576, 0x818576, 0x7D8675, 0x7D8675, 0x7D8675, 0x7D8675, 0x7D8675, 0x7C8574, 0x7D8474, 0x7D8474, 0x767D6D, 0x767D6D, 0x787C6D, 0x787C6D, 0x777B6C, 0x777B6C, 0x787A6C, 0x787A6C, 0x737A6A, 0x727969, 0x717A69, 0x717A69, 0x6F7C68, 0x6F7C68, 0x6A7B68, 0x697A67, 0x6A7D6A, 0x5F7161, 0x687A6A, 0x65776B, 0x5F6F64, 0x5E6E64, 0x5B6A63, 0x1A2A20, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000C00, 0x000A00, 0x000E00, 0x000E00, 0x4F5A4C, 0x868E81, 0x82887C, 0x878A7F, 0x888B80, 0x898B80, 0x888A7F, 0x888A7C, 0x8A8C7E, 0x8D8F81, 0x909284, 0x8D8F81, 0x8D8F81, 0x8E9082, 0x8E9082, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8C8E80, 0x898B7D, 0x8C8E80, 0x909284, 0x929486, 0x939587, 0x929486, 0x939587, 0x939587, 0x959789, 0x96988A, 0x96988A, 0x939587, 0x8F9183, 0x8E9082, 0x919385, 0x959789, 0x909284, 0x909284, 0x909284, 0x8F9183, 0x8F9183, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8C8E80, 0x898B7D, 0x86887A, 0x848678, 0x828476, 0x818375, 0x7D8172, 0x7C8071, 0x7F8374, 0x7D8172, 0x7B7F70, 0x797D6E, 0x767D6D, 0x757C6C, 0x757C6C, 0x757C6C, 0x737E6D, 0x727D6C, 0x727D6C, 0x717C6B, 0x727B6A, 0x717A69, 0x707968, 0x707968, 0x727969, 0x727969, 0x75796A, 0x767A6B, 0x767A6B, 0x777B6C, 0x787C6D, 0x787C6D, 0x747B6B, 0x737A6A, 0x727B68, 0x727B68, 0x6F7C68, 0x6F7C68, 0x6A7C66, 0x697A67, 0x6C7F6B, 0x5E7060, 0x697B6B, 0x687A6E, 0x5E7064, 0x5D6E64, 0x5F6E67, 0x223228, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000E00, 0x000800, 0x001300, 0x000600, 0x000E00, 0x606B5B, 0x7C8475, 0x777D6F, 0x787C6E, 0x7B7F71, 0x818376, 0x86887B, 0x8A8C7F, 0x8C8E81, 0x8C8E81, 0x8B8D80, 0x8E9082, 0x8E9082, 0x8E9082, 0x8F9183, 0x8E9082, 0x8E9082, 0x8D8F81, 0x8C8E80, 0x8A8C7E, 0x8D8F81, 0x919385, 0x949688, 0x949688, 0x949688, 0x949688, 0x949688, 0x959789, 0x959789, 0x959789, 0x959789, 0x949688, 0x939587, 0x929486, 0x929486, 0x848678, 0x848678, 0x848678, 0x838577, 0x818375, 0x818375, 0x828476, 0x828476, 0x848678, 0x848678, 0x838577, 0x838577, 0x848678, 0x86887A, 0x858779, 0x828476, 0x858779, 0x86887A, 0x888A7C, 0x888A7C, 0x87897B, 0x858779, 0x828476, 0x808274, 0x838578, 0x838578, 0x828477, 0x808275, 0x7F8174, 0x7E8073, 0x7C7E71, 0x7C7E71, 0x76786B, 0x75776A, 0x747669, 0x737568, 0x717366, 0x707265, 0x6F7164, 0x6E7063, 0x6B7163, 0x6D7365, 0x6F7768, 0x727A6B, 0x707D6B, 0x707D6B, 0x6D7F69, 0x6C7E68, 0x6A7D69, 0x687B67, 0x677A67, 0x657865, 0x647666, 0x657769, 0x536356, 0x344437, 0x000900, 0x000F00, 0x000900, 0x000700, 0x000900, 0x000D00, 0x000900, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000800, 0x001200, 0x000700, 0x031100, 0x6D7868, 0x858D7E, 0x808678, 0x84887A, 0x868A7C, 0x898B7E, 0x8C8E81, 0x8E9083, 0x8F9184, 0x909285, 0x909285, 0x8F9183, 0x8F9183, 0x8F9183, 0x909284, 0x909284, 0x919385, 0x919385, 0x919385, 0x8A8C7E, 0x8D8F81, 0x919385, 0x949688, 0x949688, 0x949688, 0x949688, 0x949688, 0x959789, 0x959789, 0x959789, 0x959789, 0x949688, 0x939587, 0x939587, 0x929486, 0x929486, 0x929486, 0x929486, 0x929486, 0x919385, 0x909284, 0x909284, 0x919385, 0x8F9183, 0x8E9082, 0x8C8E80, 0x8A8C7E, 0x898B7D, 0x87897B, 0x858779, 0x828476, 0x828476, 0x818375, 0x7F8173, 0x7D7F71, 0x7C7E70, 0x7C7E70, 0x7D7F71, 0x7E8072, 0x797B6E, 0x797B6E, 0x787A6D, 0x76786B, 0x75776A, 0x737568, 0x727467, 0x727467, 0x797B6E, 0x797B6E, 0x787A6D, 0x76786B, 0x75776A, 0x737568, 0x727467, 0x727467, 0x6E7466, 0x6F7567, 0x71796A, 0x737B6C, 0x717E6C, 0x717E6C, 0x6D7F69, 0x6C7E68, 0x6B7E6A, 0x697C68, 0x697C69, 0x667966, 0x657666, 0x667669, 0x57675A, 0x3C4C3F, 0x000900, 0x000D00, 0x000A00, 0x000700, 0x000900, 0x000C00, 0x000900, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000C00, 0x000B00, 0x001100, 0x000900, 0x000E00, 0x737E6E, 0x828A7B, 0x7E8476, 0x868A7C, 0x868A7C, 0x86887B, 0x86887B, 0x87897C, 0x888A7D, 0x898B7E, 0x898B7E, 0x8B8D7F, 0x8A8C7E, 0x8A8C7E, 0x8B8D7F, 0x8C8E80, 0x8E9082, 0x909284, 0x919385, 0x8B8D7F, 0x8E9082, 0x929486, 0x959789, 0x959789, 0x959789, 0x959789, 0x959789, 0x96988A, 0x96988A, 0x96988A, 0x96988A, 0x959789, 0x949688, 0x949688, 0x939587, 0x848678, 0x858779, 0x858779, 0x858779, 0x858779, 0x858779, 0x848678, 0x838577, 0x848678, 0x848678, 0x86887A, 0x8A8C7E, 0x8E9082, 0x909284, 0x909284, 0x909284, 0x8F9183, 0x8D8F81, 0x8A8C7E, 0x87897B, 0x86887A, 0x86887A, 0x87897B, 0x87897B, 0x86887B, 0x85877A, 0x848679, 0x838578, 0x828477, 0x808275, 0x7F8174, 0x7F8174, 0x7E8073, 0x7D7F72, 0x7C7E71, 0x7B7D70, 0x7A7C6F, 0x787A6D, 0x77796C, 0x767A6C, 0x717769, 0x71796A, 0x717C6C, 0x737E6E, 0x727F6D, 0x727F6D, 0x6E806A, 0x6D7F69, 0x6B7E6A, 0x6A7D69, 0x6B7E6B, 0x687B68, 0x667767, 0x67776A, 0x5D6D60, 0x47574A, 0x000A00, 0x000B00, 0x000A00, 0x000800, 0x000900, 0x000A00, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x001000, 0x000F00, 0x000A00, 0x021000, 0x7A8575, 0x81897A, 0x7D8375, 0x84887A, 0x84887A, 0x848679, 0x848679, 0x848679, 0x838578, 0x848679, 0x848679, 0x898B7D, 0x888A7C, 0x888A7C, 0x888A7C, 0x898B7D, 0x8B8D7F, 0x8E9082, 0x8F9183, 0x8C8E80, 0x8F9183, 0x939587, 0x959789, 0x96988A, 0x959789, 0x959789, 0x96988A, 0x96988A, 0x96988A, 0x97998B, 0x97998B, 0x96988A, 0x96988A, 0x959789, 0x959789, 0x959789, 0x959789, 0x959789, 0x96988A, 0x96988A, 0x96988A, 0x949688, 0x939587, 0x909485, 0x8E9283, 0x8E9283, 0x909485, 0x919586, 0x8F9384, 0x8D9182, 0x8D9182, 0x8F9384, 0x8F9384, 0x8E9283, 0x8D9182, 0x8C9081, 0x898D7E, 0x878B7C, 0x868A7B, 0x898B7D, 0x898B7D, 0x888A7C, 0x87897B, 0x86887A, 0x858779, 0x848678, 0x838577, 0x818375, 0x808274, 0x7F8173, 0x7E8072, 0x7D7F71, 0x7C7E70, 0x7B7D6F, 0x7A7E6F, 0x757B6D, 0x757D6E, 0x747F6F, 0x758070, 0x72816E, 0x72816E, 0x6E806A, 0x6E806A, 0x6B7E6A, 0x6B7E6A, 0x6D7E6C, 0x6A7B69, 0x657666, 0x67776A, 0x637164, 0x536154, 0x000E00, 0x000900, 0x000A00, 0x000B00, 0x000800, 0x000800, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000E00, 0x000D00, 0x000C00, 0x0C1A09, 0x859080, 0x889081, 0x868C7E, 0x8A8E80, 0x8A8E80, 0x8D8F82, 0x8E9083, 0x8E9083, 0x8D8F82, 0x8C8E81, 0x8C8E81, 0x8F9183, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8F9183, 0x919385, 0x929486, 0x8C8E80, 0x909284, 0x949688, 0x96988A, 0x97998B, 0x96988A, 0x96988A, 0x96988A, 0x97998B, 0x97998B, 0x989A8C, 0x989A8C, 0x97998B, 0x97998B, 0x96988A, 0x96988A, 0x949688, 0x929486, 0x919385, 0x929486, 0x939587, 0x939587, 0x929486, 0x909284, 0x949889, 0x909485, 0x909485, 0x949889, 0x949889, 0x909485, 0x8D9182, 0x8D9182, 0x888C7D, 0x888C7D, 0x888C7D, 0x898D7E, 0x898D7E, 0x898D7E, 0x888C7D, 0x888C7D, 0x86887A, 0x86887A, 0x858779, 0x848678, 0x838577, 0x828476, 0x818375, 0x818375, 0x828476, 0x828476, 0x818375, 0x808274, 0x7F8173, 0x7E8072, 0x7D7F71, 0x7C8071, 0x788071, 0x768171, 0x758271, 0x758271, 0x73826F, 0x72816E, 0x6F816B, 0x6F816B, 0x6B7E6A, 0x6A7D69, 0x6D7E6C, 0x6B7C6A, 0x677566, 0x69776A, 0x677367, 0x5B675B, 0x051604, 0x000900, 0x000900, 0x000D00, 0x000800, 0x000700, 0x000B00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000C00, 0x000D00, 0x000C00, 0x162413, 0x838E7E, 0x848C7D, 0x848A7C, 0x8A8E80, 0x8B8F81, 0x8E9083, 0x909285, 0x919386, 0x929487, 0x919386, 0x909285, 0x929486, 0x929486, 0x919385, 0x909284, 0x909284, 0x919385, 0x929486, 0x939587, 0x8D8F81, 0x909284, 0x949688, 0x97998B, 0x97998B, 0x97998B, 0x97998B, 0x97998B, 0x989A8C, 0x989A8C, 0x989A8C, 0x999B8D, 0x999B8D, 0x989A8C, 0x989A8C, 0x97998B, 0x858779, 0x838577, 0x808274, 0x808274, 0x828476, 0x828476, 0x818375, 0x7F8173, 0x727667, 0x6F7364, 0x717566, 0x787C6D, 0x7B7F70, 0x787C6D, 0x767A6B, 0x797D6E, 0x838778, 0x818576, 0x7E8273, 0x7D8172, 0x7F8374, 0x848879, 0x8A8E7F, 0x8D9182, 0x8A8C7E, 0x8A8C7E, 0x898B7D, 0x888A7C, 0x888A7C, 0x87897B, 0x86887A, 0x86887A, 0x838577, 0x828476, 0x828476, 0x818375, 0x808274, 0x7F8173, 0x7E8072, 0x7D8172, 0x7A8273, 0x778272, 0x768372, 0x768372, 0x728370, 0x71826F, 0x70826C, 0x70826C, 0x6E7F6C, 0x6B7C69, 0x6D7E6C, 0x6D7B6A, 0x687667, 0x6B776B, 0x697569, 0x5F6B5F, 0x10210F, 0x000A00, 0x000800, 0x000F00, 0x000800, 0x000700, 0x000C00, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000E00, 0x000A00, 0x000E00, 0x000B00, 0x202E1D, 0x7E8979, 0x7E8677, 0x7F8577, 0x868A7C, 0x868A7C, 0x888A7D, 0x8A8C7F, 0x8C8E81, 0x8D8F82, 0x8F9184, 0x8F9184, 0x909284, 0x909284, 0x909284, 0x919385, 0x919385, 0x919385, 0x919385, 0x929486, 0x8E9082, 0x919385, 0x959789, 0x97998B, 0x989A8C, 0x989A8C, 0x989A8C, 0x989A8C, 0x989A8C, 0x999B8D, 0x999B8D, 0x999B8D, 0x999B8D, 0x999B8D, 0x999B8D, 0x989A8C, 0x9C9E90, 0x989A8C, 0x949688, 0x939587, 0x959789, 0x96988A, 0x959789, 0x939587, 0x929989, 0x8D9484, 0x8C9383, 0x919888, 0x8F9686, 0x878E7E, 0x838A7A, 0x848B7B, 0x7F8676, 0x7D8474, 0x798070, 0x777E6E, 0x798070, 0x7D8474, 0x838A7A, 0x898D7E, 0x878B7A, 0x888B7A, 0x878A79, 0x878A79, 0x868978, 0x858877, 0x858877, 0x848776, 0x838675, 0x838675, 0x828574, 0x818473, 0x818473, 0x808372, 0x7F8271, 0x7E8273, 0x798473, 0x778473, 0x768473, 0x758372, 0x728370, 0x71826F, 0x70836D, 0x6F826C, 0x71826F, 0x6C7D6A, 0x6F7D6C, 0x6F7D6C, 0x6B7769, 0x6C786C, 0x6C766B, 0x616D61, 0x1C2A19, 0x000C00, 0x000700, 0x000F00, 0x000800, 0x000800, 0x000D00, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000E00, 0x000800, 0x001000, 0x000D00, 0x2B3928, 0x828D7D, 0x858D7E, 0x878D7F, 0x888C7E, 0x878B7D, 0x888A7D, 0x888A7D, 0x8A8C7F, 0x8D8F82, 0x909285, 0x929487, 0x909284, 0x909284, 0x919385, 0x939587, 0x939587, 0x939587, 0x939587, 0x939587, 0x8E9082, 0x919385, 0x959789, 0x989A8C, 0x989A8C, 0x989A8C, 0x989A8C, 0x989A8C, 0x999B8D, 0x999B8D, 0x999B8D, 0x9A9C8E, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x989A8C, 0x939587, 0x8D8F81, 0x8C8E80, 0x8D8F81, 0x8F9183, 0x8E9082, 0x8D8F81, 0x8C9383, 0x878E7E, 0x899080, 0x929989, 0x949B8B, 0x8E9585, 0x8C9383, 0x8F9686, 0x8F9686, 0x8E9585, 0x8C9383, 0x8B9282, 0x8A9181, 0x8B9282, 0x8B9282, 0x8E9283, 0x898D7C, 0x8A8D7C, 0x8A8D7C, 0x898C7B, 0x888B7A, 0x888B7A, 0x878A79, 0x878A79, 0x838675, 0x838675, 0x838675, 0x828574, 0x818473, 0x808372, 0x808372, 0x7F8374, 0x798473, 0x778473, 0x768473, 0x758372, 0x728370, 0x71826F, 0x70836D, 0x70836D, 0x738471, 0x6D7E6B, 0x707E6D, 0x707E6D, 0x6D796B, 0x6E7A6E, 0x6D776C, 0x616D61, 0x22301F, 0x000E00, 0x000700, 0x000F00, 0x000800, 0x000800, 0x000E00, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000E00, 0x000C00, 0x364433, 0x838E80, 0x7F877A, 0x848A7E, 0x8C8F84, 0x8C8F84, 0x8E9085, 0x8E9085, 0x8F9183, 0x919385, 0x929486, 0x939587, 0x909284, 0x919385, 0x939587, 0x949688, 0x949688, 0x949688, 0x939587, 0x939587, 0x8D8F81, 0x949688, 0x999B8D, 0x9A9C8E, 0x999B8D, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x9A9C8E, 0x9A9C8E, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x97998B, 0x97998B, 0x949688, 0x909284, 0x8F9183, 0x8F9183, 0x8D8F81, 0x898B7D, 0x85897A, 0x878B7C, 0x8B8F80, 0x8F9384, 0x929687, 0x939788, 0x929687, 0x929687, 0x919586, 0x919586, 0x919586, 0x8F9384, 0x8E9283, 0x8D9182, 0x8E9283, 0x909485, 0x8A8E7F, 0x898D7E, 0x898D7E, 0x888C7D, 0x878B7C, 0x878B7C, 0x868A7B, 0x868A7B, 0x848879, 0x838778, 0x838778, 0x828677, 0x818576, 0x808475, 0x808475, 0x7E8575, 0x788675, 0x768775, 0x758674, 0x748573, 0x748572, 0x738471, 0x73856F, 0x73856F, 0x728370, 0x70816E, 0x6F806E, 0x6E7F6D, 0x6D7E6E, 0x6A7A6D, 0x667669, 0x637368, 0x324237, 0x000C01, 0x000A00, 0x000700, 0x000F00, 0x000B00, 0x000700, 0x000C00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x000E00, 0x001200, 0x010F00, 0x3F4D3C, 0x869183, 0x838B7E, 0x888E82, 0x85887D, 0x85887D, 0x85877C, 0x84867B, 0x838577, 0x838577, 0x838577, 0x838577, 0x808274, 0x828476, 0x858779, 0x898B7D, 0x8E9082, 0x939587, 0x97998B, 0x999B8D, 0x8E9082, 0x959789, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x9A9C8E, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x9D9F91, 0x9EA092, 0x9D9F91, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x97998B, 0x929486, 0x929687, 0x949889, 0x95998A, 0x979B8C, 0x979B8C, 0x969A8B, 0x949889, 0x939788, 0x8F9384, 0x8F9384, 0x8F9384, 0x8E9283, 0x8E9283, 0x8D9182, 0x8C9081, 0x8B8F80, 0x8B8F80, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x888C7D, 0x878B7C, 0x878B7C, 0x868A7B, 0x888C7D, 0x878B7C, 0x878B7C, 0x868A7B, 0x85897A, 0x848879, 0x848879, 0x818878, 0x788675, 0x768775, 0x758674, 0x758674, 0x748572, 0x748572, 0x748670, 0x748670, 0x728370, 0x71826F, 0x6F806E, 0x6E7F6D, 0x6D7E6E, 0x6A7A6D, 0x67776A, 0x647469, 0x3D4D42, 0x000C01, 0x000B00, 0x000800, 0x000F00, 0x000A00, 0x000700, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x000D00, 0x001000, 0x000E00, 0x485645, 0x848F81, 0x848C7F, 0x878D81, 0x8C8F84, 0x8C8F84, 0x8E9085, 0x8F9186, 0x909284, 0x929486, 0x939587, 0x939587, 0x9A9C8E, 0x999B8D, 0x97998B, 0x949688, 0x939587, 0x939587, 0x939587, 0x939587, 0x8E9082, 0x959789, 0x9A9C8E, 0x9A9C8E, 0x9A9C8E, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x9B9D8F, 0x9B9D8F, 0x9C9E90, 0x9C9E90, 0x9C9E90, 0x9C9E90, 0x9B9D8F, 0x9B9D8F, 0x8F9183, 0x8F9183, 0x8C8E80, 0x888A7C, 0x87897B, 0x888A7C, 0x86887A, 0x838577, 0x8B8F80, 0x8B8F80, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x878B7C, 0x85897A, 0x848879, 0x929687, 0x909485, 0x8F9384, 0x909485, 0x919586, 0x919586, 0x8E9283, 0x8C9081, 0x8C9081, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x888C7D, 0x878B7C, 0x868A7B, 0x85897A, 0x848879, 0x848879, 0x838778, 0x828677, 0x818576, 0x818576, 0x808475, 0x7E8575, 0x798776, 0x778876, 0x768775, 0x768775, 0x768774, 0x768774, 0x768872, 0x768872, 0x738471, 0x728370, 0x70816F, 0x6F806E, 0x6E7F6F, 0x6B7B6E, 0x68786B, 0x65756A, 0x4C5C51, 0x000D02, 0x000A00, 0x000C00, 0x000D00, 0x000800, 0x000700, 0x000900, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000B00, 0x000E00, 0x000E00, 0x546251, 0x828D7F, 0x858D80, 0x868C80, 0x84877C, 0x85887D, 0x888A7F, 0x8A8C81, 0x8D8F81, 0x8E9082, 0x909284, 0x919385, 0x929486, 0x929486, 0x929486, 0x939587, 0x959789, 0x96988A, 0x97998B, 0x989A8C, 0x8F9183, 0x96988A, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x9C9E90, 0x9C9E90, 0x9B9D8F, 0x9C9E90, 0x9C9E90, 0x9C9E90, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9C9E90, 0x9C9E90, 0xA6A89A, 0xA3A597, 0x9C9E90, 0x959789, 0x949688, 0x97998B, 0x989A8C, 0x96988A, 0x969A8B, 0x969A8B, 0x969A8B, 0x95998A, 0x949889, 0x939788, 0x929687, 0x919586, 0x868A7B, 0x828677, 0x7F8374, 0x808475, 0x848879, 0x868A7B, 0x848879, 0x818576, 0x7F8374, 0x7E8273, 0x7D8172, 0x7C8071, 0x7A7E6F, 0x797D6E, 0x787C6D, 0x777B6C, 0x7F8374, 0x7F8374, 0x7E8273, 0x7D8172, 0x7C8071, 0x7C8071, 0x7B7F70, 0x798070, 0x768473, 0x738472, 0x738472, 0x738472, 0x738471, 0x738471, 0x748670, 0x748670, 0x748572, 0x738471, 0x718270, 0x718270, 0x6F8070, 0x6D7D70, 0x69796C, 0x66766B, 0x59695E, 0x000D02, 0x000900, 0x000E00, 0x000C00, 0x000700, 0x000A00, 0x000900, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000E00, 0x000C00, 0x001000, 0x021000, 0x647261, 0x838E80, 0x8A9285, 0x888E82, 0x8A8D82, 0x8B8E83, 0x8C8E83, 0x8D8F84, 0x8D8F81, 0x8D8F81, 0x8C8E80, 0x8C8E80, 0x86887A, 0x888A7C, 0x8D8F81, 0x929486, 0x96988A, 0x989A8C, 0x999B8D, 0x999B8D, 0x909284, 0x97998B, 0x9C9E90, 0x9C9E90, 0x9B9D8F, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9C9E90, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9A9C8E, 0x989A8C, 0x939587, 0x8E9082, 0x8D8F81, 0x909284, 0x919385, 0x8F9183, 0x8C9081, 0x8B8F80, 0x8B8F80, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x898D7E, 0x888C7D, 0x939788, 0x8E9283, 0x8A8E7F, 0x8B8F80, 0x8F9384, 0x939788, 0x939788, 0x929687, 0x919586, 0x909485, 0x8F9384, 0x8E9283, 0x8C9081, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x838778, 0x828677, 0x828677, 0x818576, 0x808475, 0x7F8374, 0x7F8374, 0x7D8474, 0x738170, 0x718270, 0x70816F, 0x70816F, 0x70816E, 0x71826F, 0x71836D, 0x71836D, 0x758673, 0x748572, 0x738472, 0x728371, 0x718272, 0x6F7F72, 0x6B7B6E, 0x68786D, 0x617166, 0x011106, 0x000900, 0x000C00, 0x000B00, 0x000800, 0x000B00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000E00, 0x000B00, 0x000E00, 0x010F00, 0x707E6D, 0x818C7E, 0x8B9386, 0x878D81, 0x8F9287, 0x909388, 0x93958A, 0x94968B, 0x959789, 0x959789, 0x949688, 0x949688, 0x929486, 0x949688, 0x97998B, 0x999B8D, 0x9A9C8E, 0x989A8C, 0x959789, 0x939587, 0x919385, 0x97998B, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9D9F91, 0x9EA092, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9EA092, 0x9EA092, 0x9EA092, 0x9EA092, 0x9D9F91, 0x9D9F91, 0x9B9D8F, 0x9C9E90, 0x9C9E90, 0x9A9C8E, 0x9B9D8F, 0x9EA092, 0x9C9E90, 0x989A8C, 0x979B8C, 0x979B8C, 0x979B8C, 0x969A8B, 0x95998A, 0x949889, 0x929687, 0x919586, 0x8D9182, 0x8A8E7F, 0x878B7C, 0x868A7B, 0x898D7E, 0x8C9081, 0x8D9182, 0x8D9182, 0x8C9081, 0x8C9081, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x878B7C, 0x878B7C, 0x868A7B, 0x888C7D, 0x878B7C, 0x878B7C, 0x868A7B, 0x85897A, 0x848879, 0x848879, 0x818878, 0x7A8877, 0x778876, 0x778876, 0x768775, 0x768774, 0x778875, 0x778973, 0x778973, 0x768774, 0x758673, 0x748573, 0x738472, 0x728373, 0x708073, 0x6D7D70, 0x6A7A6F, 0x647469, 0x0A1A0F, 0x000C00, 0x000800, 0x000B00, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x000900, 0x000D00, 0x010F00, 0x798776, 0x808B7D, 0x8B9386, 0x868C80, 0x82857A, 0x83867B, 0x87897E, 0x898B80, 0x8B8D7F, 0x8C8E80, 0x8C8E80, 0x8C8E80, 0x8B8D7F, 0x8D8F81, 0x919385, 0x959789, 0x97998B, 0x989A8C, 0x989A8C, 0x989A8C, 0x919385, 0x989A8C, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9EA092, 0x9EA092, 0x9D9F91, 0x9EA092, 0x9EA092, 0x9EA092, 0x9FA193, 0x9FA193, 0x9EA092, 0x9EA092, 0x9EA092, 0xA0A294, 0xA0A294, 0x9EA092, 0x9B9D8F, 0x9C9E90, 0x9FA193, 0x9FA193, 0x9D9F91, 0x989C8D, 0x999D8E, 0x9A9E8F, 0x9A9E8F, 0x999D8E, 0x979B8C, 0x95998A, 0x939788, 0x95998A, 0x949889, 0x949889, 0x939788, 0x939788, 0x939788, 0x929687, 0x929687, 0x919586, 0x909485, 0x909485, 0x8F9384, 0x8E9283, 0x8D9182, 0x8C9081, 0x8C9081, 0x898D7E, 0x898D7E, 0x888C7D, 0x888C7D, 0x878B7C, 0x868A7B, 0x85897A, 0x838A7A, 0x808E7D, 0x7D8E7C, 0x7D8E7C, 0x7C8D7B, 0x7C8D7A, 0x7B8C79, 0x7C8E78, 0x7C8E78, 0x768774, 0x758673, 0x748573, 0x748573, 0x738474, 0x718174, 0x6E7E71, 0x6B7B70, 0x66766B, 0x16261B, 0x011202, 0x000700, 0x000C00, 0x000B00, 0x000700, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x001100, 0x000C00, 0x001000, 0x041201, 0x82907F, 0x838E80, 0x90988B, 0x898F83, 0x93968B, 0x94978C, 0x96988D, 0x97998E, 0x97998B, 0x959789, 0x949688, 0x939587, 0x959789, 0x96988A, 0x97998B, 0x989A8C, 0x999B8D, 0x9A9C8E, 0x9A9C8E, 0x9A9C8E, 0x929486, 0x989A8C, 0x9EA092, 0x9EA092, 0x9D9F91, 0x9EA092, 0x9FA193, 0x9D9F91, 0x9EA092, 0x9EA092, 0x9FA193, 0x9FA193, 0x9FA193, 0x9FA193, 0x9EA092, 0x9EA092, 0x949688, 0x909284, 0x87897B, 0x7F8173, 0x7E8072, 0x848678, 0x888A7C, 0x898B7D, 0x8F9384, 0x919586, 0x939788, 0x95998A, 0x95998A, 0x939788, 0x919586, 0x909485, 0x949889, 0x969A8B, 0x979B8C, 0x979B8C, 0x949889, 0x919586, 0x8F9384, 0x8E9283, 0x8F9384, 0x8F9384, 0x8E9283, 0x8E9283, 0x8D9182, 0x8C9081, 0x8C9081, 0x8B8F80, 0x8B8F80, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x898D7E, 0x888C7D, 0x878B7C, 0x858C7C, 0x7E8C7B, 0x7B8C7A, 0x7A8B79, 0x798A78, 0x798A77, 0x788976, 0x788A74, 0x788A74, 0x778875, 0x768774, 0x758674, 0x758674, 0x748575, 0x728275, 0x6E7E71, 0x6C7C71, 0x67776C, 0x1F2F24, 0x061707, 0x000700, 0x000E00, 0x000C00, 0x000700, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000E00, 0x000800, 0x031100, 0x0E1B0A, 0x838E7E, 0x888E80, 0x81877B, 0x85887D, 0x8B8D82, 0x8D8F84, 0x909189, 0x91928A, 0x92938B, 0x94958D, 0x949790, 0x969990, 0x8F9587, 0x929989, 0x929989, 0x919888, 0x95998A, 0x999D8E, 0x9A9E8F, 0x999D8E, 0x929687, 0x999D8E, 0x9FA193, 0x9EA092, 0x9EA092, 0x9FA193, 0xA0A294, 0x9FA193, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0x9C9E90, 0x9A9C8E, 0x989A8C, 0x96988A, 0x939587, 0x8F9183, 0x8B8D7F, 0x888A7C, 0x848879, 0x818576, 0x838778, 0x878B7C, 0x838778, 0x7C8071, 0x7E8273, 0x868A7B, 0x818576, 0x848879, 0x7D8172, 0x707465, 0x727667, 0x7F8374, 0x7E8273, 0x6F7666, 0x6F7A69, 0x75806F, 0x7B8473, 0x7E8273, 0x828476, 0x8A8B7D, 0x8E9082, 0x8E9283, 0x8B9282, 0x889180, 0x85907F, 0x868F7E, 0x868F7E, 0x898D7E, 0x8A8B7D, 0x888A7C, 0x818C7B, 0x7E8D7A, 0x7D8C79, 0x7C8B78, 0x7B8A77, 0x7B8A77, 0x7C8977, 0x7C8977, 0x7C8977, 0x7B8876, 0x798674, 0x798674, 0x788573, 0x778472, 0x74816F, 0x727F6D, 0x6B7C6A, 0x2F402E, 0x021301, 0x000900, 0x000900, 0x000700, 0x000D00, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000700, 0x021000, 0x162312, 0x889383, 0x8F9587, 0x8B9185, 0x909388, 0x8D8F84, 0x8E9085, 0x8E8F87, 0x8D8E86, 0x8B8C84, 0x8A8B83, 0x888B82, 0x898C81, 0x858B7D, 0x888F7F, 0x8A9181, 0x8A9181, 0x8E9283, 0x939788, 0x969A8B, 0x95998A, 0x939788, 0x999D8E, 0x9FA193, 0x9FA193, 0x9EA092, 0x9FA193, 0xA0A294, 0x9FA193, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0xA1A395, 0x9D9F91, 0x9C9E90, 0x9EA092, 0xA2A496, 0xA5A799, 0xA4A698, 0x9EA092, 0x989A8C, 0x9DA192, 0x9B9F90, 0x9DA192, 0xA1A596, 0x9FA394, 0x999D8E, 0x9B9F90, 0xA2A697, 0x969A8B, 0x999D8E, 0x95998A, 0x8F9384, 0x919586, 0x989C8D, 0x949889, 0x868D7D, 0x7D8877, 0x818C7B, 0x858E7D, 0x85897A, 0x87897B, 0x8C8D7F, 0x8E9283, 0x8D9484, 0x899281, 0x85907F, 0x82917E, 0x828F7D, 0x838E7D, 0x868D7D, 0x878B7C, 0x848B7B, 0x83907E, 0x7F907D, 0x81907D, 0x808F7C, 0x7F8E7B, 0x7F8E7B, 0x7E8D7A, 0x7E8D7A, 0x7B8A77, 0x7A8976, 0x798875, 0x788774, 0x788774, 0x768572, 0x73826F, 0x71806D, 0x6B7C6A, 0x3B4C3A, 0x000F00, 0x000B00, 0x000A00, 0x000900, 0x000C00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000A00, 0x021000, 0x202D1C, 0x879282, 0x8D9385, 0x888E80, 0x8E9284, 0x97998E, 0x989A8F, 0x9A9C91, 0x9A9C91, 0x9A9C91, 0x999B90, 0x999C93, 0x9A9D92, 0x979D8F, 0x99A090, 0x99A090, 0x969D8D, 0x979B8C, 0x9A9E8F, 0x9A9E8F, 0x989C8D, 0x939788, 0x9A9E8F, 0x9FA193, 0x9FA193, 0x9FA193, 0xA0A294, 0xA1A395, 0xA0A294, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA5A799, 0x9A9C8E, 0x8C8E80, 0x858779, 0x86887A, 0x8A8C7E, 0x8C8E80, 0x8B8D7F, 0x8B8F80, 0x898D7E, 0x8B8F80, 0x8F9384, 0x8D9182, 0x898D7E, 0x8B8F80, 0x919586, 0x969A8B, 0x95998A, 0x929687, 0x919586, 0x939788, 0x95998A, 0x919586, 0x899080, 0x8A9382, 0x8C9584, 0x8C9383, 0x8B8D7F, 0x8A8C7E, 0x8E8F81, 0x8E9283, 0x8D9484, 0x8A9584, 0x889583, 0x839481, 0x829380, 0x859481, 0x869381, 0x869180, 0x848F7E, 0x81907D, 0x7F907D, 0x7F907D, 0x7E8F7C, 0x7D8E7B, 0x7C8D7A, 0x7B8C79, 0x7A8B78, 0x7A8B78, 0x798A77, 0x788976, 0x778875, 0x778875, 0x758673, 0x728370, 0x70816E, 0x6B7C6A, 0x4D5E4C, 0x000B00, 0x000C00, 0x000C00, 0x000B00, 0x000C00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x000C00, 0x010F00, 0x2C3928, 0x869181, 0x8A9082, 0x858B7D, 0x8C9082, 0x808275, 0x838578, 0x86887B, 0x888A7D, 0x898B80, 0x8A8C81, 0x8C8F84, 0x8D9085, 0x878E7E, 0x8A9181, 0x8C9383, 0x8D9484, 0x929687, 0x989C8D, 0x9B9F90, 0x9A9E8F, 0x949889, 0x9A9E8F, 0xA0A294, 0xA0A294, 0x9FA193, 0xA1A395, 0xA2A496, 0xA1A395, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA2A496, 0xA1A395, 0x9FA193, 0x9D9F91, 0x9FA193, 0xA1A395, 0xA1A395, 0x9C9E90, 0x989A8C, 0x95998A, 0x949889, 0x95998A, 0x979B8C, 0x969A8B, 0x929687, 0x949889, 0x989C8D, 0x8D9182, 0x878B7C, 0x828677, 0x838778, 0x85897A, 0x85897A, 0x848879, 0x85897A, 0x87907F, 0x878E7E, 0x858C7C, 0x86887A, 0x858779, 0x87897B, 0x888C7D, 0x87907F, 0x869381, 0x81927F, 0x7F927E, 0x7E917D, 0x7E917D, 0x81907D, 0x808F7C, 0x7F8E7B, 0x7E8F7C, 0x7D907C, 0x7E917D, 0x7D907C, 0x7D907C, 0x7B8E7A, 0x7A8D79, 0x798C78, 0x7A8D79, 0x798C78, 0x768C77, 0x758B76, 0x758B76, 0x738974, 0x708671, 0x70836F, 0x697C69, 0x5E6F5D, 0x000A00, 0x000C00, 0x000D00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000D00, 0x000D00, 0x3C4938, 0x8A9585, 0x8F9587, 0x8B9183, 0x939789, 0x989A8D, 0x9A9C8F, 0x9B9D90, 0x9B9D90, 0x9A9C8E, 0x9A9C8E, 0x9A9E8F, 0x9A9E8F, 0x909787, 0x929989, 0x939A8A, 0x919888, 0x95998A, 0x989C8D, 0x999D8E, 0x989C8D, 0x95998A, 0x9B9F90, 0xA1A395, 0xA1A395, 0xA0A294, 0xA2A496, 0xA2A496, 0xA2A496, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA1A395, 0xA2A496, 0xA2A496, 0xA1A395, 0x9FA193, 0x9D9F91, 0x9C9E90, 0x9C9E90, 0x9CA091, 0x9B9F90, 0x9B9F90, 0x9CA091, 0x9B9F90, 0x989C8D, 0x989C8D, 0x9A9E8F, 0xA0A495, 0x9A9E8F, 0x969A8B, 0x989C8D, 0x999D8E, 0x979B8C, 0x979B8C, 0x9A9E8F, 0x909988, 0x8F9686, 0x8E9283, 0x8F9183, 0x909183, 0x909183, 0x8F9384, 0x8E9585, 0x8A9785, 0x879683, 0x839682, 0x829581, 0x839481, 0x859481, 0x859280, 0x84917F, 0x82917E, 0x81927F, 0x81927F, 0x81927F, 0x80917E, 0x7F907D, 0x7D907C, 0x7D907C, 0x7B8E7A, 0x7A8D79, 0x778D78, 0x768C77, 0x768C77, 0x748A75, 0x708872, 0x6F8570, 0x6A7D6A, 0x677866, 0x001100, 0x000C00, 0x000C00, 0x000B00, 0x000800, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x001000, 0x000C00, 0x485544, 0x899484, 0x8C9284, 0x888E80, 0x919587, 0x959789, 0x96988A, 0x989A8C, 0x97998B, 0x96988A, 0x96988A, 0x959988, 0x969A89, 0x9DA494, 0x9EA595, 0x9DA494, 0x99A090, 0x9A9E8F, 0x9B9F90, 0x9B9F90, 0x989C8D, 0x95998A, 0x9CA091, 0xA2A496, 0xA2A496, 0xA1A395, 0xA2A496, 0xA3A597, 0xA2A496, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA3A597, 0xA7A99B, 0xA1A395, 0x959789, 0x898B7D, 0x828476, 0x848678, 0x8D8F81, 0x959789, 0x8F9384, 0x8E9283, 0x8E9283, 0x8D9182, 0x8C9081, 0x8B8F80, 0x8A8E7F, 0x8A8E7F, 0x8D9182, 0x8B8F80, 0x8C9081, 0x8E9283, 0x8F9384, 0x8C9081, 0x888C7D, 0x878B7C, 0x949D8C, 0x919888, 0x909485, 0x939486, 0x929385, 0x929083, 0x8E9082, 0x8E9283, 0x8B9483, 0x879281, 0x849380, 0x84917F, 0x84917F, 0x87907F, 0x868F7E, 0x858E7D, 0x828D7C, 0x818E7C, 0x7F8E7B, 0x7F8E7B, 0x7F8E7B, 0x7F8E7B, 0x7E8F7C, 0x7E8F7C, 0x7C8F7B, 0x7B8E7A, 0x7A8D79, 0x798C78, 0x778D78, 0x758B76, 0x728873, 0x728571, 0x6D806D, 0x687967, 0x0E1F0D, 0x000B00, 0x000A00, 0x000B00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x011202, 0x000C00, 0x525F4E, 0x889383, 0x8A9082, 0x858C7C, 0x8F9384, 0x87897B, 0x898B7D, 0x8C8F7E, 0x8D907F, 0x8D907F, 0x8E9180, 0x8E9281, 0x909483, 0x818878, 0x858C7C, 0x888F7F, 0x899080, 0x909485, 0x969A8B, 0x9A9E8F, 0x9A9E8F, 0x969A8B, 0x9CA091, 0xA2A496, 0xA2A496, 0xA1A395, 0xA3A597, 0xA4A698, 0xA3A597, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA1A395, 0xA3A597, 0xA4A698, 0xA5A799, 0xA4A698, 0xA4A698, 0xA4A698, 0xA5A799, 0x9FA394, 0x9FA394, 0x9EA293, 0x9DA192, 0x9DA192, 0x9DA192, 0x9CA091, 0x9B9F90, 0x939788, 0x95998A, 0x969A8B, 0x95998A, 0x95998A, 0x95998A, 0x919586, 0x8D9182, 0x8A9382, 0x868D7D, 0x868A7B, 0x8A8B7D, 0x8A8B7D, 0x888679, 0x848577, 0x838577, 0x848879, 0x818878, 0x7C8776, 0x7D8675, 0x7E8776, 0x818576, 0x818274, 0x808173, 0x818A79, 0x7E8978, 0x7D8877, 0x7C8776, 0x7C8977, 0x7D8A78, 0x7E8D7A, 0x808F7C, 0x7E8F7C, 0x7D8E7B, 0x7B8E7A, 0x7A8D79, 0x7A8D79, 0x788B77, 0x738974, 0x738672, 0x718471, 0x657664, 0x1F301E, 0x000C00, 0x000A00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000900, 0x001101, 0x000B00, 0x5A6756, 0x8D9888, 0x919789, 0x8E9585, 0x999D8E, 0x97998B, 0x999B8D, 0x9B9E8D, 0x9B9E8D, 0x9B9E8D, 0x9B9E8D, 0x9A9F8B, 0x9CA08F, 0x9BA290, 0x9CA393, 0x9BA292, 0x989F8F, 0x9A9E8F, 0x9CA091, 0x9CA091, 0x9A9E8F, 0x969A8B, 0x9DA192, 0xA3A597, 0xA3A597, 0xA2A496, 0xA3A597, 0xA4A698, 0xA3A597, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA2A496, 0x9EA092, 0x999B8D, 0x959789, 0x959789, 0x96988A, 0x96988A, 0x96988A, 0x9CA091, 0x9CA091, 0x9B9F90, 0x9B9F90, 0x9B9F90, 0x9CA091, 0x9CA091, 0x9A9E8F, 0x9CA091, 0x9EA293, 0x9CA091, 0x969A8B, 0x95998A, 0x999D8E, 0x9A9E8F, 0x969A8B, 0x949D8C, 0x919888, 0x929687, 0x98998B, 0x9B998C, 0x989488, 0x949285, 0x929385, 0x929486, 0x8F9384, 0x8B9282, 0x8D9182, 0x8E9082, 0x918F82, 0x918D81, 0x8F8D80, 0x868A7B, 0x818A79, 0x808978, 0x7F8877, 0x7E8978, 0x808B7A, 0x828F7D, 0x84917F, 0x808F7C, 0x7F8E7B, 0x7C8D7A, 0x7B8C79, 0x7A8D79, 0x788B77, 0x768975, 0x738672, 0x758674, 0x627361, 0x2A3B29, 0x000C00, 0x000A00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000E00, 0x000D00, 0x000D00, 0x657261, 0x889383, 0x848A7C, 0x848B7B, 0x888C7D, 0x8F9183, 0x8F9183, 0x919385, 0x929486, 0x939685, 0x949786, 0x949887, 0x949887, 0x9A9C8E, 0x9C9E90, 0x9D9F91, 0x9C9E90, 0x9C9E90, 0x9EA092, 0x9D9F91, 0x9A9C8E, 0x999B8D, 0x9FA193, 0xA3A597, 0xA2A496, 0xA2A496, 0xA3A597, 0xA4A698, 0xA3A597, 0xA3A597, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA5A799, 0xA5A799, 0xA5A799, 0xA5A799, 0xA4A698, 0xA1A395, 0x9EA092, 0x9A9C8E, 0x96988A, 0x939587, 0x919385, 0x95998A, 0x969A8B, 0x939788, 0x8E9283, 0x8D9182, 0x909485, 0x929687, 0x919586, 0x8A8E7F, 0x898D7E, 0x898D7E, 0x898D7E, 0x8A8E7F, 0x8A8E7F, 0x898D7E, 0x878B7C, 0x8F9183, 0x919385, 0x949688, 0x949688, 0x939587, 0x929486, 0x929486, 0x929486, 0x919385, 0x919385, 0x919385, 0x919385, 0x909284, 0x8E9082, 0x8D8F81, 0x8C8E80, 0x888E80, 0x898F81, 0x879282, 0x869181, 0x82907F, 0x818F7E, 0x80917F, 0x819280, 0x7D907D, 0x7D907D, 0x7D907D, 0x7C8F7C, 0x7B8E7B, 0x798C79, 0x788977, 0x778878, 0x708075, 0x6D7D73, 0x35453A, 0x000B00, 0x000F00, 0x000700, 0x000700, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000E00, 0x001000, 0x031102, 0x737F71, 0x8F9A8C, 0x8E9488, 0x8E9486, 0x979B8D, 0x96988B, 0x96988B, 0x96988B, 0x97998C, 0x96988A, 0x96988A, 0x95998A, 0x949889, 0x9D9F91, 0xA0A294, 0xA0A294, 0x9EA092, 0x9D9F91, 0x9FA193, 0x9D9F91, 0x9A9C8E, 0x999B8D, 0x9FA193, 0xA4A698, 0xA3A597, 0xA2A496, 0xA3A597, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA4A698, 0xA5A799, 0xA5A799, 0xA5A799, 0xA5A799, 0xA0A294, 0xA1A395, 0xA3A597, 0xA4A698, 0xA6A89A, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA2A697, 0xA4A899, 0xA3A798, 0xA0A495, 0x9FA394, 0xA1A596, 0xA1A596, 0x9EA293, 0x9FA394, 0x9EA293, 0x9EA293, 0x9EA293, 0x9FA394, 0x9FA394, 0x9EA293, 0x9DA192, 0x97998B, 0x989A8C, 0x999B8D, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x989A8C, 0x96988A, 0x919385, 0x919385, 0x909284, 0x909284, 0x909284, 0x8F9183, 0x8F9183, 0x8E9283, 0x8C9284, 0x8C9485, 0x8A9585, 0x889383, 0x849281, 0x82907F, 0x80917F, 0x80917F, 0x7D907D, 0x7D907D, 0x7D907D, 0x7C8F7C, 0x7C8D7B, 0x7A8B79, 0x7A8877, 0x798778, 0x728277, 0x6F7F75, 0x3E4E43, 0x000A00, 0x001000, 0x000700, 0x000A00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000E01, 0x021003, 0x7E8A7E, 0x8D978C, 0x8F958B, 0x8B9187, 0x999C93, 0x9B9D92, 0x9B9D92, 0x9B9D92, 0x9B9D92, 0x9B9D90, 0x9A9C8F, 0x989C8E, 0x989C8E, 0x989A8C, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x9B9D8F, 0x9B9D8F, 0x989A8C, 0x9A9C8E, 0xA0A294, 0xA4A698, 0xA3A597, 0xA2A496, 0xA4A698, 0xA5A799, 0xA4A698, 0xA4A698, 0xA4A698, 0xA5A799, 0xA5A799, 0xA5A799, 0xA5A799, 0xA6A89A, 0xA6A89A, 0xA9AB9D, 0xA8AA9C, 0xA5A799, 0xA2A496, 0x9D9F91, 0x999B8D, 0x96988A, 0x949688, 0x9A9E8F, 0x9EA293, 0xA0A495, 0x9EA293, 0x9EA293, 0x9EA293, 0x9CA091, 0x989C8D, 0x9CA091, 0x9B9F90, 0x999D8E, 0x999D8E, 0x999D8E, 0x989C8D, 0x969A8B, 0x95998A, 0x97998B, 0x959789, 0x949688, 0x959789, 0x97998B, 0x97998B, 0x949688, 0x919385, 0x929486, 0x919385, 0x909284, 0x909284, 0x909284, 0x909284, 0x919385, 0x919586, 0x868E7F, 0x859080, 0x849180, 0x83907F, 0x808E7D, 0x7F8D7C, 0x7D8E7C, 0x7E8F7D, 0x7F907E, 0x7F907E, 0x7F907E, 0x808E7D, 0x7F8D7C, 0x7E8B7A, 0x7C8978, 0x7B8779, 0x748479, 0x718177, 0x4C5C51, 0x000900, 0x001101, 0x000700, 0x000F00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000D02, 0x000D03, 0x849086, 0x889289, 0x8D928B, 0x83897F, 0x91948B, 0x8B8C84, 0x8C8D85, 0x8D8E86, 0x8D8E86, 0x8E9085, 0x8E9085, 0x8D9085, 0x8D9085, 0x919385, 0x949688, 0x96988A, 0x96988A, 0x989A8C, 0x9B9D8F, 0x9B9D8F, 0x999B8D, 0x9B9D8F, 0xA1A395, 0xA5A799, 0xA4A698, 0xA3A597, 0xA5A799, 0xA6A89A, 0xA5A799, 0xA5A799, 0xA5A799, 0xA5A799, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA7A99B, 0xA8AA9C, 0xA7A99B, 0xA6A89A, 0xA4A698, 0xA2A496, 0xA0A294, 0x9EA092, 0x9D9F91, 0x949889, 0x979B8C, 0x999D8E, 0x989C8D, 0x989C8D, 0x999D8E, 0x979B8C, 0x939788, 0x969A8B, 0x949889, 0x919586, 0x8F9384, 0x8E9283, 0x8D9182, 0x8B8F80, 0x898D7E, 0x949688, 0x909284, 0x8E9082, 0x8F9183, 0x919385, 0x919385, 0x8D8F81, 0x898B7D, 0x909284, 0x8F9183, 0x8D8F81, 0x8C8E80, 0x8B8D7F, 0x8C8E80, 0x8D8F81, 0x8D9182, 0x889081, 0x889383, 0x889584, 0x889584, 0x869483, 0x869483, 0x869785, 0x879886, 0x80917F, 0x80917F, 0x818F7E, 0x818F7E, 0x808D7C, 0x7F8C7B, 0x7E8979, 0x7C887A, 0x78857B, 0x728278, 0x5A6A5F, 0x000B00, 0x001101, 0x000700, 0x001100, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000F02, 0x001005, 0x07140A, 0x8D998F, 0x8E988F, 0x949992, 0x8C9288, 0x94978E, 0x9A9B93, 0x9B9C94, 0x9C9D95, 0x9D9E96, 0x9EA095, 0x9EA095, 0x9DA095, 0x9DA095, 0x96988A, 0x999B8D, 0x9B9D8F, 0x9B9D8F, 0x9C9E90, 0x9FA193, 0xA0A294, 0x9D9F91, 0x9B9D8F, 0xA1A395, 0xA6A89A, 0xA5A799, 0xA4A698, 0xA5A799, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA5A799, 0xA4A698, 0xA3A597, 0xA3A597, 0x9EA293, 0xA0A495, 0xA0A495, 0x9EA293, 0x9EA293, 0xA0A495, 0x9FA394, 0x9DA192, 0xA1A596, 0x9FA394, 0x9DA192, 0x9CA091, 0x9B9F90, 0x9B9F90, 0x9A9E8F, 0x989C8D, 0x999B8D, 0x959789, 0x929486, 0x929486, 0x949688, 0x949688, 0x919385, 0x8D8F81, 0x8F9183, 0x8E9082, 0x8B8D7F, 0x898B7D, 0x888A7C, 0x888A7C, 0x888A7C, 0x888C7D, 0x797F71, 0x798172, 0x788373, 0x798474, 0x778473, 0x778473, 0x788675, 0x798776, 0x80917F, 0x80917F, 0x82907F, 0x82907F, 0x818E7D, 0x7F8C7B, 0x7F8A7A, 0x7C887A, 0x78857B, 0x738379, 0x647469, 0x021207, 0x000F00, 0x000700, 0x001000, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x001003, 0x122013, 0x909C90, 0x929C91, 0x989E94, 0x999F95, 0x9B9E95, 0x9A9C91, 0x9A9C91, 0x9B9D92, 0x9C9E93, 0x9C9E91, 0x9C9E91, 0x9B9F91, 0x9B9F91, 0x9C9E90, 0x9FA193, 0xA0A294, 0x9EA092, 0x9EA092, 0xA0A294, 0x9FA193, 0x9D9F91, 0x9C9E90, 0xA2A496, 0xA6A89A, 0xA6A89A, 0xA5A799, 0xA6A89A, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xABAD9F, 0xA9AB9D, 0xA6A89A, 0xA3A597, 0x9EA092, 0x9A9C8E, 0x97998B, 0x959789, 0x9DA192, 0x9EA293, 0x9DA192, 0x999D8E, 0x999D8E, 0x9CA091, 0x9CA091, 0x9B9F90, 0x9EA293, 0x9DA192, 0x9B9F90, 0x9CA091, 0x9DA192, 0x9FA394, 0x9FA394, 0x9FA394, 0x9EA092, 0x9C9E90, 0x999B8D, 0x989A8C, 0x999B8D, 0x999B8D, 0x96988A, 0x949688, 0x959789, 0x949688, 0x929486, 0x909284, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8D8F81, 0x8D9183, 0x8E9284, 0x8D9385, 0x8C9284, 0x879282, 0x869181, 0x859382, 0x869483, 0x819280, 0x819280, 0x819280, 0x80917F, 0x818F7E, 0x7F8D7C, 0x7E8B7A, 0x7D897B, 0x76867B, 0x738379, 0x6B7B70, 0x0D1D12, 0x000E00, 0x000700, 0x000C00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000900, 0x011202, 0x1D2B1C, 0x8C988A, 0x8D988A, 0x8D9387, 0x989E90, 0x95998B, 0x9C9E91, 0x9D9F92, 0x9EA093, 0xA0A295, 0xA1A395, 0xA2A496, 0xA1A596, 0xA2A697, 0x9EA092, 0xA1A395, 0xA1A395, 0x9FA193, 0x9EA092, 0x9FA193, 0x9EA092, 0x9B9D8F, 0x9D9F91, 0xA3A597, 0xA7A99B, 0xA6A89A, 0xA5A799, 0xA7A99B, 0xA8AA9C, 0xA7A99B, 0xA7A99B, 0xA7A99B, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA9AB9D, 0xA9AB9D, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA7A99B, 0xA7A99B, 0xA6A89A, 0xA5A799, 0xA4A698, 0x999D8E, 0x9B9F90, 0x999D8E, 0x969A8B, 0x95998A, 0x979B8C, 0x989C8D, 0x969A8B, 0x979B8C, 0x95998A, 0x949889, 0x949889, 0x969A8B, 0x989C8D, 0x999D8E, 0x999D8E, 0x9EA092, 0x9D9F91, 0x9B9D8F, 0x9A9C8E, 0x999B8D, 0x989A8C, 0x97998B, 0x97998B, 0x989A8C, 0x989A8C, 0x97998B, 0x96988A, 0x949688, 0x939587, 0x929486, 0x929486, 0x919185, 0x929286, 0x919386, 0x8F9385, 0x8B9183, 0x899182, 0x879483, 0x889584, 0x829381, 0x829381, 0x829381, 0x819280, 0x80917F, 0x7E8F7D, 0x7C8D7B, 0x7B8C7C, 0x77877C, 0x738379, 0x6E7E73, 0x1A2A1F, 0x000E00, 0x000B00, 0x000900, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x061705, 0x283625, 0x8A9786, 0x859080, 0x7F8577, 0x909787, 0x8A8E7F, 0x909284, 0x919385, 0x949688, 0x97998B, 0x9A9D8C, 0x9C9F8E, 0x9DA190, 0x9DA190, 0x9EA092, 0xA1A395, 0xA2A496, 0xA0A294, 0xA0A294, 0xA2A496, 0xA1A395, 0x9FA193, 0x9D9F91, 0xA3A597, 0xA7A99B, 0xA7A99B, 0xA6A89A, 0xA7A99B, 0xA8AA9C, 0xA7A99B, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA8AA9C, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA8AA9C, 0xA7A99B, 0xA6A89A, 0xA5A799, 0xA4A698, 0xA5A99A, 0xA7AB9C, 0xA7AB9C, 0xA4A899, 0xA3A798, 0xA4A899, 0xA3A798, 0xA0A495, 0xA2A697, 0xA0A495, 0x9EA293, 0x9DA192, 0x9DA192, 0x9EA293, 0x9FA394, 0x9EA293, 0x9C9E90, 0x9D9F91, 0x9C9E90, 0x9A9C8E, 0x989A8C, 0x97998B, 0x97998B, 0x989A8C, 0x939587, 0x939587, 0x939587, 0x939587, 0x929486, 0x919385, 0x909284, 0x909183, 0x908E82, 0x938F84, 0x919185, 0x8F9184, 0x8E9284, 0x8A9283, 0x899484, 0x8A9786, 0x829381, 0x829381, 0x819481, 0x809380, 0x7D927F, 0x7B907D, 0x798E7B, 0x7A8C7C, 0x77897D, 0x74847A, 0x6F7F74, 0x233328, 0x000E00, 0x000E00, 0x000700, 0x000E00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000E01, 0x000800, 0x404E41, 0x8D998D, 0x8F998E, 0x8E948A, 0x959B91, 0x95988F, 0x999B90, 0x9A9C91, 0x9C9E93, 0x9B9D92, 0x999B90, 0x9A9C91, 0x9CA092, 0x9FA395, 0xA0A294, 0xA0A294, 0xA0A294, 0x9FA193, 0xA1A395, 0xA3A597, 0xA1A395, 0x9C9E90, 0x9EA092, 0xA3A597, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA8AA9C, 0xA9AB9D, 0xA8AA9C, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xADAFA1, 0xA1A395, 0xA7A99B, 0x939587, 0x929486, 0x929486, 0x949688, 0x9EA092, 0x989A8C, 0x919385, 0x989A8C, 0x989A8C, 0x929486, 0x97998B, 0x959789, 0x999B8D, 0x9C9E90, 0x9B9D8F, 0x989A8C, 0x989A8C, 0x9C9E90, 0xA0A294, 0x9FA193, 0x9EA092, 0x9C9E90, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x96988A, 0x96988A, 0x96988A, 0x959789, 0x949688, 0x939587, 0x939587, 0x929486, 0x8F9384, 0x8F9384, 0x8D9484, 0x8D9484, 0x8A9584, 0x899684, 0x889784, 0x859683, 0x829581, 0x829581, 0x7F9580, 0x7F9580, 0x7E947F, 0x7C927D, 0x7D907C, 0x7C8F7C, 0x798A78, 0x778878, 0x708171, 0x2E3F2F, 0x011202, 0x000900, 0x000700, 0x000F00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000800, 0x445245, 0x8D998D, 0x909A8F, 0x91978D, 0x999F95, 0x999C93, 0x9C9E93, 0x9C9E93, 0x9B9D92, 0x9C9E93, 0x9D9F94, 0x9FA196, 0x9FA395, 0xA0A496, 0xA4A698, 0xA4A698, 0xA2A496, 0xA0A294, 0xA0A294, 0xA2A496, 0x9EA092, 0x989A8C, 0x9FA193, 0xA3A597, 0xA6A89A, 0xA6A89A, 0xA6A89A, 0xA8AA9C, 0xA9AB9D, 0xA8AA9C, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA6A89A, 0xA7A99B, 0xACAEA0, 0xA5A799, 0xB0B2A4, 0xA3A597, 0xA6A89A, 0xAAAC9E, 0xA3A597, 0xA9AB9D, 0xA7A99B, 0xA3A597, 0xA6A89A, 0xA6A89A, 0xA3A597, 0xA3A597, 0x9D9F91, 0x9D9F91, 0x9B9D8F, 0x97998B, 0x939587, 0x919385, 0x939587, 0x949688, 0x9C9E90, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9B9D8F, 0x9B9D8F, 0x9B9D8F, 0x97998B, 0x97998B, 0x96988A, 0x959789, 0x949688, 0x949688, 0x939587, 0x939587, 0x909485, 0x909485, 0x8E9585, 0x8C9584, 0x8A9584, 0x899684, 0x869784, 0x869784, 0x829581, 0x829581, 0x809681, 0x7F9580, 0x80937F, 0x7F927E, 0x7D907C, 0x7D907D, 0x798A78, 0x788979, 0x728373, 0x3C4D3D, 0x001101, 0x000B00, 0x000800, 0x000D00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x021205, 0x000E03, 0x4E5C4F, 0x8A968A, 0x879186, 0x83897F, 0x888E84, 0x868980, 0x919388, 0x8F9186, 0x8D8F84, 0x8F9186, 0x93958A, 0x95978C, 0x94988A, 0x929688, 0x919385, 0x939587, 0x949688, 0x96988A, 0x9C9E90, 0xA2A496, 0xA1A395, 0x9EA092, 0x9FA193, 0xA4A698, 0xA7A99B, 0xA6A89A, 0xA6A89A, 0xA8AA9C, 0xA9AB9D, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xAAAC9E, 0xABAD9F, 0xA6A89A, 0xAEB0A2, 0xA3A597, 0xA4A698, 0xA8AA9C, 0xA4A698, 0xA3A597, 0xA6A89A, 0xA6A89A, 0xA2A496, 0xA4A698, 0xA5A799, 0x9FA193, 0xA6A89A, 0xA5A799, 0xA2A496, 0xA0A294, 0x9EA092, 0x9FA193, 0xA0A294, 0xA1A395, 0x9C9E90, 0x9EA092, 0x9FA193, 0x9EA092, 0x9B9D8F, 0x999B8D, 0x999B8D, 0x9A9C8E, 0x97998B, 0x97998B, 0x96988A, 0x96988A, 0x959789, 0x949688, 0x949688, 0x939587, 0x919586, 0x919586, 0x8D9685, 0x8D9685, 0x8A9785, 0x899684, 0x869784, 0x869784, 0x839682, 0x839682, 0x829581, 0x829581, 0x819480, 0x7F927E, 0x7E917D, 0x7D907D, 0x7A8B79, 0x788979, 0x738474, 0x516252, 0x000F00, 0x000D00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000E01, 0x000E03, 0x5A685B, 0x8F9B8F, 0x909A8F, 0x90968C, 0x989E94, 0x989B92, 0x9EA095, 0x9D9F94, 0x9C9E93, 0x9EA095, 0xA0A297, 0xA1A398, 0xA0A496, 0x9FA395, 0xA0A294, 0xA1A395, 0xA0A294, 0x9FA193, 0xA0A294, 0xA3A597, 0xA0A294, 0x9B9D8F, 0xA0A294, 0xA4A698, 0xA7A99B, 0xA6A89A, 0xA6A89A, 0xA8AA9C, 0xA9AB9D, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xACAEA0, 0xADAFA1, 0xAAAC9E, 0xA6A89A, 0xA8AA9C, 0x9EA092, 0x999B8D, 0x9D9F91, 0x979B8C, 0x919586, 0x969A8B, 0x999D8E, 0x909485, 0x939788, 0x989C8D, 0x8F9384, 0x929687, 0x939788, 0x949889, 0x949889, 0x939788, 0x939788, 0x969A8B, 0x989C8D, 0x959789, 0x97998B, 0x989A8C, 0x97998B, 0x949688, 0x949688, 0x96988A, 0x999B8D, 0x989A8C, 0x989A8C, 0x97998B, 0x97998B, 0x96988A, 0x959789, 0x949688, 0x949688, 0x909787, 0x909787, 0x8E9786, 0x8E9786, 0x8A9785, 0x8A9785, 0x879885, 0x869784, 0x849783, 0x839682, 0x839682, 0x829581, 0x829380, 0x81927F, 0x80917E, 0x7F907E, 0x7C8D7B, 0x798A7A, 0x738474, 0x637464, 0x000D00, 0x000E00, 0x000C00, 0x000900, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x001005, 0x657366, 0x8F9B8F, 0x909A8F, 0x91978D, 0x9AA096, 0x9B9E95, 0x9D9F94, 0x9EA095, 0xA0A297, 0xA0A297, 0x9EA095, 0x9EA095, 0x9EA294, 0x9FA395, 0xA1A395, 0xA1A395, 0xA0A294, 0x9FA193, 0xA1A395, 0xA3A597, 0xA0A294, 0x9B9D8F, 0xA0A294, 0xA5A799, 0xA8AA9C, 0xA7A99B, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA8AA9C, 0xABAD9F, 0xA8AA9C, 0xABAD9F, 0xAEB0A2, 0xAAAC9E, 0xA2A496, 0xA7A99B, 0xACB0A1, 0xA3A798, 0xA8AC9D, 0xAAAE9F, 0xA1A596, 0xA4A899, 0xAAAE9F, 0xA4A899, 0x9CA091, 0x9FA394, 0xA2A697, 0xA0A495, 0x9B9F90, 0x989C8D, 0x989C8D, 0x9A9E8F, 0x939587, 0x949688, 0x949688, 0x939587, 0x929486, 0x949688, 0x999B8D, 0x9D9F91, 0x999B8D, 0x999B8D, 0x989A8C, 0x97998B, 0x96988A, 0x96988A, 0x959789, 0x949889, 0x919888, 0x909988, 0x8D9887, 0x8D9887, 0x8A9986, 0x8A9986, 0x879885, 0x879885, 0x849783, 0x849783, 0x849783, 0x839682, 0x839481, 0x829380, 0x83927F, 0x82907F, 0x7F907E, 0x7B8C7C, 0x728373, 0x6E7F6F, 0x001000, 0x000D00, 0x000D00, 0x000900, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000E01, 0x041409, 0x6F7D70, 0x8B978B, 0x889287, 0x868C82, 0x8B9187, 0x8B8E85, 0x8E9085, 0x919388, 0x94968B, 0x93958A, 0x919388, 0x8F9186, 0x909486, 0x929688, 0x919385, 0x939587, 0x959789, 0x97998B, 0x9D9F91, 0xA2A496, 0xA2A496, 0x9EA092, 0xA1A395, 0xA6A89A, 0xA8AA9C, 0xA7A99B, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA7A99B, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xA3A597, 0xA8AA9C, 0xA2A496, 0xABAD9F, 0xADAFA1, 0xAFB1A3, 0xA7A99B, 0xAEB0A2, 0xA9AD9E, 0xA4A899, 0xA5A99A, 0xA7AB9C, 0xA3A798, 0xA3A798, 0xA5A99A, 0xA4A899, 0xA3A798, 0xA4A899, 0xA5A99A, 0xA4A899, 0xA1A596, 0x9EA293, 0x9DA192, 0x9DA192, 0xA3A597, 0xA0A294, 0x9D9F91, 0x9A9C8E, 0x999B8D, 0x9A9C8E, 0x9C9E90, 0x9D9F91, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x989A8C, 0x97998B, 0x97998B, 0x96988A, 0x95998A, 0x929989, 0x919A89, 0x8E9988, 0x8E9988, 0x8B9A87, 0x8A9986, 0x889986, 0x879885, 0x859884, 0x859884, 0x869784, 0x859683, 0x869582, 0x859481, 0x83927F, 0x839180, 0x80917F, 0x7D8E7E, 0x738474, 0x738474, 0x091A0A, 0x000C00, 0x000D00, 0x000800, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000A00, 0x041409, 0x79877A, 0x909C90, 0x909A8F, 0x93998F, 0x9BA197, 0x9DA097, 0x9C9E93, 0x9D9F94, 0x9FA196, 0xA0A297, 0xA1A398, 0xA1A398, 0xA0A496, 0xA0A496, 0xA5A799, 0xA5A799, 0xA3A597, 0xA1A395, 0xA2A496, 0xA3A597, 0x9FA193, 0x999B8D, 0xA1A395, 0xA6A89A, 0xA9AB9D, 0xA7A99B, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA7A99B, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xA1A395, 0xA3A597, 0x97998B, 0x9EA092, 0x9B9D8F, 0x9FA193, 0x96988A, 0x9EA092, 0xA1A898, 0xA2A999, 0xA1A898, 0xA3AA9A, 0xA6AD9D, 0xA0A797, 0x9CA393, 0xA0A797, 0xA4AB9B, 0xA0A797, 0x9CA393, 0x9DA494, 0xA1A898, 0xA4AB9B, 0xA2A999, 0xA2A697, 0xA1A596, 0x9FA193, 0x9C9E90, 0x9C9E90, 0x9EA092, 0x9FA193, 0x9D9F91, 0x9A9C8E, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x989A8C, 0x97998B, 0x96988A, 0x95998A, 0x929B8A, 0x909B8A, 0x8E9B89, 0x8D9A88, 0x8B9A87, 0x8A9986, 0x889986, 0x889986, 0x869985, 0x869985, 0x869784, 0x869784, 0x869582, 0x859481, 0x859280, 0x849180, 0x7F907E, 0x7F9080, 0x758676, 0x778878, 0x172818, 0x000E00, 0x000E00, 0x000700, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000F02, 0x09190E, 0x808E81, 0x919D91, 0x8E988D, 0x8F958B, 0x969C92, 0x979A91, 0x9C9E93, 0x9B9D92, 0x9B9D92, 0x9EA095, 0xA2A499, 0xA4A69B, 0xA2A698, 0xA0A496, 0xA1A395, 0xA2A496, 0xA1A395, 0xA0A294, 0xA2A496, 0xA5A799, 0xA2A496, 0x9D9F91, 0xA2A496, 0xA6A89A, 0xA9AB9D, 0xA8AA9C, 0xA7A99B, 0xA9AB9D, 0xA9AB9D, 0xA7A99B, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0x9EA092, 0x9EA092, 0x8C8E80, 0x909284, 0x8A8C7E, 0x8F9183, 0x858779, 0x8F9183, 0x7C8373, 0x838A7A, 0x818878, 0x848B7B, 0x8B9282, 0x818878, 0x777E6E, 0x7F8676, 0x8A9181, 0x7F8676, 0x767D6D, 0x777E6E, 0x7F8676, 0x848B7B, 0x818878, 0x7D8172, 0x868A7B, 0x87897B, 0x898B7D, 0x919385, 0x9B9D8F, 0xA0A294, 0x9FA193, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x9A9C8E, 0x999B8D, 0x989A8C, 0x97998B, 0x97998B, 0x969A8B, 0x929B8A, 0x909B8A, 0x8E9B89, 0x8E9B89, 0x8C9B88, 0x8B9A87, 0x889986, 0x889986, 0x869985, 0x869985, 0x879885, 0x869784, 0x879683, 0x859481, 0x859280, 0x859281, 0x7E8F7D, 0x809181, 0x778878, 0x798A7A, 0x223323, 0x000F00, 0x000E00, 0x000700, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000D00, 0x000900, 0x021303, 0x8E9C8F, 0x8C988C, 0x8C988E, 0x8B958C, 0x919891, 0x939A93, 0x929791, 0x90958F, 0x8F948D, 0x8F948D, 0x90988B, 0x90988B, 0x8E9687, 0x8D9385, 0x8F9384, 0x919385, 0x929486, 0x96988A, 0x9A9C8E, 0x9EA092, 0x9FA193, 0x9FA193, 0xA3A597, 0xA7A99B, 0xA9AB9D, 0xA8AA9C, 0xA7A99B, 0xA9AB9D, 0xAAAC9E, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xABAD9F, 0xABAD9F, 0xA8AA9C, 0xACAEA0, 0xADAFA1, 0xABAD9F, 0xAAAC9E, 0xAAAC9E, 0xA9AB9D, 0xA6A89A, 0xACAEA0, 0xAAAC9E, 0xA6A89A, 0xA4A698, 0xA7A99B, 0xAAAC9E, 0xA8AA9C, 0xA3A597, 0xA5A799, 0xA3A597, 0xA1A395, 0xA0A294, 0xA0A294, 0x9FA193, 0x9D9F91, 0x9B9D8F, 0x939587, 0x8F9183, 0x8D8F81, 0x919385, 0x9A9C8E, 0x9FA193, 0x9D9F91, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x999B8D, 0x999B8D, 0x999B8D, 0x989A8C, 0x97998B, 0x949B8B, 0x8F9C8A, 0x8C9D8A, 0x8C9D8A, 0x8C9D8A, 0x8B9C89, 0x8A9B88, 0x899A87, 0x899A87, 0x879885, 0x879885, 0x879885, 0x869784, 0x869784, 0x859683, 0x859683, 0x849583, 0x819282, 0x829285, 0x819184, 0x738376, 0x354538, 0x021205, 0x000700, 0x000E00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000D00, 0x051302, 0x142213, 0x96A296, 0x949E93, 0x939D94, 0x929991, 0x969B95, 0x989D97, 0x9EA09B, 0x9EA09B, 0x9FA299, 0xA0A39A, 0xA0A69A, 0xA1A79B, 0xA0A698, 0xA2A698, 0xA2A697, 0xA3A597, 0xA2A496, 0xA3A597, 0xA4A698, 0xA3A597, 0xA1A395, 0x9FA193, 0xA3A597, 0xA7A99B, 0xA9AB9D, 0xA8AA9C, 0xA8AA9C, 0xAAAC9E, 0xAAAC9E, 0xA9AB9D, 0xA9AB9D, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xAAAC9E, 0xA6A89A, 0x9FA193, 0x999B8D, 0x9B9D8F, 0xA2A496, 0xA6A89A, 0xA6A89A, 0x9EA092, 0x9EA092, 0xA1A395, 0xA8AA9C, 0xAEB0A2, 0xB0B2A4, 0xADAFA1, 0xAAAC9E, 0xA7A99B, 0xA5A799, 0xA3A597, 0xA3A597, 0xA2A496, 0xA2A496, 0xA0A294, 0x9EA092, 0xA3A597, 0x9FA193, 0x9C9E90, 0x9D9F91, 0xA1A395, 0xA2A496, 0xA0A294, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9D9F91, 0x9C9E90, 0x9B9D8F, 0x9A9C8E, 0x999B8D, 0x979B8C, 0x929F8D, 0x91A08D, 0x909F8C, 0x8F9E8B, 0x8B9C89, 0x8A9B88, 0x899A87, 0x889986, 0x879885, 0x879885, 0x879885, 0x869784, 0x869784, 0x859683, 0x849783, 0x849784, 0x819282, 0x819184, 0x809083, 0x758578, 0x425343, 0x001000, 0x000A00, 0x000F00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000C00, 0x000E00, 0x1A261A, 0x909A91, 0x8E958D, 0x8E958D, 0x8B9089, 0x92948F, 0x949691, 0x94958F, 0x969791, 0x989991, 0x999A92, 0x999C91, 0x999C91, 0x9A9E90, 0x9B9F91, 0x9FA193, 0x9FA193, 0xA1A395, 0xA3A597, 0xA4A698, 0xA4A698, 0xA1A395, 0x9FA193, 0xA3A597, 0xA7A99B, 0xA9AB9D, 0xA8AA9C, 0xA8AA9C, 0xAAAC9E, 0xAAAC9E, 0xA9AB9D, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xA8AA9C, 0xA2A496, 0x9D9F91, 0x9EA092, 0x9EA092, 0x9A9C8E, 0x939587, 0x96988A, 0x949688, 0x989A8C, 0xA2A496, 0xA7A99B, 0xA5A799, 0xA2A496, 0xA3A597, 0xAAAC9E, 0xA8AA9C, 0xA7A99B, 0xA6A89A, 0xA7A99B, 0xA6A89A, 0xA5A799, 0xA3A597, 0xA3A597, 0xA1A395, 0x9EA092, 0x9C9E90, 0x9C9E90, 0x9B9D8F, 0x999B8D, 0x97998B, 0x9C9E90, 0x9B9D8F, 0x9B9D8F, 0x999B8D, 0x989A8C, 0x97998B, 0x96988A, 0x949889, 0x8E9988, 0x8C9987, 0x8B9A87, 0x8B9A87, 0x8B9A87, 0x8C9B88, 0x8D9C89, 0x8E9D8A, 0x889986, 0x889986, 0x879885, 0x879885, 0x859884, 0x859884, 0x849783, 0x849784, 0x829383, 0x819184, 0x7E8F7F, 0x798A7A, 0x566757, 0x000C00, 0x001000, 0x000E00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000C00, 0x030F03, 0x2C362D, 0x929991, 0x91968F, 0x949993, 0x92948F, 0x9A9B96, 0x9C9D98, 0x979691, 0x9A9994, 0x9C9C94, 0x9C9C94, 0x9A9C8F, 0x999B8E, 0x9A9C8E, 0x9D9F91, 0x949688, 0x96988A, 0x9A9C8E, 0x9FA193, 0xA2A496, 0xA2A496, 0xA0A294, 0x9EA092, 0xA3A597, 0xA8AA9C, 0xAAAC9E, 0xA8AA9C, 0xA8AA9C, 0xAAAC9E, 0xABAD9F, 0xA9AB9D, 0xA9AD9E, 0xA9AD9E, 0xA9AD9E, 0xA9AD9E, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xA6AA9B, 0xA7AB9C, 0xA8AC9D, 0xA8AC9D, 0xAAAE9F, 0xACB0A1, 0xA9AD9E, 0xA3A798, 0xABAFA0, 0xA4A899, 0xA4A899, 0xABAFA0, 0xADB1A2, 0xAAAE9F, 0xAAAE9F, 0xAEB2A3, 0xA3A798, 0xA2A697, 0xA1A596, 0xA1A596, 0xA1A596, 0xA1A596, 0xA0A495, 0x9FA394, 0xA4A698, 0xA4A698, 0xA3A597, 0xA1A395, 0x9FA193, 0x9EA092, 0x9FA193, 0x9FA193, 0x9C9E90, 0x9B9D8F, 0x9A9C8E, 0x999B8D, 0x989A8C, 0x97998B, 0x97998B, 0x95998A, 0x919A89, 0x8E9988, 0x8D9A88, 0x8C9987, 0x8D9A88, 0x8E9B89, 0x8E9D8A, 0x8E9D8A, 0x889986, 0x889986, 0x889986, 0x879885, 0x869985, 0x859884, 0x859884, 0x859885, 0x859684, 0x819282, 0x7D8E7E, 0x7C8D7D, 0x697A6A, 0x000B00, 0x021301, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000C00, 0x041004, 0x3D473E, 0x929992, 0x919690, 0x979C96, 0x949691, 0x9C9D98, 0x9D9E99, 0x9D9C97, 0xA09F9A, 0xA2A298, 0xA2A298, 0xA0A295, 0x9FA194, 0xA1A493, 0xA3A695, 0x9D9F91, 0x9EA092, 0xA1A395, 0xA4A698, 0xA6A89A, 0xA4A698, 0xA1A395, 0x9D9F91, 0xA4A698, 0xA8AA9C, 0xAAAC9E, 0xA9AB9D, 0xA8AA9C, 0xAAAC9E, 0xABAD9F, 0xA9AB9D, 0xA9AD9E, 0xA9AD9E, 0xA9AD9E, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xABAFA0, 0xABAFA0, 0xAAAE9F, 0xA4A899, 0x9CA091, 0x989C8D, 0x9CA091, 0xA2A697, 0xA5A99A, 0xA6AA9B, 0xA0A495, 0x9FA394, 0xA3A798, 0xA4A899, 0xA1A596, 0xA2A697, 0xA7AB9C, 0xA6AA9B, 0xA5A99A, 0xA4A899, 0xA4A899, 0xA5A99A, 0xA5A99A, 0xA4A899, 0xA2A697, 0x9EA092, 0x9FA193, 0x9FA193, 0x9EA092, 0x9C9E90, 0x9C9E90, 0x9D9F91, 0x9FA193, 0x9C9E90, 0x9B9D8F, 0x9B9D8F, 0x9A9C8E, 0x999B8D, 0x999B8D, 0x999B8D, 0x999D8E, 0x989F8F, 0x969F8E, 0x939E8D, 0x919C8B, 0x909B8A, 0x8F9A89, 0x8D9A88, 0x8D9A88, 0x8B9A87, 0x8B9A87, 0x889986, 0x889986, 0x869985, 0x869985, 0x859884, 0x859885, 0x879886, 0x819282, 0x7E8F7F, 0x7E8F7F, 0x768775, 0x000F00, 0x001100, 0x000700, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000C00, 0x000B01, 0x465149, 0x8A948C, 0x8B928B, 0x919793, 0x8B908C, 0x939590, 0x92948F, 0x93948E, 0x959690, 0x97998E, 0x999B90, 0x999D8E, 0x9A9E8F, 0x9B9F8E, 0x9CA08F, 0x9EA092, 0x9FA193, 0xA2A496, 0xA4A698, 0xA6A89A, 0xA4A698, 0xA0A294, 0x9D9F91, 0xA4A698, 0xA8AA9C, 0xAAAC9E, 0xA9AB9D, 0xA9AB9D, 0xABAD9F, 0xABAD9F, 0xAAAC9E, 0xA9AD9E, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xAAAE9F, 0xABAFA0, 0xABAFA0, 0xABAFA0, 0xAAAE9F, 0xACB0A1, 0xA9AD9E, 0x9FA394, 0x979B8C, 0x979B8C, 0x9A9E8F, 0x9CA091, 0x9A9E8F, 0x9A9E8F, 0x9B9F90, 0x9A9E8F, 0x999D8E, 0x979B8C, 0x95998A, 0x949889, 0x949889, 0x939788, 0x929687, 0x929687, 0x939788, 0x939788, 0x929687, 0x909485, 0x959789, 0x959789, 0x959789, 0x939587, 0x919385, 0x919385, 0x919385, 0x929486, 0x939587, 0x929486, 0x919385, 0x909284, 0x909284, 0x919385, 0x919385, 0x929486, 0x8F9686, 0x909787, 0x909988, 0x919A89, 0x909B8A, 0x909B8A, 0x8F9C8A, 0x8F9C8A, 0x8B9A87, 0x8B9A87, 0x899A87, 0x889986, 0x879A86, 0x869985, 0x869985, 0x869985, 0x889987, 0x819280, 0x819280, 0x80917F, 0x7C8D7B, 0x0B1C0A, 0x000F00, 0x000800, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000D00, 0x031006, 0x556259, 0x8E9993, 0x8F9893, 0x97A09B, 0x919793, 0x999E98, 0x969B95, 0x979A93, 0x979A93, 0x989B90, 0x9A9D92, 0x9BA292, 0x9CA393, 0x9CA48F, 0x9EA291, 0x999D8C, 0x9B9D8F, 0x9EA092, 0xA1A395, 0xA3A597, 0xA3A597, 0xA0A294, 0x9EA092, 0xA4A698, 0xA8AA9C, 0xAAAC9E, 0xA9AB9D, 0xA9AB9D, 0xABAD9F, 0xABAD9F, 0xAAAC9E, 0xA8AF9F, 0xA8AF9F, 0xA8AF9F, 0xA8AF9F, 0xA8AF9F, 0xA9B0A0, 0xA9B0A0, 0xA9B0A0, 0xA0A797, 0xA5AC9C, 0xA9B0A0, 0xA8AF9F, 0xA7AE9E, 0xA9B0A0, 0xAAB1A1, 0xA9B0A0, 0xA4AB9B, 0xA9B0A0, 0xA9B0A0, 0xA3AA9A, 0xA2A999, 0xA6AD9D, 0xA6AD9D, 0xA1A898, 0xA4AB9B, 0xA3AA9A, 0xA1A898, 0xA1A898, 0xA2A999, 0xA2A999, 0xA0A797, 0xA1A596, 0x9EA293, 0x9EA092, 0x9C9E90, 0x9A9C8E, 0x999B8D, 0x989A8C, 0x97998B, 0x96988A, 0x929486, 0x919385, 0x8F9183, 0x8E9082, 0x8D8F81, 0x8D8F81, 0x8E9082, 0x8F9183, 0x888F7F, 0x8A9181, 0x8D9484, 0x909787, 0x929B8A, 0x939C8B, 0x919C8B, 0x919C8B, 0x8C9B88, 0x8C9B88, 0x899A87, 0x899A87, 0x879A86, 0x879A86, 0x849A85, 0x869985, 0x879A87, 0x80917F, 0x849583, 0x819280, 0x7F907E, 0x1A2B19, 0x000E00, 0x000C00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000D00, 0x000F04, 0x586760, 0x8D9A93, 0x919C96, 0x9AA59F, 0x949D98, 0x9DA49D, 0x9AA19A, 0xA0A59E, 0x9EA39C, 0x9DA397, 0x9EA496, 0xA0A998, 0xA1AA97, 0x9FA893, 0x9EA593, 0xA2A695, 0xA3A597, 0xA3A597, 0xA4A698, 0xA5A799, 0xA4A698, 0xA1A395, 0x9EA092, 0xA4A698, 0xA8AA9C, 0xABAD9F, 0xA9AB9D, 0xA9AB9D, 0xABAD9F, 0xABAD9F, 0xAAAC9E, 0xA8AF9F, 0xA8AF9F, 0xA8AF9F, 0xA8AF9F, 0xA9B0A0, 0xA9B0A0, 0xA9B0A0, 0xA9B0A0, 0xABB2A2, 0xA8AF9F, 0x9FA696, 0x959C8C, 0x919888, 0x939A8A, 0x959C8C, 0x949B8B, 0x929989, 0x99A090, 0x979E8E, 0x8F9686, 0x929989, 0xA0A797, 0xA8AF9F, 0xA6AD9D, 0xA6AD9D, 0xA4AB9B, 0xA3AA9A, 0xA2A999, 0xA3AA9A, 0xA3AA9A, 0xA1A898, 0xA2A697, 0xA5A99A, 0xA3A597, 0xA1A395, 0xA0A294, 0xA0A294, 0xA0A294, 0x9EA092, 0x9C9E90, 0xA0A294, 0x9EA092, 0x9C9E90, 0x9A9C8E, 0x989A8C, 0x989A8C, 0x999B8D, 0x999B8D, 0x949889, 0x95998A, 0x969D8D, 0x979E8E, 0x959E8D, 0x939C8B, 0x8E9988, 0x8D9887, 0x8C9B88, 0x8C9B88, 0x899A87, 0x899A87, 0x879A86, 0x879A86, 0x849A85, 0x869985, 0x869986, 0x7F907E, 0x869785, 0x829381, 0x7F907E, 0x263725, 0x000E00, 0x001000, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000D00, 0x000E00, 0x0C1510, 0x696F6F, 0x8F9591, 0x898E8A, 0x848982, 0x91948D, 0x84877C, 0x909388, 0x989A8C, 0x959789, 0x929687, 0x939788, 0x939B8C, 0x969E8F, 0x969E91, 0x949C8F, 0x97A08F, 0x9DA695, 0xA1A898, 0x9FA696, 0xA0A495, 0xA2A496, 0xA0A294, 0x9E9F91, 0xA6A799, 0xA8A99B, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AD9E, 0xABAD9F, 0xAAAC9E, 0xABAC9E, 0xABAD9F, 0xABAD9F, 0xABAFA0, 0xABAFA0, 0xA9B2A1, 0xA9B2A1, 0xA9B2A1, 0xA9B2A1, 0xA8AF9F, 0xA6AD9D, 0xA8AC9D, 0xABAFA0, 0xABAD9F, 0xA8AA9C, 0xA8AB9A, 0xA9AC9B, 0xA9AC9B, 0xAAAD9C, 0xABAE9D, 0xABAE9D, 0xAAAD9C, 0xA9AC9B, 0xA7AA99, 0xA7AA99, 0xA6A998, 0xA6A998, 0xA5A897, 0xA5A897, 0xA5A897, 0xA5A897, 0xA2A697, 0xA1A596, 0x9FA394, 0x9EA293, 0x9EA293, 0x9EA293, 0x9DA192, 0x9CA091, 0x9DA192, 0x9CA091, 0x9A9E8F, 0x989C8D, 0x989C8D, 0x989C8D, 0x9A9E8F, 0x9B9F90, 0x95998A, 0x969A8B, 0x949D8C, 0x919C8B, 0x8F9C8A, 0x8B9C89, 0x8B9E8A, 0x8AA08B, 0x889E89, 0x889E89, 0x8A9D89, 0x8B9C89, 0x8D9C89, 0x8E9988, 0x8D9887, 0x8D9887, 0x8A9986, 0x839481, 0x889986, 0x869784, 0x829380, 0x344532, 0x051603, 0x000900, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000D00, 0x000E00, 0x000B04, 0x6E7877, 0x949D98, 0x979D99, 0x9AA199, 0xA0A59E, 0x95988D, 0x9C9F94, 0xA0A295, 0x9FA194, 0x9CA092, 0x9B9F91, 0x999F91, 0x999F91, 0x989E92, 0x979D91, 0x9DA695, 0xA0A998, 0xA3AA9A, 0xA2A999, 0xA3A798, 0xA4A698, 0xA1A395, 0x9D9E90, 0xA6A799, 0xA8A99B, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AB9D, 0xA9AD9E, 0xAAAE9F, 0xAAAC9E, 0xAAAC9E, 0xABAD9F, 0xABAD9F, 0xA9B0A0, 0xAAB1A1, 0xA9B2A1, 0xA9B2A1, 0xA4AD9C, 0xA4AD9C, 0xA4AB9B, 0xA2A999, 0xA6AA9B, 0xAAAE9F, 0xACAEA0, 0xAAAC9E, 0xA7AA99, 0xAAAD9C, 0xACAF9E, 0xABAE9D, 0xA8AB9A, 0xA6A998, 0xA7AA99, 0xA8AB9A, 0xA8AB9A, 0xA7AA99, 0xA7AA99, 0xA7AA99, 0xA6A998, 0xA6A998, 0xA5A897, 0xA5A897, 0xA3A798, 0xA1A596, 0xA0A495, 0x9FA394, 0xA0A495, 0xA1A596, 0xA0A495, 0x9FA394, 0x9CA091, 0x9CA091, 0x9B9F90, 0x9A9E8F, 0x9A9E8F, 0x999D8E, 0x999D8E, 0x999D8E, 0x999B8D, 0x979B8C, 0x949B8B, 0x909B8A, 0x8F9C8A, 0x8E9D8A, 0x8A9D89, 0x8A9D89, 0x889E89, 0x889E89, 0x8A9D89, 0x8B9C89, 0x8D9C89, 0x8D9A88, 0x8E9988, 0x8C9987, 0x899885, 0x849582, 0x889986, 0x869784, 0x839481, 0x435441, 0x021300, 0x000D00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000B00, 0x000D00, 0x000F00, 0x011009, 0x818D8B, 0x909C98, 0x8F9895, 0x97A199, 0x99A099, 0x9AA096, 0xA1A49B, 0x9C9F94, 0x9FA196, 0xA0A295, 0xA1A396, 0x9FA395, 0x9FA395, 0xA0A496, 0xA1A597, 0x9CA594, 0x9EA796, 0x9FA696, 0xA0A797, 0xA4A899, 0xA5A799, 0xA1A395, 0x9D9E90, 0xA7A89A, 0xA8A99B, 0xA9AB9D, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAE9F, 0xABAFA0, 0xABAD9F, 0xABAD9F, 0xAAAE9F, 0xABAFA0, 0xA9B0A0, 0xAAB1A1, 0xA9B2A1, 0xA9B2A1, 0xA4AD9C, 0xA2AB9A, 0x9CA393, 0x949B8B, 0x929687, 0x929687, 0x909284, 0x8C8E80, 0x96988A, 0x9A9C8E, 0x9D9F91, 0x9B9D8F, 0x97998B, 0x959789, 0x96988A, 0x999B8D, 0x989A8C, 0x989A8C, 0x989A8C, 0x97998B, 0x96988A, 0x96988A, 0x959789, 0x959789, 0x989C8D, 0x969A8B, 0x95998A, 0x95998A, 0x969A8B, 0x979B8C, 0x979B8C, 0x969A8B, 0x9A9E8F, 0x9B9F90, 0x9CA091, 0x9CA091, 0x9B9F90, 0x9A9E8F, 0x999D8E, 0x989C8D, 0x9B9D8F, 0x989A8C, 0x939A8A, 0x929B8A, 0x929D8C, 0x93A08E, 0x90A18E, 0x8FA08D, 0x8A9D89, 0x8A9D89, 0x8B9C89, 0x8C9D8A, 0x8D9C89, 0x8D9C89, 0x8D9A88, 0x8D9A88, 0x889986, 0x869784, 0x889986, 0x869784, 0x859683, 0x5A6B58, 0x000D00, 0x001100, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x000F00, 0x001006, 0x889993, 0x85948F, 0x818D89, 0x8A978E, 0x869088, 0x929991, 0x969992, 0x8E9188, 0x93948C, 0x97998E, 0x989A8F, 0x97998C, 0x97998C, 0x999B8E, 0x9A9E90, 0x979E8E, 0x969F8E, 0x989F8F, 0x9BA292, 0xA2A697, 0xA5A799, 0xA1A395, 0x9C9D8F, 0xA7A89A, 0xA9AA9C, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAC9E, 0xAAAE9F, 0xABAFA0, 0xAAAE9F, 0xAAAE9F, 0xA9B0A0, 0xA9B0A0, 0xA8B1A0, 0xA9B2A1, 0xA7B2A1, 0xA7B2A1, 0xACB5A4, 0xABB4A3, 0xA9B0A0, 0xA6AD9D, 0xA9AD9E, 0xAEB2A3, 0xB1B3A5, 0xB0B2A4, 0xA2A697, 0xA5A99A, 0xA7AB9C, 0xA7AB9C, 0xA6AA9B, 0xA5A99A, 0xA5A99A, 0xA7AB9C, 0xA3A798, 0xA2A697, 0xA2A697, 0xA1A596, 0xA0A495, 0xA0A495, 0x9FA394, 0x9FA394, 0x989C8D, 0x979B8C, 0x95998A, 0x95998A, 0x969A8B, 0x969A8B, 0x95998A, 0x949889, 0x888C7D, 0x888C7D, 0x888C7D, 0x888C7D, 0x878B7C, 0x868A7B, 0x85897A, 0x85897A, 0x8D8F81, 0x898B7D, 0x838A7A, 0x858C7C, 0x879281, 0x8B9685, 0x8A9986, 0x889784, 0x8B9C89, 0x8B9C89, 0x8C9D8A, 0x8C9D8A, 0x8E9D8A, 0x8E9D8A, 0x8D9C89, 0x8D9C89, 0x899A87, 0x899A87, 0x899A87, 0x859683, 0x869784, 0x6F806D, 0x000A00, 0x011200, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x000F00, 0x001409, 0x92A39D, 0x8FA09A, 0x939F9B, 0x9DAAA3, 0x939C97, 0x9BA59D, 0x999E98, 0x979C95, 0x9C9F98, 0x9FA098, 0x9FA098, 0x9C9E91, 0x9A9C8F, 0x999D8F, 0x9A9E90, 0x969F8E, 0x949D8C, 0x969D8D, 0x9BA292, 0xA3A798, 0xA4A698, 0xA0A294, 0x9E9F91, 0xA8A99B, 0xAAAB9D, 0xAAAC9E, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xABAFA0, 0xACB0A1, 0xA9B0A0, 0xA9B0A0, 0xA8B1A0, 0xA8B1A0, 0xA6B1A0, 0xA7B2A1, 0xA7B2A1, 0xA7B2A1, 0xA7B2A1, 0xA6AF9E, 0x9EA796, 0x969D8D, 0x949889, 0x939788, 0x929486, 0x8E9082, 0x9DA192, 0x9CA091, 0x9B9F90, 0x9CA091, 0x9EA293, 0x9EA293, 0x9CA091, 0x9A9E8F, 0xA4A899, 0xA4A899, 0xA3A798, 0xA2A697, 0xA2A697, 0xA1A596, 0xA0A495, 0xA0A495, 0xA4A899, 0xA2A697, 0xA0A495, 0x9FA394, 0x9FA394, 0x9FA394, 0x9DA192, 0x9CA091, 0xA0A495, 0x9EA293, 0x9DA192, 0x9B9F90, 0x9B9F90, 0x9B9F90, 0x9CA091, 0x9DA192, 0x9A9C8E, 0x96988A, 0x919586, 0x939788, 0x959E8D, 0x99A291, 0x96A391, 0x95A290, 0x8D9C89, 0x8E9D8A, 0x8C9D8A, 0x8C9D8A, 0x8D9E8B, 0x8C9D8A, 0x8C9D8A, 0x8C9D8A, 0x8A9B88, 0x8B9C89, 0x8A9B88, 0x859683, 0x879885, 0x7D8E7B, 0x000E00, 0x001100, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000C00, 0x000E00, 0x001000, 0x13241A, 0x93A49E, 0x909F9A, 0x929E9A, 0x98A4A0, 0x929B98, 0x9DA6A1, 0x9BA19D, 0x9DA29C, 0x9FA49E, 0xA2A59C, 0xA2A59C, 0xA0A698, 0xA0A698, 0xA0A797, 0xA1A898, 0x9BA493, 0x99A291, 0x9CA393, 0xA0A797, 0xA5A99A, 0xA4A698, 0xA1A395, 0xA0A193, 0xA8A99B, 0xAAAB9D, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xABAD9F, 0xABAFA0, 0xACB0A1, 0xA9B2A1, 0xA9B2A1, 0xA7B2A1, 0xA7B2A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA8B3A2, 0xAAB3A2, 0xA7B09F, 0xA6AD9D, 0xAAAE9F, 0xAFB1A3, 0xB1B3A5, 0xB0B1A3, 0xA8AC9E, 0xA4A89A, 0xA2A698, 0xA3A799, 0xA6AA9C, 0xA7AB9D, 0xA3A799, 0x9EA294, 0x9B9F91, 0x9A9E90, 0x9A9E90, 0x999D8F, 0x989C8E, 0x979B8D, 0x969A8C, 0x969A8C, 0x909485, 0x8E9283, 0x8C9081, 0x8B8F80, 0x8A8E7F, 0x898D7E, 0x888C7D, 0x868A7B, 0x8B8F80, 0x8A8E7F, 0x878B7C, 0x85897A, 0x85897A, 0x868A7B, 0x878B7C, 0x888C7D, 0x919385, 0x8E9082, 0x8B8D7F, 0x8A8C7E, 0x899080, 0x8A9181, 0x899281, 0x889180, 0x8E9B89, 0x8F9C8A, 0x8E9D8A, 0x8F9E8B, 0x8D9E8B, 0x8D9E8B, 0x8C9F8B, 0x8C9F8B, 0x8C9D8A, 0x8B9C89, 0x8B9C89, 0x869784, 0x899A87, 0x839481, 0x091A07, 0x001000, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000C00, 0x000E00, 0x001000, 0x1E2E23, 0x889790, 0x86928E, 0x838E8A, 0x828D89, 0x828B88, 0x929896, 0x929896, 0x929793, 0x919692, 0x919890, 0x939A92, 0x969E91, 0x98A093, 0x98A392, 0x98A392, 0x9BA493, 0x9AA392, 0x9DA494, 0xA2A999, 0xA5A99A, 0xA2A496, 0xA0A294, 0xA2A395, 0xA9AA9C, 0xAAAB9D, 0xABAD9F, 0xACAEA0, 0xACAEA0, 0xACAEA0, 0xACB0A1, 0xABB2A2, 0xA9B2A1, 0xA7B2A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA7B2A1, 0xA8B1A0, 0xA3AC9B, 0xA0A495, 0x9DA192, 0x9EA092, 0x9C9D8F, 0x97998B, 0xA6AA9C, 0xA2A89A, 0xA2A89A, 0xA4AA9C, 0xA7AD9F, 0xA7AD9F, 0xA5AB9D, 0xA3A99B, 0xA8AEA0, 0xA7AD9F, 0xA6AC9E, 0xA5AB9D, 0xA4AA9C, 0xA3A99B, 0xA2A89A, 0xA2A89A, 0xA5A99A, 0xA3A798, 0xA1A596, 0xA0A495, 0xA0A495, 0xA0A495, 0x9EA293, 0x9DA192, 0x9FA394, 0x9EA293, 0x9DA192, 0x9CA091, 0x9CA091, 0x9CA091, 0x9CA091, 0x9D9F91, 0x97998B, 0x97988A, 0x949688, 0x939587, 0x919586, 0x909485, 0x8D9484, 0x8C9383, 0x909B8A, 0x909B8A, 0x8F9E8B, 0x8F9E8B, 0x8DA08C, 0x8DA08C, 0x8C9F8B, 0x8C9F8B, 0x8C9D8A, 0x8A9B88, 0x8C9D8A, 0x879885, 0x8C9D8A, 0x869784, 0x1B2C19, 0x001100, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000D00, 0x000C00, 0x000B00, 0x000C00, 0x000E00, 0x001000, 0x2C392F, 0x8E9B94, 0x97A29E, 0x9AA3A0, 0x97A09F, 0x989E9E, 0x9EA4A4, 0x9BA1A1, 0x9BA19D, 0x999F9B, 0x969D95, 0x979E96, 0x96A294, 0x98A496, 0x97A492, 0x97A291, 0x939E8D, 0x959E8D, 0x9AA191, 0xA0A797, 0xA3A798, 0xA0A294, 0x9FA193, 0xA3A496, 0xA9AA9C, 0xABAC9E, 0xABAD9F, 0xACAEA0, 0xACAEA0, 0xACAEA0, 0xACB0A1, 0xABB2A2, 0xA7B2A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA6B3A1, 0xA7B2A1, 0xA9B2A1, 0xA7B09F, 0xA6AA9B, 0xA5A99A, 0xA8AA9C, 0xA7A89A, 0xA2A496, 0x9CA092, 0x9CA294, 0x9FA597, 0xA2A89A, 0xA5AB9D, 0xA7AD9F, 0xA8AEA0, 0xA9AFA1, 0xA6AC9E, 0xA5AB9D, 0xA4AA9C, 0xA3A99B, 0xA2A89A, 0xA1A799, 0xA0A698, 0xA0A698, 0xA3A798, 0xA2A697, 0xA0A495, 0x9FA394, 0xA0A495, 0xA0A495, 0x9FA394, 0x9EA293, 0x9CA091, 0x9CA091, 0x9DA192, 0x9DA192, 0x9CA091, 0x9B9F90, 0x9A9E8F, 0x9A9C8E, 0x9A9C8E, 0x9B9C8E, 0x9B9D8F, 0x9A9C8E, 0x979B8C, 0x95998A, 0x939A8A, 0x949B8B, 0x909B8A, 0x909B8A, 0x8F9E8B, 0x8F9E8B, 0x8DA08C, 0x8DA08C, 0x8BA18C, 0x8DA08C, 0x8B9E8A, 0x899A87, 0x8D9E8B, 0x899A87, 0x8FA08D, 0x869784, 0x273825, 0x011200, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000B00, 0x000E00, 0x001101, 0x37443A, 0x8F9C95, 0x96A19B, 0x95A09A, 0x97A29A, 0x97A29A, 0x96A298, 0x9DA99F, 0x99A398, 0x9BA59A, 0x9DA89A, 0x9FAA9C, 0xA0AB9B, 0xA0AB9B, 0xA2AB9A, 0x9FAA99, 0x9AA796, 0x9CA998, 0x9EAB9A, 0xA2AA9B, 0xA2AA9B, 0xA3A799, 0xA1A597, 0xA1A396, 0xABAB9F, 0xACACA0, 0xADADA1, 0xAEAEA2, 0xAEAEA2, 0xAEAEA2, 0xADAFA2, 0xACB0A2, 0xA7B2A1, 0xA7B4A2, 0xA6B5A2, 0xA6B5A2, 0xA6B5A2, 0xA6B5A2, 0xA4B5A3, 0xA3B4A2, 0xA4B5A3, 0xA8B6A5, 0x9FAD9E, 0xA3AFA1, 0xA4AFA1, 0xA1AC9E, 0xACB4A7, 0xABB1A5, 0xAAB1A1, 0xACB0A1, 0xABB2A2, 0xACB3A3, 0xA8AF9F, 0xA4AB9B, 0xA5AE9D, 0xABB4A3, 0xA6B1A0, 0xA4AF9E, 0xA3AE9D, 0xA4AF9E, 0xA1AE9C, 0x9DAA98, 0x9CA997, 0x9DAA98, 0x9BAA95, 0x99A893, 0x99A692, 0x9AA793, 0x9DA995, 0x9DA995, 0x9DA693, 0x9AA390, 0x9BA290, 0x9DA190, 0x9CA08F, 0x9C9F8E, 0x9D9E8E, 0x9D9E8E, 0x9D9E8E, 0x9D9E8E, 0x9A9B8D, 0x9A9B8D, 0x999B8D, 0x999B8D, 0x959C8C, 0x969D8D, 0x969F8E, 0x97A08F, 0x909D8B, 0x909D8B, 0x8D9E8B, 0x8D9E8B, 0x8C9F8B, 0x8C9F8B, 0x8AA08B, 0x8C9F8B, 0x8C9F8B, 0x90A18E, 0x889986, 0x8C9D8A, 0x90A18E, 0x81927F, 0x425340, 0x000D00, 0x000E00, 0x000C00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000B00, 0x000E00, 0x001101, 0x404D44, 0x8E9A96, 0x8C9793, 0x88938F, 0x8A958F, 0x8B9690, 0x8A958D, 0x8F9A92, 0x949E95, 0x949E95, 0x949F91, 0x939E90, 0x939E90, 0x939E90, 0x959D8E, 0x949F8F, 0x9EAB9A, 0x9DAB9A, 0x9EAB9A, 0x9FAA9A, 0x9EA999, 0xA0A698, 0xA0A698, 0xA2A497, 0xABADA0, 0xACACA0, 0xADADA1, 0xAEAEA2, 0xAEAEA2, 0xAEAEA2, 0xADAFA2, 0xACB0A2, 0xA7B2A1, 0xA7B4A2, 0xA6B5A2, 0xA6B5A2, 0xA6B5A2, 0xA6B5A2, 0xA4B5A3, 0xA3B4A2, 0x9BAC9A, 0xA3B1A0, 0xA1AFA0, 0xACB8AA, 0xB0BBAD, 0xAAB5A7, 0xAFB7AA, 0xA7AFA2, 0xA3AA9A, 0xA4AB9B, 0xA6AD9D, 0xA8AF9F, 0xA8B1A0, 0xA7B09F, 0xA2AD9C, 0xA0AB9A, 0xA1AC9B, 0x9FAA99, 0x9FAC9A, 0xA2AF9D, 0xA2B19E, 0xA0AF9C, 0xA0AF9C, 0xA2B19E, 0xA2B19C, 0x9FAE99, 0x9CAB96, 0x9AA994, 0x9AA793, 0x99A692, 0x99A591, 0x98A490, 0x9AA692, 0x99A591, 0x9AA390, 0x99A28F, 0x99A08E, 0x989F8D, 0x989F8D, 0x989F8D, 0x989F8F, 0x989F8F, 0x989F8F, 0x979E8E, 0x959E8D, 0x949D8C, 0x919E8C, 0x919E8C, 0x909F8C, 0x909F8C, 0x8E9F8C, 0x8E9F8C, 0x8DA08C, 0x8DA08C, 0x8BA18C, 0x8DA08C, 0x8EA18D, 0x8FA08D, 0x8A9B88, 0x8B9C89, 0x8E9F8C, 0x859683, 0x4F604D, 0x000F00, 0x000E00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000C00, 0x000F00, 0x001101, 0x4B5851, 0x919D9B, 0x919B9A, 0x959F9E, 0x9AA5A1, 0x98A39F, 0x929D97, 0x919C96, 0x9CA69E, 0x9AA49C, 0x99A39A, 0x97A198, 0x97A196, 0x97A196, 0x9BA396, 0x99A496, 0x9FAC9B, 0x9EAC9B, 0x9EAB9A, 0x9EA999, 0x9DA898, 0xA0A698, 0xA0A698, 0xA2A698, 0xABADA0, 0xABADA0, 0xADADA1, 0xAEAEA2, 0xADAFA2, 0xAEB0A3, 0xAEB0A3, 0xACB0A2, 0xA8B3A2, 0xA7B4A2, 0xA6B5A2, 0xA7B6A3, 0xA7B6A3, 0xA6B5A2, 0xA4B5A3, 0xA4B5A3, 0x94A593, 0x97A594, 0x8D9B8C, 0x929E90, 0x96A193, 0x949F91, 0x9CA497, 0x959D90, 0xA1AA99, 0xA2AB9A, 0xA0A998, 0x9FA897, 0xA2AD9C, 0xA6B1A0, 0xA0AD9B, 0x96A391, 0x9AA795, 0x98A593, 0x98A794, 0x9BAA97, 0x9AAB98, 0x98A996, 0x98A996, 0x9AAB98, 0x94A592, 0x96A794, 0x99AA97, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9DAC99, 0x9DAC99, 0x9BAA97, 0x9AA996, 0x98A794, 0x97A693, 0x95A491, 0x94A390, 0x94A390, 0x93A28F, 0x95A491, 0x95A491, 0x95A491, 0x94A390, 0x94A390, 0x93A28F, 0x8FA08D, 0x8FA08D, 0x8E9F8C, 0x8E9F8C, 0x8E9F8C, 0x8E9F8C, 0x8DA08C, 0x8DA08C, 0x8DA08C, 0x8DA08C, 0x90A18E, 0x8D9E8B, 0x8C9D8A, 0x8C9D8A, 0x8B9C89, 0x8A9B88, 0x61725F, 0x001100, 0x000E00, 0x000D00, 0x000B00, 0x000A00, 0x000B00, 0x000B00, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000F00, 0x011202, 0x596561, 0x94A0A0, 0x919B9C, 0x99A3A4, 0x9CA6A5, 0x9CA6A5, 0x99A4A0, 0x98A39F, 0x9EA7A2, 0x9DA6A1, 0x9DA79E, 0x9CA69D, 0x9DA79E, 0x9EA89F, 0xA1A99E, 0xA0AA9F, 0x9BA998, 0x9CAA99, 0x9CAA99, 0x9EA999, 0x9EA999, 0xA0A698, 0xA0A698, 0xA1A597, 0xABADA0, 0xABADA0, 0xACAEA1, 0xADAFA2, 0xAEB0A3, 0xAEB0A3, 0xAEB0A3, 0xADB1A3, 0xA8B3A2, 0xA7B4A2, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA4B5A3, 0xA4B5A3, 0x9AAB99, 0x9AA897, 0x8B998A, 0x8C988A, 0x909B8D, 0x919C8E, 0x9AA295, 0x929A8D, 0x8E9687, 0x8E9687, 0x859080, 0x7D8878, 0x808B7B, 0x8A9585, 0x879483, 0x7C8978, 0x7E8C7B, 0x7C8A79, 0x798A78, 0x7A8B79, 0x788977, 0x748573, 0x728572, 0x748573, 0x7F907D, 0x899885, 0x90A18E, 0x97A895, 0x98A996, 0x96A794, 0x93A692, 0x93A692, 0x94AA95, 0x93A994, 0x92A893, 0x91A792, 0x8FA791, 0x8EA690, 0x8DA58F, 0x8DA58F, 0x8EA48F, 0x8EA48F, 0x8EA48F, 0x8FA590, 0x91A490, 0x91A490, 0x8FA28E, 0x8EA18D, 0x8EA18D, 0x8EA18D, 0x8EA18D, 0x8EA18D, 0x8FA08D, 0x8FA08D, 0x8FA08D, 0x8FA08D, 0x90A18E, 0x8B9C89, 0x8FA08D, 0x8D9E8B, 0x8A9B88, 0x8D9E8B, 0x71826F, 0x001100, 0x000F00, 0x000D00, 0x000B00, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x001000, 0x021303, 0x687470, 0x94A0A0, 0x879192, 0x8A9495, 0x889291, 0x899392, 0x919C98, 0x96A19D, 0x939C97, 0x949D98, 0x959F96, 0x96A097, 0x96A097, 0x96A097, 0x98A095, 0x959F94, 0x97A594, 0x97A896, 0x9CAA99, 0x9FAC9B, 0x9FAC9B, 0xA1A99A, 0x9EA697, 0xA0A496, 0xAAAEA0, 0xACAEA1, 0xADAFA2, 0xADAFA2, 0xAEB0A3, 0xAEB0A3, 0xADB1A3, 0xABB1A3, 0xA8B3A2, 0xA7B4A2, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA4B5A3, 0xA4B5A3, 0xA3B4A2, 0xA9B7A6, 0xA2B0A1, 0xA8B4A6, 0xAEB9AB, 0xADB8AA, 0xB0B8AB, 0xA3AB9E, 0xABB3A4, 0xAEB6A7, 0xA9B1A2, 0xA0A899, 0x9FAA9A, 0xA8B3A3, 0xA7B4A3, 0x9FAC9B, 0xA2AF9E, 0x9FAC9B, 0x9DAB9A, 0x9EAC9B, 0x9BAC9A, 0x97A896, 0x95A694, 0x96A795, 0x909F8C, 0x96A592, 0x9DAC99, 0xA0AF9C, 0x9CAD9A, 0x98A996, 0x94A793, 0x93A692, 0x92A893, 0x92A893, 0x90A892, 0x8FA791, 0x8FA791, 0x8FA791, 0x8EA791, 0x8EA791, 0x8DA58F, 0x8CA48E, 0x8BA38D, 0x8CA48E, 0x8EA48F, 0x8EA48F, 0x8CA28D, 0x8AA08B, 0x8FA28E, 0x8FA28E, 0x8FA28E, 0x8FA28E, 0x90A18E, 0x90A18E, 0x90A18E, 0x90A18E, 0x8E9F8C, 0x8B9C89, 0x91A28F, 0x8FA08D, 0x8C9D8A, 0x8E9F8C, 0x7C8D7A, 0x021300, 0x000F00, 0x000D00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000D00, 0x001100, 0x021303, 0x6F7C75, 0x95A19F, 0x8D9796, 0x98A2A1, 0x95A09C, 0x929D99, 0x96A19B, 0x97A29C, 0x959F97, 0x96A098, 0x97A198, 0x98A299, 0x97A196, 0x96A095, 0x969E91, 0x939E90, 0x96A493, 0x96A795, 0x9CAA99, 0xA0AD9C, 0xA1AE9D, 0xA2AA9B, 0x9EA697, 0x9DA395, 0xABAFA1, 0xABAFA1, 0xADAFA2, 0xAEB0A3, 0xADB1A3, 0xADB1A3, 0xADB1A3, 0xABB1A3, 0xA8B3A2, 0xA8B5A3, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA5B6A4, 0xA4B5A3, 0xA2B3A1, 0xA8B6A5, 0x9EAC9D, 0x9FAB9D, 0xA3AEA0, 0xA3AEA0, 0xA9B1A4, 0x9EA699, 0x9FA599, 0xA5AB9F, 0xA8AEA2, 0xA5AB9F, 0xA4AC9F, 0xA8B0A3, 0xA7AFA2, 0xA2AA9D, 0xA2AD9F, 0xA0AB9D, 0xA0AC9E, 0xA3AFA1, 0xA3B1A2, 0xA1AFA0, 0xA1AFA0, 0xA3B1A2, 0xA1AE9D, 0xA1AE9D, 0x9FAC9B, 0x9DAA99, 0x9BA897, 0x9BA897, 0x9BA998, 0x9CAA99, 0x98A997, 0x98A997, 0x97A896, 0x97A896, 0x95A895, 0x95A895, 0x96A996, 0x96A996, 0x94AA95, 0x91A792, 0x8EA48F, 0x8EA48F, 0x92A591, 0x92A591, 0x91A490, 0x8FA28E, 0x90A38F, 0x90A38F, 0x90A38F, 0x90A38F, 0x91A28F, 0x91A28F, 0x91A28F, 0x91A28F, 0x8D9E8B, 0x8E9F8C, 0x92A390, 0x8E9F8C, 0x90A18E, 0x8C9D8A, 0x849582, 0x0C1D0A, 0x000F00, 0x000D00, 0x000C00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000B00, 0x000C00, 0x000E00, 0x001100, 0x031404, 0x78857C, 0x98A4A0, 0x919C98, 0xA4AFAB, 0xA3AEA8, 0x9EA9A3, 0x9DA8A0, 0x97A29A, 0x9EA89F, 0x9EA89F, 0x9EA99B, 0x9EA99B, 0x9EA99B, 0x9EA99B, 0x9FA798, 0x9DA898, 0x98A997, 0x9AAB99, 0x9DAE9C, 0xA1AE9D, 0xA1AE9D, 0xA2AA9B, 0x9FA798, 0x9EA496, 0xABAFA1, 0xABAFA1, 0xACB0A2, 0xADB1A3, 0xADB1A3, 0xAEB2A4, 0xAEB2A4, 0xABB1A3, 0xA9B4A3, 0xA8B5A3, 0xA7B6A3, 0xA8B7A4, 0xA8B7A4, 0xA7B6A3, 0xA5B6A4, 0xA5B6A4, 0x9FB09E, 0xA4B2A1, 0x94A293, 0x8E9A8C, 0x8E998B, 0x919C8E, 0x9CA497, 0x969C90, 0x8E9186, 0x929489, 0x96988D, 0x999B90, 0x999C91, 0x999C91, 0x969C90, 0x949A8E, 0x939B8E, 0x91998C, 0x929A8D, 0x959D90, 0x939E90, 0x919C8E, 0x919C8E, 0x939E90, 0x99A192, 0x959D8E, 0x8E9989, 0x8B9686, 0x8C9787, 0x8F9A8A, 0x929D8D, 0x949F8F, 0x98A393, 0x97A292, 0x96A191, 0x95A090, 0x94A190, 0x93A08F, 0x93A08F, 0x93A08F, 0x92A390, 0x8FA08D, 0x8C9D8A, 0x8D9E8B, 0x91A28F, 0x94A592, 0x94A592, 0x93A491, 0x91A28F, 0x91A28F, 0x91A28F, 0x91A28F, 0x91A28F, 0x91A28F, 0x90A38F, 0x90A38F, 0x8E9F8C, 0x91A28F, 0x91A28F, 0x8B9C89, 0x94A592, 0x8B9C89, 0x8B9C89, 0x1B2C19, 0x000F00, 0x000E00, 0x000C00, 0x000B00, 0x000C00, 0x000C00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000C00, 0x000B00, 0x000C00, 0x000E00, 0x001100, 0x031404, 0x869389, 0x96A39C, 0x7F8A84, 0x8B9690, 0x8A958D, 0x8B968E, 0x919D93, 0x8E9A90, 0x9AA499, 0x9AA499, 0x99A496, 0x99A496, 0x9AA595, 0x9CA797, 0xA1AA99, 0xA0AB9A, 0x9EAF9D, 0x9DB09D, 0x9EAF9D, 0xA0AE9D, 0xA0AD9C, 0xA1A99A, 0x9FA798, 0x9FA597, 0xABAFA1, 0xABAFA1, 0xACB0A2, 0xADB1A3, 0xADB1A3, 0xAEB2A4, 0xACB2A4, 0xABB3A4, 0xA9B4A3, 0xA8B5A3, 0xA7B6A3, 0xA8B7A4, 0xA8B7A4, 0xA7B6A3, 0xA5B6A4, 0xA5B6A4, 0xA2B3A1, 0xADBBAA, 0xA7B5A6, 0xA6B2A4, 0xA6B1A3, 0xA6B1A3, 0xADB5A8, 0xA4AA9E, 0xABADA2, 0xA9A99F, 0xA8A89E, 0xACACA2, 0xADAFA4, 0xACAEA3, 0xABAEA3, 0xADB0A5, 0xA7ADA1, 0xA4AA9E, 0xA3A99D, 0xA4AA9E, 0xA2AA9D, 0x9EA699, 0x9CA497, 0x9DA598, 0x9DA596, 0x98A091, 0x939B8C, 0x909889, 0x91998A, 0x939B8C, 0x949A8C, 0x93998B, 0x8F9587, 0x8E9486, 0x8D9385, 0x8B9183, 0x8A9082, 0x898F81, 0x898F81, 0x878F80, 0x889382, 0x84917F, 0x828F7D, 0x859280, 0x8A9986, 0x909F8C, 0x93A28F, 0x93A28F, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x91A490, 0x91A490, 0x8E9F8C, 0x93A491, 0x90A18E, 0x899A87, 0x96A794, 0x8A9B88, 0x8FA08D, 0x273825, 0x000F00, 0x000E00, 0x000C00, 0x000B00, 0x000C00, 0x000C00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000900, 0x000F00, 0x000700, 0x001100, 0x000900, 0x000C00, 0x001003, 0x08180E, 0x90A096, 0x909F98, 0x929F98, 0x939E98, 0x939E96, 0x949E96, 0x969D96, 0x969D96, 0x979E97, 0x979E97, 0x969D95, 0x959C94, 0x929C93, 0x939D94, 0x96A298, 0x98A59B, 0x9DAE9E, 0x9FB1A1, 0x9CAE9E, 0x9DAE9E, 0xA1B2A2, 0x9FAB9D, 0x9BA897, 0xA1AC9C, 0xA9B1A2, 0xAAB0A2, 0xAAB1A1, 0xAEB0A2, 0xAEB0A2, 0xAFB0A2, 0xAFB0A2, 0xAEB2A3, 0xA8B5A1, 0xA5B7A1, 0xA5B7A1, 0xA5B7A1, 0xA5B6A3, 0xA5B6A3, 0xA7B6A3, 0xA7B6A3, 0xA5B4A1, 0xAAB9A6, 0xAFBCAB, 0xADBAA9, 0xA6B3A2, 0xA2AF9E, 0xA4B1A0, 0xA9B4A4, 0xA9B1A2, 0xA9AFA1, 0xA8AEA0, 0xA9AFA1, 0xABB1A3, 0xABB1A3, 0xA9AFA1, 0xA7AD9F, 0xA7AD9F, 0xA5AB9D, 0xA3A99B, 0xA2A89A, 0xA4AA9C, 0xA6AC9E, 0xA7AD9F, 0xA9AD9F, 0xA6AA9C, 0xA7A99C, 0xA6A89B, 0xA5A79A, 0xA4A699, 0xA3A598, 0xA2A698, 0xA1A597, 0x9FA395, 0x9EA294, 0x9BA193, 0x9BA193, 0x9BA193, 0x9BA193, 0x999F91, 0x979F90, 0x95A491, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x91A28F, 0x91A28F, 0x91A28F, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x91A28F, 0x94A592, 0x8C9D8A, 0x96A794, 0x8FA08D, 0x91A28F, 0x8C9D8A, 0x3E4F3C, 0x000D00, 0x000D00, 0x000D00, 0x000C00, 0x000C00, 0x000C00, 0x000A00, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000900, 0x000E00, 0x000700, 0x001100, 0x000900, 0x000D00, 0x001003, 0x0E1E14, 0x91A197, 0x92A19A, 0x95A29B, 0x98A39D, 0x9AA59D, 0x9DA79F, 0xA0A7A0, 0xA1A8A1, 0xA0A7A0, 0xA0A7A0, 0xA0A79F, 0x9EA59D, 0x9CA69D, 0x9DA79E, 0xA0ACA2, 0xA2AFA5, 0x9DAE9E, 0x9FB1A1, 0x9DAE9E, 0x9DAE9E, 0xA3B1A2, 0x9EAC9D, 0x9BA897, 0xA0AD9C, 0xA7B2A2, 0xA7B2A2, 0xAAB3A2, 0xAAB3A2, 0xABB2A2, 0xABB2A2, 0xADB1A2, 0xACB3A3, 0xA7B6A1, 0xA5B7A1, 0xA5B7A1, 0xA5B7A1, 0xA5B6A3, 0xA5B6A3, 0xA7B6A3, 0xA7B6A3, 0x9FAE9B, 0x9DAC99, 0x99A695, 0x909D8C, 0x8A9786, 0x8B9887, 0x929F8E, 0x99A494, 0x9BA394, 0x989E90, 0x959B8D, 0x959B8D, 0x979D8F, 0x979D8F, 0x959B8D, 0x92988A, 0x9EA496, 0x9DA395, 0x9BA193, 0x9BA193, 0x9BA193, 0x9BA193, 0x9AA092, 0x989E90, 0x9B9F91, 0x9CA092, 0x9EA294, 0xA0A496, 0x9FA597, 0x9EA496, 0x9DA395, 0x9DA395, 0xA1A799, 0xA0A698, 0x9EA697, 0x9FA798, 0x9FA798, 0x9FA798, 0x9EA697, 0x9AA595, 0x95A491, 0x93A491, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x91A28F, 0x91A28F, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x90A18E, 0x94A592, 0x8E9F8C, 0x96A794, 0x8FA08D, 0x91A28F, 0x8D9E8B, 0x485946, 0x001000, 0x001000, 0x000E00, 0x000C00, 0x000B00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000900, 0x000D00, 0x000700, 0x001000, 0x000900, 0x000D00, 0x001003, 0x18281E, 0x90A096, 0x909F98, 0x929F98, 0x949F99, 0x949F97, 0x959F97, 0x979E97, 0x989F98, 0x9EA59E, 0x9EA59E, 0x9EA59D, 0x9DA49C, 0x9BA59C, 0x9CA69D, 0x9FABA1, 0xA1ADA3, 0x9DAE9E, 0x9FB0A0, 0x9DAE9E, 0x9DAE9E, 0xA1B2A2, 0x9CAD9D, 0x9AA897, 0xA0AE9D, 0xA5B3A2, 0xA6B4A3, 0xA7B4A2, 0xA7B4A2, 0xA7B4A2, 0xA7B4A2, 0xA8B5A3, 0xA8B5A3, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B7A4, 0xA6B7A4, 0xA8B7A4, 0xA8B7A4, 0xADBCA9, 0xACBBA8, 0xACB9A8, 0xA9B6A5, 0xA7B4A3, 0xA7B4A3, 0xA9B6A5, 0xACB7A7, 0xA9B4A4, 0xA8B0A1, 0xA4AC9D, 0xA4AC9D, 0xA6AE9F, 0xA6AE9F, 0xA4AC9D, 0xA1A99A, 0x9CA495, 0x9BA394, 0x9BA394, 0x9BA394, 0x9BA394, 0x9AA293, 0x98A091, 0x959D8E, 0x99A192, 0x9BA394, 0x9EA697, 0xA1A99A, 0xA3AB9C, 0xA3AB9C, 0x9FAA9A, 0x9EA999, 0x9BA696, 0x9AA595, 0x97A493, 0x97A493, 0x98A594, 0x97A493, 0x95A291, 0x93A08F, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x90A18E, 0x94A592, 0x90A18E, 0x96A794, 0x8E9F8C, 0x91A28F, 0x90A18E, 0x586956, 0x021300, 0x001100, 0x000F00, 0x000C00, 0x000A00, 0x000900, 0x000A00, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000A00, 0x000C00, 0x000800, 0x000F00, 0x000A00, 0x000E01, 0x011104, 0x26362C, 0x91A197, 0x91A099, 0x929F98, 0x939E98, 0x929D95, 0x939D95, 0x959C95, 0x949B94, 0x949B94, 0x959C95, 0x959C94, 0x949B93, 0x929C93, 0x949E95, 0x96A298, 0x99A59B, 0x9DAE9E, 0x9FB0A0, 0x9DAE9E, 0x9EAF9F, 0xA2B3A3, 0x9DAE9E, 0x99AA98, 0x9EAF9D, 0xA4B5A3, 0xA4B5A3, 0xA3B6A2, 0xA3B6A2, 0xA4B7A3, 0xA4B7A3, 0xA4B7A3, 0xA4B7A3, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B7A4, 0xA6B7A4, 0xA8B7A4, 0xA8B7A4, 0xA5B4A1, 0xA4B3A0, 0xA5B2A1, 0xA6B3A2, 0xA6B3A2, 0xA5B2A1, 0xA4B1A0, 0xA3AE9E, 0xA7B2A2, 0xA9B1A2, 0xAAB2A3, 0xABB3A4, 0xAAB2A3, 0xAAB2A3, 0xA9B1A2, 0xA9B1A2, 0xA7AFA0, 0xA6AE9F, 0xA6AE9F, 0xA7AFA0, 0xA8B0A1, 0xA8B0A1, 0xA7AFA0, 0xA6AE9F, 0x9AA796, 0x9BA897, 0x9CA998, 0x9EAB9A, 0x9EAB9A, 0x9DAA99, 0x9CA998, 0x9BA897, 0x9DAB9A, 0x9CAA99, 0x9BA998, 0x9BA998, 0x9BA998, 0x9AA897, 0x96A795, 0x94A593, 0x94A592, 0x94A592, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x92A390, 0x90A18E, 0x94A592, 0x93A491, 0x95A693, 0x8E9F8C, 0x91A28F, 0x92A390, 0x6B7C69, 0x021300, 0x001000, 0x000E00, 0x000D00, 0x000A00, 0x000800, 0x000900, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000900, 0x000E00, 0x000B00, 0x000E01, 0x011104, 0x35453B, 0x94A49A, 0x95A49D, 0x98A59E, 0x9AA59F, 0x9CA79F, 0x9FA9A1, 0xA2A9A2, 0xA2A9A2, 0xA0A7A0, 0xA1A8A1, 0xA2A9A1, 0xA2A9A1, 0xA0AAA1, 0xA1ABA2, 0xA3AFA5, 0xA5B1A7, 0x9FAD9E, 0xA1AFA0, 0x9DAE9E, 0x9EAF9F, 0xA2B3A3, 0x9DAE9E, 0x99AA98, 0x9FB09E, 0xA3B6A3, 0xA3B6A3, 0xA3B6A2, 0xA3B6A2, 0xA2B8A3, 0xA2B8A3, 0xA2B8A3, 0xA4B7A3, 0xA5B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B7A4, 0xA6B7A4, 0xA8B7A4, 0xA8B7A4, 0xA5B4A1, 0x9EAD9A, 0x96A392, 0x8F9C8B, 0x8E9B8A, 0x909D8C, 0x93A08F, 0x94A190, 0x939E8E, 0x99A494, 0x9EA999, 0x9EA999, 0x9AA595, 0x96A191, 0x97A292, 0x99A494, 0x9FAA9A, 0x9DA898, 0x9AA595, 0x9AA595, 0x9BA696, 0x9CA797, 0x9DA898, 0x9CA797, 0xA1AE9D, 0xA0AD9C, 0x9FAC9B, 0x9EAB9A, 0x9DAB9A, 0x9CAA99, 0x9DAB9A, 0x9DAB9A, 0x94A593, 0x93A492, 0x93A492, 0x95A694, 0x97A896, 0x97A896, 0x96A795, 0x95A694, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x91A28F, 0x93A491, 0x94A592, 0x95A693, 0x8E9F8C, 0x91A28F, 0x93A491, 0x7C8D7A, 0x031401, 0x000F00, 0x000C00, 0x000D00, 0x000C00, 0x000900, 0x000900, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000900, 0x000A00, 0x000D00, 0x000C00, 0x000F02, 0x011104, 0x435349, 0x92A298, 0x93A29B, 0x95A29B, 0x97A29C, 0x98A39B, 0x99A39B, 0x9CA39C, 0x9CA39C, 0x9FA69F, 0xA0A7A0, 0xA1A8A0, 0xA1A8A0, 0x9FA9A0, 0xA0AAA1, 0xA1ADA3, 0xA3AFA5, 0x9FAD9E, 0xA1AFA0, 0x9DAE9E, 0x9EAF9F, 0xA2B3A3, 0x9EAF9F, 0x9AAB99, 0xA0B19F, 0xA4B5A3, 0xA4B5A3, 0xA4B7A3, 0xA4B7A3, 0xA4B7A3, 0xA4B7A3, 0xA4B7A3, 0xA5B8A4, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B8A2, 0xA6B7A4, 0xA6B7A4, 0xA8B7A4, 0xA8B7A4, 0xADBCA9, 0xAAB9A6, 0xA6B3A2, 0xA3B09F, 0xA1AE9D, 0xA1AE9D, 0xA2AF9E, 0xA2AF9E, 0x9BA897, 0xA0AD9C, 0xA5B2A1, 0xA4B1A0, 0x9EAB9A, 0x9AA796, 0x9AA796, 0x9CA998, 0x9AA796, 0x97A493, 0x94A190, 0x929F8E, 0x919E8D, 0x929F8E, 0x929F8E, 0x929F8E, 0x8E9B8A, 0x8D9A89, 0x8B9887, 0x8A9786, 0x899786, 0x8A9887, 0x8C9A89, 0x8D9B8A, 0x8C9A89, 0x8C9A89, 0x8B9C8A, 0x8E9F8D, 0x91A290, 0x93A492, 0x94A593, 0x93A492, 0x95A693, 0x95A693, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x92A390, 0x93A491, 0x95A693, 0x94A592, 0x8FA08D, 0x92A390, 0x92A390, 0x889986, 0x091A07, 0x001100, 0x000A00, 0x000C00, 0x000E00, 0x000A00, 0x000800, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000900, 0x000A00, 0x000C00, 0x000C00, 0x000F02, 0x011104, 0x4E5E54, 0x91A197, 0x909F98, 0x929F98, 0x929D97, 0x929D95, 0x929C94, 0x949B94, 0x939A93, 0x919891, 0x929992, 0x939A92, 0x939A92, 0x909A91, 0x919B92, 0x919D93, 0x939F95, 0x9DAE9E, 0x9FB0A0, 0x9DAE9E, 0x9EAF9F, 0xA3B4A4, 0x9EAF9F, 0x9CAA99, 0xA2B09F, 0xA6B4A3, 0xA7B5A4, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA7B6A3, 0xA8B7A4, 0xA8B7A4, 0xA7B9A3, 0xA7B9A3, 0xA7B9A3, 0xA7B9A3, 0xA7B8A5, 0xA7B8A5, 0xA9B8A5, 0xA9B8A5, 0xA1B09D, 0xA5B4A1, 0xAAB7A6, 0xADBAA9, 0xADBAA9, 0xAAB7A6, 0xA7B4A3, 0xA4B1A0, 0xA8B5A4, 0xA9B6A5, 0xAAB7A6, 0xA8B5A4, 0xA6B3A2, 0xA4B1A0, 0xA4B1A0, 0xA5B2A1, 0xA9B6A5, 0xA7B4A3, 0xA4B1A0, 0xA3B09F, 0xA2AF9E, 0xA2AF9E, 0xA1AE9D, 0xA0AD9C, 0x9FAC9B, 0x9FAC9B, 0x9EAB9A, 0x9EAB9A, 0x9DAA99, 0x9EAB9A, 0x9FAC9B, 0x9FAC9B, 0x97A594, 0x96A493, 0x97A594, 0x99A796, 0x99AA98, 0x9BAC9A, 0x9AAB99, 0x99AA98, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x94A592, 0x92A390, 0x96A794, 0x93A491, 0x91A28F, 0x93A491, 0x90A18E, 0x8FA08D, 0x152613, 0x051603, 0x000A00, 0x000A00, 0x000D00, 0x000B00, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000C00, 0x000800, 0x000B00, 0x000B00, 0x000C00, 0x001003, 0x011104, 0x536359, 0x95A59B, 0x95A49D, 0x98A59E, 0x9AA59F, 0x9CA79F, 0x9EA8A0, 0xA1A8A1, 0xA1A8A1, 0xA2A9A2, 0xA3AAA3, 0xA4ABA3, 0xA4ABA3, 0xA1ABA2, 0xA1ABA2, 0xA2AEA4, 0xA3AFA5, 0x9DAE9E, 0x9FB0A0, 0x9DAE9E, 0x9EAF9F, 0xA5B3A4, 0xA0AE9F, 0x9DAB9A, 0xA2B09F, 0xA7B4A3, 0xA8B5A4, 0xA8B5A3, 0xA8B5A3, 0xA9B4A3, 0xAAB5A4, 0xAAB5A4, 0xA9B6A4, 0xA9B8A3, 0xA7B9A3, 0xA7B9A3, 0xA7B9A3, 0xA7B8A5, 0xA7B8A5, 0xA9B8A5, 0xA9B8A5, 0xACBBA8, 0xABBAA7, 0xABB8A7, 0xA8B5A4, 0xA6B3A2, 0xA6B3A2, 0xA8B5A4, 0xABB8A7, 0xABB9A8, 0xA7B5A4, 0xA4B2A1, 0xA3B1A0, 0xA6B4A3, 0xA8B6A5, 0xA8B6A5, 0xA7B5A4, 0xA3B1A0, 0xA2B09F, 0xA2B09F, 0xA3B1A0, 0xA3B1A0, 0xA3B1A0, 0xA1AF9E, 0xA0AD9C, 0xA0AD9C, 0xA1AC9C, 0xA1AC9C, 0xA1AC9C, 0x9FAC9B, 0x9EAB9A, 0x9CA998, 0x9CA998, 0x9EAB9A, 0x9DAA99, 0x9BA998, 0x9BA998, 0x9BA998, 0x9AA897, 0x98A695, 0x96A493, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x95A693, 0x92A390, 0x96A794, 0x93A491, 0x92A390, 0x93A491, 0x8FA08D, 0x92A390, 0x1F301D, 0x0A1B08, 0x000A00, 0x000900, 0x000C00, 0x000B00, 0x000900, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000800, 0x000E00, 0x000B00, 0x000B00, 0x011106, 0x041409, 0x5F6F65, 0x99A69F, 0x96A39C, 0x96A39C, 0x99A69F, 0x9AA79E, 0x9AA79E, 0x9CA79F, 0x9FAAA2, 0x9CA89E, 0x9DA99F, 0x9FAB9F, 0xA1ADA1, 0xA4AFA1, 0xA4AFA1, 0xA4AFA1, 0xA3AFA1, 0xA1B09D, 0xA0B19E, 0xA1B29F, 0xA1B29F, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0xA1B29F, 0xA5B6A3, 0xA5B6A3, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA7B8A5, 0xA7B8A5, 0xA5B8A5, 0xA5B8A5, 0xA5B8A5, 0xA6B9A6, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA8B9A7, 0xA8B9A7, 0xA8B9A7, 0xA9B7A6, 0xA6B4A3, 0xA1AF9E, 0x9AA897, 0x94A291, 0x919D8F, 0x8B978B, 0x919B92, 0x959F96, 0x949E95, 0x949E93, 0x96A095, 0x94A094, 0x929E92, 0x98A498, 0x95A195, 0x919D8F, 0x8E9A8C, 0x909E8F, 0x94A293, 0x93A192, 0x8F9D8C, 0x8C9F8B, 0x90A38D, 0x97AA94, 0x9DB09A, 0x9FB29C, 0x9EB19B, 0x9AAD97, 0x98AB95, 0x98AB95, 0x97AA94, 0x97AA94, 0x97AA94, 0x98AB95, 0x98AB95, 0x96A993, 0x95A892, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x92A390, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x91A28F, 0x90A18E, 0x90A18E, 0x334431, 0x021300, 0x001100, 0x000700, 0x000C00, 0x000B00, 0x000700, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000A00, 0x000A00, 0x000D00, 0x000B00, 0x000E01, 0x001005, 0x041409, 0x6A7A70, 0x8D9A93, 0x8A9790, 0x89968F, 0x8C9992, 0x8D9A93, 0x8C9992, 0x8F9A92, 0x929D95, 0x8D9890, 0x8E9991, 0x8F9B91, 0x8F9B91, 0x919B92, 0x909A91, 0x909A8F, 0x8E9A8C, 0x9BA998, 0x9BAC99, 0x9EAF9C, 0x9FB09D, 0xA0B19E, 0xA1B29F, 0xA3B4A1, 0xA4B5A2, 0xA5B6A3, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA7B8A5, 0xA7B8A5, 0xA7B8A5, 0xA5B8A5, 0xA5B8A5, 0xA6B9A6, 0xA6B9A6, 0xA7B8A6, 0xA7B8A6, 0xA8B9A7, 0xA8B9A7, 0xA5B6A4, 0xA5B6A4, 0xA8B6A5, 0xA8B6A5, 0xAAB8A7, 0xABB9A8, 0xADBBAA, 0xAFBBAD, 0xA9B5A9, 0xADB7AE, 0xADB9AF, 0xAAB6AC, 0xA8B4A8, 0xA9B5A9, 0xA8B4A8, 0xA6B2A6, 0xA7B5A6, 0xA6B4A5, 0xA2B0A1, 0x9EAC9D, 0x9DAB9C, 0x9EAC9D, 0x99AA9A, 0x94A593, 0x96A995, 0x97AA94, 0x99AC96, 0x9AAD97, 0x9BAE98, 0x9BAE98, 0x9BAE98, 0x9BAE98, 0x99AC96, 0x9AAD97, 0x9BAE98, 0x99AC96, 0x97AA94, 0x96A993, 0x95A892, 0x96A993, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x95A693, 0x95A693, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x92A390, 0x93A491, 0x93A491, 0x93A491, 0x93A491, 0x92A390, 0x90A18E, 0x90A18E, 0x41523F, 0x021300, 0x001000, 0x000900, 0x000C00, 0x000A00, 0x000800, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000900, 0x000C00, 0x000B00, 0x000A00, 0x021205, 0x000E03, 0x041409, 0x7A8A80, 0x9DAAA3, 0x9AA7A0, 0x99A69F, 0x9BA8A1, 0x9BA8A1, 0x9BA8A1, 0x9EA9A3, 0xA2ADA7, 0x9FAAA4, 0x9FAAA4, 0xA0ABA5, 0xA0ABA5, 0xA1AAA5, 0xA0A9A4, 0xA0A9A4, 0x9EA9A1, 0xA1AF9E, 0xA1B29F, 0xA2B3A0, 0xA3B4A1, 0xA2B3A0, 0xA2B3A0, 0xA3B4A1, 0xA4B5A2, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA6B7A4, 0xA7B8A5, 0xA7B8A5, 0xA7B8A5, 0xA6B9A6, 0xA6B9A6, 0xA6B9A6, 0xA6B9A6, 0xA8B9A7, 0xA8B9A7, 0xA8B9A7, 0xA8B9A7, 0xA9BAA8, 0xA8B9A7, 0xA9B7A6, 0xA8B6A5, 0xA7B5A4, 0xA6B4A3, 0xA6B4A3, 0xA6B4A5, 0xA2AEA0, 0xA5B1A5, 0xA5B3A6, 0xA2B0A3, 0xA1AFA2, 0xA4B2A5, 0xA6B4A7, 0xA5B3A6, 0xA0B1A1, 0xA2B3A3, 0xA2B3A3, 0xA0B1A1, 0xA0B19F, 0xA3B4A2, 0xA2B3A1, 0xA0B19F, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9BAC99, 0x9DAE9B, 0x9EAF9C, 0x9DAE9B, 0x9AAB98, 0x98A996, 0x98A996, 0x99AA97, 0x97A895, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x93A491, 0x93A491, 0x94A592, 0x94A592, 0x93A491, 0x92A390, 0x91A28F, 0x90A18E, 0x566754, 0x011200, 0x000E00, 0x000E00, 0x000B00, 0x000800, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000900, 0x000E00, 0x000B00, 0x000900, 0x031306, 0x000B00, 0x031308, 0x889790, 0x9CA9A2, 0x99A5A1, 0x97A39F, 0x99A5A1, 0x99A5A1, 0x99A5A1, 0x9DA8A4, 0xA1ACA8, 0xA2ADA9, 0xA2ADA9, 0xA3AEAA, 0xA4AFAB, 0xA5AEAB, 0xA6AFAC, 0xA6AFAC, 0xA5B0AA, 0xA1AFA0, 0xA1B2A0, 0xA2B3A1, 0xA3B4A2, 0xA2B3A1, 0xA2B3A1, 0xA3B4A2, 0xA4B5A3, 0xA6B7A5, 0xA6B7A5, 0xA6B7A5, 0xA6B7A5, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA6B9A6, 0xA6B9A6, 0xA7BAA7, 0xA7BAA7, 0xA8B9A7, 0xA8B9A7, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xABB9A8, 0xA9B7A6, 0xA5B3A2, 0x9FAD9C, 0x98A695, 0x95A394, 0x93A192, 0x95A396, 0x96A497, 0x93A194, 0x93A194, 0x97A598, 0x98A999, 0x98A999, 0x91A292, 0x95A696, 0x96A996, 0x95A895, 0x97AA97, 0x9CAF9C, 0xA0B3A0, 0xA0B3A0, 0x9EAF9C, 0x9FB09D, 0xA0B19E, 0xA1B29F, 0xA0B19E, 0x9DAE9B, 0x9AAB98, 0x98A996, 0x99AA97, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x93A491, 0x94A592, 0x94A592, 0x94A592, 0x94A592, 0x93A491, 0x91A28F, 0x91A28F, 0x6A7B68, 0x001100, 0x000A00, 0x011200, 0x000900, 0x000700, 0x000E00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000A00, 0x000E00, 0x000B00, 0x000900, 0x011104, 0x000A00, 0x06160B, 0x8F9E97, 0x8F9C95, 0x8C9894, 0x8B9793, 0x8C9894, 0x8D9995, 0x8D9995, 0x909B97, 0x949F9B, 0x95A09C, 0x95A09C, 0x96A19D, 0x96A19D, 0x98A19E, 0x99A29F, 0x99A29F, 0x99A49E, 0x95A394, 0x96A795, 0x9AAB99, 0x9DAE9C, 0x9FB09E, 0xA2B3A1, 0xA5B6A4, 0xA7B8A6, 0xA6B7A5, 0xA6B7A5, 0xA6B7A5, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA8B9A7, 0xA7BAA7, 0xA7BAA7, 0xA7BAA7, 0xA7BAA7, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA3B4A2, 0xA5B6A4, 0xAAB8A7, 0xACBAA9, 0xACBAA9, 0xA9B7A6, 0xA6B4A3, 0xA4B2A3, 0xA4B0A2, 0xA6B2A6, 0xA4B2A3, 0xA0AE9F, 0x9EAC9D, 0x9FAD9E, 0xA0AE9F, 0x9FAD9E, 0x9AAB99, 0x9CAD9B, 0x9BAC9A, 0x98A997, 0x98A997, 0x9CAD9B, 0x9FB29E, 0x9FB29E, 0x9DAE9B, 0x9EAF9C, 0x9FB09D, 0xA0B19E, 0x9FB09D, 0x9DAE9B, 0x9AAB98, 0x99AA97, 0x9BAC99, 0x99AA97, 0x97A895, 0x98A996, 0x9AAB98, 0x9AAB98, 0x97A895, 0x95A693, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x94A592, 0x94A592, 0x95A693, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x91A28F, 0x7A8B78, 0x021300, 0x000800, 0x011200, 0x000900, 0x000700, 0x001000, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000C00, 0x000C00, 0x000A00, 0x000E01, 0x000B00, 0x0E1E13, 0x93A29B, 0x99A5A1, 0x97A3A1, 0x97A3A1, 0x9AA6A4, 0x9BA7A3, 0x9BA7A3, 0x9DA8A4, 0xA0ABA7, 0x9FAAA4, 0x9EA9A3, 0x9EA9A1, 0x9DA8A0, 0x9EA89F, 0x9EA89F, 0x9FA9A0, 0x9EAAA0, 0x99A798, 0x99AA9A, 0x9CAD9D, 0x9EAF9F, 0xA0B1A1, 0xA2B3A3, 0xA4B5A5, 0xA6B7A7, 0xA6B7A7, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA8B9A9, 0xA8B9A9, 0xA8B9A9, 0xA7BAA7, 0xA7BAA7, 0xA8BBA8, 0xA8BBA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xA8B9A7, 0xA7B8A6, 0xA9B7A6, 0xA8B6A5, 0xA8B6A5, 0xA9B7A6, 0xA9B7A6, 0xAAB7A6, 0xA8B4A6, 0xADB8AA, 0xAEB9AB, 0xABB6A8, 0xA8B4A6, 0xA9B5A7, 0xA9B6A5, 0xA7B4A3, 0xA9B6A5, 0xAAB7A6, 0xA7B5A4, 0xA4B2A1, 0xA4B3A0, 0xA8B7A4, 0xA9B8A5, 0xA8B7A4, 0xA5B3A2, 0xA4B2A1, 0xA2B09F, 0xA0AE9D, 0xA0AE9D, 0xA1AF9E, 0xA2B09F, 0xA3B1A0, 0xA2B09F, 0xA0AE9D, 0x9FAD9C, 0x9EAC9B, 0x9FAD9C, 0x9EAC9B, 0x9BA998, 0x99A796, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x94A592, 0x95A693, 0x95A693, 0x95A693, 0x95A693, 0x94A592, 0x92A390, 0x92A390, 0x849582, 0x091A07, 0x000900, 0x001100, 0x000A00, 0x000700, 0x001000, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000900, 0x000E00, 0x000C00, 0x000B00, 0x001005, 0x19291E, 0x96A59E, 0x9BA7A5, 0x9AA6A6, 0x9BA7A7, 0x9FABAB, 0xA1ADA9, 0xA0ACA8, 0xA1ACA6, 0xA4AFA9, 0xA3AFA5, 0xA3AFA5, 0xA2AEA0, 0xA1AD9F, 0xA3AE9E, 0xA3AE9E, 0xA4AF9F, 0xA4B1A0, 0xA3B1A2, 0xA2B3A3, 0xA4B5A5, 0xA4B5A5, 0xA3B4A4, 0xA2B3A3, 0xA3B4A4, 0xA4B5A5, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA8B9A9, 0xA8B9A9, 0xA8B9A9, 0xA8BBA8, 0xA8BBA8, 0xA8BBA8, 0xA8BBA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xADBEAC, 0xACBDAB, 0xAAB8A7, 0xA7B5A4, 0xA3B1A0, 0xA0AE9D, 0x9EAC9B, 0x9EAB9A, 0x9AA597, 0xA2AA9D, 0xA6AEA1, 0xA5ADA0, 0xA5AD9E, 0xA6AE9F, 0xA3AE9E, 0xA1AC9C, 0xA2AD9D, 0xA3AE9E, 0xA2AD9C, 0xA2AD9C, 0xA3B09E, 0xA6B3A1, 0xA4B19F, 0xA0AD9B, 0x9FAD9C, 0x9EAC9B, 0x9DAB9A, 0x9CAA99, 0x9CAA99, 0x9DAB9A, 0x9EAC9B, 0x9EAC9B, 0x97A594, 0x9BA998, 0x9DAB9A, 0x9CAA99, 0x99A796, 0x98A695, 0x9AA897, 0x9CAA99, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x95A693, 0x95A693, 0x96A794, 0x96A794, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x8C9D8A, 0x142512, 0x000D00, 0x000F00, 0x000B00, 0x000800, 0x000D00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000C00, 0x000C00, 0x000C00, 0x000B00, 0x000700, 0x000F00, 0x000E00, 0x000900, 0x041409, 0x213126, 0x97A69F, 0x8E9A9A, 0x8E999B, 0x909C9C, 0x95A1A1, 0x97A39F, 0x96A29E, 0x97A29A, 0x99A49C, 0x9CA89C, 0x9CA89C, 0x9CA998, 0x9DAA99, 0x9FAB97, 0xA1AD99, 0xA3AF99, 0xA4B19D, 0xA0AE9D, 0xA0B1A1, 0xA2B3A3, 0xA3B4A4, 0xA3B4A4, 0xA4B5A5, 0xA5B6A6, 0xA6B7A7, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA7B8A8, 0xA8B9A9, 0xA8B9A9, 0xA8B9A9, 0xA8B9A9, 0xA8BBA8, 0xA8BBA8, 0xA8BBA8, 0xA8BBA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xA8B9A7, 0xAABBA9, 0xADBBAA, 0xADBBAA, 0xAAB8A7, 0xA6B4A3, 0xA2B09F, 0xA0AD9C, 0x9CA497, 0xA3A99D, 0xA7ADA1, 0xA6ACA0, 0xA4AA9C, 0xA2A89A, 0x9DA596, 0x99A192, 0x959D8E, 0x969E8F, 0x959E8D, 0x959E8D, 0x96A190, 0x96A190, 0x909B8A, 0x889382, 0x8D9B8A, 0x8F9D8C, 0x92A08F, 0x94A291, 0x94A291, 0x92A08F, 0x8F9D8C, 0x8C9A89, 0x82907F, 0x8B9988, 0x92A08F, 0x92A08F, 0x8C9A89, 0x8B9988, 0x92A08F, 0x99A796, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x95A693, 0x95A693, 0x96A794, 0x96A794, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x91A28F, 0x1C2D1A, 0x001100, 0x000F00, 0x000D00, 0x000900, 0x000C00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x001000, 0x000900, 0x001000, 0x000E00, 0x000A00, 0x35443D, 0x94A39C, 0x93A097, 0x95A299, 0x95A299, 0x95A29B, 0x95A19D, 0x96A29E, 0x96A09F, 0x959F9E, 0x98A39F, 0x97A29E, 0x95A09A, 0x949F99, 0x949E95, 0x949E95, 0x949F91, 0x94A092, 0x919F90, 0x94A595, 0x9BAC9C, 0x9FB0A0, 0xA1B2A0, 0xA2B3A1, 0xA4B5A3, 0xA6B7A5, 0xA7B8A6, 0xA7B8A6, 0xA7B8A5, 0xA8B9A6, 0xA9BAA7, 0xA9BAA7, 0xAABBA8, 0xAABBA8, 0xA6BCA5, 0xA6BCA5, 0xA7BDA6, 0xA7BDA6, 0xA9BCA8, 0xA9BCA8, 0xA9BCA8, 0xA8BBA7, 0xA8BBA7, 0xABBEAA, 0xAEBFAD, 0xAABBA9, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA8B6A5, 0xAAB8A7, 0xA6B3A2, 0xA5B2A1, 0xA8B5A4, 0xA9B6A5, 0xA8B5A4, 0xA7B4A3, 0xA9B6A5, 0xA8B5A4, 0xADBAA9, 0xA3B09F, 0xABB8A7, 0xA5B2A1, 0xA8B5A4, 0x9DAA99, 0xA6B3A2, 0x9DAE9B, 0x9EAF9C, 0x9FB09D, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9EAF9C, 0x9DAE9B, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x98A996, 0x97A895, 0x96A794, 0x95A693, 0x95A693, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x96A892, 0x96A892, 0x96A892, 0x96A892, 0x95A791, 0x94A690, 0x93A58F, 0x92A48E, 0x849680, 0x3C4E38, 0x000B00, 0x011300, 0x000D00, 0x000800, 0x000F00, 0x000E00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000E00, 0x000800, 0x001000, 0x000E00, 0x000C01, 0x3C4B44, 0x96A5A0, 0x9CA9A0, 0x9DAAA1, 0x9EABA4, 0x9FACA5, 0xA1ADA9, 0xA2AEAA, 0xA4AFAB, 0xA4AFAB, 0xA4AFAB, 0xA3AEAA, 0xA3AEA8, 0xA3AEA8, 0xA5AFA6, 0xA6B0A7, 0xA6B0A5, 0xA6B2A6, 0xA0AE9F, 0xA1B2A2, 0xA5B6A6, 0xA5B6A6, 0xA4B5A3, 0xA2B3A1, 0xA2B3A1, 0xA3B4A2, 0xA7B8A6, 0xA7B8A6, 0xA8B9A6, 0xA8B9A6, 0xA9BAA7, 0xA9BAA7, 0xAABBA8, 0xAABBA8, 0xA7BDA6, 0xA7BDA6, 0xA7BDA6, 0xA7BDA6, 0xA9BCA8, 0xA9BCA8, 0xA8BBA7, 0xA8BBA7, 0xA7B8A5, 0xA7B8A5, 0xA8B9A7, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xADBEAC, 0xB3C1B0, 0xAAB8A7, 0xABB8A7, 0xABB8A7, 0xACB9A8, 0xADBAA9, 0xADBAA9, 0xADBAA9, 0xACB9A8, 0xA4B1A0, 0xA7B4A3, 0x9FAC9B, 0xA3B09F, 0xA4B1A0, 0xA6B3A2, 0xA2AF9E, 0xACB9A8, 0xA4B5A2, 0xA2B3A0, 0xA0B19E, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9AAB98, 0x99AA97, 0x99AA97, 0x98A996, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x96A892, 0x96A892, 0x96A892, 0x96A892, 0x95A791, 0x94A690, 0x93A58F, 0x92A48E, 0x899B85, 0x455741, 0x000D00, 0x011300, 0x000E00, 0x000800, 0x000E00, 0x000C00, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000800, 0x000F00, 0x000E00, 0x000F04, 0x485750, 0x97A6A1, 0x96A39A, 0x96A39A, 0x98A59E, 0x9AA7A0, 0x9BA7A3, 0x9CA8A4, 0x9DA8A4, 0x9EA9A5, 0x9FAAA4, 0xA0ABA5, 0xA1ACA4, 0xA2ADA5, 0xA3ADA4, 0xA4AEA5, 0xA4AEA3, 0xA3AFA3, 0x9EAC9D, 0x9FB0A0, 0xA3B4A4, 0xA4B5A5, 0xA3B4A4, 0xA2B3A3, 0xA4B5A3, 0xA6B7A5, 0xA7B8A6, 0xA8B9A7, 0xA8B9A7, 0xA8B9A7, 0xA9BAA7, 0xA9BAA7, 0xAABBA8, 0xAABBA8, 0xA9BCA6, 0xA9BCA6, 0xAABDA7, 0xA9BCA6, 0xA9BCA8, 0xA8BBA7, 0xA8BBA7, 0xA7BAA6, 0xADBEAB, 0xA8B9A6, 0xA5B6A4, 0xA6B7A5, 0xA5B3A2, 0xA1AF9E, 0xA4B2A1, 0xAAB8A7, 0xA7B4A3, 0xAAB7A6, 0xA8B5A4, 0xA2AF9E, 0xA1AE9D, 0xA7B4A3, 0xAAB7A6, 0xA8B5A4, 0xACB9A8, 0xACB9A8, 0xABB8A7, 0xA7B4A3, 0xAEBBAA, 0xA5B2A1, 0xA4B1A0, 0xA8B5A4, 0xA0B19E, 0xA0B19E, 0xA1B29F, 0xA2B3A0, 0xA2B3A0, 0xA2B3A0, 0xA1B29F, 0xA1B29F, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x8FA08D, 0x536451, 0x000E00, 0x001100, 0x000D00, 0x000900, 0x000C00, 0x000A00, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000B00, 0x000900, 0x000F00, 0x000E00, 0x001005, 0x55645D, 0x97A6A1, 0x8D9A93, 0x8C9992, 0x8D9A93, 0x8F9C95, 0x909D96, 0x8E9B94, 0x8F9A94, 0x919C96, 0x929D97, 0x929D97, 0x939E96, 0x949F97, 0x959F96, 0x949E95, 0x939D94, 0x919D93, 0x9AA89B, 0x9BAB9E, 0x9FB0A0, 0xA1B2A2, 0xA1B2A2, 0xA3B4A4, 0xA6B7A7, 0xAABBAB, 0xA8B9A7, 0xA8B9A7, 0xA8B9A7, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA8, 0xAABBA8, 0xAABDA7, 0xAABDA7, 0xAABDA7, 0xAABDA7, 0xA9BCA8, 0xA8BBA7, 0xA8B9A6, 0xA8B9A6, 0x8FA08D, 0x879885, 0x849281, 0x839180, 0x7E8C7B, 0x768473, 0x768473, 0x7C8A79, 0x8D9B8A, 0x909E8D, 0x8B9988, 0x7E8C7B, 0x7B8978, 0x859382, 0x8B9988, 0x8A9887, 0x707E6D, 0x748271, 0x808E7D, 0x808E7D, 0x93A190, 0x879584, 0x8A9887, 0x8B9988, 0x839481, 0x899A87, 0x92A390, 0x9BAC99, 0xA0B19E, 0xA0B19E, 0x9EAF9C, 0x9CAD9A, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x91A28F, 0x627360, 0x001000, 0x000F00, 0x000C00, 0x000B00, 0x000B00, 0x000A00, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000F00, 0x000B00, 0x000B00, 0x001000, 0x000D00, 0x011106, 0x62716A, 0x95A49F, 0x9EAAA6, 0x9CA8A4, 0x9CA8A4, 0xA0ACA8, 0xA1AEA7, 0x9EABA4, 0x9FAAA2, 0xA2ADA5, 0x9FAAA2, 0xA0ABA3, 0xA1ACA4, 0xA2ADA5, 0xA3ADA5, 0xA3ADA5, 0xA2ACA4, 0xA0ABA3, 0xA2B0A3, 0xA3B3A6, 0xA5B5A8, 0xA5B5A8, 0xA3B3A6, 0xA3B3A6, 0xA5B6A6, 0xA7B8A8, 0xA9BAAA, 0xA9BAAA, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABDA7, 0xAABDA7, 0xABBDA7, 0xABBDA7, 0xAABBA8, 0xA9BAA7, 0xA8B9A6, 0xA8B9A6, 0xB3C2AF, 0xADBCA9, 0xABB9A8, 0xAEBCAB, 0xADBBAA, 0xA8B6A5, 0xA9B6A5, 0xADBAA9, 0xA6B4A3, 0xAAB8A7, 0xA6B4A3, 0x9AA897, 0x98A695, 0xA0AE9D, 0xA3B1A0, 0x9FAD9C, 0x9BA998, 0x9BA998, 0xA0AE9D, 0x96A493, 0x9FAD9C, 0x8A9887, 0x869483, 0x839180, 0x839481, 0x899A87, 0x92A390, 0x9AAB98, 0x9FB09D, 0xA0B19E, 0x9EAF9C, 0x9CAD9A, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x96A794, 0x96A794, 0x96A794, 0x96A794, 0x95A693, 0x94A592, 0x93A491, 0x92A390, 0x90A18E, 0x70816E, 0x011200, 0x000E00, 0x000B00, 0x000D00, 0x000B00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x001200, 0x000B00, 0x000D00, 0x001000, 0x000D00, 0x031308, 0x6E7D76, 0x94A39E, 0x9BA7A5, 0x98A4A2, 0x9AA6A2, 0xA0ACA8, 0xA2AFA8, 0x9FACA5, 0xA1ACA4, 0xA6B1A9, 0xA3AFA5, 0xA4B0A6, 0xA5B1A7, 0xA7B3A9, 0xA9B3AB, 0xABB5AD, 0xABB4AF, 0xABB6AE, 0xA1AEA4, 0xA2B2A5, 0xA5B5A8, 0xA5B5A8, 0xA4B4A7, 0xA3B3A6, 0xA5B5A8, 0xA7B7AA, 0xA9BAAA, 0xA9BAAA, 0xA9BAAA, 0xA9BAAA, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABCA6, 0xAABCA6, 0xABBDA7, 0xAABCA6, 0xAABBA8, 0xA9BAA7, 0xABBAA7, 0xAAB9A6, 0xAAB9A6, 0xA8B7A4, 0xA7B5A4, 0xA8B6A5, 0xABB8A7, 0xACB9A8, 0xACB9A8, 0xACB9A8, 0xA5B6A4, 0xA9BAA8, 0xAABBA9, 0xA7B8A6, 0xA9BAA8, 0xACBDAB, 0xA9BAA8, 0xA2B3A1, 0xA3B4A2, 0xA3B4A2, 0xA4B5A3, 0xA2B3A1, 0xABBCAA, 0xA4B5A3, 0xA5B6A4, 0xAABBA9, 0xA0B19E, 0xA0B19E, 0xA1B29F, 0xA2B3A0, 0xA2B3A0, 0xA2B3A0, 0xA1B29F, 0xA1B29F, 0x9FB09D, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x96A795, 0x96A795, 0x96A795, 0x96A795, 0x95A694, 0x94A593, 0x93A492, 0x92A391, 0x8E9F8D, 0x7E8F7D, 0x051604, 0x000E00, 0x000A00, 0x000D00, 0x000A00, 0x000C00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x011300, 0x000A00, 0x000D00, 0x001000, 0x000C00, 0x041409, 0x798881, 0x94A39E, 0x899593, 0x85918F, 0x87938F, 0x8E9A96, 0x909D94, 0x8D9A91, 0x8F9B91, 0x94A096, 0x929E94, 0x939F95, 0x939F95, 0x95A197, 0x97A199, 0x99A39B, 0x9BA49F, 0x9BA6A0, 0x98A59B, 0x9AAA9F, 0x9FAFA4, 0xA2B2A7, 0xA3B3A6, 0xA3B3A6, 0xA6B6A9, 0xA8B8AB, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xA9BAAA, 0xA9BAA8, 0xAABBA8, 0xAABCA6, 0xAABCA6, 0xAABCA6, 0xACBBA8, 0xACBBA8, 0xABBAA7, 0xABBAA7, 0xA9B8A5, 0xAAB9A6, 0xA6B3A2, 0x9FAC9B, 0x9FAC9B, 0xA4B1A0, 0xA4B1A0, 0x9EAB9A, 0x98A997, 0x98A997, 0x9AAB99, 0xA0B19F, 0xA7B8A6, 0xAABBA9, 0xA7B8A6, 0xA2B3A1, 0xAABBA9, 0xACBDAB, 0xA3B4A2, 0xA4B5A3, 0xA3B4A2, 0xA3B4A2, 0x9EAF9D, 0xA6B7A5, 0xA6B7A4, 0xA4B5A2, 0xA2B3A0, 0x9FB09D, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x96A795, 0x96A795, 0x96A795, 0x96A795, 0x95A694, 0x94A593, 0x93A492, 0x92A391, 0x8D9E8C, 0x8B9C8A, 0x0B1C0A, 0x001100, 0x000B00, 0x000D00, 0x000800, 0x000D00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x011300, 0x000900, 0x000D00, 0x001000, 0x000D00, 0x06160B, 0x7F8E87, 0x95A4A1, 0x9CA8A6, 0x98A4A4, 0x99A5A1, 0x9FABA7, 0xA0ADA4, 0x9BA89F, 0x9DA99F, 0xA2AEA4, 0x9DA99D, 0x9CA89C, 0x9CA89E, 0x9CA89E, 0x9DA79F, 0x9FA8A3, 0xA0A9A4, 0xA0ABA5, 0x98A59C, 0x9BABA0, 0xA0B0A5, 0xA3B3A8, 0xA3B3A6, 0xA3B3A6, 0xA4B4A7, 0xA6B6A9, 0xAABAAD, 0xAABAAD, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xAABBAB, 0xA9BAAA, 0xA9BAA8, 0xA9BAA7, 0xA9BBA5, 0xAABCA6, 0xAABCA6, 0xACBBA8, 0xACBBA8, 0xACBBA8, 0xABBAA7, 0xA8B7A4, 0xAAB9A6, 0xA4B1A0, 0x97A493, 0x96A392, 0x9EAB9A, 0x9EAB9A, 0x96A392, 0x94A593, 0x8D9E8C, 0x8D9E8C, 0x96A795, 0xA1B2A0, 0xA7B8A6, 0xA8B9A7, 0xA9BAA8, 0xA5B6A4, 0xAABBA9, 0xA1B2A0, 0xABBCAA, 0xA5B6A4, 0xAABBA9, 0x9FB09E, 0xA9BAA8, 0xA2B3A0, 0xA2B3A0, 0xA4B5A2, 0xA4B5A2, 0xA4B5A2, 0xA4B5A2, 0xA2B3A0, 0xA1B29F, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x96A795, 0x96A795, 0x96A795, 0x96A795, 0x95A694, 0x94A593, 0x93A492, 0x92A391, 0x8E9F8D, 0x93A492, 0x10210F, 0x031402, 0x000B00, 0x000D00, 0x000800, 0x000E00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000A00, 0x000B00, 0x000B00, 0x000B00, 0x000A00, 0x000700, 0x000A00, 0x000C00, 0x000900, 0x000B00, 0x000E00, 0x000B00, 0x000800, 0x000600, 0x011104, 0x000F02, 0x000700, 0x000900, 0x000D00, 0x000C00, 0x000F00, 0x000E00, 0x000D00, 0x000E01, 0x000B00, 0x08180D, 0x8A9A90, 0x97A69F, 0x98A7A0, 0x9BAAA5, 0x9EADA8, 0x9FAEA9, 0x9FAEA9, 0x9EADA8, 0x9EADA6, 0x9EADA6, 0x9EADA6, 0xA0AFA8, 0xA1B0A9, 0xA0AFA8, 0xA1B1A7, 0xA4B4AA, 0xA5B5AB, 0xA3B3A9, 0xA3B3A6, 0xA4B4A7, 0xA4B4A7, 0xA4B4A7, 0xA3B3A6, 0xA4B4A7, 0xA6B7A7, 0xA8B9A9, 0xA8B9A9, 0xA9BAAA, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xADBBAA, 0xADBBAA, 0xAAB8A7, 0xAAB8A7, 0xA9B5A7, 0xADB9AB, 0xB4C0B2, 0xAEBAAC, 0xA7B3A5, 0xA9B7A8, 0xA5B8A5, 0xA3B8A5, 0xA5B8A5, 0xA5B8A5, 0xA5B8A5, 0xA5B8A5, 0xA6B7A5, 0xA7B8A6, 0xA6B7A5, 0xA8B9A7, 0xA9B7A6, 0xA6B4A3, 0xA5B3A2, 0xA7B5A4, 0xA7B4A3, 0xA4B1A0, 0xA1B2A0, 0x9FB09E, 0xA0B19F, 0xA2B3A1, 0xA2B3A1, 0x9FB09E, 0x9EAF9D, 0xA0B19F, 0xA1B2A0, 0xA0B19F, 0x9EAF9D, 0x9EAF9D, 0x9EAF9D, 0x9EAF9D, 0x9CAD9B, 0x9BAC9A, 0x9BAC9A, 0x9BAC9A, 0x9AAB99, 0x9AAB99, 0x99AA98, 0x99AA98, 0x98A997, 0x98A997, 0x96A795, 0x9AAB99, 0x9BAC9A, 0x98A997, 0x98A997, 0x9AAB99, 0x9AAB99, 0x97A895, 0x97A993, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x8D9D90, 0x8B9B8E, 0x2E3F2F, 0x000E00, 0x000F00, 0x000C00, 0x000900, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000900, 0x000C00, 0x001000, 0x000D00, 0x000700, 0x000700, 0x001100, 0x000800, 0x001000, 0x001000, 0x000A00, 0x000A00, 0x000E00, 0x000B00, 0x000700, 0x000700, 0x000F02, 0x000600, 0x000600, 0x001000, 0x000D00, 0x000A00, 0x031402, 0x000E00, 0x000E00, 0x000D00, 0x000E01, 0x000C00, 0x0F1F14, 0x8E9E94, 0x99A8A1, 0x8F9E97, 0x91A09B, 0x94A39E, 0x97A6A1, 0x99A8A3, 0x9CABA6, 0x9FAEA7, 0xA1B0A9, 0x9FAEA7, 0xA1B0A9, 0xA1B0A9, 0x9FAEA7, 0x9FAFA5, 0xA2B2A8, 0xA3B3A9, 0xA1B1A7, 0xA4B4A7, 0xA4B4A7, 0xA5B5A8, 0xA4B4A7, 0xA4B5A5, 0xA4B5A5, 0xA6B7A7, 0xA8B9A9, 0xA9BAAA, 0xA9BAAA, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xADBBAA, 0xADBBAA, 0xB2C0AF, 0xB2C0AF, 0xADB9AB, 0xAAB6A8, 0xACB8AA, 0xA8B4A6, 0xA8B4A6, 0xB1BFB0, 0xA7B8A6, 0xA4B7A4, 0xA3B6A3, 0xA4B7A4, 0xA6B9A6, 0xA7BAA7, 0xA6B7A5, 0xA4B5A3, 0xA4B5A3, 0xA6B7A5, 0xA8B6A5, 0xA6B4A3, 0xA7B5A4, 0xA9B7A6, 0xAAB7A6, 0xA8B5A4, 0xA7B8A6, 0xA5B6A4, 0xA5B6A4, 0xA7B8A6, 0xA5B6A4, 0xA2B3A1, 0xA0B19F, 0xA1B2A0, 0x9DAE9C, 0x9CAD9B, 0x9BAC9A, 0x9CAD9B, 0x9DAE9C, 0x9DAE9C, 0x9CAD9B, 0x9BAC9A, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9BAC9A, 0x98A997, 0x9BAC9A, 0x9BAC9A, 0x98A997, 0x98A997, 0x9AAB99, 0x9AAB99, 0x97A895, 0x97A993, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x8F9F92, 0x8B9B8E, 0x364737, 0x000F00, 0x000F00, 0x000C00, 0x000B00, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000F00, 0x000700, 0x000700, 0x000F00, 0x021300, 0x081906, 0x21321F, 0x40513E, 0x001100, 0x132411, 0x10210E, 0x031401, 0x0A1B08, 0x0F200D, 0x061704, 0x011200, 0x102111, 0x203023, 0x091A0A, 0x000D00, 0x000E00, 0x001100, 0x041503, 0x000700, 0x000D00, 0x000D00, 0x000D00, 0x000F02, 0x000E01, 0x19291E, 0x91A197, 0x9AA9A2, 0x8A9992, 0x8B9A95, 0x8C9B96, 0x8C9B96, 0x8C9B96, 0x8E9D98, 0x909F98, 0x93A29B, 0x86958E, 0x899891, 0x8C9B94, 0x8E9D96, 0x93A399, 0x9CACA2, 0xA1B1A7, 0xA1B1A7, 0xA4B4A7, 0xA5B5A8, 0xA5B6A6, 0xA5B6A6, 0xA4B5A5, 0xA5B6A6, 0xA7B8A8, 0xA8B9A9, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xADBBAA, 0xADBBAA, 0xA2B09F, 0xA4B2A1, 0x9DA99B, 0x94A092, 0x919D8F, 0x8C988A, 0x8F9B8D, 0x9BA99A, 0x97A896, 0x93A693, 0x92A391, 0x93A492, 0x97A896, 0x98A997, 0x95A694, 0x92A391, 0x98A695, 0x9AA897, 0x9AA897, 0x97A594, 0x98A695, 0x9AA897, 0x9BA998, 0x99A796, 0x97A896, 0x96A795, 0x98A997, 0x9CAD9B, 0x9DAE9C, 0x9CAD9B, 0x9CAD9B, 0x9EAF9D, 0xA1B2A0, 0xA0B19F, 0x9EAF9D, 0x9EAF9D, 0x9FB09E, 0x9FB09E, 0x9EAF9D, 0x9DAE9C, 0x9BAC9A, 0x9BAC9A, 0x9BAC9A, 0x9BAC9A, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x9AAB99, 0x98A997, 0x9AAB99, 0x99AA98, 0x97A896, 0x97A896, 0x99AA98, 0x9AAB99, 0x99AA97, 0x97A993, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x91A194, 0x8B9B8E, 0x445545, 0x001000, 0x000F00, 0x000C00, 0x000C00, 0x000D00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000A00, 0x000800, 0x000B00, 0x000D00, 0x000700, 0x000700, 0x000700, 0x000D00, 0x000A00, 0x0D1E0B, 0x061704, 0x000D00, 0x091A07, 0x0B1C09, 0x011200, 0x051604, 0x000900, 0x061707, 0x051604, 0x051604, 0x000A00, 0x1B2C19, 0x425340, 0x061704, 0x000D00, 0x000D00, 0x000C00, 0x000E01, 0x000E01, 0x233328, 0x92A298, 0x99A8A1, 0xA0AFA8, 0xA0AFAA, 0xA1B0AB, 0xA0AFAA, 0x9EADA8, 0x9EADA8, 0x9FAEA7, 0xA0AFA8, 0xA3B2AB, 0xA4B3AC, 0xA2B1AA, 0x9FAEA7, 0xA0B0A6, 0xA4B4AA, 0xA6B6AC, 0xA6B6AB, 0xA4B4A7, 0xA5B6A6, 0xA6B7A7, 0xA5B6A6, 0xA4B5A5, 0xA5B6A6, 0xA7B8A6, 0xA9BAA8, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA8, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xADBBAA, 0xADBBAA, 0xAEBCAB, 0xB3C1B0, 0xB1BDAF, 0xABB7A9, 0xA9B5A7, 0xA4B0A2, 0xA4B0A2, 0xADB9AB, 0xA6B7A5, 0xA4B5A3, 0xA4B5A3, 0xA4B5A3, 0xA8B6A5, 0xA8B6A5, 0xA6B4A3, 0xA4B2A1, 0xA0AE9D, 0xA2B09F, 0xA1AF9E, 0x9DAB9A, 0x9CAA99, 0x9EAC9B, 0x9EAC9B, 0x9CAA99, 0x9AAB99, 0x99AA98, 0x9BAC9A, 0xA0B19F, 0xA2B3A1, 0xA2B3A1, 0xA2B3A1, 0xA5B6A4, 0xA0B19F, 0x9FB09E, 0x9DAE9C, 0x9CAD9B, 0x9CAD9B, 0x9BAC9A, 0x9AAB99, 0x98A997, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x96A794, 0x97A895, 0x99AA97, 0x9AAB98, 0x99AA97, 0x97A991, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x92A295, 0x8A9A8D, 0x556656, 0x001101, 0x000E00, 0x000A00, 0x000C00, 0x000D00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000700, 0x000700, 0x000C00, 0x000D00, 0x000D00, 0x000D00, 0x000E00, 0x000C00, 0x000700, 0x000F00, 0x000700, 0x000700, 0x021300, 0x000E00, 0x000700, 0x041502, 0x122311, 0x182917, 0x162714, 0x182916, 0x001100, 0x10210E, 0x324330, 0x000E00, 0x000D00, 0x000D00, 0x000C00, 0x000D00, 0x000D00, 0x2E3E33, 0x93A399, 0x95A49D, 0x93A29B, 0x96A5A0, 0x9AA9A4, 0x9DACA7, 0x9EADA8, 0x9FAEA9, 0xA1B0A9, 0xA2B1AA, 0xA4B3AC, 0xA5B4AD, 0xA4B3AC, 0xA1B0A9, 0xA2B2A8, 0xA6B6AC, 0xA8B8AE, 0xA7B7AC, 0xA5B5A8, 0xA6B7A7, 0xA6B7A7, 0xA6B7A7, 0xA5B6A4, 0xA6B7A5, 0xA8B9A7, 0xAABBA9, 0xAABBA9, 0xAABBA9, 0xAABBA8, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xADBBAA, 0xADBBAA, 0xA8B6A5, 0xADBBAA, 0xABB7A9, 0xA9B5A7, 0xADB9AB, 0xADB9AB, 0xAAB6A8, 0xAEBAAC, 0xAAB7A6, 0xACB9A8, 0xAEBBAA, 0xAEBBAA, 0xABB9A8, 0xA9B7A6, 0xA9B7A6, 0xAAB8A7, 0xA8B6A5, 0xA9B7A6, 0xA8B6A5, 0xA4B2A1, 0xA3B1A0, 0xA5B3A2, 0xA6B4A3, 0xA4B2A1, 0xA2B3A1, 0xA0B19F, 0xA0B19F, 0xA2B3A1, 0xA2B3A1, 0x9EAF9D, 0x9DAE9C, 0x9EAF9D, 0xA3B4A2, 0xA2B3A1, 0xA1B2A0, 0xA0B19F, 0xA1B2A0, 0xA0B19F, 0x9FB09E, 0x9EAF9D, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x9CAD9A, 0x9AAB98, 0x98A996, 0x98A996, 0x9AAB98, 0x99AA97, 0x99AA97, 0x98A996, 0x97A991, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x91A194, 0x87978A, 0x677868, 0x011202, 0x000D00, 0x000900, 0x000C00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000C00, 0x000E00, 0x000800, 0x000700, 0x000700, 0x000700, 0x000A00, 0x000700, 0x000B00, 0x000C00, 0x000700, 0x000800, 0x001000, 0x000900, 0x000700, 0x031401, 0x000700, 0x000C00, 0x000800, 0x000D00, 0x001000, 0x000800, 0x000B00, 0x000D00, 0x000D00, 0x000C00, 0x000C00, 0x000D00, 0x000E01, 0x39493F, 0x94A49A, 0x93A29B, 0x81908B, 0x84938E, 0x899893, 0x8D9C97, 0x8F9E99, 0x909F9A, 0x91A099, 0x92A19A, 0x899891, 0x8D9C95, 0x8F9E97, 0x91A099, 0x97A79D, 0x9FAFA5, 0xA4B4AA, 0xA5B5AA, 0xA5B6A6, 0xA6B7A5, 0xA7B8A6, 0xA6B7A5, 0xA5B6A4, 0xA6B7A5, 0xA8B9A7, 0xAABBA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xACBDAA, 0xACBEA8, 0xACBEA8, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xAEBCAB, 0xAEBCAB, 0xABB9A8, 0xACBAA9, 0xA4B0A2, 0x9EAA9C, 0xA3AFA1, 0xA3AFA1, 0x9FAB9D, 0x9FAB9D, 0xA5B0A0, 0xAAB5A5, 0xACB9A8, 0xABB8A7, 0xA7B4A3, 0xA4B1A0, 0xA5B2A1, 0xA7B4A3, 0xA9B7A6, 0xABB9A8, 0xAAB8A7, 0xA7B5A4, 0xA7B5A4, 0xAAB8A7, 0xACBAA9, 0xABB9A8, 0xA5B6A4, 0xA3B4A2, 0xA3B4A2, 0xA5B6A4, 0xA5B6A4, 0xA1B2A0, 0xA0B19F, 0xA1B2A0, 0x9DAE9C, 0x9CAD9B, 0x9BAC9A, 0x9CAD9B, 0x9DAE9C, 0x9EAF9D, 0x9DAE9C, 0x9CAD9B, 0x98A996, 0x98A996, 0x99AA97, 0x99AA97, 0x99AA97, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9EAF9C, 0x9AAB98, 0x98A996, 0x9BAC99, 0x9CAD9A, 0x9AAB98, 0x97A895, 0x96A794, 0x97A991, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x91A194, 0x859588, 0x798A7A, 0x041505, 0x000E00, 0x000800, 0x000C00, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000700, 0x000800, 0x000B00, 0x000D00, 0x000E00, 0x000D00, 0x000800, 0x000700, 0x000800, 0x000700, 0x000700, 0x000700, 0x000700, 0x000700, 0x000700, 0x000900, 0x000900, 0x000E00, 0x000800, 0x000E00, 0x061800, 0x000B00, 0x000800, 0x061802, 0x000C00, 0x000C00, 0x000C00, 0x000D00, 0x000F02, 0x435349, 0x97A79D, 0x94A39C, 0x98A7A2, 0x9AA9A4, 0x9CABA6, 0x9DACA7, 0x9DACA7, 0x9DACA7, 0x9EADA6, 0x9FAEA7, 0xA1B0A9, 0xA4B3AC, 0xA4B3AC, 0xA2B1AA, 0xA2B2A8, 0xA5B5AB, 0xA6B6AC, 0xA4B4A9, 0xA6B7A7, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA6B7A5, 0xA7B8A6, 0xA9BAA7, 0xAABBA8, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xACBDAA, 0xACBEA8, 0xACBEA8, 0xACBEA8, 0xACBEA8, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xAEBCAB, 0xAEBCAB, 0xA9B7A6, 0xA9B7A6, 0x9FAB9D, 0x97A395, 0x9AA698, 0x9AA698, 0x95A193, 0x94A092, 0x97A292, 0x9AA595, 0x9DA898, 0x9CA797, 0x9AA595, 0x97A292, 0x95A291, 0x96A392, 0x93A08F, 0x94A190, 0x92A08F, 0x8F9D8C, 0x8F9D8C, 0x92A08F, 0x92A391, 0x91A290, 0x8B9C8A, 0x8A9B89, 0x8B9C8A, 0x8E9F8D, 0x8FA08E, 0x8D9E8C, 0x8C9D8B, 0x8E9F8D, 0x90A18F, 0x8FA08E, 0x8E9F8D, 0x8E9F8D, 0x8E9F8D, 0x8E9F8D, 0x8D9E8C, 0x8B9C8A, 0x95A791, 0x95A791, 0x94A690, 0x94A690, 0x94A690, 0x93A58F, 0x93A58F, 0x93A58F, 0x95A791, 0x91A38D, 0x92A48E, 0x98AA94, 0x9CAE98, 0x9AAC96, 0x97A993, 0x97A993, 0x97A991, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x91A194, 0x849487, 0x879888, 0x071808, 0x000F00, 0x000900, 0x000C00, 0x000900, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000D00, 0x000800, 0x000700, 0x000900, 0x000900, 0x000700, 0x000700, 0x000D00, 0x000800, 0x000A00, 0x000D00, 0x000B00, 0x000800, 0x000D00, 0x001000, 0x000700, 0x000A00, 0x000800, 0x000F00, 0x000900, 0x000800, 0x000D00, 0x000E00, 0x000800, 0x000C00, 0x000B00, 0x000C00, 0x000E01, 0x001005, 0x49594F, 0x9BAAA3, 0x97A69F, 0x9BAAA5, 0x9CABA6, 0x9DACA7, 0x9DACA7, 0x9DACA7, 0x9FAEA9, 0xA1B0A9, 0xA4B3AC, 0x9FAEA7, 0xA2B1AA, 0xA3B2AB, 0xA2B1AA, 0xA3B3A9, 0xA6B6AC, 0xA6B6AC, 0xA4B4A9, 0xA6B7A7, 0xA7B8A6, 0xA7B8A6, 0xA7B8A6, 0xA6B7A5, 0xA7B8A6, 0xA9BAA7, 0xABBCA9, 0xABBCA9, 0xABBCA9, 0xABBDA7, 0xACBEA8, 0xACBEA8, 0xACBEA8, 0xACBEA8, 0xADBFA9, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xAEBCAB, 0xAEBCAB, 0xACBAA9, 0xB1BFAE, 0xADB9AB, 0xA9B5A7, 0xAEBAAC, 0xB0BCAE, 0xACB8AA, 0xACB7A9, 0xAEB9A9, 0xB1B9AA, 0xB0BBAB, 0xB1BCAC, 0xB1BCAC, 0xAFBAAA, 0xABB8A7, 0xAAB7A6, 0xADBAA9, 0xAEBBAA, 0xAAB8A7, 0xA6B4A3, 0xA5B3A2, 0xA8B6A5, 0xA7B8A6, 0xA6B7A5, 0xA4B5A3, 0xA2B3A1, 0xA2B3A1, 0xA3B4A2, 0xA2B3A1, 0x9EAF9D, 0x9CAD9B, 0x9DAE9C, 0x9EAF9D, 0x9CAD9B, 0x99AA98, 0x97A896, 0x96A795, 0x94A593, 0x91A290, 0x8FA08E, 0x94A690, 0x93A58F, 0x91A38D, 0x8FA18B, 0x8D9F89, 0x8B9D87, 0x899B85, 0x889A84, 0x889A84, 0x859781, 0x899B85, 0x93A58F, 0x9AAC96, 0x9AAC96, 0x99AB95, 0x99AB95, 0x97A991, 0x97A993, 0x97A993, 0x97A895, 0x96A795, 0x94A593, 0x93A494, 0x92A393, 0x91A194, 0x849487, 0x90A191, 0x0A1B0B, 0x001100, 0x000A00, 0x000D00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000C00, 0x000800, 0x000800, 0x000700, 0x000900, 0x000A00, 0x000B00, 0x000D00, 0x000D00, 0x000800, 0x000D00, 0x000F00, 0x07170A, 0x5B6B61, 0x94A3A0, 0x8A9898, 0x909F9A, 0x92A19C, 0x96A5A0, 0x98A7A2, 0x99A8A3, 0x99A8A3, 0x9AA9A2, 0x9BAAA3, 0x9FAEA7, 0xA0AFA8, 0xA0AFA8, 0xA1B0A9, 0xA2B2A8, 0xA3B3A9, 0xA4B4AA, 0xA4B3AC, 0xA5B4AD, 0xA6B5B0, 0xA6B5AE, 0xA6B6AC, 0xA6B6AB, 0xA7B7AA, 0xA9BAA8, 0xABBCAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAB, 0xACBDAB, 0xAABDA7, 0xABBEA8, 0xABBEAA, 0xABBEAA, 0xACBDAA, 0xACBDAA, 0xACBDAB, 0xACBDAB, 0xAFC0AE, 0xACBDAB, 0xA6B4A5, 0x9BA99A, 0x97A596, 0x9AA899, 0x9CAA9D, 0x9BA99C, 0x97A493, 0xA3B09F, 0xA8B5A4, 0xA1AE9D, 0x9FAC9B, 0xA6B3A2, 0xA7B4A3, 0xA1AE9D, 0xA3B09F, 0xA0AD9C, 0x9EAB9A, 0xA0AD9C, 0xA5B2A1, 0xA9B6A5, 0xAAB7A6, 0xA9B6A5, 0xA3AFA1, 0xA0AC9E, 0xA1AD9F, 0xA5B1A3, 0xA7B3A5, 0xA3AFA1, 0xA1AE9D, 0xA2AF9E, 0xA2AF9E, 0xA2AF9E, 0xA2AF9D, 0xA2AF9D, 0xA2AF9D, 0xA1AE9C, 0xA1AE9C, 0xA0AD9B, 0x98A996, 0x9BAC99, 0x9DAE9B, 0x9CAD9A, 0x99AA97, 0x99AA97, 0x9CAD9A, 0x9FB09D, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x8FA08D, 0x8C9D8A, 0x869785, 0x243523, 0x000B00, 0x000B00, 0x000A00, 0x000A00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000F00, 0x011200, 0x000E00, 0x000B00, 0x000900, 0x001200, 0x000D00, 0x000D00, 0x000E00, 0x021205, 0x637369, 0x98A7A4, 0x909E9E, 0x8B9A95, 0x8D9C97, 0x909F9A, 0x91A09B, 0x91A09B, 0x909F9A, 0x909F98, 0x91A099, 0x8A9992, 0x8C9B94, 0x909F98, 0x95A49D, 0x9BABA1, 0xA0B0A6, 0xA4B4AA, 0xA6B5AE, 0xA5B4AD, 0xA6B5B0, 0xA6B5AE, 0xA6B6AC, 0xA6B6AB, 0xA7B7AA, 0xA9BAA8, 0xABBCAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAB, 0xACBDAB, 0xAABDA9, 0xAABDA9, 0xABBEAA, 0xABBEAA, 0xADBEAB, 0xACBDAA, 0xACBDAB, 0xACBDAB, 0xA5B6A4, 0xA6B7A5, 0xA6B4A5, 0xA2B0A1, 0xA1AFA0, 0xA4B2A3, 0xA5B3A4, 0xA3B1A2, 0xA0AD9C, 0xA6B3A2, 0xA5B2A1, 0x9DAA99, 0x99A695, 0x9DAA99, 0xA0AD9C, 0x9EAB9A, 0xA0AD9C, 0x9BA897, 0x97A493, 0x96A392, 0x97A493, 0x97A493, 0x95A291, 0x929F8E, 0x95A193, 0x929E90, 0x919D8F, 0x94A092, 0x939F91, 0x8F9B8D, 0x8C9988, 0x8C9988, 0x8D9A89, 0x8D9A89, 0x8E9B89, 0x8E9B89, 0x8D9A88, 0x8D9A88, 0x8C9987, 0x8C9987, 0x889986, 0x8B9C89, 0x8D9E8B, 0x8D9E8B, 0x8C9D8A, 0x8C9D8A, 0x8FA08D, 0x93A491, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x8E9F8C, 0x8C9D8A, 0x879886, 0x2E3F2D, 0x000F00, 0x000C00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000C00, 0x001100, 0x021301, 0x000F00, 0x000A00, 0x000900, 0x000B00, 0x000E00, 0x000800, 0x000F00, 0x000F02, 0x74847A, 0x9CABA8, 0x97A5A5, 0x9BAAA5, 0x9DACA7, 0xA0AFAA, 0xA1B0AB, 0xA0AFAA, 0xA0AFAA, 0xA1B0A9, 0xA1B0A9, 0xA7B6AF, 0xA7B6AF, 0xA6B5AE, 0xA6B5AE, 0xA5B5AB, 0xA4B4AA, 0xA3B3A9, 0xA3B2AB, 0xA5B4AD, 0xA6B5B0, 0xA7B6AF, 0xA6B5AE, 0xA6B6AB, 0xA7B7AC, 0xA9BAAA, 0xABBCAC, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAB, 0xACBDAB, 0xAABDA9, 0xAABDA9, 0xABBEAA, 0xABBEAA, 0xADBEAB, 0xADBEAB, 0xADBEAC, 0xACBDAB, 0xB1C2B0, 0xB2C3B1, 0xB2C0B1, 0xADBBAC, 0xABB9AA, 0xACBAAB, 0xACBAAB, 0xAAB8A9, 0xADBAA9, 0xAEBBAA, 0xAEBBAA, 0xACB9A8, 0xA9B6A5, 0xA9B6A5, 0xABB8A7, 0xAFBCAB, 0xABB8A7, 0xA8B5A4, 0xA6B3A2, 0xA6B3A2, 0xA8B5A4, 0xAAB7A6, 0xAAB7A6, 0xA8B5A4, 0xABB7A9, 0xA8B4A6, 0xA7B3A5, 0xA8B4A6, 0xA7B3A5, 0xA4B0A2, 0xA0AD9C, 0xA0AD9C, 0xA1AE9D, 0xA2AF9E, 0xA2AF9D, 0xA2AF9D, 0xA2AF9D, 0xA1AE9C, 0xA0AD9B, 0xA0AD9B, 0x94A592, 0x96A794, 0x97A895, 0x97A895, 0x96A794, 0x96A794, 0x98A996, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x8E9F8C, 0x8C9D8A, 0x899A88, 0x3D4E3C, 0x011200, 0x000E00, 0x000C00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000800, 0x000800, 0x000A00, 0x000B00, 0x000A00, 0x000A00, 0x000C00, 0x000800, 0x001000, 0x000700, 0x011200, 0x001003, 0x819187, 0x97A6A3, 0x919F9F, 0x94A39E, 0x96A5A0, 0x99A8A3, 0x9BAAA5, 0x9CABA6, 0x9DACA7, 0x9FAEA7, 0xA1B0A9, 0xA2B1AA, 0xA2B1AA, 0xA2B1AA, 0xA3B2AB, 0xA4B4AA, 0xA5B5AB, 0xA5B5AB, 0xA6B5AE, 0xA5B4AD, 0xA6B5B0, 0xA7B6AF, 0xA7B6AF, 0xA6B6AC, 0xA7B7AD, 0xA9B9AC, 0xABBBAE, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAA, 0xA9BCA9, 0xAABDAA, 0xABBEAB, 0xABBEAB, 0xADBEAC, 0xADBEAC, 0xADBEAC, 0xADBEAC, 0xA8B9A7, 0xA4B5A3, 0xA0AE9D, 0x99A796, 0x95A392, 0x95A392, 0x96A493, 0x97A594, 0x98A695, 0x97A594, 0x9AA897, 0xA0AE9D, 0x9EAC9B, 0x98A695, 0x96A493, 0x9AA897, 0x98A695, 0x97A594, 0x98A695, 0x9CAA99, 0xA1AF9E, 0xA5B3A2, 0xA7B5A4, 0xA7B5A4, 0xA5B3A4, 0xA3B1A2, 0xA3B1A2, 0xA4B2A3, 0xA5B3A4, 0xA4B2A3, 0xA2B09F, 0xA1AF9E, 0xA0AE9D, 0xA0AE9D, 0xA0AF9C, 0xA0AF9C, 0xA0AF9C, 0x9FAE9B, 0x9FAE9B, 0x9EAD9A, 0x9DAE9B, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9AAB98, 0x99AA97, 0x9AAB98, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x8FA08D, 0x8C9D8A, 0x889987, 0x4D5E4C, 0x011200, 0x000D00, 0x000D00, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000C00, 0x000800, 0x000800, 0x000700, 0x000700, 0x000700, 0x000900, 0x000E00, 0x000A00, 0x041600, 0x000A00, 0x021301, 0x021205, 0x87978D, 0x8E9D9A, 0x8B9999, 0x879691, 0x899893, 0x8B9A95, 0x8D9C97, 0x8E9D98, 0x909F9A, 0x92A19A, 0x94A39C, 0x92A19A, 0x93A29B, 0x95A49D, 0x97A69F, 0x99A99F, 0x9CACA2, 0x9EAEA4, 0x9EAEA4, 0xA6B5AE, 0xA6B5AE, 0xA7B6AF, 0xA7B6AF, 0xA6B6AC, 0xA7B7AD, 0xAABAAF, 0xACBCB1, 0xACBCAF, 0xACBCAF, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAA, 0xAABDAA, 0xAABDAA, 0xABBEAB, 0xABBEAB, 0xADBEAC, 0xADBEAC, 0xACBDAB, 0xACBDAB, 0xB3C4B2, 0xB0C1AF, 0xAEBCAB, 0xABB9A8, 0xA9B7A6, 0xA9B7A6, 0xABB9A8, 0xADBBAA, 0xAAB8A7, 0xA6B4A3, 0xA8B6A5, 0xAEBCAB, 0xABB9A8, 0xA1AF9E, 0x9DAB9A, 0xA1AF9E, 0xA3B1A0, 0xA3B1A0, 0xA3B1A0, 0xA5B3A2, 0xA7B5A4, 0xA8B6A5, 0xA9B7A6, 0xA9B7A6, 0xA7B5A6, 0xA7B5A6, 0xA7B5A6, 0xA7B5A6, 0xA9B7A8, 0xAAB8A9, 0xA9B7A6, 0xA7B5A4, 0xA4B2A1, 0xA4B2A1, 0xA4B3A0, 0xA4B3A0, 0xA4B3A0, 0xA4B3A0, 0xA3B29F, 0xA3B29F, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x99AA97, 0x98A996, 0x98A996, 0x99AA97, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x91A28F, 0x8B9C89, 0x869785, 0x5D6E5C, 0x001100, 0x000B00, 0x000F00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000A00, 0x000900, 0x000A00, 0x000B00, 0x000A00, 0x000800, 0x000900, 0x000C00, 0x000C00, 0x011300, 0x000800, 0x001000, 0x08180B, 0x8E9E94, 0x93A29F, 0x95A3A3, 0x94A39E, 0x96A5A0, 0x97A6A1, 0x98A7A2, 0x98A7A2, 0x99A8A3, 0x9BAAA3, 0x9DACA5, 0xA2B1AA, 0xA1B0A9, 0xA0AFA8, 0x9FAEA7, 0x9DADA3, 0x9CACA2, 0x9AAAA0, 0x9AAAA0, 0xA6B5AE, 0xA7B6AF, 0xA7B6AF, 0xA7B6AF, 0xA7B6AF, 0xA8B7B0, 0xAAB9B2, 0xACBBB4, 0xACBCB1, 0xACBCB1, 0xACBCAF, 0xACBCAF, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAB, 0xABBEAB, 0xABBDAD, 0xABBDAD, 0xACBEAE, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xABBCAA, 0xADBEAC, 0xABBCAA, 0xAEBDAA, 0xB1C0AD, 0xB0BFAC, 0xADBCA9, 0xACBBA8, 0xADBCA9, 0xADBEAC, 0xA8B9A7, 0xA7B8A6, 0xAABBA9, 0xA9BAA8, 0xA5B6A4, 0xA5B6A4, 0xA9BAA8, 0xA5B6A4, 0xA5B6A4, 0xA4B5A3, 0xA4B5A3, 0xA3B4A2, 0xA3B4A2, 0xA2B3A1, 0xA1B2A0, 0xA2B3A3, 0xA2B3A3, 0xA1B2A2, 0x9FB0A0, 0xA0B1A1, 0xA2B3A3, 0xA1B2A0, 0x9EAF9D, 0x9CAD9B, 0x9CAD9B, 0x9CAD9A, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9EAF9C, 0x9EAF9C, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x92A390, 0x8B9C89, 0x859684, 0x6D7E6C, 0x001100, 0x000B00, 0x001000, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000D00, 0x000B00, 0x000800, 0x000800, 0x000B00, 0x000A00, 0x000C00, 0x000800, 0x000F00, 0x132316, 0x94A49A, 0x96A5A2, 0x9AA8A8, 0x9AA9A4, 0x9BAAA5, 0x9DACA7, 0x9DACA7, 0x9EADA8, 0x9FAEA9, 0xA1B0A9, 0xA3B2AB, 0x9FAEA7, 0xA0AFA8, 0xA0AFA8, 0xA2B1AA, 0xA3B3A9, 0xA4B4AA, 0xA5B5AB, 0xA5B5AB, 0xA6B5AE, 0xA7B6AF, 0xA8B7B0, 0xA7B6AF, 0xA7B6B1, 0xA8B7B2, 0xAAB9B2, 0xACBBB4, 0xACBCB2, 0xACBCB2, 0xACBCAF, 0xACBCAF, 0xACBDAB, 0xACBDAB, 0xACBEA8, 0xACBDAA, 0xACBFAC, 0xACBEAE, 0xACBEAE, 0xACBEAE, 0xACBDAD, 0xACBDAD, 0xABBCAA, 0xAABBA9, 0xA9BAA8, 0xA7B8A6, 0xABBAA7, 0xAEBDAA, 0xAEBDAA, 0xAAB9A6, 0xA8B7A4, 0xAAB9A6, 0xA8B9A7, 0xA7B8A6, 0xA8B9A7, 0xAABBA9, 0xABBCAA, 0xACBDAB, 0xADBEAC, 0xAEBFAD, 0xA8B9A7, 0xA8B9A7, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA9BAA8, 0xA8B9A7, 0xA9BAA8, 0xA8B9A9, 0xA9BAAA, 0xA7B8A8, 0xA4B5A5, 0xA4B5A5, 0xA7B8A8, 0xA6B7A5, 0xA3B4A2, 0xA1B2A0, 0xA2B3A1, 0xA2B3A0, 0xA2B3A0, 0xA2B3A0, 0xA1B29F, 0xA0B19E, 0xA0B19E, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x92A390, 0x8B9C89, 0x879886, 0x7C8D7B, 0x041503, 0x000C00, 0x001100, 0x000800, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000B00, 0x000900, 0x000A00, 0x000A00, 0x000800, 0x000700, 0x000800, 0x000D00, 0x000D00, 0x000D00, 0x000E00, 0x031402, 0x1C2C1F, 0x92A298, 0x8D9C99, 0x909E9E, 0x93A29D, 0x95A49F, 0x97A6A1, 0x98A7A2, 0x9AA9A4, 0x9CABA6, 0x9FAEA7, 0xA2B1AA, 0xA5B4AD, 0xA5B4AD, 0xA5B4AD, 0xA5B4AD, 0xA5B5AB, 0xA5B5AB, 0xA6B6AC, 0xA6B6AC, 0xA6B6AC, 0xA7B7AD, 0xA8B7B0, 0xA7B6AF, 0xA7B6B1, 0xA8B7B2, 0xAAB9B4, 0xACBBB6, 0xACBBB4, 0xACBCB2, 0xACBCB1, 0xACBCAF, 0xACBDAB, 0xACBDAB, 0xACBEA8, 0xACBDAA, 0xACBEAE, 0xACBEB0, 0xACBEAE, 0xACBEAE, 0xACBDAD, 0xABBCAC, 0xAABBA9, 0xAABBA9, 0xA6B7A5, 0xA3B4A2, 0xA5B4A1, 0xA8B7A4, 0xAAB9A6, 0xAAB9A6, 0xADBCA7, 0xB2C1AE, 0xA1B29F, 0xA5B6A4, 0xA8B9A7, 0xA8B9A7, 0xA9BAA8, 0xA8B9A7, 0xA4B5A3, 0x9FB09E, 0xA3B4A2, 0xA4B5A3, 0xA5B6A4, 0xA5B6A4, 0xA4B5A3, 0xA4B5A3, 0xA3B4A2, 0xA4B5A3, 0xA2B3A3, 0xA4B5A5, 0xA3B4A4, 0x9FB0A0, 0xA1B2A2, 0xA4B5A5, 0xA4B5A3, 0xA0B19F, 0xA0B19F, 0xA0B19F, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0xA0B19E, 0x9FB09D, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AB95, 0x99AB95, 0x99AB95, 0x99AB95, 0x97A895, 0x96A794, 0x94A592, 0x93A491, 0x92A390, 0x8B9C89, 0x889987, 0x869785, 0x071806, 0x000D00, 0x011200, 0x000700, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x011202, 0x000D02, 0x2A3A2F, 0x90A096, 0x8B9A93, 0x94A39C, 0x85958B, 0x84948A, 0x85958B, 0x88988E, 0x8B9B91, 0x8C9C92, 0x8A9A90, 0x87978D, 0x92A298, 0x94A49A, 0x95A59B, 0x95A59B, 0x93A399, 0x92A298, 0x92A298, 0x92A298, 0xA0B0A6, 0xA2B2A8, 0xA5B5AB, 0xA7B7AD, 0xA8B8AD, 0xA8B8AD, 0xA8B8AB, 0xA9B9AC, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xADBEAB, 0xACBDAA, 0xAABBA8, 0xABBCA9, 0xAABBA8, 0xA1B29F, 0x96A794, 0x97A895, 0x98A996, 0x768774, 0x768774, 0x7B8C79, 0x7C8D7A, 0x8B9C89, 0x768774, 0x94A592, 0x798A77, 0x879885, 0x7E8F7C, 0x7F907D, 0x92A390, 0x80917E, 0x788976, 0x7A8B78, 0x7A8B78, 0x8A9B88, 0x80917E, 0x6C7D6A, 0x889986, 0x98A996, 0x728370, 0x899A87, 0x92A390, 0x9EAF9C, 0xA5B6A3, 0xA5B6A3, 0xA3B4A1, 0xA1B29F, 0xA0B19E, 0xA1B29F, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x97A895, 0x96A794, 0x94A593, 0x93A492, 0x93A492, 0x8A9B89, 0x8B9C8C, 0x7E8F7F, 0x1E2F1F, 0x000A00, 0x000C00, 0x000E00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000C00, 0x000F00, 0x000B00, 0x324237, 0x95A59B, 0x93A29B, 0x9AA9A2, 0x9BABA1, 0x9CACA2, 0x9DADA3, 0xA0B0A6, 0xA2B2A8, 0xA4B4AA, 0xA3B3A9, 0xA2B2A8, 0xA1B1A7, 0xA1B1A7, 0xA2B2A8, 0xA1B1A7, 0xA1B1A7, 0xA2B2A8, 0xA5B5AB, 0xA7B7AD, 0xA3B3A9, 0xA5B5AB, 0xA7B7AD, 0xA8B8AE, 0xA8B8AD, 0xA8B8AD, 0xAABAAD, 0xABBBAE, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xACBDAB, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xACBDAA, 0xAABBA8, 0xACBDAA, 0xADBEAB, 0xABBCA9, 0xABBCA9, 0xACBDAA, 0xA9BAA7, 0xA4B5A2, 0xA9BAA7, 0xB2C3B0, 0x9EAF9C, 0xA4B5A2, 0xA9BAA7, 0xA6B7A4, 0xB3C4B1, 0xA0B19E, 0xADBEAB, 0x9CAD9A, 0xA7B8A5, 0x9DAE9B, 0x9EAF9C, 0xADBEAB, 0x9CAD9A, 0x9CAD9A, 0x9AAB98, 0x94A592, 0x9DAE9B, 0x98A996, 0x869784, 0x92A390, 0x9BAC99, 0x849582, 0x92A390, 0x99AA97, 0xA0B19E, 0xA4B5A2, 0xA4B5A2, 0xA2B3A0, 0xA1B29F, 0xA2B3A0, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x97A895, 0x96A794, 0x94A593, 0x93A492, 0x92A391, 0x8B9C8A, 0x8D9E8E, 0x819282, 0x273828, 0x000C00, 0x000C00, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x000B00, 0x000E00, 0x000C01, 0x3F4F44, 0x96A69C, 0x92A19A, 0x94A39C, 0x95A49D, 0x97A69F, 0x99A8A1, 0x9BAAA3, 0x9CABA4, 0x9EADA6, 0xA0AFA8, 0xA2B1AA, 0xA3B2AB, 0xA5B4AD, 0xA7B6AF, 0xA8B7B0, 0xA7B6AF, 0xA7B6AF, 0xA9B8B1, 0xAAB9B2, 0xA6B6AB, 0xA7B7AC, 0xA8B8AD, 0xA8B8AD, 0xA8B8AD, 0xA9B9AE, 0xABBBAE, 0xADBDB0, 0xACBDAD, 0xACBDAD, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xACBDAB, 0xA9BAA7, 0xACBDAA, 0xADBEAB, 0xABBCA9, 0xAABBA8, 0xABBCA9, 0xAEBFAC, 0xAEBFAC, 0xA0B19E, 0xAEBFAC, 0xA4B5A2, 0xAABBA8, 0xABBCA9, 0xA7B8A5, 0xB2C3B0, 0xA6B7A4, 0xADBEAB, 0xA8B9A6, 0xB0C1AE, 0xA3B4A1, 0xA5B6A3, 0xAEBFAC, 0x9FB09D, 0xA7B8A5, 0xACBDAA, 0xA3B4A1, 0xA6B7A4, 0xAABBA8, 0xA3B4A1, 0xA1B29F, 0xA4B5A2, 0xA1B29F, 0x9DAE9B, 0xA0B19E, 0xA4B5A2, 0xA4B5A2, 0xA2B3A0, 0xA1B29F, 0xA1B29F, 0xA3B4A1, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x99AA97, 0x99AA97, 0x98A996, 0x97A895, 0x95A693, 0x94A593, 0x93A492, 0x91A290, 0x8C9D8B, 0x8E9F8F, 0x839484, 0x364737, 0x000E00, 0x000C00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x001000, 0x000F04, 0x4B5B50, 0x90A096, 0x8B9A93, 0x889790, 0x8C9B94, 0x8E9D96, 0x91A099, 0x91A099, 0x91A099, 0x93A29B, 0x97A69F, 0x9AA9A2, 0x95A49D, 0x9AA9A2, 0xA0AFA8, 0xA4B3AC, 0xA6B5AE, 0xA5B4AD, 0xA4B3AC, 0xA3B2AB, 0xA9B9AE, 0xA9B9AE, 0xA8B8AD, 0xA7B7AC, 0xA6B6A9, 0xA8B8AB, 0xABBBAE, 0xADBDB0, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAA, 0xABBCAA, 0xABBCAA, 0xABBCAA, 0xABBCA9, 0xACBDAA, 0xACBDAA, 0xAABBA8, 0xA8B9A6, 0xA9BAA7, 0xAABBA8, 0xABBCA9, 0xAABBA8, 0xB1C2AF, 0xA7B8A5, 0xA6B7A4, 0xA2B3A0, 0x9FB09D, 0xABBCA9, 0xA5B6A3, 0x9DAE9B, 0xA2B3A0, 0xA5B6A3, 0x9CAD9A, 0xA7B8A5, 0xAEBFAC, 0xA3B4A1, 0xAFC0AD, 0xA8B9A6, 0xA5B6A3, 0xA4B5A2, 0xAABBA8, 0xACBDAA, 0xA7B8A5, 0xA6B7A4, 0xADBEAB, 0xA2B3A0, 0xA4B5A2, 0xA5B6A3, 0xA4B5A2, 0xA2B3A0, 0xA1B29F, 0xA1B29F, 0xA2B3A0, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x95A693, 0x94A593, 0x93A492, 0x91A290, 0x8E9F8D, 0x8D9E8E, 0x839484, 0x465747, 0x000E00, 0x000C00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000B00, 0x000B00, 0x011202, 0x001005, 0x57675C, 0x8C9C92, 0x8D9C95, 0x8C9B94, 0x8F9E97, 0x92A19A, 0x94A39C, 0x94A39C, 0x94A39C, 0x95A49D, 0x98A7A0, 0x9BAAA3, 0x94A39C, 0x99A8A1, 0x9EADA6, 0xA3B2AB, 0xA6B5AE, 0xA7B6AF, 0xA8B7B0, 0xA9B9AF, 0xA8B8AD, 0xA8B8AB, 0xA7B7AA, 0xA6B6A9, 0xA5B5A8, 0xA6B6A9, 0xA9B9AC, 0xACBCAF, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCA9, 0xAABBA8, 0xA9BAA7, 0xAABBA8, 0xAABBA8, 0xA9BAA7, 0xA8B9A6, 0xA7B8A5, 0xA2B3A0, 0xA2B3A0, 0x97A895, 0x91A28F, 0x8E9F8C, 0x8E9F8C, 0x96A794, 0x95A693, 0x849582, 0x8A9B88, 0x8B9C89, 0x8A9B88, 0x9AAB98, 0xA4B5A2, 0x9DAE9B, 0xA6B7A4, 0xA6B7A4, 0xAABBA8, 0xA6B7A4, 0xA3B4A1, 0xA8B9A6, 0xA5B6A3, 0xA1B29F, 0xA5B6A3, 0xA2B3A0, 0xA3B4A1, 0xA4B5A2, 0xA4B5A2, 0xA3B4A1, 0xA1B29F, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x95A693, 0x93A492, 0x92A391, 0x91A290, 0x8FA08E, 0x8A9B8B, 0x809181, 0x556656, 0x000D00, 0x000C00, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000B00, 0x001000, 0x000E03, 0x627267, 0x8D9D93, 0x97A69F, 0x97A69F, 0x97A6A1, 0x99A8A3, 0x9BAAA5, 0x9CABA6, 0x9CABA6, 0x9DACA7, 0x9FAEA9, 0xA1B0AB, 0xA5B4AF, 0xA5B4AF, 0xA5B4AF, 0xA4B3AE, 0xA3B2AD, 0xA4B3AE, 0xA7B6B1, 0xA9B8B1, 0xA7B7AC, 0xA7B7AA, 0xA6B6A9, 0xA5B5A8, 0xA4B4A7, 0xA6B6A9, 0xA9B9AC, 0xABBBAE, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xABBCAC, 0xAABBA8, 0xA8B9A6, 0xA8B9A6, 0xACBDAA, 0xADBEAB, 0xABBCA9, 0xA9BAA7, 0xA8B9A6, 0xADBEAB, 0xABBCA9, 0xA6B7A4, 0xA3B4A1, 0xA3B4A1, 0xA4B5A2, 0xA6B7A4, 0xA5B6A3, 0xA1B29F, 0xA3B4A1, 0xA0B19E, 0xA0B19E, 0xAABBA8, 0xB0C1AE, 0xACBDAA, 0xABBCA9, 0xA4B5A2, 0xABBCA9, 0xA8B9A6, 0xA2B3A0, 0xA5B6A3, 0xA6B7A4, 0xA3B4A1, 0xA3B4A1, 0xA0B19E, 0xA1B29F, 0xA2B3A0, 0xA3B4A1, 0xA3B4A1, 0xA2B3A0, 0xA0B19E, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9CAD9A, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x96A794, 0x95A693, 0x93A492, 0x92A391, 0x91A290, 0x90A18F, 0x899A8A, 0x7F9080, 0x647565, 0x000E00, 0x000D00, 0x000C00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000D00, 0x000A00, 0x000F00, 0x000E03, 0x6D7D72, 0x8B9B91, 0x94A39C, 0x8F9E97, 0x97A6A1, 0x98A7A2, 0x99A8A3, 0x9CABA6, 0x9FAEA9, 0xA1B0AB, 0xA1B0AB, 0xA0AFAA, 0xA3B2AD, 0xA4B3AE, 0xA4B3AE, 0xA4B3AE, 0xA2B1AC, 0xA2B1AC, 0xA3B2AD, 0xA4B3AC, 0xA6B6A9, 0xA6B7A7, 0xA7B8A8, 0xA6B7A7, 0xA5B6A6, 0xA6B7A7, 0xA9BAAA, 0xABBCAC, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBCAC, 0xA9BAA8, 0xA8B9A6, 0xAABBA8, 0xADBEAB, 0xACBDAA, 0xA8B9A6, 0xA9BAA7, 0xACBDAA, 0xABBCA9, 0xAABBA8, 0xACBDAA, 0xAABBA8, 0xA9BAA7, 0xA9BAA7, 0xA7B8A5, 0xA9BAA7, 0xABBCA9, 0xA7B8A5, 0xA4B5A2, 0xA4B5A2, 0xA3B4A1, 0xA5B6A3, 0xA7B8A5, 0x9DAE9B, 0xA5B6A3, 0xA5B6A3, 0xA5B6A3, 0xA6B7A4, 0xA5B6A3, 0xA6B7A4, 0xA7B8A5, 0xA6B7A4, 0xA2B3A0, 0xA1B29F, 0xA1B29F, 0xA1B29F, 0xA1B29F, 0xA1B29F, 0xA1B29F, 0xA0B19E, 0x9FB09D, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9DAE9B, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x98A996, 0x98A996, 0x97A895, 0x96A794, 0x94A592, 0x93A492, 0x92A391, 0x90A18F, 0x92A391, 0x8A9B8B, 0x819282, 0x738474, 0x001000, 0x000D00, 0x000B00, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000900, 0x000800, 0x000800, 0x000A00, 0x000A00, 0x000A00, 0x000A00, 0x000C00, 0x000900, 0x000F00, 0x001005, 0x748479, 0x87978D, 0x87968F, 0x798881, 0x83928D, 0x83928D, 0x84938E, 0x899893, 0x8E9D98, 0x909F9A, 0x8F9E99, 0x8D9C97, 0x8B9A95, 0x91A09B, 0x9BAAA5, 0xA3B2AD, 0xA7B6B1, 0xA8B7B2, 0xA7B6B1, 0xA7B6AF, 0xA5B5A8, 0xA6B7A7, 0xA7B8A8, 0xA7B8A8, 0xA6B7A7, 0xA7B8A8, 0xA9BAAA, 0xABBCAC, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBBAE, 0xABBCAC, 0xA9BAA8, 0xA9BAA7, 0xACBDAA, 0xACBDAA, 0xA8B9A6, 0xA3B4A1, 0xA6B7A4, 0xADBEAB, 0xA3B4A1, 0xA1B29F, 0xA6B7A4, 0xA2B3A0, 0x9DAE9B, 0x9CAD9A, 0x98A996, 0x9EAF9C, 0xA4B5A2, 0xA0B19E, 0xA0B19E, 0xA4B5A2, 0x9EAF9C, 0xA2B3A0, 0xABBCA9, 0x9DAE9B, 0xABBCA9, 0xA2B3A0, 0xA4B5A2, 0xA9BAA7, 0xA4B5A2, 0xA0B19E, 0xA1B29F, 0xA2B3A0, 0xA4B5A2, 0xA3B4A1, 0xA0B19E, 0x9FB09D, 0xA0B19E, 0xA1B29F, 0xA1B29F, 0xA1B29F, 0x9FB09D, 0x9FB09D, 0x9FB09D, 0x9EAF9C, 0x9EAF9C, 0x9DAE9B, 0x9DAE9B, 0x9CAD9A, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9BAC99, 0x9AAB98, 0x9AAB98, 0x99AA97, 0x99AA97, 0x99AA97, 0x99AA97, 0x98A996, 0x98A996, 0x98A996, 0x98A996, 0x97A895, 0x98A996, 0x98A996, 0x97A895, 0x96A794, 0x94A592, 0x93A492, 0x92A391, 0x8FA08E, 0x92A391, 0x8C9D8D, 0x849585, 0x7D8E7E, 0x011202, 0x000D00, 0x000900 }; ================================================ FILE: esp_jpeg/test_apps/main/tjpgd_test.c ================================================ /* * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include "sdkconfig.h" #include "unity.h" #include "jpeg_decoder.h" #include "test_logo_jpg.h" #include "test_logo_rgb888.h" #include "test_usb_camera_2_jpg.h" #include "test_usb_camera_2_rgb888.h" #define TESTW 46 #define TESTH 46 void esp_jpeg_print_ascii(unsigned char *rgb888, esp_jpeg_image_output_t *outimg) { char aapix[] = " .:;+=xX$$"; unsigned char *p = rgb888 + 2; for (int y = 0; y < outimg->width; y++) { for (int x = 0; x < outimg->height; x++) { int v = ((*p) * (sizeof(aapix) - 2) * 2) / 256; printf("%c%c", aapix[v / 2], aapix[(v + 1) / 2]); p += 3; } printf("%c%c", ' ', '\n'); } } TEST_CASE("Test JPEG decompression library", "[esp_jpeg]") { unsigned char *decoded, *p; const unsigned char *o; int decoded_outsize = TESTW * TESTH * 3; decoded = malloc(decoded_outsize); for (int x = 0; x < decoded_outsize; x += 2) { decoded[x] = 0; decoded[x + 1] = 0xff; } /* JPEG decode */ esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)logo_jpg, .indata_size = logo_jpg_len, .outbuf = decoded, .outbuf_size = decoded_outsize, .out_format = JPEG_IMAGE_FORMAT_RGB888, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 0, } }; esp_jpeg_image_output_t outimg; esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(err, ESP_OK); /* Decoded image size */ TEST_ASSERT_EQUAL(outimg.width, TESTW); TEST_ASSERT_EQUAL(outimg.height, TESTH); p = decoded; o = logo_rgb888; for (int x = 0; x < outimg.width * outimg.height; x++) { /* The color can be +- 2 */ TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]); TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]); TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]); p += 3; o += 3; } esp_jpeg_print_ascii(decoded, &outimg); free(decoded); } /** * @brief JPEG unknown size test * * This test case verifies the functionality of the JPEG decompression library * when decoding an image with unknown size. The image is decoded from a * JPEG file, and the output size is determined dynamically. The test checks * that the decoded image dimensions match the expected values and that the * pixel data is within an acceptable tolerance range. */ TEST_CASE("Test JPEG unknown size", "[esp_jpeg]") { unsigned char *decoded, *p; const unsigned char *o; /* JPEG decode */ esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)logo_jpg, .indata_size = logo_jpg_len, .out_format = JPEG_IMAGE_FORMAT_RGB888, }; // 1. Get required output size esp_jpeg_image_output_t outimg; esp_err_t err = esp_jpeg_get_image_info(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(err, ESP_OK); TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len); TEST_ASSERT_EQUAL(outimg.width, TESTW); TEST_ASSERT_EQUAL(outimg.height, TESTH); // 2. Allocate output buffer and assign it to the config decoded = malloc(outimg.output_len); TEST_ASSERT_NOT_NULL(decoded); jpeg_cfg.outbuf = decoded; jpeg_cfg.outbuf_size = outimg.output_len; // 3. Decode the image err = esp_jpeg_decode(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(err, ESP_OK); /* Decoded image size */ TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len); TEST_ASSERT_EQUAL(outimg.width, TESTW); TEST_ASSERT_EQUAL(outimg.height, TESTH); p = decoded; o = logo_rgb888; for (int x = 0; x < outimg.width * outimg.height; x++) { /* The color can be +- 2 */ TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]); TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]); TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]); p += 3; o += 3; } free(decoded); } #define WORKING_BUFFER_SIZE 4096 TEST_CASE("Test JPEG decompression library: User defined working buffer", "[esp_jpeg]") { unsigned char *decoded, *p; const unsigned char *o; int decoded_outsize = TESTW * TESTH * 3; decoded = malloc(decoded_outsize); uint8_t *working_buf = malloc(WORKING_BUFFER_SIZE); assert(decoded); assert(working_buf); for (int x = 0; x < decoded_outsize; x += 2) { decoded[x] = 0; decoded[x + 1] = 0xff; } /* JPEG decode */ esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)logo_jpg, .indata_size = logo_jpg_len, .outbuf = decoded, .outbuf_size = decoded_outsize, .out_format = JPEG_IMAGE_FORMAT_RGB888, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 0, }, .advanced = { .working_buffer = working_buf, .working_buffer_size = WORKING_BUFFER_SIZE, }, }; esp_jpeg_image_output_t outimg; esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(err, ESP_OK); /* Decoded image size */ TEST_ASSERT_EQUAL(outimg.width, TESTW); TEST_ASSERT_EQUAL(outimg.height, TESTH); p = decoded; o = logo_rgb888; for (int x = 0; x < outimg.width * outimg.height; x++) { /* The color can be +- 2 */ TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]); TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]); TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]); p += 3; o += 3; } free(working_buf); free(decoded); } #if CONFIG_JD_DEFAULT_HUFFMAN #include "test_usb_camera_jpg.h" #include "test_usb_camera_rgb888.h" /** * @brief Test for JPEG decompression without Huffman tables * * This test case verifies the functionality of the JPEG decompression library * when decoding an image that lacks Huffman tables, such as a USB frame * from a Logitech C270 USB camera. The image was reconstructed from raw USB data * (using `hex_to_jpg.py`) and then converted into an RGB888 C-style array * (using `jpg_to_rgb888_hex.py`). * * Due to the unique structure of the JPEG data (double block size, 16x8 pixels) * and absence of Huffman tables, this test assesses whether the decompression * library correctly decodes the image and outputs RGB888 pixel data within * an acceptable tolerance range. * * The test performs the following steps: * - Allocates a buffer for the decoded image. * - Configures and runs the JPEG decoder with the RGB888 output format. * - Checks that the decoded image dimensions match expected values. * - Compares the decompressed image data against the reference RGB888 data, * allowing a tolerance of ±16 in each color component due to potential * differences in Huffman tables or decompression accuracy. * * @note This test allows a margin of error in pixel values due to potential * differences in how color data is interpreted across different decoders. * * @param None * * @return None * * @test Requirements: * - JPEG decompression library support for images without Huffman tables. * - JPEG decompression accuracy within acceptable error margins. */ TEST_CASE("Test JPEG decompression library: No Huffman tables", "[esp_jpeg]") { unsigned char *decoded, *p; const unsigned int *o; int decoded_outsize = 160 * 120 * 3; decoded = malloc(decoded_outsize); /* JPEG decode */ esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)jpeg_no_huffman, .indata_size = jpeg_no_huffman_len, .outbuf = decoded, .outbuf_size = decoded_outsize, .out_format = JPEG_IMAGE_FORMAT_RGB888, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 0, } }; esp_jpeg_image_output_t outimg; esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(err, ESP_OK); /* Decoded image size */ TEST_ASSERT_EQUAL(outimg.width, 160); TEST_ASSERT_EQUAL(outimg.height, 120); p = decoded; o = jpeg_no_huffman_rgb888; for (int x = 0; x < outimg.width * outimg.height; x++) { /* The color can be +- 16 */ // Here we allow bigger decoding error // It might be that the Windows decoder used slightly different Huffman tables TEST_ASSERT_UINT8_WITHIN(16, (*o) & 0xff, p[0]); TEST_ASSERT_UINT8_WITHIN(16, (*o >> 8) & 0xff, p[1]); TEST_ASSERT_UINT8_WITHIN(16, (*o >> 16) & 0xff, p[2]); p += 3; // this is uint8_t o ++; // this is unt32_t } free(decoded); } #endif /** * @brief Invalid JPEG marker test * * This test case verifies the behavior of the JPEG decompression library * when encountering an invalid marker (0xFFFF) in the JPEG data stream. * The test uses a known JPEG image (camera_2_jpg) that contains this invalid * marker. The test checks whether the library can handle the invalid marker * gracefully and still decode the image correctly. */ TEST_CASE("Test JPEG invalid marker 0xFFFF", "[esp_jpeg]") { unsigned char *decoded; int decoded_outsize = 160 * 120 * 3; decoded = malloc(decoded_outsize); assert(decoded); for (int x = 0; x < decoded_outsize; x += 2) { decoded[x] = 0; decoded[x + 1] = 0xff; } /* JPEG decode */ esp_jpeg_image_cfg_t jpeg_cfg = { .indata = (uint8_t *)camera_2_jpg, .indata_size = camera_2_jpg_len, .outbuf = decoded, .outbuf_size = decoded_outsize, .out_format = JPEG_IMAGE_FORMAT_RGB888, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { .swap_color_bytes = 0, } }; esp_jpeg_image_output_t outimg; esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg); TEST_ASSERT_EQUAL(ESP_OK, err); /* Decoded image size */ TEST_ASSERT_EQUAL(160, outimg.width); TEST_ASSERT_EQUAL(120, outimg.height); free(decoded); } ================================================ FILE: esp_jpeg/test_apps/pytest_esp_jpeg.py ================================================ import pytest @pytest.mark.generic def test_esp_jpeg(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_jpeg/test_apps/sdkconfig.ci ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n CONFIG_JD_USE_ROM=n CONFIG_JD_DEFAULT_HUFFMAN=y ================================================ FILE: esp_jpeg/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: esp_jpeg/tjpgd/tjpgd.c ================================================ /*----------------------------------------------------------------------------/ / TJpgDec - Tiny JPEG Decompressor R0.03 (C)ChaN, 2021 /-----------------------------------------------------------------------------/ / The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. / This is a free software that opened for education, research and commercial / developments under license policy of following terms. / / Copyright (C) 2021, ChaN, all right reserved. / / * The TJpgDec module is a free software and there is NO WARRANTY. / * No restriction on use. You can use, modify and redistribute it for / personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. / * Redistributions of source code must retain the above copyright notice. / /-----------------------------------------------------------------------------/ / Oct 04, 2011 R0.01 First release. / Feb 19, 2012 R0.01a Fixed decompression fails when scan starts with an escape seq. / Sep 03, 2012 R0.01b Added JD_TBLCLIP option. / Mar 16, 2019 R0.01c Supported stdint.h. / Jul 01, 2020 R0.01d Fixed wrong integer type usage. / May 08, 2021 R0.02 Supported grayscale image. Separated configuration options. / Jun 11, 2021 R0.02a Some performance improvement. / Jul 01, 2021 R0.03 Added JD_FASTDECODE option. / Some performance improvement. /----------------------------------------------------------------------------*/ #include "tjpgd.h" #if JD_FASTDECODE == 2 #define HUFF_BIT 10 /* Bit length to apply fast huffman decode */ #define HUFF_LEN (1 << HUFF_BIT) #define HUFF_MASK (HUFF_LEN - 1) #endif /*-----------------------------------------------*/ /* Zigzag-order to raster-order conversion table */ /*-----------------------------------------------*/ static const uint8_t Zig[64] = { /* Zigzag-order to raster-order conversion table */ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; /*-------------------------------------------------*/ /* Input scale factor of Arai algorithm */ /* (scaled up 16 bits for fixed point operations) */ /*-------------------------------------------------*/ static const uint16_t Ipsf[64] = { /* See also aa_idct.png */ (uint16_t)(1.00000 * 8192), (uint16_t)(1.38704 * 8192), (uint16_t)(1.30656 * 8192), (uint16_t)(1.17588 * 8192), (uint16_t)(1.00000 * 8192), (uint16_t)(0.78570 * 8192), (uint16_t)(0.54120 * 8192), (uint16_t)(0.27590 * 8192), (uint16_t)(1.38704 * 8192), (uint16_t)(1.92388 * 8192), (uint16_t)(1.81226 * 8192), (uint16_t)(1.63099 * 8192), (uint16_t)(1.38704 * 8192), (uint16_t)(1.08979 * 8192), (uint16_t)(0.75066 * 8192), (uint16_t)(0.38268 * 8192), (uint16_t)(1.30656 * 8192), (uint16_t)(1.81226 * 8192), (uint16_t)(1.70711 * 8192), (uint16_t)(1.53636 * 8192), (uint16_t)(1.30656 * 8192), (uint16_t)(1.02656 * 8192), (uint16_t)(0.70711 * 8192), (uint16_t)(0.36048 * 8192), (uint16_t)(1.17588 * 8192), (uint16_t)(1.63099 * 8192), (uint16_t)(1.53636 * 8192), (uint16_t)(1.38268 * 8192), (uint16_t)(1.17588 * 8192), (uint16_t)(0.92388 * 8192), (uint16_t)(0.63638 * 8192), (uint16_t)(0.32442 * 8192), (uint16_t)(1.00000 * 8192), (uint16_t)(1.38704 * 8192), (uint16_t)(1.30656 * 8192), (uint16_t)(1.17588 * 8192), (uint16_t)(1.00000 * 8192), (uint16_t)(0.78570 * 8192), (uint16_t)(0.54120 * 8192), (uint16_t)(0.27590 * 8192), (uint16_t)(0.78570 * 8192), (uint16_t)(1.08979 * 8192), (uint16_t)(1.02656 * 8192), (uint16_t)(0.92388 * 8192), (uint16_t)(0.78570 * 8192), (uint16_t)(0.61732 * 8192), (uint16_t)(0.42522 * 8192), (uint16_t)(0.21677 * 8192), (uint16_t)(0.54120 * 8192), (uint16_t)(0.75066 * 8192), (uint16_t)(0.70711 * 8192), (uint16_t)(0.63638 * 8192), (uint16_t)(0.54120 * 8192), (uint16_t)(0.42522 * 8192), (uint16_t)(0.29290 * 8192), (uint16_t)(0.14932 * 8192), (uint16_t)(0.27590 * 8192), (uint16_t)(0.38268 * 8192), (uint16_t)(0.36048 * 8192), (uint16_t)(0.32442 * 8192), (uint16_t)(0.27590 * 8192), (uint16_t)(0.21678 * 8192), (uint16_t)(0.14932 * 8192), (uint16_t)(0.07612 * 8192) }; /*---------------------------------------------*/ /* Conversion table for fast clipping process */ /*---------------------------------------------*/ #if JD_TBLCLIP #define BYTECLIP(v) Clip8[(unsigned int)(v) & 0x3FF] static const uint8_t Clip8[1024] = { /* 0..255 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, /* 256..511 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, /* -512..-257 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* -256..-1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #else /* JD_TBLCLIP */ static uint8_t BYTECLIP (int val) { if (val < 0) { return 0; } if (val > 255) { return 255; } return (uint8_t)val; } #endif /*-----------------------------------------------------------------------*/ /* Allocate a memory block from memory pool */ /*-----------------------------------------------------------------------*/ static void *alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ JDEC *jd, /* Pointer to the decompressor object */ size_t ndata /* Number of bytes to allocate */ ) { char *rp = 0; ndata = (ndata + 3) & ~3; /* Align block size to the word boundary */ if (jd->sz_pool >= ndata) { jd->sz_pool -= ndata; rp = (char *)jd->pool; /* Get start of available memory pool */ jd->pool = (void *)(rp + ndata); /* Allocate required bytes */ } return (void *)rp; /* Return allocated memory block (NULL:no memory to allocate) */ } #if JD_DEFAULT_HUFFMAN /*-----------------------------------------------------------------------*/ /* Load default Huffman table */ /*-----------------------------------------------------------------------*/ extern unsigned char esp_jpeg_lum_dc_num_bits[], esp_jpeg_lum_dc_values[]; extern unsigned char esp_jpeg_chrom_dc_num_bits[], esp_jpeg_chrom_dc_values[]; extern unsigned char esp_jpeg_lum_ac_num_bits[], esp_jpeg_lum_ac_values[]; extern unsigned char esp_jpeg_chrom_ac_num_bits[], esp_jpeg_chrom_ac_values[]; extern unsigned esp_jpeg_lum_dc_codes_total, esp_jpeg_lum_ac_codes_total, esp_jpeg_chrom_dc_codes_total, esp_jpeg_chrom_ac_codes_total; JRESULT jd_load_default_huffman (JDEC *jd) { // Variable declarations to keep a similar structure to create_huffman_tbl() unsigned int i, j, b; uint8_t *pb; uint16_t hc, *ph; // Group default tables for Y/CbCr channels and DC/AC components to access them in loops // These arrays store predefined Huffman bit lengths and values for JPEG decoding unsigned char *num_bits[2][2] = { {esp_jpeg_lum_dc_num_bits, esp_jpeg_lum_ac_num_bits}, // Luminance (Y) DC and AC bit lengths {esp_jpeg_chrom_dc_num_bits, esp_jpeg_chrom_ac_num_bits} // Chrominance (CbCr) DC and AC bit lengths }; unsigned codes_total[2][2] = { {esp_jpeg_lum_dc_codes_total, esp_jpeg_lum_ac_codes_total}, // Total codes for Y DC and AC components {esp_jpeg_chrom_dc_codes_total, esp_jpeg_chrom_ac_codes_total} // Total codes for CbCr DC and AC components }; unsigned char *values[2][2] = { {esp_jpeg_lum_dc_values, esp_jpeg_lum_ac_values}, // Default Huffman values for Y DC and AC components {esp_jpeg_chrom_dc_values, esp_jpeg_chrom_ac_values} // Default Huffman values for CbCr DC and AC components }; // Loop over Y/CbCr channels and DC/AC components to initialize Huffman tables for (int ycbcr = 0; ycbcr < 2; ycbcr++) { // Loop for Luminance (Y) and Chrominance (CbCr) for (int dcac = 0; dcac < 2; dcac++) { // Loop for DC and AC tables // Assign the bit lengths and values arrays to Huffman table fields in the JDEC structure jd->huffbits[ycbcr][dcac] = num_bits[ycbcr][dcac]; jd->huffdata[ycbcr][dcac] = values[ycbcr][dcac]; // Calculate Huffman codes from bit lengths to construct codeword tables pb = num_bits[ycbcr][dcac]; // Access bit length array size_t np = codes_total[ycbcr][dcac]; // Total number of codes // The bits and values are usually in the Huffman table of the JPEG picture. // The codes themselves must be calculated based on the bits and values; that is what we do here. // Since this function uses default bits and values that are constant and known at compile time, // We could optimize this even more by providing pre-calculated codes too... // Allocate memory for the Huffman codeword table ph = alloc_pool(jd, np * sizeof(uint16_t)); if (!ph) { return JDR_MEM1; // Error: Memory allocation failed } jd->huffcode[ycbcr][dcac] = ph; // Store allocated memory address for code table hc = 0; // Initialize Huffman code // Generate Huffman codes based on the bit lengths in pb for (j = i = 0; i < 16; i++) { // Iterate over 16 possible code lengths b = pb[i]; // Number of codes with length (i+1) bits while (b--) { ph[j++] = hc++; // Assign code and increment index } hc <<= 1; // Left shift code to increase bit length } } } return JDR_OK; // Return success status } #endif /*-----------------------------------------------------------------------*/ /* Create de-quantization and prescaling tables with a DQT segment */ /*-----------------------------------------------------------------------*/ static JRESULT create_qt_tbl ( /* 0:OK, !0:Failed */ JDEC *jd, /* Pointer to the decompressor object */ const uint8_t *data, /* Pointer to the quantizer tables */ size_t ndata /* Size of input data */ ) { unsigned int i, zi; uint8_t d; int32_t *pb; while (ndata) { /* Process all tables in the segment */ if (ndata < 65) { return JDR_FMT1; /* Err: table size is unaligned */ } ndata -= 65; d = *data++; /* Get table property */ if (d & 0xF0) { return JDR_FMT1; /* Err: not 8-bit resolution */ } i = d & 3; /* Get table ID */ pb = alloc_pool(jd, 64 * sizeof (int32_t));/* Allocate a memory block for the table */ if (!pb) { return JDR_MEM1; /* Err: not enough memory */ } jd->qttbl[i] = pb; /* Register the table */ for (i = 0; i < 64; i++) { /* Load the table */ zi = Zig[i]; /* Zigzag-order to raster-order conversion */ pb[zi] = (int32_t)((uint32_t) * data++ * Ipsf[zi]); /* Apply scale factor of Arai algorithm to the de-quantizers */ } } return JDR_OK; } /*-----------------------------------------------------------------------*/ /* Create huffman code tables with a DHT segment */ /*-----------------------------------------------------------------------*/ static JRESULT create_huffman_tbl ( /* 0:OK, !0:Failed */ JDEC *jd, /* Pointer to the decompressor object */ const uint8_t *data, /* Pointer to the packed huffman tables */ size_t ndata /* Size of input data */ ) { unsigned int i, j, b, cls, num; size_t np; uint8_t d, *pb, *pd; uint16_t hc, *ph; while (ndata) { /* Process all tables in the segment */ if (ndata < 17) { return JDR_FMT1; /* Err: wrong data size */ } ndata -= 17; d = *data++; /* Get table number and class */ if (d & 0xEE) { return JDR_FMT1; /* Err: invalid class/number */ } cls = d >> 4; num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ if (!pb) { return JDR_MEM1; /* Err: not enough memory */ } jd->huffbits[num][cls] = pb; for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ np += (pb[i] = *data++); /* Get sum of code words for each code */ } ph = alloc_pool(jd, np * sizeof (uint16_t));/* Allocate a memory block for the code word table */ if (!ph) { return JDR_MEM1; /* Err: not enough memory */ } jd->huffcode[num][cls] = ph; hc = 0; for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ b = pb[i]; while (b--) { ph[j++] = hc++; } hc <<= 1; } if (ndata < np) { return JDR_FMT1; /* Err: wrong data size */ } ndata -= np; pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ if (!pd) { return JDR_MEM1; /* Err: not enough memory */ } jd->huffdata[num][cls] = pd; for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code word */ d = *data++; if (!cls && d > 11) { return JDR_FMT1; } pd[i] = d; } #if JD_FASTDECODE == 2 { /* Create fast huffman decode table */ unsigned int span, td, ti; uint16_t *tbl_ac = 0; uint8_t *tbl_dc = 0; if (cls) { tbl_ac = alloc_pool(jd, HUFF_LEN * sizeof (uint16_t)); /* LUT for AC elements */ if (!tbl_ac) { return JDR_MEM1; /* Err: not enough memory */ } jd->hufflut_ac[num] = tbl_ac; memset(tbl_ac, 0xFF, HUFF_LEN * sizeof (uint16_t)); /* Default value (0xFFFF: may be long code) */ } else { tbl_dc = alloc_pool(jd, HUFF_LEN * sizeof (uint8_t)); /* LUT for AC elements */ if (!tbl_dc) { return JDR_MEM1; /* Err: not enough memory */ } jd->hufflut_dc[num] = tbl_dc; memset(tbl_dc, 0xFF, HUFF_LEN * sizeof (uint8_t)); /* Default value (0xFF: may be long code) */ } for (i = b = 0; b < HUFF_BIT; b++) { /* Create LUT */ for (j = pb[b]; j; j--) { ti = ph[i] << (HUFF_BIT - 1 - b) & HUFF_MASK; /* Index of input pattern for the code */ if (cls) { td = pd[i++] | ((b + 1) << 8); /* b15..b8: code length, b7..b0: zero run and data length */ for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_ac[ti++] = (uint16_t)td) ; } else { td = pd[i++] | ((b + 1) << 4); /* b7..b4: code length, b3..b0: data length */ for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_dc[ti++] = (uint8_t)td) ; } } } jd->longofs[num][cls] = i; /* Code table offset for long code */ } #endif } return JDR_OK; } /*-----------------------------------------------------------------------*/ /* Extract a huffman decoded data from input stream */ /*-----------------------------------------------------------------------*/ static int huffext ( /* >=0: decoded data, <0: error code */ JDEC *jd, /* Pointer to the decompressor object */ unsigned int id, /* Table ID (0:Y, 1:C) */ unsigned int cls /* Table class (0:DC, 1:AC) */ ) { size_t dc = jd->dctr; uint8_t *dp = jd->dptr; unsigned int d, flg = 0; #if JD_FASTDECODE == 0 uint8_t bm, nd, bl; const uint8_t *hb = jd->huffbits[id][cls]; /* Bit distribution table */ const uint16_t *hc = jd->huffcode[id][cls]; /* Code word table */ const uint8_t *hd = jd->huffdata[id][cls]; /* Data table */ bm = jd->dbit; /* Bit mask to extract */ d = 0; bl = 16; /* Max code length */ do { if (!bm) { /* Next byte? */ if (!dc) { /* No input data is available, re-fill input buffer */ dp = jd->inbuf; /* Top of input buffer */ dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ } } else { dp++; /* Next data ptr */ } dc--; /* Decrement number of available bytes */ if (flg) { /* In flag sequence? */ flg = 0; /* Exit flag sequence */ if (*dp != 0) { return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be corrupted data) */ } *dp = 0xFF; /* The flag is a data 0xFF */ } else { if (*dp == 0xFF) { /* Is start of flag sequence? */ flg = 1; continue; /* Enter flag sequence, get trailing byte */ } } bm = 0x80; /* Read from MSB */ } d <<= 1; /* Get a bit */ if (*dp & bm) { d++; } bm >>= 1; for (nd = *hb++; nd; nd--) { /* Search the code word in this bit length */ if (d == *hc++) { /* Matched? */ jd->dbit = bm; jd->dctr = dc; jd->dptr = dp; return *hd; /* Return the decoded data */ } hd++; } bl--; } while (bl); #else const uint8_t *hb, *hd; const uint16_t *hc; unsigned int nc, bl, wbit = jd->dbit % 32; uint32_t w = jd->wreg & ((1UL << wbit) - 1); while (wbit < 16) { /* Prepare 16 bits into the working register */ if (jd->marker) { d = 0xFF; /* Input stream has stalled for a marker. Generate stuff bits */ } else { if (!dc) { /* Buffer empty, re-fill input buffer */ dp = jd->inbuf; /* Top of input buffer */ dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ } } d = *dp++; dc--; if (flg) { /* In flag sequence? */ flg = 0; /* Exit flag sequence */ if (d != 0) { jd->marker = d; /* Not an escape of 0xFF but a marker */ } d = 0xFF; } else { if (d == 0xFF) { /* Is start of flag sequence? */ flg = 1; continue; /* Enter flag sequence, get trailing byte */ } } } w = w << 8 | d; /* Shift 8 bits in the working register */ wbit += 8; } jd->dctr = dc; jd->dptr = dp; jd->wreg = w; #if JD_FASTDECODE == 2 /* Table search for the short codes */ d = (unsigned int)(w >> (wbit - HUFF_BIT)); /* Short code as table index */ if (cls) { /* AC element */ d = jd->hufflut_ac[id][d]; /* Table decode */ if (d != 0xFFFF) { /* It is done if hit in short code */ jd->dbit = wbit - (d >> 8); /* Snip the code length */ return d & 0xFF; /* b7..0: zero run and following data bits */ } } else { /* DC element */ d = jd->hufflut_dc[id][d]; /* Table decode */ if (d != 0xFF) { /* It is done if hit in short code */ jd->dbit = wbit - (d >> 4); /* Snip the code length */ return d & 0xF; /* b3..0: following data bits */ } } /* Incremental search for the codes longer than HUFF_BIT */ hb = jd->huffbits[id][cls] + HUFF_BIT; /* Bit distribution table */ hc = jd->huffcode[id][cls] + jd->longofs[id][cls]; /* Code word table */ hd = jd->huffdata[id][cls] + jd->longofs[id][cls]; /* Data table */ bl = HUFF_BIT + 1; #else /* Incremental search for all codes */ hb = jd->huffbits[id][cls]; /* Bit distribution table */ hc = jd->huffcode[id][cls]; /* Code word table */ hd = jd->huffdata[id][cls]; /* Data table */ bl = 1; #endif for ( ; bl <= 16; bl++) { /* Incremental search */ nc = *hb++; if (nc) { d = w >> (wbit - bl); do { /* Search the code word in this bit length */ if (d == *hc++) { /* Matched? */ jd->dbit = wbit - bl; /* Snip the huffman code */ return *hd; /* Return the decoded data */ } hd++; } while (--nc); } } #endif return 0 - (int)JDR_FMT1; /* Err: code not found (may be corrupted data) */ } /*-----------------------------------------------------------------------*/ /* Extract N bits from input stream */ /*-----------------------------------------------------------------------*/ static int bitext ( /* >=0: extracted data, <0: error code */ JDEC *jd, /* Pointer to the decompressor object */ unsigned int nbit /* Number of bits to extract (1 to 16) */ ) { size_t dc = jd->dctr; uint8_t *dp = jd->dptr; unsigned int d, flg = 0; #if JD_FASTDECODE == 0 uint8_t mbit = jd->dbit; d = 0; do { if (!mbit) { /* Next byte? */ if (!dc) { /* No input data is available, re-fill input buffer */ dp = jd->inbuf; /* Top of input buffer */ dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ } } else { dp++; /* Next data ptr */ } dc--; /* Decrement number of available bytes */ if (flg) { /* In flag sequence? */ flg = 0; /* Exit flag sequence */ if (*dp != 0) { return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be corrupted data) */ } *dp = 0xFF; /* The flag is a data 0xFF */ } else { if (*dp == 0xFF) { /* Is start of flag sequence? */ flg = 1; continue; /* Enter flag sequence */ } } mbit = 0x80; /* Read from MSB */ } d <<= 1; /* Get a bit */ if (*dp & mbit) { d |= 1; } mbit >>= 1; nbit--; } while (nbit); jd->dbit = mbit; jd->dctr = dc; jd->dptr = dp; return (int)d; #else unsigned int wbit = jd->dbit % 32; uint32_t w = jd->wreg & ((1UL << wbit) - 1); while (wbit < nbit) { /* Prepare nbit bits into the working register */ if (jd->marker) { d = 0xFF; /* Input stream stalled, generate stuff bits */ } else { if (!dc) { /* Buffer empty, re-fill input buffer */ dp = jd->inbuf; /* Top of input buffer */ dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ } } d = *dp++; dc--; if (flg) { /* In flag sequence? */ flg = 0; /* Exit flag sequence */ if (d != 0) { jd->marker = d; /* Not an escape of 0xFF but a marker */ } d = 0xFF; } else { if (d == 0xFF) { /* Is start of flag sequence? */ flg = 1; continue; /* Enter flag sequence, get trailing byte */ } } } w = w << 8 | d; /* Get 8 bits into the working register */ wbit += 8; } jd->wreg = w; jd->dbit = wbit - nbit; jd->dctr = dc; jd->dptr = dp; return (int)(w >> ((wbit - nbit) % 32)); #endif } /*-----------------------------------------------------------------------*/ /* Process restart interval */ /*-----------------------------------------------------------------------*/ static JRESULT restart ( JDEC *jd, /* Pointer to the decompressor object */ uint16_t rstn /* Expected restert sequence number */ ) { unsigned int i; uint8_t *dp = jd->dptr; size_t dc = jd->dctr; #if JD_FASTDECODE == 0 uint16_t d = 0; /* Get two bytes from the input stream */ for (i = 0; i < 2; i++) { if (!dc) { /* No input data is available, re-fill input buffer */ dp = jd->inbuf; dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return JDR_INP; } } else { dp++; } dc--; d = d << 8 | *dp; /* Get a byte */ } jd->dptr = dp; jd->dctr = dc; jd->dbit = 0; /* Check the marker */ if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) { return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be corrupted data) */ } #else uint16_t marker; if (jd->marker) { /* Generate a maker if it has been detected */ marker = 0xFF00 | jd->marker; jd->marker = 0; } else { marker = 0; for (i = 0; i < 2; i++) { /* Get a restart marker */ if (!dc) { /* No input data is available, re-fill input buffer */ dp = jd->inbuf; dc = jd->infunc(jd, dp, JD_SZBUF); if (!dc) { return JDR_INP; } } marker = (marker << 8) | *dp++; /* Get a byte */ dc--; } jd->dptr = dp; jd->dctr = dc; } /* Check the marker */ if ((marker & 0xFFD8) != 0xFFD0 || (marker & 7) != (rstn & 7)) { return JDR_FMT1; /* Err: expected RSTn marker was not detected (may be corrupted data) */ } jd->dbit = 0; /* Discard stuff bits */ #endif jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Reset DC offset */ return JDR_OK; } /*-----------------------------------------------------------------------*/ /* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ /*-----------------------------------------------------------------------*/ static void block_idct ( int32_t *src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ jd_yuv_t *dst /* Pointer to the destination to store the block as byte array */ ) { const int32_t M13 = (int32_t)(1.41421 * 4096), M2 = (int32_t)(1.08239 * 4096), M4 = (int32_t)(2.61313 * 4096), M5 = (int32_t)(1.84776 * 4096); int32_t v0, v1, v2, v3, v4, v5, v6, v7; int32_t t10, t11, t12, t13; int i; /* Process columns */ for (i = 0; i < 8; i++) { v0 = src[8 * 0]; /* Get even elements */ v1 = src[8 * 2]; v2 = src[8 * 4]; v3 = src[8 * 6]; t10 = v0 + v2; /* Process the even elements */ t12 = v0 - v2; t11 = (v1 - v3) * M13 >> 12; v3 += v1; t11 -= v3; v0 = t10 + v3; v3 = t10 - v3; v1 = t11 + t12; v2 = t12 - t11; v4 = src[8 * 7]; /* Get odd elements */ v5 = src[8 * 1]; v6 = src[8 * 5]; v7 = src[8 * 3]; t10 = v5 - v4; /* Process the odd elements */ t11 = v5 + v4; t12 = v6 - v7; v7 += v6; v5 = (t11 - v7) * M13 >> 12; v7 += t11; t13 = (t10 + t12) * M5 >> 12; v4 = t13 - (t10 * M2 >> 12); v6 = t13 - (t12 * M4 >> 12) - v7; v5 -= v6; v4 -= v5; src[8 * 0] = v0 + v7; /* Write-back transformed values */ src[8 * 7] = v0 - v7; src[8 * 1] = v1 + v6; src[8 * 6] = v1 - v6; src[8 * 2] = v2 + v5; src[8 * 5] = v2 - v5; src[8 * 3] = v3 + v4; src[8 * 4] = v3 - v4; src++; /* Next column */ } /* Process rows */ src -= 8; for (i = 0; i < 8; i++) { v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ v1 = src[2]; v2 = src[4]; v3 = src[6]; t10 = v0 + v2; /* Process the even elements */ t12 = v0 - v2; t11 = (v1 - v3) * M13 >> 12; v3 += v1; t11 -= v3; v0 = t10 + v3; v3 = t10 - v3; v1 = t11 + t12; v2 = t12 - t11; v4 = src[7]; /* Get odd elements */ v5 = src[1]; v6 = src[5]; v7 = src[3]; t10 = v5 - v4; /* Process the odd elements */ t11 = v5 + v4; t12 = v6 - v7; v7 += v6; v5 = (t11 - v7) * M13 >> 12; v7 += t11; t13 = (t10 + t12) * M5 >> 12; v4 = t13 - (t10 * M2 >> 12); v6 = t13 - (t12 * M4 >> 12) - v7; v5 -= v6; v4 -= v5; /* Descale the transformed values 8 bits and output a row */ #if JD_FASTDECODE >= 1 dst[0] = (int16_t)((v0 + v7) >> 8); dst[7] = (int16_t)((v0 - v7) >> 8); dst[1] = (int16_t)((v1 + v6) >> 8); dst[6] = (int16_t)((v1 - v6) >> 8); dst[2] = (int16_t)((v2 + v5) >> 8); dst[5] = (int16_t)((v2 - v5) >> 8); dst[3] = (int16_t)((v3 + v4) >> 8); dst[4] = (int16_t)((v3 - v4) >> 8); #else dst[0] = BYTECLIP((v0 + v7) >> 8); dst[7] = BYTECLIP((v0 - v7) >> 8); dst[1] = BYTECLIP((v1 + v6) >> 8); dst[6] = BYTECLIP((v1 - v6) >> 8); dst[2] = BYTECLIP((v2 + v5) >> 8); dst[5] = BYTECLIP((v2 - v5) >> 8); dst[3] = BYTECLIP((v3 + v4) >> 8); dst[4] = BYTECLIP((v3 - v4) >> 8); #endif dst += 8; src += 8; /* Next row */ } } /*-----------------------------------------------------------------------*/ /* Load all blocks in an MCU into working buffer */ /*-----------------------------------------------------------------------*/ static JRESULT mcu_load ( JDEC *jd /* Pointer to the decompressor object */ ) { int32_t *tmp = (int32_t *)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ int d, e; unsigned int blk, nby, i, bc, z, id, cmp; jd_yuv_t *bp; const int32_t *dqf; nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ bp = jd->mcubuf; /* Pointer to the first block of MCU */ for (blk = 0; blk < nby + 2; blk++) { /* Get nby Y blocks and two C blocks */ cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ if (cmp && jd->ncomp != 3) { /* Clear C blocks if not exist (monochrome image) */ for (i = 0; i < 64; bp[i++] = 128) ; } else { /* Load Y/C blocks from input stream */ id = cmp ? 1 : 0; /* Huffman table ID of this component */ /* Extract a DC element from input stream */ d = huffext(jd, id, 0); /* Extract a huffman coded data (bit length) */ if (d < 0) { return (JRESULT)(0 - d); /* Err: invalid code or input */ } bc = (unsigned int)d; d = jd->dcv[cmp]; /* DC value of previous block */ if (bc) { /* If there is any difference from previous block */ e = bitext(jd, bc); /* Extract data bits */ if (e < 0) { return (JRESULT)(0 - e); /* Err: input */ } bc = 1 << (bc - 1); /* MSB position */ if (!(e & bc)) { e -= (bc << 1) - 1; /* Restore negative value if needed */ } d += e; /* Get current value */ jd->dcv[cmp] = (int16_t)d; /* Save current DC value for next block */ } dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ /* Extract following 63 AC elements from input stream */ memset(&tmp[1], 0, 63 * sizeof (int32_t)); /* Initialize all AC elements */ z = 1; /* Top of the AC elements (in zigzag-order) */ do { d = huffext(jd, id, 1); /* Extract a huffman coded value (zero runs and bit length) */ if (d == 0) { break; /* EOB? */ } if (d < 0) { return (JRESULT)(0 - d); /* Err: invalid code or input error */ } bc = (unsigned int)d; z += bc >> 4; /* Skip leading zero run */ if (z >= 64) { return JDR_FMT1; /* Too long zero run */ } if (bc &= 0x0F) { /* Bit length? */ d = bitext(jd, bc); /* Extract data bits */ if (d < 0) { return (JRESULT)(0 - d); /* Err: input device */ } bc = 1 << (bc - 1); /* MSB position */ if (!(d & bc)) { d -= (bc << 1) - 1; /* Restore negative value if needed */ } i = Zig[z]; /* Get raster-order index */ tmp[i] = d * dqf[i] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ } } while (++z < 64); /* Next AC element */ if (JD_FORMAT != 2 || !cmp) { /* C components may not be processed if in grayscale output */ if (z == 1 || (JD_USE_SCALE && jd->scale == 3)) { /* If no AC element or scale ratio is 1/8, IDCT can be omitted and the block is filled with DC value */ d = (jd_yuv_t)((*tmp / 256) + 128); if (JD_FASTDECODE >= 1) { for (i = 0; i < 64; bp[i++] = d) ; } else { memset(bp, d, 64); } } else { block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ } } } bp += 64; /* Next block */ } return JDR_OK; /* All blocks have been loaded successfully */ } /*-----------------------------------------------------------------------*/ /* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ /*-----------------------------------------------------------------------*/ static JRESULT mcu_output ( JDEC *jd, /* Pointer to the decompressor object */ int (*outfunc)(JDEC *, void *, JRECT *), /* RGB output function */ unsigned int x, /* MCU location in the image */ unsigned int y /* MCU location in the image */ ) { const int CVACC = (sizeof (int) > 2) ? 1024 : 128; /* Adaptive accuracy for both 16-/32-bit systems */ unsigned int ix, iy, mx, my, rx, ry; int yy, cb, cr; jd_yuv_t *py, *pc; uint8_t *pix; JRECT rect; mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end of image) */ ry = (y + my <= jd->height) ? my : jd->height - y; if (JD_USE_SCALE) { rx >>= jd->scale; ry >>= jd->scale; if (!rx || !ry) { return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ } x >>= jd->scale; y >>= jd->scale; } rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */ rect.top = y; rect.bottom = y + ry - 1; if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ pix = (uint8_t *)jd->workbuf; if (JD_FORMAT != 2) { /* RGB output (build an RGB MCU from Y/C component) */ for (iy = 0; iy < my; iy++) { pc = py = jd->mcubuf; if (my == 16) { /* Double block height? */ pc += 64 * 4 + (iy >> 1) * 8; if (iy >= 8) { py += 64; } } else { /* Single block height */ pc += mx * 8 + iy * 8; } py += iy * 8; for (ix = 0; ix < mx; ix++) { cb = pc[0] - 128; /* Get Cb/Cr component and remove offset */ cr = pc[64] - 128; if (mx == 16) { /* Double block width? */ if (ix == 8) { py += 64 - 8; /* Jump to next block if double block height */ } /* Step forward chroma pointer every two pixels */ if (ix % 2) { pc++; } } else { /* Single block width */ pc++; /* Step forward chroma pointer every pixel */ } yy = *py++; /* Get Y component */ *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr) / CVACC); *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb) / CVACC); } } } else { /* Monochrome output (build a grayscale MCU from Y component) */ for (iy = 0; iy < my; iy++) { py = jd->mcubuf + iy * 8; if (my == 16) { /* Double block height? */ if (iy >= 8) { py += 64; } } for (ix = 0; ix < mx; ix++) { if (mx == 16) { /* Double block width? */ if (ix == 8) { py += 64 - 8; /* Jump to next block if double block height */ } } *pix++ = (uint8_t) * py++; /* Get and store a Y value as grayscale */ } } } /* Descale the MCU rectangular if needed */ if (JD_USE_SCALE && jd->scale) { unsigned int x, y, r, g, b, s, w, a; uint8_t *op; /* Get averaged RGB value of each square corresponds to a pixel */ s = jd->scale * 2; /* Number of shifts for averaging */ w = 1 << jd->scale; /* Width of square */ a = (mx - w) * (JD_FORMAT != 2 ? 3 : 1); /* Bytes to skip for next line in the square */ op = (uint8_t *)jd->workbuf; for (iy = 0; iy < my; iy += w) { for (ix = 0; ix < mx; ix += w) { pix = (uint8_t *)jd->workbuf + (iy * mx + ix) * (JD_FORMAT != 2 ? 3 : 1); r = g = b = 0; for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ for (x = 0; x < w; x++) { r += *pix++; /* Accumulate R or Y (monochrome output) */ if (JD_FORMAT != 2) { /* RGB output? */ g += *pix++; /* Accumulate G */ b += *pix++; /* Accumulate B */ } } pix += a; } /* Put the averaged pixel value */ *op++ = (uint8_t)(r >> s); /* Put R or Y (monochrome output) */ if (JD_FORMAT != 2) { /* RGB output? */ *op++ = (uint8_t)(g >> s); /* Put G */ *op++ = (uint8_t)(b >> s); /* Put B */ } } } } } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ /* Build a 1/8 descaled RGB MCU from discrete components */ pix = (uint8_t *)jd->workbuf; pc = jd->mcubuf + mx * my; cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ cr = pc[64] - 128; for (iy = 0; iy < my; iy += 8) { py = jd->mcubuf; if (iy == 8) { py += 64 * 2; } for (ix = 0; ix < mx; ix += 8) { yy = *py; /* Get Y component */ py += 64; if (JD_FORMAT != 2) { *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr / CVACC)); *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb / CVACC)); } else { *pix++ = yy; } } } } /* Squeeze up pixel table if a part of MCU is to be truncated */ mx >>= jd->scale; if (rx < mx) { /* Is the MCU spans rigit edge? */ uint8_t *s, *d; unsigned int x, y; s = d = (uint8_t *)jd->workbuf; for (y = 0; y < ry; y++) { for (x = 0; x < rx; x++) { /* Copy effective pixels */ *d++ = *s++; if (JD_FORMAT != 2) { *d++ = *s++; *d++ = *s++; } } s += (mx - rx) * (JD_FORMAT != 2 ? 3 : 1); /* Skip truncated pixels */ } } /* Convert RGB888 to RGB565 if needed */ if (JD_FORMAT == 1) { uint8_t *s = (uint8_t *)jd->workbuf; uint16_t w, *d = (uint16_t *)s; unsigned int n = rx * ry; do { w = (*s++ & 0xF8) << 8; /* RRRRR----------- */ w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */ w |= *s++ >> 3; /* -----------BBBBB */ *d++ = w; } while (--n); } /* Output the rectangular */ return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; } /*-----------------------------------------------------------------------*/ /* Analyze the JPEG image and Initialize decompressor object */ /*-----------------------------------------------------------------------*/ #define LDB_WORD(ptr) (uint16_t)(((uint16_t)*((uint8_t*)(ptr))<<8)|(uint16_t)*(uint8_t*)((ptr)+1)) JRESULT jd_prepare ( JDEC *jd, /* Blank decompressor object */ size_t (*infunc)(JDEC *, uint8_t *, size_t), /* JPEG stream input function */ void *pool, /* Working buffer for the decompression session */ size_t sz_pool, /* Size of working buffer */ void *dev /* I/O device identifier for the session */ ) { uint8_t *seg, b; uint16_t marker; unsigned int n, i, ofs; size_t len; JRESULT rc; memset(jd, 0, sizeof (JDEC)); /* Clear decompression object (this might be a problem if machine's null pointer is not all bits zero) */ jd->pool = pool; /* Work memory */ jd->sz_pool = sz_pool; /* Size of given work memory */ jd->infunc = infunc; /* Stream input function */ jd->device = dev; /* I/O device identifier */ jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ if (!seg) { return JDR_MEM1; } ofs = marker = 0; /* Find SOI marker */ do { if (jd->infunc(jd, seg, 1) != 1) { return JDR_INP; /* Err: SOI was not detected */ } ofs++; marker = marker << 8 | seg[0]; } while (marker != 0xFFD8); for (;;) { /* Parse JPEG segments */ /* Get a JPEG marker */ if (jd->infunc(jd, seg, 4) != 4) { return JDR_INP; } marker = LDB_WORD(seg); /* Marker */ len = LDB_WORD(seg + 2); /* Length field */ /* In the baseline JPEG specification, 0xFF is always used as the "marker prefix," and the byte that follows determines the marker type (e.g., 0xD8 for SOI, 0xD9 for EOI, 0xDA for SOS, etc.). A 0xFFFF sequence, however, does not correspond to any valid, standard JPEG marker. In JPEG-compressed data, any single 0xFF in the entropy-coded segment is supposed to be followed by 0x00 if it is not a marker. Sometimes, encoders or hardware incorrectly insert repeated 0xFF bytes without the 0x00 "stuffing" byte. This confuses decoders that strictly follow the JPEG standard. */ if (marker == 0xFFFF) { // Check if ignoring seg[0] byte gives us valid marker // We must read 1 more byte from the input stream if (jd->infunc(jd, &seg[4], 1) != 1) { return JDR_INP; } marker = LDB_WORD(seg + 1); len = LDB_WORD(seg + 3); } if (len <= 2 || (marker >> 8) != 0xFF) { return JDR_FMT1; } len -= 2; /* Segent content size */ ofs += 4 + len; /* Number of bytes loaded */ switch (marker & 0xFF) { case 0xC0: /* SOF0 (baseline JPEG) */ if (len > JD_SZBUF) { return JDR_MEM2; } if (jd->infunc(jd, seg, len) != len) { return JDR_INP; /* Load segment data */ } jd->width = LDB_WORD(&seg[3]); /* Image width in unit of pixel */ jd->height = LDB_WORD(&seg[1]); /* Image height in unit of pixel */ jd->ncomp = seg[5]; /* Number of color components */ if (jd->ncomp != 3 && jd->ncomp != 1) { return JDR_FMT3; /* Err: Supports only Grayscale and Y/Cb/Cr */ } /* Check each image component */ for (i = 0; i < jd->ncomp; i++) { b = seg[7 + 3 * i]; /* Get sampling factor */ if (i == 0) { /* Y component */ if (b != 0x11 && b != 0x22 && b != 0x21) { /* Check sampling factor */ return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ } jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ } else { /* Cb/Cr component */ if (b != 0x11) { return JDR_FMT3; /* Err: Sampling factor of Cb/Cr must be 1 */ } } jd->qtid[i] = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ if (jd->qtid[i] > 3) { return JDR_FMT3; /* Err: Invalid ID */ } } break; case 0xDD: /* DRI - Define Restart Interval */ if (len > JD_SZBUF) { return JDR_MEM2; } if (jd->infunc(jd, seg, len) != len) { return JDR_INP; /* Load segment data */ } jd->nrst = LDB_WORD(seg); /* Get restart interval (MCUs) */ break; case 0xC4: /* DHT - Define Huffman Tables */ if (len > JD_SZBUF) { return JDR_MEM2; } if (jd->infunc(jd, seg, len) != len) { return JDR_INP; /* Load segment data */ } rc = create_huffman_tbl(jd, seg, len); /* Create huffman tables */ if (rc) { return rc; } break; case 0xDB: /* DQT - Define Quaitizer Tables */ if (len > JD_SZBUF) { return JDR_MEM2; } if (jd->infunc(jd, seg, len) != len) { return JDR_INP; /* Load segment data */ } rc = create_qt_tbl(jd, seg, len); /* Create de-quantizer tables */ if (rc) { return rc; } break; case 0xDA: /* SOS - Start of Scan */ if (len > JD_SZBUF) { return JDR_MEM2; } if (jd->infunc(jd, seg, len) != len) { return JDR_INP; /* Load segment data */ } if (!jd->width || !jd->height) { return JDR_FMT1; /* Err: Invalid image size */ } if (seg[0] != jd->ncomp) { return JDR_FMT3; /* Err: Wrong color components */ } /* Check if all tables corresponding to each components have been loaded */ for (i = 0; i < jd->ncomp; i++) { b = seg[2 + 2 * i]; /* Get huffman table ID */ if (b != 0x00 && b != 0x11) { return JDR_FMT3; /* Err: Different table number for DC/AC element */ } n = i ? 1 : 0; /* Component class */ if (!jd->huffbits[n][0] || !jd->huffbits[n][1]) { /* Check huffman table for this component */ #if JD_DEFAULT_HUFFMAN jd_load_default_huffman(jd); // Always returns OK #else return JDR_FMT1; /* Err: Nnot loaded */ #endif } if (!jd->qttbl[jd->qtid[i]]) { /* Check dequantizer table for this component */ return JDR_FMT1; /* Err: Not loaded */ } } /* Allocate working buffer for MCU and pixel output */ n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ if (!n) { return JDR_FMT1; /* Err: SOF0 has not been loaded */ } len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ if (len < 256) { len = 256; /* but at least 256 byte is required for IDCT */ } jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ if (!jd->workbuf) { return JDR_MEM1; /* Err: not enough memory */ } jd->mcubuf = alloc_pool(jd, (n + 2) * 64 * sizeof (jd_yuv_t)); /* Allocate MCU working buffer */ if (!jd->mcubuf) { return JDR_MEM1; /* Err: not enough memory */ } /* Align stream read offset to JD_SZBUF */ if (ofs %= JD_SZBUF) { jd->dctr = jd->infunc(jd, seg + ofs, (size_t)(JD_SZBUF - ofs)); } jd->dptr = seg + ofs - (JD_FASTDECODE ? 0 : 1); return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ case 0xC1: /* SOF1 */ case 0xC2: /* SOF2 */ case 0xC3: /* SOF3 */ case 0xC5: /* SOF5 */ case 0xC6: /* SOF6 */ case 0xC7: /* SOF7 */ case 0xC9: /* SOF9 */ case 0xCA: /* SOF10 */ case 0xCB: /* SOF11 */ case 0xCD: /* SOF13 */ case 0xCE: /* SOF14 */ case 0xCF: /* SOF15 */ case 0xD9: /* EOI */ return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ default: /* Unknown segment (comment, exif or etc..) */ /* Skip segment data (null pointer specifies to remove data from the stream) */ if (jd->infunc(jd, 0, len) != len) { return JDR_INP; } } } } /*-----------------------------------------------------------------------*/ /* Start to decompress the JPEG picture */ /*-----------------------------------------------------------------------*/ JRESULT jd_decomp ( JDEC *jd, /* Initialized decompression object */ int (*outfunc)(JDEC *, void *, JRECT *), /* RGB output function */ uint8_t scale /* Output de-scaling factor (0 to 3) */ ) { unsigned int x, y, mx, my; uint16_t rst, rsc; JRESULT rc; if (scale > (JD_USE_SCALE ? 3 : 0)) { return JDR_PAR; } jd->scale = scale; mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ rst = rsc = 0; rc = JDR_OK; for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ rc = restart(jd, rsc++); if (rc != JDR_OK) { return rc; } rst = 1; } rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream, dequantize and apply IDCT) */ if (rc != JDR_OK) { return rc; } rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (YCbCr to RGB, scaling and output) */ if (rc != JDR_OK) { return rc; } } } return rc; } ================================================ FILE: esp_jpeg/tjpgd/tjpgd.h ================================================ /*----------------------------------------------------------------------------/ / TJpgDec - Tiny JPEG Decompressor R0.03 include file (C)ChaN, 2021 /----------------------------------------------------------------------------*/ #ifndef DEF_TJPGDEC #define DEF_TJPGDEC #ifdef __cplusplus extern "C" { #endif #include "tjpgdcnf.h" #include #if defined(_WIN32) /* VC++ or some compiler without stdint.h */ typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef short int16_t; typedef unsigned long uint32_t; typedef long int32_t; #else /* Embedded platform */ #include #endif #if JD_FASTDECODE >= 1 typedef int16_t jd_yuv_t; #else typedef uint8_t jd_yuv_t; #endif /* Error code */ typedef enum { JDR_OK = 0, /* 0: Succeeded */ JDR_INTR, /* 1: Interrupted by output function */ JDR_INP, /* 2: Device error or wrong termination of input stream */ JDR_MEM1, /* 3: Insufficient memory pool for the image */ JDR_MEM2, /* 4: Insufficient stream input buffer */ JDR_PAR, /* 5: Parameter error */ JDR_FMT1, /* 6: Data format error (may be broken data) */ JDR_FMT2, /* 7: Right format but not supported */ JDR_FMT3 /* 8: Not supported JPEG standard */ } JRESULT; /* Rectangular region in the output image */ typedef struct { uint16_t left; /* Left end */ uint16_t right; /* Right end */ uint16_t top; /* Top end */ uint16_t bottom; /* Bottom end */ } JRECT; /* Decompressor object structure */ typedef struct JDEC JDEC; struct JDEC { size_t dctr; /* Number of bytes available in the input buffer */ uint8_t *dptr; /* Current data read ptr */ uint8_t *inbuf; /* Bit stream input buffer */ uint8_t dbit; /* Number of bits available in wreg or reading bit mask */ uint8_t scale; /* Output scaling ratio */ uint8_t msx, msy; /* MCU size in unit of block (width, height) */ uint8_t qtid[3]; /* Quantization table ID of each component, Y, Cb, Cr */ uint8_t ncomp; /* Number of color components 1:grayscale, 3:color */ int16_t dcv[3]; /* Previous DC element of each component */ uint16_t nrst; /* Restart interval */ uint16_t width, height; /* Size of the input image (pixel) */ uint8_t *huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */ uint16_t *huffcode[2][2]; /* Huffman code word tables [id][dcac] */ uint8_t *huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */ int32_t *qttbl[4]; /* Dequantizer tables [id] */ #if JD_FASTDECODE >= 1 uint32_t wreg; /* Working shift register */ uint8_t marker; /* Detected marker (0:None) */ #if JD_FASTDECODE == 2 uint8_t longofs[2][2]; /* Table offset of long code [id][dcac] */ uint16_t *hufflut_ac[2]; /* Fast huffman decode tables for AC short code [id] */ uint8_t *hufflut_dc[2]; /* Fast huffman decode tables for DC short code [id] */ #endif #endif void *workbuf; /* Working buffer for IDCT and RGB output */ jd_yuv_t *mcubuf; /* Working buffer for the MCU */ void *pool; /* Pointer to available memory pool */ size_t sz_pool; /* Size of memory pool (bytes available) */ size_t (*infunc)(JDEC *, uint8_t *, size_t); /* Pointer to jpeg stream input function */ void *device; /* Pointer to I/O device identifier for the session */ }; /* TJpgDec API functions */ JRESULT jd_prepare (JDEC *jd, size_t (*infunc)(JDEC *, uint8_t *, size_t), void *pool, size_t sz_pool, void *dev); JRESULT jd_decomp (JDEC *jd, int (*outfunc)(JDEC *, void *, JRECT *), uint8_t scale); #ifdef __cplusplus } #endif #endif /* _TJPGDEC */ ================================================ FILE: esp_jpeg/tjpgd/tjpgdcnf.h ================================================ /*----------------------------------------------*/ /* TJpgDec System Configurations R0.03 */ /*----------------------------------------------*/ #include "sdkconfig.h" #define JD_SZBUF CONFIG_JD_SZBUF /* Specifies size of stream input buffer */ #define JD_FORMAT CONFIG_JD_FORMAT /* Specifies output pixel format. / 0: RGB888 (24-bit/pix) / 1: RGB565 (16-bit/pix) / 2: Grayscale (8-bit/pix) */ #if defined(CONFIG_JD_USE_SCALE) #define JD_USE_SCALE CONFIG_JD_USE_SCALE #else #define JD_USE_SCALE 0 #endif /* Switches output descaling feature. / 0: Disable / 1: Enable */ #if defined(CONFIG_JD_TBLCLIP) #define JD_TBLCLIP CONFIG_JD_TBLCLIP #else #define JD_TBLCLIP 0 #endif /* Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size. / 0: Disable / 1: Enable */ #define JD_FASTDECODE CONFIG_JD_FASTDECODE /* Optimization level / 0: Basic optimization. Suitable for 8/16-bit MCUs. / 1: + 32-bit barrel shifter. Suitable for 32-bit MCUs. / 2: + Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM) */ #if defined(CONFIG_JD_DEFAULT_HUFFMAN) #define JD_DEFAULT_HUFFMAN CONFIG_JD_DEFAULT_HUFFMAN #else #define JD_DEFAULT_HUFFMAN 0 #endif ================================================ FILE: esp_lcd_qemu_rgb/CHANGELOG.md ================================================ ## 1.0.2 - Switched to RGB565 color format in the example by default - Fixed compatibility with IDF v5.3-dev ## 1.0.1 - Added support for RGB565 color format ## 1.0.0 - Initial driver version ================================================ FILE: esp_lcd_qemu_rgb/CMakeLists.txt ================================================ idf_component_register(SRCS "src/esp_lcd_qemu_rgb.c" INCLUDE_DIRS "interface" REQUIRES "esp_lcd") ================================================ FILE: esp_lcd_qemu_rgb/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_lcd_qemu_rgb/README.md ================================================ # QEMU RGB Panel This component presents an interface for the virtual QEMU RGB panel, implemented into Espressif's QEMU fork starting from version 8.1.3-20231206. This virtual RGB panel that can be used to display graphical interfaces. This panel also includes a dedicated frame buffer, absent in real hardware and independent from the internal RAM, that allows user program to populate the pixels in. **Please note** that the virtual RGB panel currently supports only two color modes: ARGB8888 (32-bit) and RGB565 (16-bit). ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/CMakeLists.txt ================================================ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # The following five lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(lcd_qemu_rgb_panel) ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/README.md ================================================ # QEMU RGB Panel This example demonstrates how to use the virtual QEMU RGB panel. In this case, LVGL uses the virtual panel to render its graphical user interface. The frame buffer can be chosen between internal RAM or dedicated frame buffer. ## How to Use Example ### Hardware Required * No hardware target is required to run this example ### Configure the Example By default, the example will use the target internal RAM as the frame buffer. To utilize the QEMU dedicated frame buffer, enable the option `Use QEMU RGB panel dedicated framebuffer` within the `menuconfig`. ### Build and run To build the example, run `idf.py build` command. Please refer to the [QEMU Guide](https://github.com/espressif/esp-toolchain-docs/blob/main/qemu/README.md) for the detailed steps to setup and run the image. ## Example Output ```text I (55) example: Install RGB LCD panel driver I (55) example: Initialize RGB LCD panel I (55) example: Initialize LVGL library I (55) example: Allocate separate LVGL draw buffer I (55) example: Register display driver to LVGL I (55) example: Install LVGL tick timer I (55) example: Create LVGL task I (55) example: Starting LVGL task I (65) example: Display LVGL Scatter Chart I (75) main_task: Returned from app_main() ``` ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/main/CMakeLists.txt ================================================ idf_component_register(SRCS "lcd_qemu_rgb_panel_main.c" "lvgl_demo_ui.c" INCLUDE_DIRS "." REQUIRES "esp_lcd") ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/main/Kconfig.projbuild ================================================ menu "Example Configuration" config EXAMPLE_QEMU_RGB_PANEL_DEDIC_FB bool "Use QEMU RGB panel dedicated framebuffer" default "n" help Use QEMU RGB panel dedicated framebuffer as the framebuffer for LVGL. endmenu ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/esp_lcd_qemu_rgb: version: '^1' override_path: '../../../' lvgl/lvgl: "~8.3.0" ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/main/lcd_qemu_rgb_panel_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "esp_timer.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_qemu_rgb.h" #include "driver/gpio.h" #include "esp_err.h" #include "esp_log.h" #include "lvgl.h" static const char *TAG = "example"; /** * 32-bit and 16-bit colors are currently supported by QEMU RGB Panel */ #if CONFIG_LV_COLOR_DEPTH_32 #define CURRENT_COLOR_DEPTH RGB_QEMU_BPP_32 #elif CONFIG_LV_COLOR_DEPTH_16 #define CURRENT_COLOR_DEPTH RGB_QEMU_BPP_16 #else #error "QEMU RGB Panel only supports 32-bit and 16-bit colors, please enable LV_COLOR_DEPTH_32 or LV_COLOR_DEPTH_16" #endif // The pixel number in horizontal and vertical #define EXAMPLE_LCD_H_RES 800 #define EXAMPLE_LCD_V_RES 480 #define EXAMPLE_LVGL_TICK_PERIOD_MS 2 #define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500 #define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1 #define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024) #define EXAMPLE_LVGL_TASK_PRIORITY 2 static SemaphoreHandle_t lvgl_mux = NULL; extern void example_lvgl_demo_ui(lv_disp_t *disp); static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data; int offsetx1 = area->x1; int offsetx2 = area->x2; int offsety1 = area->y1; int offsety2 = area->y2; // pass the draw buffer to the driver esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); lv_disp_flush_ready(drv); } static void example_increase_lvgl_tick(void *arg) { /* Tell LVGL how many milliseconds has elapsed */ lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS); } bool example_lvgl_lock(int timeout_ms) { // Convert timeout in milliseconds to FreeRTOS ticks // If `timeout_ms` is set to -1, the program will block until the condition is met const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE; } void example_lvgl_unlock(void) { xSemaphoreGiveRecursive(lvgl_mux); } static void example_lvgl_port_task(void *arg) { ESP_LOGI(TAG, "Starting LVGL task"); uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS; while (1) { // Lock the mutex due to the LVGL APIs are not thread-safe if (example_lvgl_lock(-1)) { task_delay_ms = lv_timer_handler(); // Release the mutex example_lvgl_unlock(); } if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) { task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS; } else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) { task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS; } vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); } } static size_t example_lvgl_get_buffers(esp_lcd_panel_handle_t panel_handle, void **buf1, void **buf2) { if (buf2) { *buf2 = NULL; } #if CONFIG_EXAMPLE_QEMU_RGB_PANEL_DEDIC_FB ESP_LOGI(TAG, "Use QEMU dedicated frame buffer as LVGL draw buffer"); ESP_ERROR_CHECK(esp_lcd_rgb_qemu_get_frame_buffer(panel_handle, buf1)); return EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES; #else ESP_LOGI(TAG, "Allocate separate LVGL draw buffer"); /* Allocate 10 horizontal lines as the frame buffer */ *buf1 = malloc(EXAMPLE_LCD_H_RES * 10 * sizeof(lv_color_t)); assert(*buf1); return EXAMPLE_LCD_H_RES * 10; #endif // CONFIG_EXAMPLE_DOUBLE_FB } void app_main(void) { static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s) static lv_disp_drv_t disp_drv; // contains callback functions ESP_LOGI(TAG, "Install RGB LCD panel driver"); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_rgb_qemu_config_t panel_config = { .width = EXAMPLE_LCD_H_RES, .height = EXAMPLE_LCD_V_RES, .bpp = CURRENT_COLOR_DEPTH, }; ESP_ERROR_CHECK(esp_lcd_new_rgb_qemu(&panel_config, &panel_handle)); ESP_LOGI(TAG, "Initialize RGB LCD panel"); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_LOGI(TAG, "Initialize LVGL library"); lv_init(); void *buf1 = NULL; void *buf2 = NULL; const size_t buf_pixels = example_lvgl_get_buffers(panel_handle, &buf1, &buf2); lv_disp_draw_buf_init(&disp_buf, buf1, buf2, buf_pixels); ESP_LOGI(TAG, "Register display driver to LVGL"); lv_disp_drv_init(&disp_drv); disp_drv.hor_res = EXAMPLE_LCD_H_RES; disp_drv.ver_res = EXAMPLE_LCD_V_RES; disp_drv.flush_cb = example_lvgl_flush_cb; disp_drv.draw_buf = &disp_buf; disp_drv.user_data = panel_handle; #if CONFIG_EXAMPLE_QEMU_RGB_PANEL_DEDIC_FB disp_drv.full_refresh = true; // the full_refresh mode can maintain the synchronization between the two frame buffers #endif lv_disp_t *disp = lv_disp_drv_register(&disp_drv); ESP_LOGI(TAG, "Install LVGL tick timer"); // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = &example_increase_lvgl_tick, .name = "lvgl_tick" }; esp_timer_handle_t lvgl_tick_timer = NULL; ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000)); lvgl_mux = xSemaphoreCreateRecursiveMutex(); assert(lvgl_mux); ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL); ESP_LOGI(TAG, "Display LVGL Scatter Chart"); // Lock the mutex due to the LVGL APIs are not thread-safe if (example_lvgl_lock(-1)) { example_lvgl_demo_ui(disp); // Release the mutex example_lvgl_unlock(); } } ================================================ FILE: esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel/main/lvgl_demo_ui.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ // This demo UI is adapted from LVGL official example: https://docs.lvgl.io/master/examples.html#scatter-chart #include "lvgl.h" #include static void draw_event_cb(lv_event_t *e) { lv_obj_draw_part_dsc_t *dsc = lv_event_get_draw_part_dsc(e); if (dsc->part == LV_PART_ITEMS) { lv_obj_t *obj = lv_event_get_target(e); lv_chart_series_t *ser = lv_chart_get_series_next(obj, NULL); uint32_t cnt = lv_chart_get_point_count(obj); /*Make older value more transparent*/ dsc->rect_dsc->bg_opa = (LV_OPA_COVER * dsc->id) / (cnt - 1); /*Make smaller values blue, higher values red*/ lv_coord_t *x_array = lv_chart_get_x_array(obj, ser); lv_coord_t *y_array = lv_chart_get_y_array(obj, ser); /*dsc->id is the tells drawing order, but we need the ID of the point being drawn.*/ uint32_t start_point = lv_chart_get_x_start_point(obj, ser); uint32_t p_act = (start_point + dsc->id) % cnt; /*Consider start point to get the index of the array*/ lv_opa_t x_opa = (x_array[p_act] * LV_OPA_50) / 200; lv_opa_t y_opa = (y_array[p_act] * LV_OPA_50) / 1000; dsc->rect_dsc->bg_color = lv_color_mix(lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_BLUE), x_opa + y_opa); } } static void add_data(lv_timer_t *timer) { lv_obj_t *chart = timer->user_data; lv_chart_set_next_value2(chart, lv_chart_get_series_next(chart, NULL), lv_rand(0, 200), lv_rand(0, 1000)); } void example_lvgl_demo_ui(lv_disp_t *disp) { lv_obj_t *scr = lv_disp_get_scr_act(disp); lv_obj_t *chart = lv_chart_create(scr); lv_obj_set_size(chart, 200, 150); lv_obj_align(chart, LV_ALIGN_CENTER, 0, 0); lv_obj_add_event_cb(chart, draw_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL); lv_obj_set_style_line_width(chart, 0, LV_PART_ITEMS); /*Remove the lines*/ lv_chart_set_type(chart, LV_CHART_TYPE_SCATTER); lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_X, 5, 5, 5, 1, true, 30); lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 10, 5, 6, 5, true, 50); lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_X, 0, 200); lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 1000); lv_chart_set_point_count(chart, 50); lv_chart_series_t *ser = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y); for (int i = 0; i < 50; i++) { lv_chart_set_next_value2(chart, ser, lv_rand(0, 200), lv_rand(0, 1000)); } lv_timer_create(add_data, 100, chart); } ================================================ FILE: esp_lcd_qemu_rgb/idf_component.yml ================================================ version: "1.0.2" description: Driver for the virtual QEMU RGB panel url: https://github.com/espressif/idf-extra-components/tree/master/esp_lcd_qemu_rgb repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.3" ================================================ FILE: esp_lcd_qemu_rgb/interface/esp_lcd_qemu_rgb.h ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "esp_lcd_types.h" #ifdef __cplusplus extern "C" { #endif typedef enum { RGB_QEMU_BPP_32 = 32, RGB_QEMU_BPP_16 = 16, } esp_lcd_rgb_qemu_bpp_t; /** * @brief QEMU RGB panel configuration structure */ typedef struct { uint32_t width; /*!< Width of the graphical window in pixels */ uint32_t height; /*!< Height of the graphical window in pixels */ esp_lcd_rgb_qemu_bpp_t bpp; /*!< BPP - bit per pixel*/ } esp_lcd_rgb_qemu_config_t; /** * @brief Create QEMU RGB panel * * @param[in] rgb_config QEMU RGB panel configuration * @param[out] ret_panel Returned panel handle * @return * - ESP_ERR_INVALID_ARG: Creation failed because of invalid argument, check the configuration parameter * - ESP_ERR_NO_MEM: Creation failed because of the lack of free memory in the heap * - ESP_ERR_NOT_SUPPORTED: Creation failed because this API must only be used within a QEMU virtual machine * - ESP_OK: Panel created successfully */ esp_err_t esp_lcd_new_rgb_qemu(const esp_lcd_rgb_qemu_config_t *rgb_config, esp_lcd_panel_handle_t *ret_panel); /** * @brief Get the address of the frame buffer for the QEMU RGB panel * * @param[in] panel QEMU RGB panel handle, returned from `esp_lcd_new_rgb_qemu` * @param[out] fb Returned address of the frame buffer * @return * - ESP_OK: Frame buffer returned successfully */ esp_err_t esp_lcd_rgb_qemu_get_frame_buffer(esp_lcd_panel_handle_t panel, void **fb); /** * @brief Manually trigger once transmission of the frame buffer to the panel * * @param[in] panel QEMU RGB panel handle, returned from `esp_lcd_new_rgb_qemu` * @returns ESP_OK unconditionally */ esp_err_t esp_lcd_rgb_qemu_refresh(esp_lcd_panel_handle_t panel); #ifdef __cplusplus } #endif ================================================ FILE: esp_lcd_qemu_rgb/src/esp_lcd_qemu_rgb.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include "esp_check.h" #include "esp_lcd_panel_interface.h" #include "esp_lcd_qemu_rgb_struct.h" #include "esp_lcd_qemu_rgb.h" #include "esp_lcd_panel_ops.h" #include "soc/syscon_reg.h" /* "QEMU" as a 32-bit value, used to check whether the current application is running in * QEMU or on real hardware */ #define RGB_QEMU_ORIGIN 0x51454d55 static const char *TAG = "lcd_qemu.rgb"; static rgb_qemu_dev_t *s_rgb_dev = (void *) 0x21000000; static uint32_t *s_rgb_framebuffer = (void *) 0x20000000; /* Software handler for the RGB Qemu virtual panel */ typedef struct esp_rgb_qemu_t { esp_lcd_panel_t base; // Base class of generic lcd panel int panel_id; // LCD panel ID uint32_t width; uint32_t height; } esp_rgb_qemu_t; static_assert(offsetof(esp_rgb_qemu_t, base) == 0, "Base field must be the first"); static esp_err_t rgb_qemu_del(esp_lcd_panel_t *panel); static esp_err_t rgb_qemu_reset(esp_lcd_panel_t *panel); static esp_err_t rgb_qemu_init(esp_lcd_panel_t *panel); static esp_err_t rgb_qemu_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); static esp_err_t rgb_qemu_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); static esp_err_t rgb_qemu_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); static esp_err_t rgb_qemu_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); static esp_err_t rgb_qemu_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); static esp_err_t rgb_qemu_disp_on_off(esp_lcd_panel_t *panel, bool off); esp_err_t esp_lcd_new_rgb_qemu(const esp_lcd_rgb_qemu_config_t *rgb_config, esp_lcd_panel_handle_t *ret_panel) { esp_err_t ret = ESP_OK; esp_rgb_qemu_t *rgb_panel = NULL; ESP_GOTO_ON_FALSE(ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter"); /* Check if we are actually running on QEMU, read the special register allocated just before the * SYSCON date one. */ const uint32_t origin = REG_READ(SYSCON_DATE_REG - 4); /* In case we are running in QEMU, this register contains "QEMU" as a 32-bit value */ ESP_GOTO_ON_FALSE(origin == RGB_QEMU_ORIGIN, ESP_ERR_NOT_SUPPORTED, err, TAG, "qemu panel is not available on real hardware"); rgb_panel = calloc(1, sizeof(esp_rgb_qemu_t)); ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb qemu panel"); /* Resize the window and setup bpp*/ s_rgb_dev->size.height = rgb_config->height; s_rgb_dev->size.width = rgb_config->width; s_rgb_dev->bpp = rgb_config->bpp ? rgb_config->bpp : RGB_QEMU_BPP_32; /* If the configured size is bigger than authorized, the hardware will arrange it. * So, read back the configured size */ rgb_panel->height = rgb_config->height; rgb_panel->width = rgb_config->width; /* Fill function table */ rgb_panel->base.del = rgb_qemu_del; rgb_panel->base.reset = rgb_qemu_reset; rgb_panel->base.init = rgb_qemu_init; rgb_panel->base.draw_bitmap = rgb_qemu_draw_bitmap; rgb_panel->base.disp_on_off = rgb_qemu_disp_on_off; rgb_panel->base.invert_color = rgb_qemu_invert_color; rgb_panel->base.mirror = rgb_qemu_mirror; rgb_panel->base.swap_xy = rgb_qemu_swap_xy; rgb_panel->base.set_gap = rgb_qemu_set_gap; /* Return base class */ *ret_panel = &(rgb_panel->base); ret = ESP_OK; err: return ret; } esp_err_t esp_lcd_rgb_qemu_get_frame_buffer(esp_lcd_panel_handle_t panel, void **fb) { if (fb) { *fb = (void *) s_rgb_framebuffer; } return ESP_OK; } esp_err_t esp_lcd_rgb_qemu_refresh(esp_lcd_panel_handle_t panel) { esp_rgb_qemu_t *rgb_panel = (esp_rgb_qemu_t *) panel; return rgb_qemu_draw_bitmap(panel, 0, 0, rgb_panel->width, rgb_panel->height, s_rgb_framebuffer); } /*** PRIVATE FUNCTIONS ***/ static esp_err_t rgb_qemu_del(esp_lcd_panel_t *panel) { free(panel); return ESP_OK; } static esp_err_t rgb_qemu_reset(esp_lcd_panel_t *panel) { return ESP_OK; } static esp_err_t rgb_qemu_init(esp_lcd_panel_t *panel) { return ESP_OK; } static esp_err_t rgb_qemu_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) { esp_rgb_qemu_t *rgb_panel = (esp_rgb_qemu_t *) panel; assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); s_rgb_dev->update_from.x = x_start; s_rgb_dev->update_from.y = y_start; /* The rendering WON'T include end (x,y) coordinates */ s_rgb_dev->update_to.x = x_end; s_rgb_dev->update_to.y = y_end; s_rgb_dev->update_content = (void *) color_data; s_rgb_dev->update_st.ena = 1; /* Wait for the driver to finish updating the window to avoid screen tearing effect. * This issue is on the ESP32 QEMU target (making this loop necessary) but not on the ESP32-C3. */ while (s_rgb_dev->update_st.ena == 1) {} (void) rgb_panel; return ESP_OK; } static esp_err_t rgb_qemu_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) { return ESP_ERR_NOT_SUPPORTED; } static esp_err_t rgb_qemu_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) { return ESP_ERR_NOT_SUPPORTED; } static esp_err_t rgb_qemu_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) { return ESP_ERR_NOT_SUPPORTED; } static esp_err_t rgb_qemu_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) { return ESP_ERR_NOT_SUPPORTED; } static esp_err_t rgb_qemu_disp_on_off(esp_lcd_panel_t *panel, bool on_off) { return ESP_ERR_NOT_SUPPORTED; } ================================================ FILE: esp_lcd_qemu_rgb/src/esp_lcd_qemu_rgb_struct.h ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #ifdef __cplusplus extern "C" { #endif /* Organization of the RGB QEMU panel registers */ typedef volatile struct rgb_qemu_dev_s { union { struct { uint32_t minor: 16; uint32_t major: 16; }; uint32_t val; } version; union { struct { uint32_t height: 16; uint32_t width: 16; }; uint32_t val; } size; union { struct { uint32_t y: 16; uint32_t x: 16; }; uint32_t val; } update_from; union { struct { uint32_t y: 16; uint32_t x: 16; }; uint32_t val; } update_to; /* Address of the buffer containing the new pixels of the area defined above */ void *update_content; union { struct { uint32_t ena: 1; uint32_t reserved: 31; }; uint32_t val; } update_st; uint32_t bpp; } rgb_qemu_dev_t; #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/.build-test-rules.yml ================================================ esp_linenoise/test_apps: enable: - if: IDF_TARGET == "linux" reason: "Sufficient to test on Linux target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 4 reason: "those versions of esp-idf do not support eventfd for linux target" esp_linenoise/examples/basic_line_reading: enable: - if: IDF_TARGET == "esp32" reason: "Sufficient to run on esp32 target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 2 reason: "UART component renamed to esp_driver_uart from 5.3 onwards" esp_linenoise/examples/history_usage: enable: - if: IDF_TARGET == "linux" reason: "Sufficient to run on linux target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 4 reason: "those versions of esp-idf do not support eventfd for linux target" esp_linenoise/examples/completion: enable: - if: IDF_TARGET == "esp32" reason: "Sufficient to run on esp32 target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 2 reason: "UART component renamed to esp_driver_uart from 5.3 onwards" esp_linenoise/examples/multi_instance: enable: - if: IDF_TARGET == "esp32" reason: "Sufficient to run on esp32 target" disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 2 reason: "UART component renamed to esp_driver_uart from 5.3 onwards" ================================================ FILE: esp_linenoise/CMakeLists.txt ================================================ idf_component_register(SRCS "linenoise/linenoise.c" "src/esp_linenoise.c" "src/esp_linenoise_internals.c" PRIV_INCLUDE_DIRS "private_include" PRIV_REQUIRES vfs INCLUDE_DIRS "include" "." ) ================================================ FILE: esp_linenoise/Kconfig ================================================ menu "esp_linenoise configuration" config ESP_LINENOISE_MAX_INSTANCE_NB int "Maximum number of esp_linenoise instances that can be created simultaneously" range 1 32 default 5 help Specifies the maximum number of esp_linenoise instances that can be created simultaneously. This limitation applies only when esp_linenoise_abort is used, and the following note is relevant in that context. A dynamic memory allocation is performed when the first esp_linenoise instance is created and is freed once the last instance is deleted. The allocated memory size is proportional to the value set in this configuration. Therefore, it is recommended to keep this value as low as possible to reduce memory consumption while ensuring the application’s functional requirements are met. endmenu ================================================ FILE: esp_linenoise/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright Espressif Systems Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_linenoise/README.md ================================================ # esp_linenoise (Multi-Instance Line Editor) `esp_linenoise` is an enhanced version of the [linenoise](https://github.com/antirez/linenoise) line editing library, adapted for Espressif's IDF Extra Components ecosystem. It now supports **multi-instance usage**, making it ideal for applications that require multiple input contexts—such as multiple UART consoles, shells, or network sessions. ## ✨ Features - Independent instances - Configurable history, prompt, and line-editing behavior per instance - Support for completion and hint callbacks - IDF-style memory and error handling ## 🛠️ Usage Example ```c esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); esp_linenoise_handle_t handle; const esp_err_t ret = esp_linenoise_create_instance(&config, &handle); const size_t buffer_size = 128; char buffer[128]; const char *line = esp_linenoise_get_line(&handle, buffer, buffer_size); const esp_err_t ret_val = esp_linenoise_delete_instance(&handle); ``` ## 📚 API Highlights | Function | Description | |----------|-------------| | `esp_linenoise_create_instance()` / `esp_linenoise_delete_instance()` | Create or destroy a linenoise | | `esp_linenoise_get_line()` | Read a line from a given instance | The user can pass to the configuration structure or set via setter functions, custom read and write functions that will used by esp_linenoise in place of the default read / write. The user can provide a custom set of file descriptors that esp_linenoise will use in place of the default standard input file descriptors (STDIN_FILENO, STDOUT_FILENO). For full API details, see [`esp_linenoise.h`](https://github.com/espressif/idf-extra-components/blob/master/esp_linenoise/include/esp_linenoise.h). ## 🧪 Build & Test For detailed information concerning the integration of idf components into an idf project, please refer to [esp component manager documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). ## 📄 License Apache 2.0. See `LICENSE` file. ================================================ FILE: esp_linenoise/examples/basic_line_reading/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(basic_line_reading) ================================================ FILE: esp_linenoise/examples/basic_line_reading/README.md ================================================ # Basic Line Reading Example This example demonstrates how to use esp_linenoise to create an instance, read a line of input, and delete the instance. ## Key Features - Create a linenoise instance - Read a line from the user - Delete the instance ## Source See main/basic_line_reading.c for implementation. ================================================ FILE: esp_linenoise/examples/basic_line_reading/main/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) idf_build_get_property(target IDF_TARGET) set(priv_requires esp_linenoise) if(NOT "${target}" STREQUAL "linux") list(APPEND priv_requires esp_driver_uart) endif() idf_component_register( SRCS "basic_line_reading.c" "../../utils/common_io.c" INCLUDE_DIRS "." "../../utils" PRIV_REQUIRES ${priv_requires} ) ================================================ FILE: esp_linenoise/examples/basic_line_reading/main/basic_line_reading.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "esp_linenoise.h" #include "common_io.h" void app_main(void) { common_init_io(); esp_linenoise_config_t config; esp_linenoise_handle_t handle; esp_linenoise_get_instance_config_default(&config); config.prompt = "esp_linenoise> "; ESP_ERROR_CHECK(esp_linenoise_create_instance(&config, &handle)); bool dumb_mode = false; esp_linenoise_is_dumb_mode(handle, &dumb_mode); if (dumb_mode) { printf("Running in dumb mode\n"); } else { printf("Running in normal mode\n"); } char buffer[128]; const esp_err_t ret_val = esp_linenoise_get_line(handle, buffer, sizeof(buffer)); if (ret_val == ESP_OK) { printf("You entered: %s\n", buffer); } else { printf("No input received\n"); } ESP_ERROR_CHECK(esp_linenoise_delete_instance(handle)); common_deinit_io(); printf("end of example\n"); } ================================================ FILE: esp_linenoise/examples/basic_line_reading/main/idf_component.yml ================================================ dependencies: espressif/esp_linenoise: "*" ================================================ FILE: esp_linenoise/examples/basic_line_reading/pytest_basic_line_reading.py ================================================ # SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_examples_basic_line_reading(dut: Dut) -> None: message = "test_msg" prompt = "esp_linenoise> " dut.expect(prompt, timeout=10) dut.write(message + '\n') dut.expect("end of example", timeout=10) ================================================ FILE: esp_linenoise/examples/completion/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(completion) ================================================ FILE: esp_linenoise/examples/completion/README.md ================================================ # esp_linenoise Completion Example This example demonstrates how to use esp_linenoise with tab-completion: - Register a completion callback function - Suggest command completions as the user types - Complete commands with the TAB key ## How it works - Type the beginning of a command (e.g., "h") and press TAB - Available completions are displayed or auto-completed - Commands: help, history, clear, exit, status, config, reset ## Build & Run See the top-level README for build instructions. This example is portable for ESP-IDF and Linux. ================================================ FILE: esp_linenoise/examples/completion/main/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) idf_build_get_property(target IDF_TARGET) set(priv_requires esp_linenoise) if(NOT "${target}" STREQUAL "linux") list(APPEND priv_requires esp_driver_uart) endif() idf_component_register( SRCS "completion.c" "../../utils/common_io.c" INCLUDE_DIRS "." "../../utils" PRIV_REQUIRES ${priv_requires} ) ================================================ FILE: esp_linenoise/examples/completion/main/completion.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "common_io.h" #include "esp_linenoise.h" #include #include // List of commands for completion static const char *commands[] = { "help", "history", "clear", "exit", "status", "config", "reset", NULL }; // Completion callback function static void completion_callback(const char *buf, void *cb_ctx, esp_linenoise_completion_cb_t cb) { for (int i = 0; commands[i] != NULL; i++) { if (strncmp(buf, commands[i], strlen(buf)) == 0) { cb(cb_ctx, commands[i]); } } } void app_main(void) { common_init_io(); esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); config.prompt = "completion> "; config.completion_cb = completion_callback; esp_linenoise_handle_t handle; esp_err_t err = esp_linenoise_create_instance(&config, &handle); if (err != ESP_OK) { printf("Failed to create linenoise instance\n"); return; } printf("Tab completion example. Try typing 'h' and press TAB.\n"); printf("Available commands: help, history, clear, exit, status, config, reset\n"); char line[256]; while (1) { err = esp_linenoise_get_line(handle, line, sizeof(line)); if (err != ESP_OK) { break; } if (strlen(line) > 0) { printf("You entered: %s\n", line); esp_linenoise_history_add(handle, line); if (strcmp(line, "exit") == 0) { break; } } } esp_linenoise_delete_instance(handle); common_deinit_io(); printf("end of example\n"); } ================================================ FILE: esp_linenoise/examples/completion/main/idf_component.yml ================================================ dependencies: espressif/esp_linenoise: "*" ================================================ FILE: esp_linenoise/examples/completion/pytest_completion.py ================================================ # SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_examples_completion(dut: Dut) -> None: message = "exit" prompt = "completion> " dut.expect(prompt, timeout=10) dut.write(message + '\n') dut.expect("end of example", timeout=10) ================================================ FILE: esp_linenoise/examples/history_usage/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) # Use custom partition table for SPIFFS storage (ESP32 targets only) if(NOT "$ENV{IDF_TARGET}" STREQUAL "linux") set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/components/spiffs) set(SDKCONFIG_DEFAULTS "sdkconfig.defaults") endif() include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(history_usage) ================================================ FILE: esp_linenoise/examples/history_usage/README.md ================================================ # esp_linenoise History Usage Example This example demonstrates how to use esp_linenoise with input history: - Enable and configure history length - Load and save history to a file - Add new entries to history after each input ## How it works - Prompts the user for input with `history> ` - After each line, adds it to the history and saves to disk - Loads history from disk on startup ## Build & Run See the top-level README for build instructions. This example is portable for ESP-IDF and Linux. ================================================ FILE: esp_linenoise/examples/history_usage/main/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) set(priv_requires esp_linenoise) if(NOT "${target}" STREQUAL "linux") list(APPEND priv_requires spiffs) endif() idf_component_register( SRCS "history_usage.c" INCLUDE_DIRS "." PRIV_REQUIRES ${priv_requires} ) ================================================ FILE: esp_linenoise/examples/history_usage/main/history_usage.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_linenoise.h" #include #include #if CONFIG_IDF_TARGET_LINUX #define HISTORY_PATH "linenoise_history.txt" #else #include "esp_spiffs.h" #define STORAGE_MOUNT_POINT "/storage" #define HISTORY_PATH STORAGE_MOUNT_POINT "/linenoise_history.txt" #endif #define HISTORY_LEN 10 #if !CONFIG_IDF_TARGET_LINUX static void init_filesystem(void) { esp_vfs_spiffs_conf_t conf = { .base_path = STORAGE_MOUNT_POINT, .partition_label = "storage", .max_files = 2, .format_if_mount_failed = true, }; esp_err_t ret = esp_vfs_spiffs_register(&conf); if (ret != ESP_OK) { if (ret == ESP_FAIL) { printf("Failed to mount or format filesystem\n"); } else if (ret == ESP_ERR_NOT_FOUND) { printf("Failed to find SPIFFS partition\n"); } else { printf("Failed to initialize SPIFFS (%s)\n", esp_err_to_name(ret)); } return; } size_t total = 0, used = 0; ret = esp_spiffs_info("storage", &total, &used); if (ret == ESP_OK) { printf("SPIFFS partition size: total: %d, used: %d\n", total, used); } } #endif void app_main(void) { #if !CONFIG_IDF_TARGET_LINUX init_filesystem(); #endif esp_linenoise_config_t config; esp_linenoise_handle_t handle; esp_linenoise_get_instance_config_default(&config); config.prompt = "esp_linenoise> "; ESP_ERROR_CHECK(esp_linenoise_create_instance(&config, &handle)); /* create a fake history saved in filename_history */ FILE *fp = fopen(HISTORY_PATH, "w"); if (fp == NULL) { printf("Failed to create history file\n"); return; } fputs("first command line\n", fp); fputs("second command line\n", fp); fclose(fp); ESP_ERROR_CHECK(esp_linenoise_history_set_max_len(handle, HISTORY_LEN)); ESP_ERROR_CHECK(esp_linenoise_history_load(handle, HISTORY_PATH)); ESP_ERROR_CHECK(esp_linenoise_history_add(handle, "random command line 1")); ESP_ERROR_CHECK(esp_linenoise_history_add(handle, "random command line 2")); ESP_ERROR_CHECK(esp_linenoise_history_save(handle, HISTORY_PATH)); fp = fopen(HISTORY_PATH, "r"); if (fp == NULL) { printf("Failed to create history file\n"); return; } char buffer[64]; while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("History entry: %s", buffer); } fclose(fp); printf("end of example\n"); } ================================================ FILE: esp_linenoise/examples/history_usage/main/idf_component.yml ================================================ dependencies: espressif/esp_linenoise: "*" ================================================ FILE: esp_linenoise/examples/history_usage/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, storage, data, spiffs, , 0x10000, ================================================ FILE: esp_linenoise/examples/history_usage/pytest_history_usage.py ================================================ # SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_examples_history_usage(dut: Dut) -> None: dut.expect("end of example", timeout=10) ================================================ FILE: esp_linenoise/examples/history_usage/sdkconfig.defaults ================================================ ================================================ FILE: esp_linenoise/examples/history_usage/sdkconfig.defaults.esp32 ================================================ # Use custom partition table with SPIFFS partition CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" ================================================ FILE: esp_linenoise/examples/multi_instance/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(multi_instance) ================================================ FILE: esp_linenoise/examples/multi_instance/README.md ================================================ # esp_linenoise Multi-Instance Example This example demonstrates how to use multiple esp_linenoise instances: - Create separate instances with different configurations - Maintain independent history for each instance - Switch between instances at runtime - Save separate history files for each context ## How it works - Two instances are created: "user" and "admin" - Type commands in either mode - Type 'switch' to toggle between user and admin mode - Each mode maintains its own command history - Type 'exit' to quit ## Build & Run See the top-level README for build instructions. This example is portable for ESP-IDF and Linux. ================================================ FILE: esp_linenoise/examples/multi_instance/main/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) set(priv_requires esp_linenoise) if(NOT "${target}" STREQUAL "linux") list(APPEND priv_requires esp_driver_uart vfs) endif() idf_component_register( SRCS "multi_instance.c" "../../utils/common_io.c" INCLUDE_DIRS "." "../../utils" PRIV_REQUIRES ${priv_requires} ) ================================================ FILE: esp_linenoise/examples/multi_instance/main/idf_component.yml ================================================ dependencies: espressif/esp_linenoise: "*" ================================================ FILE: esp_linenoise/examples/multi_instance/main/multi_instance.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "common_io.h" #include "esp_linenoise.h" #include #include #define HISTORY_PATH_1 "history_instance1.txt" #define HISTORY_PATH_2 "history_instance2.txt" #define HISTORY_LEN 5 void app_main(void) { common_init_io(); // Create first instance for "user" commands esp_linenoise_config_t config1; esp_linenoise_get_instance_config_default(&config1); config1.prompt = "user> "; config1.history_max_length = HISTORY_LEN; esp_linenoise_handle_t handle1; esp_err_t err = esp_linenoise_create_instance(&config1, &handle1); if (err != ESP_OK) { printf("Failed to create first linenoise instance\n"); return; } // Create second instance for "admin" commands esp_linenoise_config_t config2; esp_linenoise_get_instance_config_default(&config2); config2.prompt = "admin> "; config2.history_max_length = HISTORY_LEN; esp_linenoise_handle_t handle2; err = esp_linenoise_create_instance(&config2, &handle2); if (err != ESP_OK) { printf("Failed to create second linenoise instance\n"); esp_linenoise_delete_instance(handle1); return; } char line[256]; bool use_first = true; while (1) { esp_linenoise_handle_t current_handle = use_first ? handle1 : handle2; const char *mode = use_first ? "user" : "admin"; printf("Current mode: %s\n", mode); err = esp_linenoise_get_line(current_handle, line, sizeof(line)); if (err != ESP_OK) { break; } if (strlen(line) > 0) { if (strcmp(line, "exit") == 0) { break; } else if (strcmp(line, "switch") == 0) { use_first = !use_first; printf("Switched to %s mode\n\n", use_first ? "user" : "admin"); } else { printf("[%s] You entered: %s\n", mode, line); esp_linenoise_history_add(current_handle, line); } } memset(line, 0, sizeof(line)); } // Save separate histories esp_linenoise_history_save(handle1, HISTORY_PATH_1); esp_linenoise_history_save(handle2, HISTORY_PATH_2); // Cleanup esp_linenoise_delete_instance(handle1); esp_linenoise_delete_instance(handle2); common_deinit_io(); printf("end of example\n"); } ================================================ FILE: esp_linenoise/examples/multi_instance/pytest_multi_instance.py ================================================ # SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_examples_multi_instance(dut: Dut) -> None: dut.expect_exact("Current mode: user", timeout=10) dut.write("test") dut.expect_exact("Current mode: user", timeout=10) dut.write("switch") dut.expect_exact("Current mode: admin", timeout=10) dut.write("test") dut.expect_exact("Current mode: admin", timeout=10) dut.write("exit") dut.expect_exact("end of example", timeout=10) ================================================ FILE: esp_linenoise/examples/utils/README.md ================================================ # Common I/O for esp_linenoise Examples This folder provides functions to configure and select the input/output file descriptors for esp_linenoise examples. - On ESP-IDF, it configures UART for use with linenoise. - On Linux, it uses default stdin/stdout. ## Usage Include common_io.h in your example and use: - esp_linenoise_get_default_in_fd() - esp_linenoise_get_default_out_fd() - esp_linenoise_configure_uart() (ESP-IDF only) This ensures all examples work on both ESP-IDF and Linux environments. ================================================ FILE: esp_linenoise/examples/utils/common_io.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include #include #include #include "common_io.h" #if !CONFIG_IDF_TARGET_LINUX #include "driver/uart.h" #include "driver/uart_vfs.h" #include "esp_vfs_dev.h" #include "esp_log.h" static int s_uart_fd = -1; #endif // !CONFIG_IDF_TARGET_LINUX // init UART and VFS, and set up stdin/stdout to use UART0 as POSIX fd void common_init_io(void) { #ifndef CONFIG_IDF_TARGET_LINUX const uart_config_t uart_config = { .baud_rate = 115200, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE }; uart_param_config(UART_NUM_0, &uart_config); uart_driver_install(UART_NUM_0, 1024, 0, 0, NULL, 0); uart_vfs_dev_use_driver(UART_NUM_0); s_uart_fd = fileno(stdin); // stdin now routed to UART0 #endif } // Deinit UART and VFS routing used by esp_linenoise examples void common_deinit_io(void) { #ifndef CONFIG_IDF_TARGET_LINUX if (s_uart_fd >= 0) { uart_vfs_dev_use_nonblocking(UART_NUM_0); uart_driver_delete(UART_NUM_0); s_uart_fd = -1; } #endif } // Return a POSIX fd for UART (ESP-IDF only) int common_open_uart_fd(void) { #ifndef CONFIG_IDF_TARGET_LINUX if (s_uart_fd < 0) { common_init_io(); } return s_uart_fd; #else return fileno(stdin); #endif } // Portable input fd for linenoise int common_get_default_in_fd(void) { #ifndef CONFIG_IDF_TARGET_LINUX return common_open_uart_fd(); #else return fileno(stdin); #endif } // Portable output fd for linenoise int common_get_default_out_fd(void) { #ifndef CONFIG_IDF_TARGET_LINUX return common_open_uart_fd(); #else return fileno(stdout); #endif } ================================================ FILE: esp_linenoise/examples/utils/common_io.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif /** * @brief Initialize common I/O functionality. * * This function performs the necessary initialization for common I/O operations. * It should be called before using any I/O related functions in the application. */ void common_init_io(void); /** * @brief Deinitialize common I/O functionality. * * This function performs the necessary cleanup for common I/O operations. * It should be called when the application is done using any I/O related functions. */ void common_deinit_io(void); /** * @brief Return a POSIX fd for UART (ESP-IDF only) */ int common_open_uart_fd(void); /** * @brief Return a portable input fd for linenoise */ int common_get_default_in_fd(void); /** * @brief Return a portable output fd for linenoise */ int common_get_default_out_fd(void); #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/idf_component.yml ================================================ version: "1.0.4" description: "ESP Linenoise - Line editing C library" url: https://github.com/espressif/idf-extra-components/tree/master/esp_linenoise license: Apache-2.0 sbom: manifests: - path: sbom_esp_linenoise.yml dest: . ================================================ FILE: esp_linenoise/include/esp_linenoise.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include "esp_err.h" /* * IMPORTANT: This library is not thread safe. * Multiple threads operating on one esp_linenoise instance * can yield unexpected behavior from the esp_linenoise library. * * An esp_linenoise instance is to be used by one thread only. */ /** * @brief Callback type used to report a completion candidate to esp_linenoise * * This callback is invoked by `esp_linenoise_completion_t` for each possible * completion string. * * @note When esp_linenoise calls `esp_linenoise_completion_t`, it provides * `esp_linenoise_add_completion` as the `esp_linenoise_completion_cb_t`. * This allows user code to return completion candidates back into * esp_linenoise. * * @note esp_linenoise also passes an opaque pointer of type * `esp_linenoise_completions_t` as the `cb_ctx`. User code must forward * this `cb_ctx` unchanged when invoking the callback. This design hides * the internal structure of esp_linenoise from the user. * * @param cb_ctx Opaque context pointer provided by esp_linenoise. Must be * passed unchanged when calling the callback. * @param str A candidate completion string. */ typedef void (*esp_linenoise_completion_cb_t)(void *cb_ctx, const char *str); /** * @brief User-provided callback type for generating command completions. * * This function is called by esp_linenoise when the user requests tab * completion. The implementation should analyze the input string and invoke * the provided completion callback (`cb`) for each possible completion. * * configuration. Allows the completion function to access application-specific state. * @param str The current input string typed by the user. * @param cb_ctx Opaque pointer provided by esp_linenoise. This must be * forwarded unchanged when calling the completion callback. * @param cb Callback function to be invoked once for each candidate * completion string discovered by this function. */ typedef void (*esp_linenoise_completion_t)(const char *str, void *cb_ctx, esp_linenoise_completion_cb_t cb); /** * @brief Callback function for providing inline hints during input. * * @param str Input string from the user. * @param color Pointer to an integer to set the hint text color (e.g., ANSI color code). * @param bold Pointer to an integer to set bold attribute (non-zero for bold). * @return A dynamically allocated hint string, or NULL if no hint is available. */ typedef char *(*esp_linenoise_hints_t)(const char *str, int *color, int *bold); /** * @brief Callback function to free hint strings returned by the hints callback. * * @param ptr Pointer to the hint string to be freed. */ typedef void (*esp_linenoise_free_hints_t)( void *ptr); /** * @brief Function pointer type for reading bytes. * * @param fd File descriptor. * @param buf Buffer to store the read bytes. * @param count Number of bytes to read. * @return Number of bytes read, or -1 on error. */ typedef ssize_t (*esp_linenoise_read_bytes_t)(int fd, void *buf, size_t count); /** * @brief Function pointer type for writing bytes. * * @param fd File descriptor. * @param buf Buffer containing bytes to write. * @param count Number of bytes to write. * @return Number of bytes written, or -1 on error. */ typedef ssize_t (*esp_linenoise_write_bytes_t)(int fd, const void *buf, size_t count); /** * @brief Structure defining the parameters needed by a linenoise * instance */ typedef struct esp_linenoise_config { const char *prompt; /*!< Prompt string displayed to the user */ size_t max_cmd_line_length; /*!< Maximum length (in bytes) of the input command line */ int history_max_length; /*!< Maximum number of entries to store in command history */ int in_fd; /*!< File descriptor to read input from (e.g., STDIN_FILENO) */ int out_fd; /*!< File descriptor to write output to (e.g., STDOUT_FILENO) */ bool allow_multi_line; /*!< Whether to allow multi-line input (true to enable) */ bool allow_empty_line; /*!< Whether to allow accepting an empty line as valid input */ bool allow_dumb_mode; /*!< Whether to allow running in dumb terminal mode (without advanced features) */ esp_linenoise_completion_t completion_cb; /*!< Callback function for handling input completions */ esp_linenoise_hints_t hints_cb; /*!< Callback function to provide input hints (e.g., usage suggestions) */ esp_linenoise_free_hints_t free_hints_cb; /*!< Callback function to free hints returned by `hints_cb` */ esp_linenoise_read_bytes_t read_bytes_cb; /*!< Function used to read bytes from the input stream */ esp_linenoise_write_bytes_t write_bytes_cb; /*!< Function used to write bytes to the output stream */ char **history; /*!< Pointer to the history buffer (used internally; typically initialized to NULL) */ } esp_linenoise_config_t; /** * @brief Opaque handle to a linenoise instance. */ typedef struct esp_linenoise_instance *esp_linenoise_handle_t; /** * @brief Probe the terminal to check weather it supports escape sequences * * @param handle The linenoise handle used to check * @return int 0 if the terminal supports escape sequences */ int esp_linenoise_probe(esp_linenoise_handle_t handle); /** * @brief Returns the default parameters for creating a linenoise instance. * * @param[out] config esp_linenoise_config_t structure to populate with default values. */ void esp_linenoise_get_instance_config_default(esp_linenoise_config_t *config); /** * @brief Creates a new linenoise instance. * * @param config Pointer to the configuration parameters for the instance. * @param[out] out_handle Handle to the created instance, or NULL on failure. * @return ESP_OK if instance created successfully, */ esp_err_t esp_linenoise_create_instance(const esp_linenoise_config_t *config, esp_linenoise_handle_t *out_handle); /** * @brief Destroys a linenoise instance and frees associated memory. * * @param handle Handle to the instance to delete. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_delete_instance(esp_linenoise_handle_t handle); /** * @brief Reads a line of input from the user into a buffer. * * @note In the event where the input line matches or exceeds * the size of the buffer passed in parameter, the function * will stop registering newly received characters until new line * is received. Then, the function will return cmd_line_length - 1 * characters (the last character being the nullterm '\0') * This behavior applies whether the dumb mode is on or off. * * @param handle Handle to the linenoise instance. * @param cmd_line_buffer Buffer to store the input line. * @param cmd_line_length Length of the command line buffer. * @return ESP_OK on success. * ESP_FAIL if empty line returned and allow_empty_line is set to false. * ESP_ERR_INVALID_ARG if cmd_line_buffer is NULL or cmd_line_length * is equal to 0 or superior to the value of max_cmd_line_length. */ esp_err_t esp_linenoise_get_line(esp_linenoise_handle_t handle, char *cmd_line_buffer, size_t cmd_line_length); /** * @brief Triggers an internal mechanism to return from esp_linenoise_get_line. * * @note This function has no effect if the field read_bytes_cb of * esp_linenoise_config_t is populate with a custom read function. * In that case, it is the user responsibility to handle the way to * return from the custom read it provided to linenoise. * * @param handle Handle to the linenoise instance. * @return esp_err_t ESP_OK on success, other on failures. */ esp_err_t esp_linenoise_abort(esp_linenoise_handle_t handle); /** * @brief Adds a line to the instance's history. * * @param handle Handle to the linenoise instance. * @param line The line to add to history. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_history_add(esp_linenoise_handle_t handle, const char *line); /** * @brief Saves the history to a file. * * @param handle Handle to the linenoise instance. * @param filename Path to the history file. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_history_save(esp_linenoise_handle_t handle, const char *filename); /** * @brief Loads history from a file. * * @param handle Handle to the linenoise instance. * @param filename Path to the history file. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_history_load(esp_linenoise_handle_t handle, const char *filename); /** * @brief Sets the maximum number of entries in the history. * * @param handle Handle to the linenoise instance. * @param len Maximum number of entries. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_history_set_max_len(esp_linenoise_handle_t handle, int len); /** * @brief Frees the history associated with the instance. * * @param handle Handle to the linenoise instance. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_history_free(esp_linenoise_handle_t handle); /** * @brief Clears the terminal screen for the instance. * * @param handle Handle to the linenoise instance. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_clear_screen(esp_linenoise_handle_t handle); /** * @brief Sets whether an empty line is allowed to be returned. * * @param handle Handle to the linenoise instance. * @param empty_line true to allow empty lines, false to disallow. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_set_empty_line(esp_linenoise_handle_t handle, bool empty_line); /** * @brief Checks whether the instance allows returning an empty line. * * @param handle Handle to the linenoise instance. * @param is_empty_line Pointer to bool that will be set with result. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_is_empty_line(esp_linenoise_handle_t handle, bool *is_empty_line); /** * @brief Enables or disables multi-line editing. * * @param handle Handle to the linenoise instance. * @param multi_line true to enable multi-line input, false to disable. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_set_multi_line(esp_linenoise_handle_t handle, bool multi_line); /** * @brief Checks if multi-line editing is enabled. * * @param handle Handle to the linenoise instance. * @param is_multi_line Pointer to bool that will be set with result. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_is_multi_line(esp_linenoise_handle_t handle, bool *is_multi_line); /** * @brief Enables or disables dumb mode (disables line editing features). * * @param handle Handle to the linenoise instance. * @param dumb_mode true to enable dumb mode, false to disable. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_set_dumb_mode(esp_linenoise_handle_t handle, bool dumb_mode); /** * @brief Checks if dumb mode is enabled. * * @param handle Handle to the linenoise instance. * @param is_dumb_mode Pointer to bool that will be set with result. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_is_dumb_mode(esp_linenoise_handle_t handle, bool *is_dumb_mode); /** * @brief Sets the maximum length of the command line buffer. * * @param handle Handle to the linenoise instance. * @param length Maximum length in bytes. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_set_max_cmd_line_length(esp_linenoise_handle_t handle, size_t length); /** * @brief Gets the maximum allowed command line buffer length. * * @param handle Handle to the linenoise instance. * @param max_cmd_line_length Pointer to receive the max length. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_get_max_cmd_line_length(esp_linenoise_handle_t handle, size_t *max_cmd_line_length); /** * @brief Sets the prompt used by the esp_linenoise instance. * * @param handle Handle to the linenoise instance. * @param prompt Prompt to be set. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_set_prompt(esp_linenoise_handle_t handle, const char *prompt); /** * @brief Gets the current esp_linenoise instance prompt. * * @param handle Handle to the linenoise instance. * @param prompt esp_linenoise instance current prompt. * @return ESP_OK on success, or error code on failure. */ esp_err_t esp_linenoise_get_prompt(esp_linenoise_handle_t handle, const char **prompt); /** * @brief Return the output file descriptor used by esp_linenoise * * @param handle The esp_linenoise handle from which to get * the file descriptor * @param fd Return value containing the output file descriptor * @return ESP_OK on success, ESP_ERR_INVALID_ARG otherwise */ esp_err_t esp_linenoise_get_out_fd(esp_linenoise_handle_t handle, int *fd); /** * @brief Return the input file descriptor used by esp_linenoise * * @param handle The esp_linenoise handle from which to get * the file descriptor * @param fd Return value containing the input file descriptor * @return ESP_OK on success, ESP_ERR_INVALID_ARG otherwise */ esp_err_t esp_linenoise_get_in_fd(esp_linenoise_handle_t handle, int *fd); /** * @brief Return the read function used by linenoise * * @param handle The esp_linenoise handle from which to get * the file descriptor * @param read_func Return the read_func as set in the configuration structure * of the given esp_linenoise instance * @return ESP_OK on success, ESP_ERR_INVALID_ARG otherwise */ esp_err_t esp_linenoise_get_read(esp_linenoise_handle_t handle, esp_linenoise_read_bytes_t *read_func); /** * @brief Return the write function used by linenoise * * @param handle The esp_linenoise handle from which to get * the file descriptor * @param write_func Return the write_func as set in the configuration structure * of the given esp_linenoise instance * @return ESP_OK on success, ESP_ERR_INVALID_ARG otherwise */ esp_err_t esp_linenoise_get_write(esp_linenoise_handle_t handle, esp_linenoise_write_bytes_t *write_func); #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/linenoise/linenoise.c ================================================ /* linenoise.c -- guerrilla line editing library against the idea that a * line editing lib needs to be 20,000 lines of C code. * * You can find the latest source code at: * * http://github.com/antirez/linenoise * * Does a number of crazy assumptions that happen to be true in 99.9999% of * the 2010 UNIX computers around. * * ------------------------------------------------------------------------ * * Copyright (c) 2010-2016, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ------------------------------------------------------------------------ * * References: * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html * * Todo list: * - Filter bogus Ctrl+ combinations. * - Win32 support * * Bloat: * - History search like Ctrl+r in readline? * * List of escape sequences used by this program, we do everything just * with three sequences. In order to be so cheap we may have some * flickering effect with some slow terminal, but the lesser sequences * the more compatible. * * EL (Erase Line) * Sequence: ESC [ n K * Effect: if n is 0 or missing, clear from cursor to end of line * Effect: if n is 1, clear from beginning of line to cursor * Effect: if n is 2, clear entire line * * CUF (CUrsor Forward) * Sequence: ESC [ n C * Effect: moves cursor forward n chars * * CUB (CUrsor Backward) * Sequence: ESC [ n D * Effect: moves cursor backward n chars * * The following is used to get the terminal width if getting * the width with the TIOCGWINSZ ioctl fails * * DSR (Device Status Report) * Sequence: ESC [ 6 n * Effect: reports the current cusor position as ESC [ n ; m R * where n is the row and m is the column * * When multi line mode is enabled, we also use an additional escape * sequence. However multi line editing is disabled by default. * * CUU (Cursor Up) * Sequence: ESC [ n A * Effect: moves cursor up of n chars. * * CUD (Cursor Down) * Sequence: ESC [ n B * Effect: moves cursor down of n chars. * * When linenoiseClearScreen() is called, two additional escape sequences * are used in order to clear the screen and position the cursor at home * position. * * CUP (Cursor position) * Sequence: ESC [ H * Effect: moves the cursor to upper left corner * * ED (Erase display) * Sequence: ESC [ 2 J * Effect: clear the whole screen * */ #include #include "esp_linenoise_private.h" #include "esp_linenoise.h" #include "linenoise.h" #include "esp_err.h" static esp_linenoise_instance_t *s_linenoise_instance = NULL; static linenoiseCompletionCallback *s_completion_cb = NULL; static void completion_default_cb(const char *str, void *cb_ctx, esp_linenoise_completion_cb_t cb) { /* unused because incompatible with legacy code */ (void)cb; if (!s_completion_cb) { return; } s_completion_cb(str, (linenoiseCompletions *)cb_ctx); } static inline __attribute__((always_inline)) esp_linenoise_instance_t *linenoise_get_static_instance(void) { if (!s_linenoise_instance) { s_linenoise_instance = esp_linenoise_create_instance_static(); } return s_linenoise_instance; } __attribute__((weak)) void linenoiseSetReadCharacteristics(void) { linenoiseSetReadFunction(read); /* By default linenoise uses blocking reads */ int fd_in = s_linenoise_instance->config.in_fd; int flags = fcntl(fd_in, F_GETFL); flags &= ~O_NONBLOCK; (void)fcntl(fd_in, F_SETFL, flags); } void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { esp_linenoise_add_completion(lc, str); } void linenoiseSetMultiLine(int ml) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); /* since we get the static instance, the input validation will always * yield a positive result, it is not necessary to check the return value */ (void)esp_linenoise_set_multi_line(instance, (bool)ml); } void linenoiseSetDumbMode(int set) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); /* since we get the static instance, the input validation will always * yield a positive result, it is not necessary to check the return value */ (void)esp_linenoise_set_dumb_mode(instance, (bool)set); } bool linenoiseIsDumbMode(void) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); bool is_dump_mode = false; /* since we get the static instance, the input validation will always * yield a positive result, it is not necessary to check the return value */ (void)esp_linenoise_is_dumb_mode(instance, &is_dump_mode); return is_dump_mode; } void linenoiseAllowEmpty(bool val) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); /* since we get the static instance, the input validation will always * yield a positive result, it is not necessary to check the return value */ (void)esp_linenoise_set_empty_line(instance, (bool)val); } void linenoiseSetWriteFunction(linenoiseWriteBytesFn write_fn) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); if (write_fn != NULL) { instance->config.write_bytes_cb = write_fn; } } void linenoiseSetReadFunction(linenoiseReadBytesFn read_fn) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); if (read_fn != NULL) { instance->config.read_bytes_cb = read_fn; } } /* Register a callback function to be called for tab-completion. */ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); if (fn != NULL) { s_completion_cb = fn; instance->config.completion_cb = completion_default_cb; } } /* Register a hits function to be called to show hits to the user at the * right of the prompt. */ void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); if (fn != NULL) { instance->config.hints_cb = fn; } } /* Register a function to free the hints returned by the hints callback * registered with linenoiseSetHintsCallback(). */ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); if (fn != NULL) { instance->config.free_hints_cb = fn; } } /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); (void)esp_linenoise_clear_screen(instance); } int linenoiseProbe(void) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); linenoiseSetReadCharacteristics(); return esp_linenoise_probe(instance); } /* The high level function that is the main API of the linenoise library. */ char *linenoise(const char *prompt) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); /* update the default prompt value set when the static linenoise * instance was created */ const char *prompt_copy = instance->config.prompt; instance->config.prompt = prompt; size_t cmd_line_length = 0; esp_err_t ret_val = esp_linenoise_get_max_cmd_line_length(instance, &cmd_line_length); if (ret_val != ESP_OK) { cmd_line_length = ESP_LINENOISE_COMMAND_MAX_LEN; } char *cmd_line = calloc(1, cmd_line_length); if (!cmd_line) { return NULL; } ret_val = esp_linenoise_get_line(instance, cmd_line, cmd_line_length); if (ret_val != ESP_OK) { free(cmd_line); return NULL; } /* reset the prompt to its default value */ instance->config.prompt = prompt_copy; /* return the line */ return cmd_line; } /* This is just a wrapper the user may want to call in order to make sure * the linenoise returned buffer is freed with the same allocator it was * created with. Useful when the main program is using an alternative * allocator. */ void linenoiseFree(void *ptr) { free(ptr); } void linenoiseHistoryFree(void) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); (void)esp_linenoise_history_free(instance); } /* This is the API call to add a new entry in the linenoise history. * It uses a fixed array of char pointers that are shifted (memmoved) * when the history max length is reached in order to remove the older * entry and make room for the new one, so it is not exactly suitable for huge * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ int linenoiseHistoryAdd(const char *line) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); const esp_err_t ret_val = esp_linenoise_history_add(instance, line); if (ret_val != ESP_OK) { return -1; } return 0; } /* Set the maximum length for the history. This function can be called even * if there is already some history, the function will make sure to retain * just the latest 'len' elements if the new history length value is smaller * than the amount of items already inside the history. */ int linenoiseHistorySetMaxLen(int len) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); const esp_err_t ret_val = esp_linenoise_history_set_max_len(instance, len); if (ret_val != ESP_OK) { return 0; } return 1; } /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char *filename) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); const esp_err_t ret_val = esp_linenoise_history_save(instance, filename); if (!ret_val) { return -1; } return 0; } /* Load the history from the specified file. If the file does not exist * zero is returned and no operation is performed. * * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ int linenoiseHistoryLoad(const char *filename) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); const esp_err_t ret_val = esp_linenoise_history_load(instance, filename); if (ret_val != ESP_OK) { return -1; } return 0; } /* Set line maximum length. If len configeter is smaller than * ESP_LINENOISE_MINIMAL_MAX_LINE, -1 is returned * otherwise 0 is returned. */ int linenoiseSetMaxLineLen(size_t len) { esp_linenoise_instance_t *instance = linenoise_get_static_instance(); esp_err_t ret_val = esp_linenoise_set_max_cmd_line_length(instance, len); if (ret_val != ESP_OK) { return -1; } return 0; } ================================================ FILE: esp_linenoise/linenoise/linenoise.h ================================================ /* linenoise.h -- VERSION 1.0 * * Guerrilla line editing library against the idea that a line editing lib * needs to be 20,000 lines of C code. * * See linenoise.c for more information. * * ------------------------------------------------------------------------ * * Copyright (c) 2010-2014, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include typedef struct esp_linenoise_completions linenoiseCompletions; typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char *(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); typedef ssize_t (*linenoiseReadBytesFn)(int fd, void *buf, size_t count); typedef ssize_t (*linenoiseWriteBytesFn)(int fd, const void *buf, size_t count); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); void linenoiseSetReadFunction(linenoiseReadBytesFn read_fn); void linenoiseSetWriteFunction(linenoiseWriteBytesFn write_fn); void linenoiseSetReadCharacteristics(void); int linenoiseProbe(void); char *linenoise(const char *prompt); void linenoiseFree(void *ptr); int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); void linenoiseHistoryFree(void); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoiseSetDumbMode(int set); bool linenoiseIsDumbMode(void); void linenoiseAllowEmpty(bool); int linenoiseSetMaxLineLen(size_t len); #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/private_include/esp_linenoise_private.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_linenoise.h" #define ESP_LINENOISE_DEFAULT_PROMPT ">" #define ESP_LINENOISE_DEFAULT_HISTORY_MAX_LENGTH 100 #define ESP_LINENOISE_DEFAULT_MAX_LINE 4096 #define ESP_LINENOISE_MINIMAL_MAX_LINE 64 #define ESP_LINENOISE_COMMAND_MAX_LEN 32 #define ESP_LINENOISE_PASTE_KEY_DELAY 30 /* Delay, in milliseconds, between two characters being pasted from clipboard */ enum KEY_ACTION { KEY_NULL = 0, /* NULL */ CTRL_A = 1, /* Ctrl+a */ CTRL_B = 2, /* Ctrl-b */ CTRL_C = 3, /* Ctrl-c */ CTRL_D = 4, /* Ctrl-d */ CTRL_E = 5, /* Ctrl-e */ CTRL_F = 6, /* Ctrl-f */ CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ CTRL_K = 11, /* Ctrl+k */ CTRL_L = 12, /* Ctrl+l */ ENTER = 10, /* Enter */ CTRL_N = 14, /* Ctrl-n */ CTRL_P = 16, /* Ctrl-p */ CTRL_T = 20, /* Ctrl-t */ CTRL_U = 21, /* Ctrl+u */ CTRL_W = 23, /* Ctrl+w */ ESC = 27, /* Escape */ UNIT_SEP = 31, /* ctrl-_ */ BACKSPACE = 127 /* Backspace */ }; typedef struct esp_linenoise_state { char *buffer; /* Edited line buffer. */ size_t buffer_length; /* Edited line buffer size. */ size_t prompt_length; /* Prompt length. */ size_t cur_cursor_position; /* Current cursor position. */ size_t old_cursor_position; /* Previous refresh cursor position. */ size_t len; /* Current edited line length. */ size_t columns; /* Number of columns in terminal. */ size_t max_rows_used; /* Maximum num of rows used so far (multiline mode) */ int history_index; /* The history index we are currently editing. */ int history_length; /* The current length of the history*/ SemaphoreHandle_t mux; int abort_read_fd; } esp_linenoise_state_t; typedef struct esp_linenoise_instance { esp_linenoise_config_t config; esp_linenoise_state_t state; } esp_linenoise_instance_t; /** * @brief Stores a list of completion strings. */ typedef struct esp_linenoise_completions { size_t len; /**< Number of completions. */ char **cvec; /**< Array of completion strings. */ } esp_linenoise_completions_t; inline __attribute__((always_inline)) esp_linenoise_instance_t *esp_linenoise_create_instance_static(void) { esp_linenoise_instance_t *instance = malloc(sizeof(esp_linenoise_instance_t)); assert(instance != NULL); esp_linenoise_get_instance_config_default(&instance->config); /* set the state part of the esp_linenoise_instance_t to 0 to init all values to 0 (or NULL) */ memset(&instance->state, 0x00, sizeof(esp_linenoise_state_t)); return instance; } /** * @brief This function is used by the callback function registered by the user * in order to add completion options given the input string when the * user typed . See the example.c source code for a very easy to * understand example. * * @param ctx opaque pointer interpreted in line completion structure being * filled by the function * @param str completed command to add to lc */ void esp_linenoise_add_completion(void *ctx, const char *str); /** * @brief esp_linenoise default read function. * * @note This function waits for available data on the file descriptor * provided as parameter on on the eventfd using select. It either returns * the data read from the file descriptor or a new line character if data was * read from the eventfd first. This new line will trigger the esp_linenoise_get_line * to return. * * @param fd file descriptor from which to read * @param buf buffer used to store the read data * @param count size of the buffer * @return ssize_t size of the data read */ ssize_t esp_linenoise_default_read_bytes(int fd, void *buf, size_t count); /** * @brief Sets the eventfd used to return from esp_linenoise_default_read_bytes. * * @note This function is only implemented if esp_linenoise_abort is used. * * @param instance linenoise instance associated with the eventfd to create * @return esp_err_t ESP_OK on success, other errors on failures */ __attribute__((weak)) esp_err_t esp_linenoise_set_event_fd(esp_linenoise_instance_t *instance); /** * @brief Remove the eventfd used to return from esp_linenoise_default_read_bytes. * * @note This function is only implemented if esp_linenoise_abort is used. * * @param instance linenoise instance associated with the eventfd to create * @return esp_err_t ESP_OK on success, other errors on failures */ __attribute__((weak)) esp_err_t esp_linenoise_remove_event_fd(esp_linenoise_instance_t *instance); #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/sbom_esp_linenoise.yml ================================================ name: esp_linenoise description: Line editing C library url: https://github.com/espressif/idf-extra-components/tree/master/esp_linenoise version: 1.0.0 cpe: cpe:2.3:a:espressif:esp_linenoise:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems' ================================================ FILE: esp_linenoise/src/esp_linenoise.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "sdkconfig.h" #if !CONFIG_IDF_TARGET_LINUX // On Linux, we don't need __fbufsize (see comments below), and // __fbufsize not available on MacOS (which is also considered "Linux" target) #include // for __fbufsize #endif // !CONFIG_IDF_TARGET_LINUX #include #include #include #include #include #include #include #include #include #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_linenoise.h" #include "esp_linenoise_private.h" static ssize_t esp_linenoise_default_write_bytes(int fd, const void *buf, size_t count) { const int nb_bytes_written = write(fd, buf, count); if (nb_bytes_written == count) { fsync(fd); } return nb_bytes_written; } __attribute__((weak)) ssize_t esp_linenoise_default_read_bytes(int fd, void *buf, size_t count) { return read(fd, buf, count); } /* Debugging macro. */ #if 0 FILE *lndebug_fp = NULL; #define lndebug(...) \ do { \ if (lndebug_fp == NULL) { \ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ fprintf(lndebug_fp, \ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ (int)state->len,(int)state->cur_cursor_position,(int)state->old_cursor_position,prompt_length,rows,rpos, \ (int)state->max_rows_used,old_rows); \ } \ fprintf(lndebug_fp, ", " __VA_ARGS__); \ fflush(lndebug_fp); \ } while (0) #else #define lndebug(fmt, ...) #endif /* We define a very simple "append buffer" structure, that is an heap * allocated string where we can append to. This is useful in order to * write all the escape sequences in a buffer and flush them to the standard * output in a single call, to avoid flickering effects. */ typedef struct append_buffer { char *b; int len; } append_buffer_t; static void esp_linenoise_append_buffer_init(append_buffer_t *ab) { ab->b = NULL; ab->len = 0; } static void esp_linenoise_append_buffer_append(append_buffer_t *ab, const char *s, int len) { char *new = realloc(ab->b, ab->len + len); if (new == NULL) { return; } memcpy(new + ab->len, s, len); ab->b = new; ab->len += len; } static void esp_linenoise_append_buffer_free(append_buffer_t *ab) { free(ab->b); } /* Helper of esp_linenoise_refresh_single_line() and esp_linenoise_refresh_multi_line() to show hints * to the right of the prompt. */ static void esp_linenoise_refresh_show_hints(append_buffer_t *ab, esp_linenoise_instance_t *instance) { esp_linenoise_state_t state = instance->state; esp_linenoise_config_t config = instance->config; char seq[64]; if (config.hints_cb && state.prompt_length + state.len < state.columns) { int color = -1, bold = 0; char *hint = config.hints_cb(state.buffer, &color, &bold); if (hint) { int hintlen = strlen(hint); int hintmaxlen = state.columns - (state.prompt_length + state.len); if (hintlen > hintmaxlen) { hintlen = hintmaxlen; } if (bold == 1 && color == -1) { color = 37; } if (color != -1 || bold != 0) { snprintf(seq, 64, "\033[%d;%d;49m", bold, color); esp_linenoise_append_buffer_append(ab, seq, strlen(seq)); } esp_linenoise_append_buffer_append(ab, hint, hintlen); if (color != -1 || bold != 0) { esp_linenoise_append_buffer_append(ab, "\033[0m", 4); } /* Call the function to free the hint returned. */ if (config.free_hints_cb) { config.free_hints_cb(hint); } } } } /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void esp_linenoise_refresh_single_line(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; char seq[64]; size_t prompt_length = state->prompt_length; int fd = instance->config.out_fd; char *buf = state->buffer; size_t len = state->len; size_t cur_cursor_position = state->cur_cursor_position; append_buffer_t ab; while ((prompt_length + cur_cursor_position) >= state->columns) { buf++; len--; cur_cursor_position--; } while (prompt_length + len > state->columns) { len--; } esp_linenoise_append_buffer_init(&ab); /* Cursor to left edge */ snprintf(seq, 64, "\r"); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); /* Write the prompt and the current buffer content */ esp_linenoise_append_buffer_append(&ab, config->prompt, strlen(config->prompt)); esp_linenoise_append_buffer_append(&ab, buf, len); /* Show hits if any. */ esp_linenoise_refresh_show_hints(&ab, instance); /* Erase to right */ snprintf(seq, 64, "\x1b[0K"); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); /* Move cursor to original position. */ snprintf(seq, 64, "\r\x1b[%dC", (int)(cur_cursor_position + prompt_length)); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); if (config->write_bytes_cb(fd, ab.b, ab.len) == -1) {} /* Can't recover from write error. */ esp_linenoise_append_buffer_free(&ab); } /* Multi line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void esp_linenoise_refresh_multi_line(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; char seq[64]; int prompt_length = state->prompt_length; int rows = (prompt_length + state->len + state->columns - 1) / state->columns; /* rows used by current buf. */ int rpos = (prompt_length + state->old_cursor_position + state->columns) / state->columns; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* column position, zero-based. */ int old_rows = state->max_rows_used; int j; int fd = instance->config.out_fd; append_buffer_t ab; /* Update max_rows_used if needed. */ if (rows > (int)state->max_rows_used) { state->max_rows_used = rows; } /* First step: clear all the lines used before. To do so start by * going to the last row. */ esp_linenoise_append_buffer_init(&ab); if (old_rows - rpos > 0) { lndebug("go down %d", old_rows - rpos); snprintf(seq, 64, "\x1b[%dB", old_rows - rpos); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); } /* Now for every row clear it, go up. */ for (j = 0; j < old_rows - 1; j++) { lndebug("clear+up"); snprintf(seq, 64, "\r\x1b[0K\x1b[1A"); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); } /* Clean the top line. */ lndebug("clear"); snprintf(seq, 64, "\r\x1b[0K"); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); /* Write the prompt and the current buffer content */ esp_linenoise_append_buffer_append(&ab, config->prompt, strlen(config->prompt)); esp_linenoise_append_buffer_append(&ab, state->buffer, state->len); /* Show hits if any. */ esp_linenoise_refresh_show_hints(&ab, instance); /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ if (state->cur_cursor_position && state->cur_cursor_position == state->len && (state->cur_cursor_position + prompt_length) % state->columns == 0) { lndebug(""); esp_linenoise_append_buffer_append(&ab, "\n", 1); snprintf(seq, 64, "\r"); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); rows++; if (rows > (int)state->max_rows_used) { state->max_rows_used = rows; } } /* Move cursor to right position. */ rpos2 = (prompt_length + state->cur_cursor_position + state->columns) / state->columns; /* current cursor relative row. */ lndebug("rpos2 %d", rpos2); /* Go up till we reach the expected position. */ if (rows - rpos2 > 0) { lndebug("go-up %d", rows - rpos2); snprintf(seq, 64, "\x1b[%dA", rows - rpos2); esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); } /* Set column. */ col = (prompt_length + (int)state->cur_cursor_position) % (int)state->columns; lndebug("set col %d", 1 + col); if (col) { snprintf(seq, 64, "\r\x1b[%dC", col); } else { snprintf(seq, 64, "\r"); } esp_linenoise_append_buffer_append(&ab, seq, strlen(seq)); lndebug("\n"); state->old_cursor_position = state->cur_cursor_position; if (config->write_bytes_cb(fd, ab.b, ab.len) == -1) {} /* Can't recover from write error. */ esp_linenoise_append_buffer_free(&ab); } /* Calls the two low level functions esp_linenoise_refresh_single_line() or * esp_linenoise_refresh_multi_line() according to the selected mode. */ static void esp_linenoise_refresh_line(esp_linenoise_instance_t *instance) { if (instance->config.allow_multi_line) { esp_linenoise_refresh_multi_line(instance); } else { esp_linenoise_refresh_single_line(instance); } } /* Use the ESC [6n escape sequence to query the horizontal cursor position * and return it. On error -1 is returned, on success the position of the * cursor. */ static int esp_linenoise_get_cursor_position(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; char buf[ESP_LINENOISE_COMMAND_MAX_LEN] = { 0 }; int columns = 0; int rows = 0; int i = 0; const int out_fd = instance->config.out_fd; const int in_fd = instance->config.in_fd; /* The following ANSI escape sequence is used to get from the TTY the * cursor position. */ const char get_cursor_cmd[] = "\x1b[6n"; /* Send the command to the TTY on the other end of the UART. * Let's use unistd's write function. Thus, data sent through it are raw * reducing the overhead compared to using fputs, fprintf, etc... */ const int num_written = config->write_bytes_cb(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd)); if (num_written != sizeof(get_cursor_cmd)) { return -1; } /* The other end will send its response which format is ESC [ rows ; columns R * We don't know exactly how many bytes we have to read, thus, perform a * read for each byte. * Stop right before the last character of the buffer, to be able to NULL * terminate it. */ while (i < sizeof(buf) - 1) { /* Keep using unistd's functions. Here, using `read` instead of `fgets` * or `fgets` guarantees us that we we can read a byte regardless on * whether the sender sent end of line character(s) (CR, CRLF, LF). */ if (config->read_bytes_cb(in_fd, buf + i, 1) != 1 || buf[i] == 'R') { /* If we couldn't read a byte from in_fd or if 'R' was received, * the transmission is finished. */ break; } /* For some reasons, it is possible that we receive new line character * after querying the cursor position on some UART. Let's ignore them, * this will not affect the rest of the program. */ if (buf[i] != '\n') { i++; } } /* NULL-terminate the buffer, this is required by `sscanf`. */ buf[i] = '\0'; /* Parse the received data to get the position of the cursor. */ if (buf[0] != ESC || buf[1] != '[' || sscanf(buf + 2, "%d;%d", &rows, &columns) != 2) { return -1; } return columns; } /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ static int esp_linenoise_get_columns(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; int start = 0; int columns = 0; int written = 0; char seq[ESP_LINENOISE_COMMAND_MAX_LEN] = { 0 }; const int fd = instance->config.out_fd; /* The following ANSI escape sequence is used to tell the TTY to move * the cursor to the most-right position. */ const char move_cursor_right[] = "\x1b[999C"; const size_t cmd_len = sizeof(move_cursor_right); /* This one is used to set the cursor position. */ const char set_cursor_pos[] = "\x1b[%dD"; /* Get the initial position so we can restore it later. */ start = esp_linenoise_get_cursor_position(instance); if (start == -1) { goto failed; } /* Send the command to go to right margin. Use `write` function instead of * `fwrite` for the same reasons explained in `esp_linenoise_get_cursor_position()` */ if (config->write_bytes_cb(fd, move_cursor_right, cmd_len) != cmd_len) { goto failed; } /* After sending this command, we can get the new position of the cursor, * we'd get the size, in columns, of the opened TTY. */ columns = esp_linenoise_get_cursor_position(instance); if (columns == -1) { goto failed; } /* Restore the position of the cursor back. */ if (columns > start) { /* Generate the move cursor command. */ written = snprintf(seq, ESP_LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, columns - start); /* If `written` is equal or bigger than ESP_LINENOISE_COMMAND_MAX_LEN, it * means that the output has been truncated because the size provided * is too smalstate. */ assert (written < ESP_LINENOISE_COMMAND_MAX_LEN); /* Send the command with `write`, which is not buffered. */ if (config->write_bytes_cb(fd, seq, written) == -1) { /* Can't recover... */ } } return columns; failed: return 80; } /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ static void esp_linenoise_make_beep_sound(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; char bip_screen_str[] = "\x7"; (void)config->write_bytes_cb(instance->config.out_fd, bip_screen_str, sizeof(bip_screen_str)); } /* Free a list of completion option populated by linenoiseAddCompletion(). */ static void free_completions(esp_linenoise_completions_t *lc) { size_t i; for (i = 0; i < lc->len; i++) { free(lc->cvec[i]); } if (lc->cvec != NULL) { free(lc->cvec); } } /* This is an helper function for esp_linenoise_edit() and is called when the * user types the key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed esp_linenoise_state * structure as described in the structure definition. */ static int esp_linenoise_complete_line(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; esp_linenoise_completions_t lc = { 0, NULL }; int nread, nwritten; char c = 0; int in_fd = instance->config.in_fd; config->completion_cb(state->buffer, &lc, esp_linenoise_add_completion); if (lc.len == 0) { esp_linenoise_make_beep_sound(instance); } else { size_t stop = 0, i = 0; while (!stop) { /* Show completion or original buffer */ if (i < lc.len) { esp_linenoise_state_t saved = *state; state->len = state->cur_cursor_position = strlen(lc.cvec[i]); state->buffer = lc.cvec[i]; esp_linenoise_refresh_line(instance); state->len = saved.len; state->cur_cursor_position = saved.cur_cursor_position; state->buffer = saved.buffer; } else { esp_linenoise_refresh_line(instance); } nread = config->read_bytes_cb(in_fd, &c, 1); if (nread <= 0) { free_completions(&lc); return -1; } switch (c) { case TAB: /* tab */ i = (i + 1) % (lc.len + 1); if (i == lc.len) { esp_linenoise_make_beep_sound(instance); } break; case ESC: /* escape */ /* Re-show original buffer */ if (i < lc.len) { esp_linenoise_refresh_line(instance); } stop = 1; break; default: /* Update buffer and return */ if (i < lc.len) { nwritten = snprintf(state->buffer, state->buffer_length, "%s", lc.cvec[i]); state->len = state->cur_cursor_position = nwritten; } stop = 1; break; } } } free_completions(&lc); return c; /* Return last read character */ } /* =========================== Line editing ================================= */ /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ static int esp_linenoise_edit_insert(esp_linenoise_instance_t *instance, char c) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; int fd = instance->config.out_fd; if (state->len < state->buffer_length) { if (state->len == state->cur_cursor_position) { state->buffer[state->cur_cursor_position] = c; state->cur_cursor_position++; state->len++; state->buffer[state->len] = '\0'; if ((!config->allow_multi_line && state->prompt_length + state->len < state->columns && !config->hints_cb)) { /* Avoid a full update of the line in the * trivial case. */ if (config->write_bytes_cb(fd, &c, 1) == -1) { return -1; } } else { esp_linenoise_refresh_line(instance); } } else { memmove(state->buffer + state->cur_cursor_position + 1, state->buffer + state->cur_cursor_position, state->len - state->cur_cursor_position); state->buffer[state->cur_cursor_position] = c; state->len++; state->cur_cursor_position++; state->buffer[state->len] = '\0'; esp_linenoise_refresh_line(instance); } } return 0; } static int esp_linenoise_insert_pasted_char(esp_linenoise_instance_t *instance, char c) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; int fd = instance->config.out_fd; if (state->len < state->buffer_length && state->len == state->cur_cursor_position) { state->buffer[state->cur_cursor_position] = c; state->cur_cursor_position++; state->len++; state->buffer[state->len] = '\0'; if (config->write_bytes_cb(fd, &c, 1) == -1) { return -1; } } return 0; } /* Move cursor on the left. */ static void esp_linenoise_edit_move_left(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->cur_cursor_position > 0) { state->cur_cursor_position--; esp_linenoise_refresh_line(instance); } } /* Move cursor on the right. */ static void esp_linenoise_edit_move_right(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->cur_cursor_position != state->len) { state->cur_cursor_position++; esp_linenoise_refresh_line(instance); } } /* Move cursor to the start of the line. */ static void esp_linenoise_edit_move_home(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->cur_cursor_position != 0) { state->cur_cursor_position = 0; esp_linenoise_refresh_line(instance); } } /* Move cursor to the end of the line. */ static void esp_linenoise_edit_move_end(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->cur_cursor_position != state->len) { state->cur_cursor_position = state->len; esp_linenoise_refresh_line(instance); } } /* Substitute the currently edited line with the next or previous history * entry as specified by 'dir'. */ #define esp_LINENOISE_HISTORY_NEXT 0 #define esp_LINENOISE_HISTORY_PREV 1 static void esp_linenoise_edit_history_next(esp_linenoise_instance_t *instance, int dir) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; if (state->history_length - state->history_index >= 1) { /* Update the current history entry before to * overwrite it with the next one. */ char *buffer_copy = strdup(state->buffer); if (!buffer_copy) { return; } free(config->history[state->history_index]); config->history[state->history_index] = buffer_copy; /* Show the new entry */ state->history_index += (dir == esp_LINENOISE_HISTORY_PREV) ? 1 : -1; if (state->history_index < 0) { state->history_index = 0; return; } else if (state->history_index >= state->history_length) { state->history_index = state->history_length - 1; return; } strncpy(state->buffer, config->history[state->history_index], state->buffer_length); state->buffer[state->buffer_length - 1] = '\0'; state->len = state->cur_cursor_position = strlen(state->buffer); esp_linenoise_refresh_line(instance); } } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ static void esp_linenoise_edit_delete(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->len > 0 && state->cur_cursor_position < state->len) { memmove(state->buffer + state->cur_cursor_position, state->buffer + state->cur_cursor_position + 1, state->len - state->cur_cursor_position - 1); state->len--; state->buffer[state->len] = '\0'; esp_linenoise_refresh_line(instance); } } /* Backspace implementation. */ static void esp_linenoise_edit_backspace(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; if (state->cur_cursor_position > 0 && state->len > 0) { memmove(state->buffer + state->cur_cursor_position - 1, state->buffer + state->cur_cursor_position, state->len - state->cur_cursor_position); state->cur_cursor_position--; state->len--; state->buffer[state->len] = '\0'; esp_linenoise_refresh_line(instance); } } /* Delete the previous word, maintaining the cursor at the start of the * current word. */ static void esp_linenoise_edit_delete_prev_word(esp_linenoise_instance_t *instance) { esp_linenoise_state_t *state = &instance->state; size_t old_pos = state->cur_cursor_position; size_t diff; while (state->cur_cursor_position > 0 && state->buffer[state->cur_cursor_position - 1] == ' ') { state->cur_cursor_position--; } while (state->cur_cursor_position > 0 && state->buffer[state->cur_cursor_position - 1] != ' ') { state->cur_cursor_position--; } diff = old_pos - state->cur_cursor_position; memmove(state->buffer + state->cur_cursor_position, state->buffer + old_pos, state->len - old_pos + 1); state->len -= diff; esp_linenoise_refresh_line(instance); } static uint32_t get_millis(void) { struct timeval tv = { 0 }; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } static inline size_t esp_linenoise_prompt_len_ignore_escape_seq(const char *prompt) { size_t prompt_length = 0; bool in_escape_sequence = false; for (; *prompt != '\0'; ++prompt) { if (*prompt == '\033') { in_escape_sequence = true; } else if (in_escape_sequence && *prompt == 'm') { in_escape_sequence = false; } else if (!in_escape_sequence) { ++prompt_length; } } return prompt_length; } /* This function is the core of the line editing capability of linenoise. * It expects 'fd' to be already in "raw mode" so that every key pressed * will be returned ASAP to read(). * * The resulting string is put into 'buf' when the user type enter, or * when ctrl+d is typed. * * The function returns the length of the current buffer. */ static int esp_linenoise_edit(esp_linenoise_instance_t *instance, char *buffer, size_t buffer_length) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; uint32_t t1 = 0; int out_fd = instance->config.out_fd; int in_fd = instance->config.in_fd; /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ state->buffer = buffer; state->buffer_length = buffer_length; state->prompt_length = strlen(config->prompt); state->old_cursor_position = state->cur_cursor_position = 0; state->len = 0; state->columns = esp_linenoise_get_columns(instance); state->max_rows_used = 0; state->history_index = 0; /* Buffer starts empty. */ state->buffer[0] = '\0'; state->buffer_length--; /* Make sure there is always space for the nulterm */ /* The latest history entry is always our current buffer, that * initially is just an empty string. */ const esp_err_t ret_val = esp_linenoise_history_add(instance, ""); if (ret_val != ESP_OK) { return -1; } if (config->write_bytes_cb(out_fd, config->prompt, state->prompt_length) == -1) { return -1; } /* If the prompt has been registered with ANSI escape sequences * for terminal colors then we remove them from the prompt length * calculation. */ state->prompt_length = esp_linenoise_prompt_len_ignore_escape_seq(config->prompt); while (1) { char c; /** * To determine whether the user is pasting data or typing itself, we * need to calculate how many milliseconds elapsed between two key * presses. Indeed, if there is less than ESP_LINENOISE_PASTE_KEY_DELAY * (typically 30-40ms), then a paste is being performed, else, the * user is typing. * NOTE: pressing a key down without releasing it will also spend * about 40ms (or even more) */ t1 = get_millis(); int nread = config->read_bytes_cb(in_fd, &c, 1); if (nread <= 0) { return state->len; } if ( (get_millis() - t1) < ESP_LINENOISE_PASTE_KEY_DELAY && c != ENTER) { /* Pasting data, insert characters without formatting. * This can only be performed when the cursor is at the end of the * line. */ if (esp_linenoise_insert_pasted_char(instance, c)) { return -1; } continue; } /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ if (c == 9 && config->completion_cb != NULL) { int c2 = esp_linenoise_complete_line(instance); /* Return on errors */ if (c2 < 0) { return state->len; } /* Read next character when 0 */ if (c2 == 0) { continue; } c = c2; } switch (c) { case ENTER: /* enter */ state->history_length--; free(config->history[state->history_length]); if (config->allow_multi_line) { esp_linenoise_edit_move_end(instance); } if (config->hints_cb) { /* Force a refresh without hints to leave the previous * line as the user typed it after a newline. */ esp_linenoise_hints_t hc = config->hints_cb; config->hints_cb = NULL; esp_linenoise_refresh_line(instance); config->hints_cb = hc; } return (int)state->len; case CTRL_C: /* ctrl-c */ errno = EAGAIN; return -1; case BACKSPACE: /* backspace */ case CTRL_H: /* ctrl-h */ esp_linenoise_edit_backspace(instance); break; case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the line is empty, act as end-of-file. */ if (state->len > 0) { esp_linenoise_edit_delete(instance); } else { state->history_length--; free(config->history[state->history_length]); return -1; } break; case CTRL_T: /* ctrl-t, swaps current character with previous. */ if (state->cur_cursor_position > 0 && state->cur_cursor_position < state->len) { int aux = state->buffer[state->cur_cursor_position - 1]; state->buffer[state->cur_cursor_position - 1] = state->buffer[state->cur_cursor_position]; state->buffer[state->cur_cursor_position] = aux; if (state->cur_cursor_position != state->len - 1) { state->cur_cursor_position++; } esp_linenoise_refresh_line(instance); } break; case CTRL_B: /* ctrl-b */ esp_linenoise_edit_move_left(instance); break; case CTRL_F: /* ctrl-f */ esp_linenoise_edit_move_right(instance); break; case CTRL_P: /* ctrl-p */ esp_linenoise_edit_history_next(instance, esp_LINENOISE_HISTORY_PREV); break; case CTRL_N: /* ctrl-n */ esp_linenoise_edit_history_next(instance, esp_LINENOISE_HISTORY_NEXT); break; case CTRL_U: /* Ctrl+u, delete the whole line. */ state->buffer[0] = '\0'; state->cur_cursor_position = state->len = 0; esp_linenoise_refresh_line(instance); break; case CTRL_K: /* Ctrl+k, delete from current to end of line. */ state->buffer[state->cur_cursor_position] = '\0'; state->len = state->cur_cursor_position; esp_linenoise_refresh_line(instance); break; case CTRL_A: /* Ctrl+a, go to the start of the line */ esp_linenoise_edit_move_home(instance); break; case CTRL_E: /* ctrl+e, go to the end of the line */ esp_linenoise_edit_move_end(instance); break; case CTRL_L: /* ctrl+l, clear screen */ (void)esp_linenoise_clear_screen(instance); esp_linenoise_refresh_line(instance); break; case CTRL_W: /* ctrl+w, delete previous word */ esp_linenoise_edit_delete_prev_word(instance); break; case ESC: { /* escape sequence */ /* ESC [ sequences. */ char seq[3]; int r = config->read_bytes_cb(in_fd, seq, 1); if (r != 1) { return -1; } if (seq[0] == '[') { int r = config->read_bytes_cb(in_fd, seq + 1, 1); if (r != 1) { return -1; } if (seq[1] >= '0' && seq[1] <= '9') { /* Extended escape, read additional byte. */ r = config->read_bytes_cb(in_fd, seq + 2, 1); if (r != 1) { return -1; } if (seq[2] == '~') { switch (seq[1]) { case '3': /* Delete key. */ esp_linenoise_edit_delete(instance); break; } } } else { switch (seq[1]) { case 'A': /* Up */ esp_linenoise_edit_history_next(instance, esp_LINENOISE_HISTORY_PREV); break; case 'B': /* Down */ esp_linenoise_edit_history_next(instance, esp_LINENOISE_HISTORY_NEXT); break; case 'C': /* Right */ esp_linenoise_edit_move_right(instance); break; case 'D': /* Left */ esp_linenoise_edit_move_left(instance); break; case 'H': /* Home */ esp_linenoise_edit_move_home(instance); break; case 'F': /* End*/ esp_linenoise_edit_move_end(instance); break; } } } /* ESC O sequences. */ else if (seq[0] == 'O') { int r = config->read_bytes_cb(in_fd, seq + 1, 1); if (r != 1) { return -1; } switch (seq[1]) { case 'H': /* Home */ esp_linenoise_edit_move_home(instance); break; case 'F': /* End*/ esp_linenoise_edit_move_end(instance); break; } } break; } default: if (esp_linenoise_edit_insert(instance, c)) { return -1; } break; } fsync(instance->config.out_fd); } return state->len; } static int esp_linenoise_raw(esp_linenoise_instance_t *instance, char *buffer, size_t buffer_length) { esp_linenoise_config_t *config = &instance->config; int count; if (buffer_length == 0) { errno = EINVAL; return -1; } count = esp_linenoise_edit(instance, buffer, buffer_length); config->write_bytes_cb(config->out_fd, "\n", 1); return count; } static int esp_linenoise_dumb(esp_linenoise_instance_t *instance, char *buffer, size_t buffer_length) { esp_linenoise_config_t *config = &instance->config; config->write_bytes_cb(instance->config.out_fd, config->prompt, strlen(config->prompt)); size_t count = 0; const int in_fd = instance->config.in_fd; char c = 'c'; // leave the last character free so the user can null terminate the string if needed, // also, this is to be consistent with esp_linenoise_edit() bool exit_loop = false; while (!exit_loop) { int nread = config->read_bytes_cb(in_fd, &c, 1); if (nread < 0) { exit_loop = true; count = nread; continue; } if (c == '\n') { exit_loop = true; continue; } // if the number of bytes are reached, wait for the user to input // a new line character to return, just like in esp_linenoise_edit() if (count >= buffer_length - 1) { continue; } else if (c == BACKSPACE || c == CTRL_H) { if (count > 0) { buffer[count - 1] = 0; count--; /* Only erase symbol echoed from in_fd. */ char erase_symbol_str[] = "\x08"; config->write_bytes_cb(instance->config.out_fd, erase_symbol_str, sizeof(erase_symbol_str)); /* Windows CMD: erase symbol under cursor */ } else { /* Consume backspace if the command line is empty to avoid erasing the prompt */ continue; } } else if (c <= UNIT_SEP) { /* Consume all character that are non printable (the backspace * case is handled above) */ continue; } else { buffer[count] = c; ++count; } config->write_bytes_cb(instance->config.out_fd, &c, 1); /* echo */ } config->write_bytes_cb(instance->config.out_fd, "\n", 1); // null terminate the string buffer[count + 1] = '\0'; return count; } static void esp_linenoise_sanitize(char *src) { char *dst = src; for (int c = *src; c != 0; src++, c = *src) { if (isprint(c)) { *dst = c; ++dst; } } *dst = 0; } int esp_linenoise_probe(esp_linenoise_handle_t handle) { esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; /* Make sure we are in non blocking mode before performing the terminal probing */ int fd_in = instance->config.in_fd; int out_fd = instance->config.out_fd; int old_flags = fcntl(fd_in, F_GETFL); int new_flags = old_flags | O_NONBLOCK; int res = fcntl(fd_in, F_SETFL, new_flags); if (res != 0) { return -1; } /* Device status request */ char status_request_str[] = "\x1b[5n"; instance->config.write_bytes_cb(out_fd, status_request_str, sizeof(status_request_str)); /* Try to read response */ int timeout_ms = 500; const int retry_ms = 10; size_t read_bytes = 0; while (timeout_ms > 0 && read_bytes < 4) { // response is ESC[0n or ESC[3n usleep(retry_ms * 1000); timeout_ms -= retry_ms; char c; int cb = instance->config.read_bytes_cb(fd_in, &c, 1); if (cb < 0) { continue; } if (read_bytes == 0 && c != ESC) { /* invalid response, try again until the timeout triggers */ continue; } read_bytes += cb; } /* Switch back to whatever mode we had before the function call */ res = fcntl(fd_in, F_SETFL, old_flags); if (res != 0) { return -1; } if (read_bytes < 4) { return -2; } return 0; } #define ESP_LINENOISE_CHECK_INSTANCE(handle) \ if(handle == NULL) { \ return ESP_ERR_INVALID_ARG; \ } void esp_linenoise_get_instance_config_default(esp_linenoise_config_t *config) { *config = (esp_linenoise_config_t) { .prompt = ESP_LINENOISE_DEFAULT_PROMPT, .max_cmd_line_length = ESP_LINENOISE_DEFAULT_MAX_LINE, .history_max_length = ESP_LINENOISE_DEFAULT_HISTORY_MAX_LENGTH, .in_fd = STDIN_FILENO, .out_fd = STDOUT_FILENO, .allow_multi_line = false, /* Multi line mode. Default is single line. */ .allow_empty_line = true, /* Allow linenoise to return an empty string. On by default */ .allow_dumb_mode = false, /* Dumb mode where line editing is disabled. Off by default */ .completion_cb = NULL, .hints_cb = NULL, .free_hints_cb = NULL, .write_bytes_cb = esp_linenoise_default_write_bytes, .read_bytes_cb = esp_linenoise_default_read_bytes, .history = NULL, }; } esp_err_t esp_linenoise_create_instance(const esp_linenoise_config_t *config, esp_linenoise_handle_t *out_handle) { if (!config || !out_handle) { return ESP_ERR_INVALID_ARG; } /* make sure the history is NULL since the linenoise library will allocate it */ if (config->history != NULL) { return ESP_ERR_INVALID_ARG; } esp_linenoise_instance_t *instance = malloc(sizeof(esp_linenoise_instance_t)); if (!instance) { return ESP_ERR_NO_MEM; } instance->config = *config; /* set the state part of the esp_linenoise_instance_t to 0 to * init all values to 0 (or NULL) */ memset(&instance->state, 0x00, sizeof(esp_linenoise_state_t)); if (instance->config.in_fd == -1) { instance->config.in_fd = STDIN_FILENO; } if (instance->config.out_fd == -1) { instance->config.out_fd = STDOUT_FILENO; } if (!instance->config.prompt) { instance->config.prompt = ESP_LINENOISE_DEFAULT_PROMPT; } if (!instance->config.max_cmd_line_length) { instance->config.max_cmd_line_length = ESP_LINENOISE_DEFAULT_MAX_LINE; } if (!instance->config.history_max_length) { instance->config.history_max_length = ESP_LINENOISE_DEFAULT_HISTORY_MAX_LENGTH; } if (instance->config.write_bytes_cb == NULL) { instance->config.write_bytes_cb = esp_linenoise_default_write_bytes; } if ((instance->config.read_bytes_cb == NULL) || (instance->config.read_bytes_cb == esp_linenoise_default_read_bytes)) { /* since we are using the default read function, make sure * blocking read are set */ int flags = fcntl(instance->config.in_fd, F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(instance->config.in_fd, F_SETFL, flags); instance->config.read_bytes_cb = esp_linenoise_default_read_bytes; if (esp_linenoise_set_event_fd != NULL) { const esp_err_t ret_val = esp_linenoise_set_event_fd(instance); if (ret_val != ESP_OK) { free(instance); return ret_val; } } else { /* make sure the state->mux is set to NULL */ instance->state.mux = NULL; } } const int probe_status = esp_linenoise_probe(instance); if (probe_status == 0) { /* escape sequences supported*/ instance->config.allow_dumb_mode = false; } else { /* error during the probing or escape sequences not supported */ instance->config.allow_dumb_mode = true; char buf[256]; int len = snprintf(buf, sizeof(buf), "\r\n" "Your terminal application does not support escape sequences.\n\n" "Line editing and history features are disabled.\n\n" "On Windows, try using Windows Terminal or Putty instead.\r\n"); instance->config.write_bytes_cb(instance->config.out_fd, buf, len); } *out_handle = (esp_linenoise_handle_t)instance; return ESP_OK; } esp_err_t esp_linenoise_delete_instance(esp_linenoise_handle_t handle) { ESP_LINENOISE_CHECK_INSTANCE(handle); esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; // free the history first since it was allocated by linenoise esp_err_t ret_val = esp_linenoise_history_free(handle); if (ret_val != ESP_OK) { return ret_val; } // delete the mutex in the state and close the eventfd // if it was created if ((instance->config.write_bytes_cb == esp_linenoise_default_write_bytes) && (esp_linenoise_remove_event_fd != NULL)) { ret_val = esp_linenoise_remove_event_fd(instance); if (ret_val != ESP_OK) { return ret_val; } } // reset the memory memset(instance, 0x00, sizeof(esp_linenoise_instance_t)); // free the instance free(instance); return ESP_OK; } esp_err_t esp_linenoise_get_line(esp_linenoise_handle_t handle, char *cmd_line_buffer, size_t cmd_line_length) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (cmd_line_buffer == NULL) { return ESP_ERR_INVALID_ARG; } esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; if ((cmd_line_length == 0) || (cmd_line_length > config->max_cmd_line_length)) { return ESP_ERR_INVALID_ARG; } /* take the mutex, it will be released only when esp_linenoise_raw * or esp_linenoise_dumb returns */ if (state->mux != NULL) { (void)xSemaphoreTake(state->mux, portMAX_DELAY); } int count = 0; if (!config->allow_dumb_mode) { count = esp_linenoise_raw(instance, cmd_line_buffer, cmd_line_length); } else { count = esp_linenoise_dumb(instance, cmd_line_buffer, cmd_line_length); } esp_err_t ret_val = ESP_OK; if (count > 0) { esp_linenoise_sanitize(cmd_line_buffer); } else if (count == 0 && config->allow_empty_line) { /* will return an empty (0-length) string */ } else { ret_val = ESP_FAIL; } /* release the mutex, signaling that esp_linenoise_get_line returned */ if (state->mux != NULL) { xSemaphoreGive(state->mux); } return ret_val; } void esp_linenoise_add_completion(void *ctx, const char *str) { if ((ctx == NULL) || str == NULL) { return; } esp_linenoise_completions_t *lc = (esp_linenoise_completions_t *)ctx; size_t len = strlen(str); char *copy, **cvec; copy = malloc(len + 1); if (copy == NULL) { return; } memcpy(copy, str, len + 1); cvec = realloc(lc->cvec, sizeof(char *) * (lc->len + 1)); if (cvec == NULL) { free(copy); return; } cvec[lc->len] = copy; // Store copy in new slot before updating struct lc->cvec = cvec; lc->len++; } esp_err_t esp_linenoise_history_add(esp_linenoise_handle_t handle, const char *line) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (line == NULL) { return ESP_ERR_INVALID_ARG; } esp_linenoise_config_t *config = &((esp_linenoise_instance_t *)handle)->config; esp_linenoise_state_t *state = &((esp_linenoise_instance_t *)handle)->state; if (config->history_max_length == 0) { return ESP_ERR_NO_MEM; } /* Initialization on first calstate. */ if (config->history == NULL) { config->history = malloc(sizeof(char *) * config->history_max_length); if (config->history == NULL) { return ESP_ERR_NO_MEM; } memset(config->history, 0, (sizeof(char *) * config->history_max_length)); state->history_length = 0; } /* Don't add duplicated lines. */ if ((state->history_length == 0) || (strcmp(config->history[state->history_length - 1], line) != 0)) { /* Add an heap allocated copy of the line in the history. * If we reached the max length, remove the older line. */ char *line_copy = strdup(line); if (!line_copy) { return ESP_ERR_NO_MEM; } if (state->history_length == config->history_max_length) { free(config->history[0]); memmove(config->history, config->history + 1, sizeof(char *) * (config->history_max_length - 1)); state->history_length--; } config->history[state->history_length] = line_copy; state->history_length++; } return ESP_OK; } esp_err_t esp_linenoise_history_save(esp_linenoise_handle_t handle, const char *filename) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (filename == NULL) { return ESP_ERR_INVALID_ARG; } esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; FILE *fp; int j; fp = fopen(filename, "w"); if (fp == NULL) { return ESP_FAIL; } for (j = 0; j < instance->state.history_length; j++) { fprintf(fp, "%s\n", instance->config.history[j]); } fclose(fp); return ESP_OK; } esp_err_t esp_linenoise_history_load(esp_linenoise_handle_t handle, const char *filename) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (filename == NULL) { return ESP_ERR_INVALID_ARG; } esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; FILE *fp = fopen(filename, "r"); if (fp == NULL) { return ESP_FAIL; } char *buf = calloc(1, instance->config.max_cmd_line_length); if (buf == NULL) { fclose(fp); return ESP_ERR_NO_MEM; } while (fgets(buf, instance->config.max_cmd_line_length, fp) != NULL) { char *p; p = strchr(buf, '\r'); if (!p) { p = strchr(buf, '\n'); } if (p) { *p = '\0'; } const esp_err_t ret_val = esp_linenoise_history_add(handle, buf); if (ret_val != ESP_OK) { free(buf); fclose(fp); return ret_val; } } free(buf); fclose(fp); return ESP_OK; } esp_err_t esp_linenoise_history_set_max_len(esp_linenoise_handle_t handle, int new_length) { ESP_LINENOISE_CHECK_INSTANCE(handle); esp_linenoise_config_t *config = &((esp_linenoise_instance_t *)handle)->config; esp_linenoise_state_t *state = &((esp_linenoise_instance_t *)handle)->state; if (new_length == config->history_max_length) { /* the requested history size is the same as the current * one, return ok without changing anything */ return ESP_OK; } if (new_length < 1) { return ESP_ERR_INVALID_ARG; } char **new_history; if (config->history) { int to_copy = state->history_length; new_history = malloc(sizeof(char *) * new_length); if (new_history == NULL) { return ESP_ERR_NO_MEM; } /* If we can't copy everything, free the elements we'll not use. */ if (new_length < to_copy) { int j; for (j = 0; j < to_copy - new_length; j++) { free(config->history[j]); } to_copy = new_length; } memset(new_history, 0, sizeof(char *) * new_length); memcpy(new_history, config->history + (state->history_length - to_copy), sizeof(char *)*to_copy); free(config->history); config->history = new_history; } config->history_max_length = new_length; if (state->history_length > config->history_max_length) { state->history_length = config->history_max_length; } return ESP_OK; } esp_err_t esp_linenoise_history_free(esp_linenoise_handle_t handle) { ESP_LINENOISE_CHECK_INSTANCE(handle); esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; if (instance->config.history) { for (int j = 0; j < instance->state.history_length; j++) { free(instance->config.history[j]); } free(instance->config.history); } instance->config.history = NULL; instance->state.history_length = 0; return ESP_OK; } esp_err_t esp_linenoise_clear_screen(esp_linenoise_handle_t handle) { ESP_LINENOISE_CHECK_INSTANCE(handle); esp_linenoise_instance_t *instance = (esp_linenoise_instance_t *)handle; esp_linenoise_config_t *config = &instance->config; char erase_screen_str[] = "\x1b[H\x1b[2J"; size_t msg_size = sizeof(erase_screen_str); ssize_t nb_bytes = config->write_bytes_cb(config->out_fd, erase_screen_str, msg_size); if (nb_bytes < 0 || nb_bytes != msg_size) { return ESP_FAIL; } return ESP_OK; } esp_err_t esp_linenoise_set_empty_line(esp_linenoise_handle_t handle, bool empty_line) { ESP_LINENOISE_CHECK_INSTANCE(handle); ((esp_linenoise_instance_t *)handle)->config.allow_empty_line = empty_line; return ESP_OK; } esp_err_t esp_linenoise_is_empty_line(esp_linenoise_handle_t handle, bool *is_empty_line) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (is_empty_line == NULL) { return ESP_ERR_INVALID_ARG; } *is_empty_line = ((esp_linenoise_instance_t *)handle)->config.allow_empty_line; return ESP_OK; } esp_err_t esp_linenoise_set_multi_line(esp_linenoise_handle_t handle, bool multi_line) { ESP_LINENOISE_CHECK_INSTANCE(handle); ((esp_linenoise_instance_t *)handle)->config.allow_multi_line = multi_line; return ESP_OK; } esp_err_t esp_linenoise_is_multi_line(esp_linenoise_handle_t handle, bool *is_multi_line) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (is_multi_line == NULL) { return ESP_ERR_INVALID_ARG; } *is_multi_line = ((esp_linenoise_instance_t *)handle)->config.allow_multi_line; return ESP_OK; } esp_err_t esp_linenoise_set_dumb_mode(esp_linenoise_handle_t handle, bool dumb_mode) { ESP_LINENOISE_CHECK_INSTANCE(handle); ((esp_linenoise_instance_t *)handle)->config.allow_dumb_mode = dumb_mode; return ESP_OK; } esp_err_t esp_linenoise_is_dumb_mode(esp_linenoise_handle_t handle, bool *is_dumb_mode) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (is_dumb_mode == NULL) { return ESP_ERR_INVALID_ARG; } *is_dumb_mode = ((esp_linenoise_instance_t *)handle)->config.allow_dumb_mode; return ESP_OK; } esp_err_t esp_linenoise_set_max_cmd_line_length(esp_linenoise_handle_t handle, size_t length) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (length >= ESP_LINENOISE_MINIMAL_MAX_LINE) { ((esp_linenoise_instance_t *)handle)->config.max_cmd_line_length = length; } else { return ESP_ERR_INVALID_ARG; } return ESP_OK; } esp_err_t esp_linenoise_get_max_cmd_line_length(esp_linenoise_handle_t handle, size_t *max_cmd_line_length) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (max_cmd_line_length == NULL) { return ESP_ERR_INVALID_ARG; } *max_cmd_line_length = ((esp_linenoise_instance_t *)handle)->config.max_cmd_line_length; return ESP_OK; } esp_err_t esp_linenoise_set_prompt(esp_linenoise_handle_t handle, const char *prompt) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (prompt == NULL || strlen(prompt) == 0) { return ESP_ERR_INVALID_ARG; } ((esp_linenoise_instance_t *)handle)->config.prompt = prompt; return ESP_OK; } esp_err_t esp_linenoise_get_prompt(esp_linenoise_handle_t handle, const char **prompt) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (prompt == NULL) { return ESP_ERR_INVALID_ARG; } *prompt = ((esp_linenoise_instance_t *)handle)->config.prompt; return ESP_OK; } esp_err_t esp_linenoise_get_out_fd(esp_linenoise_handle_t handle, int *fd) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (fd == NULL) { return ESP_ERR_INVALID_ARG; } *fd = handle->config.out_fd; return ESP_OK; } esp_err_t esp_linenoise_get_in_fd(esp_linenoise_handle_t handle, int *fd) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (fd == NULL) { return ESP_ERR_INVALID_ARG; } *fd = handle->config.in_fd; return ESP_OK; } esp_err_t esp_linenoise_get_read(esp_linenoise_handle_t handle, esp_linenoise_read_bytes_t *read_func) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (read_func == NULL) { return ESP_ERR_INVALID_ARG; } *read_func = handle->config.read_bytes_cb; return ESP_OK; } esp_err_t esp_linenoise_get_write(esp_linenoise_handle_t handle, esp_linenoise_write_bytes_t *write_func) { ESP_LINENOISE_CHECK_INSTANCE(handle); if (write_func == NULL) { return ESP_ERR_INVALID_ARG; } *write_func = handle->config.write_bytes_cb; return ESP_OK; } ================================================ FILE: esp_linenoise/src/esp_linenoise_internals.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include "sys/queue.h" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_vfs_eventfd.h" #include "esp_linenoise.h" #include "esp_linenoise_private.h" typedef struct eventfd_pair { int eventfd; int in_fd; SLIST_ENTRY(eventfd_pair) next_pair; } eventfd_pair_t; static const uint64_t s_abort_signal = 1; static SLIST_HEAD(eventfd_pair_ll, eventfd_pair) s_eventfd_pairs = SLIST_HEAD_INITIALIZER(eventfd_pair); static int esp_linenoise_get_eventfd_from_fd(const int fd) { /* find the eventfd to use to abort for the given fd */ eventfd_pair_t *eventfd_pair = NULL; SLIST_FOREACH(eventfd_pair, &s_eventfd_pairs, next_pair) { if (eventfd_pair->in_fd == fd) { return eventfd_pair->eventfd; } } return -1; } ssize_t esp_linenoise_default_read_bytes(int fd, void *buf, size_t count) { if ((fcntl(fd, F_GETFL, 0) & O_NONBLOCK) != 0) { /* non blocking read, call read directly */ return read(fd, buf, count); } fd_set read_fds; FD_ZERO(&read_fds); FD_SET(fd, &read_fds); /* find the eventfd to use to abort for the given fd */ int max_fd = -1; int abort_read_fd = esp_linenoise_get_eventfd_from_fd(fd); if (abort_read_fd != -1) { FD_SET(abort_read_fd, &read_fds); max_fd = MAX(fd, abort_read_fd); } else { max_fd = fd; } /* call select to wait for data to read or an abort signal */ int nread = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (nread < 0) { return -1; } if (FD_ISSET(abort_read_fd, &read_fds)) { /* read termination request happened, return */ int temp_buf[sizeof(s_abort_signal)]; nread = read(abort_read_fd, temp_buf, sizeof(s_abort_signal)); if ((nread == sizeof(s_abort_signal)) && (temp_buf[0] == s_abort_signal)) { /* populate the buffer with a new line character, forcing esp_linenoise_raw * or esp_linenoise_dumb to return */ nread = 1; memcpy(buf, "\n", 1); } } else if (FD_ISSET(fd, &read_fds)) { /* a read ready triggered the select to return. call the * read function with the number of bytes max_bytes */ nread = read(fd, buf, count); } return nread; } esp_err_t esp_linenoise_set_event_fd(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; /* Tell linenoise what file descriptor to add to the read file descriptor set, * that will be used to signal a read termination */ esp_vfs_eventfd_config_t eventfd_config = { .max_fds = CONFIG_ESP_LINENOISE_MAX_INSTANCE_NB }; esp_err_t ret = esp_vfs_eventfd_register(&eventfd_config); int new_eventfd = -1; if (ret != ESP_ERR_INVALID_ARG) { new_eventfd = eventfd(0, 0); } else { /* issue with arg, this should not happen */ return ESP_FAIL; } /* make sure the FD returned is not -1, which would indicate that eventfd * has reached the maximum number of FDs it can create */ if (new_eventfd == -1) { return ESP_FAIL; } state->mux = xSemaphoreCreateMutex(); if (state->mux == NULL) { close(new_eventfd); return ESP_ERR_NO_MEM; } /* one eventfd will be created for a given instance. In order to be able * to use the proper eventfd in the read, couple the created eventfd to * the in_fd from the config of the given instance. */ eventfd_pair_t *new_pair = malloc(sizeof(eventfd_pair_t)); if (new_pair == NULL) { close(new_eventfd); vSemaphoreDelete(state->mux); return ESP_ERR_NO_MEM; } new_pair->eventfd = new_eventfd; new_pair->in_fd = config->in_fd; SLIST_INSERT_HEAD(&s_eventfd_pairs, new_pair, next_pair); xSemaphoreGive(state->mux); return ESP_OK; } esp_err_t esp_linenoise_remove_event_fd(esp_linenoise_instance_t *instance) { esp_linenoise_config_t *config = &instance->config; esp_linenoise_state_t *state = &instance->state; /* find the in_fd in the list of eventfd / in_fd pairs. * if found, remove the item from the list, close the eventfd */ eventfd_pair_t *cur = NULL; eventfd_pair_t *prev = NULL; SLIST_FOREACH(cur, &s_eventfd_pairs, next_pair) { if (cur->in_fd == config->in_fd) { /* close the eventfd */ close(cur->eventfd); if (prev == NULL) { /* remove head */ SLIST_REMOVE_HEAD(&s_eventfd_pairs, next_pair); } else { /* remove item in the middle of the list */ prev->next_pair.sle_next = cur->next_pair.sle_next; } /* return from the loop */ break; } prev = cur; } if (cur == NULL) { return ESP_ERR_NOT_FOUND; } /* free the item that was removed */ free(cur); /* free the mutex */ vSemaphoreDelete(state->mux); /* if the list is empty, it means the last instance of esp_linenoise * running is being deleted. Unregister eventfd to free the heap memory * allocated when calling esp_vfs_eventfd_register */ if (SLIST_EMPTY(&s_eventfd_pairs)) { return esp_vfs_eventfd_unregister(); } return ESP_OK; } esp_err_t esp_linenoise_abort(esp_linenoise_handle_t handle) { esp_linenoise_config_t *config = &handle->config; esp_linenoise_state_t *state = &handle->state; if (config->read_bytes_cb != esp_linenoise_default_read_bytes) { /* we are not using the default read bytes function provided\ * by esp_linenoise, therefore it is not esp_linenoise responsibility * to trigger the return from esp_linenoise_get_line */ return ESP_ERR_INVALID_STATE; } /* send the signal to force esp_linenoise_default_read_bytes * to return */ int abort_read_fd = esp_linenoise_get_eventfd_from_fd(config->in_fd); if (abort_read_fd == -1) { return ESP_FAIL; } int nwrite = write(abort_read_fd, &s_abort_signal, sizeof(s_abort_signal)); if (nwrite != sizeof(s_abort_signal)) { return ESP_FAIL; } /* wait for esp_linenoise_get_line to signal it returned */ (void)xSemaphoreTake(state->mux, portMAX_DELAY); /* mutex acquired successfully, esp_linenoise_get_line returned * we can release the mutex directly so it can be taken again * when esp_linenoise_get_line is called again */ xSemaphoreGive(state->mux); return ESP_OK; } ================================================ FILE: esp_linenoise/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_linenoise_test) ================================================ FILE: esp_linenoise/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_esp_linenoise_get_set.c" "test_esp_linenoise_behavioral.c" "test_linenoise_legacy.c" "test_utils.c" "test_main.c" PRIV_INCLUDE_DIRS "." "include" PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: esp_linenoise/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_linenoise: version: "*" override_path: "../.." ================================================ FILE: esp_linenoise/test_apps/main/include/test_utils.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #define CMD_LINE_LENGTH 32 /* set to the value of ESP_LINENOISE_COMMAND_MAX_LEN */ #define COMPOUND_LITERAL(x) ((char[]){(char)x, '\0'}) enum KEY_ACTION { KEY_NULL = 0, /* NULL */ CTRL_A = 1, /* Ctrl+a */ CTRL_B = 2, /* Ctrl-b */ CTRL_C = 3, /* Ctrl-c */ CTRL_D = 4, /* Ctrl-d */ CTRL_E = 5, /* Ctrl-e */ CTRL_F = 6, /* Ctrl-f */ CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ CTRL_K = 11, /* Ctrl+k */ CTRL_L = 12, /* Ctrl+l */ ENTER = 10, /* Enter */ CTRL_N = 14, /* Ctrl-n */ CTRL_P = 16, /* Ctrl-p */ CTRL_T = 20, /* Ctrl-t */ CTRL_U = 21, /* Ctrl+u */ CTRL_W = 23, /* Ctrl+w */ ESC = 27, /* Escape */ UNIT_SEP = 31, /* ctrl-_ */ BACKSPACE = 127 /* Backspace */ }; typedef struct command { const char *request; const char *response; } command_t; extern const command_t commands[]; extern const size_t commands_count; inline __attribute__((always_inline)) uint32_t get_millis(void) { struct timeval tv = { 0 }; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } inline __attribute__((always_inline)) void wait_ms(int ms) { int cur_time = 0; const int timeout = get_millis() + ms; do { cur_time = get_millis(); } while (cur_time < timeout); } void test_send_characters(int socket_fd, const char *msg); #ifdef __cplusplus } #endif ================================================ FILE: esp_linenoise/test_apps/main/test_esp_linenoise_behavioral.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "fcntl.h" #include "unity.h" #include "esp_linenoise.h" #include "test_utils.h" static int s_socket_fd_a[2]; static int s_socket_fd_b[2]; static esp_linenoise_handle_t s_linenoise_hdl; static char s_line_returned[CMD_LINE_LENGTH] = {0}; static bool s_completions_called = false; static bool s_hint_called = false; static bool s_free_hint_called = false; static void custom_completion_cb(const char *str, void *cb_ctx, esp_linenoise_completion_cb_t cb) { // we just want to see that the callback is indeed called // so flip the s_completions_called to true if (!s_completions_called) { s_completions_called = true; } } static char *custom_hint_cb(const char *str, int *color, int *bold) { // we just want to see that the callback is indeed called // so flip the s_hint_called to true if (!s_hint_called) { s_hint_called = true; } return "something"; } static void custom_free_hint_cb(void *ptr) { // we just want to see that the callback is indeed called // so flip the s_free_hint_called to true if (!s_free_hint_called) { s_free_hint_called = true; } } static ssize_t custom_read(int fd, void *buf, size_t count) { int nread = -1; fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds); int ret = select(fd + 1, &rfds, NULL, NULL, NULL); if (ret > 0 && FD_ISSET(fd, &rfds)) { nread = read(fd, buf, count); } return nread; } static ssize_t custom_write(int fd, const void *buf, size_t count) { // find the request in the list of commands and send the response for (size_t i = 0; i < commands_count; i++) { if (strstr(commands[i].request, buf) != NULL) { const char *response = commands[i].response; if (response != NULL) { const size_t size = strlen(commands[i].response); // write the expected response to the socket, so linenoise // can read the response // conveniently, the socketpair FDs are following each other so // to simulate a write from the device, call write on fd + 1 const ssize_t nwrite = write(fd + 1, response, size); TEST_ASSERT_EQUAL(size, nwrite); } // return the count like a normal write would do // do not propagate to the socket to not pollute // the buffers return count; } } // otherwise just propagate the write return write(fd, buf, count); } static void test_instance_setup(int socket_fd[2], pthread_mutex_t *lock, esp_linenoise_config_t *config) { // 2 fd are generated, simulating the full-duplex // communication between linenoise and the terminal TEST_ASSERT_EQUAL(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd)); // assure that the read will be blocking int flags = fcntl(socket_fd[0], F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(socket_fd[0], F_SETFL, flags); flags = fcntl(socket_fd[1], F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(socket_fd[0], F_SETFL, flags); esp_linenoise_get_instance_config_default(config); // all data written by the test on socket_fd[1] will // bbe available for reading on socket_fd[0]. All data // written to socket_fd[0] will be available for reading // on socket_fd[1]. config->in_fd = socket_fd[0]; config->out_fd = socket_fd[0]; // redirect read and write calls from linenoise to be able // to e.g., process sequences sent from linenoise and thus // simulate that the terminal supports escape sequences config->read_bytes_cb = custom_read; config->write_bytes_cb = custom_write; // take the semaphore pthread_mutex_lock(lock); } static void test_instance_teardown(int socket_fd[2], esp_linenoise_handle_t handle, pthread_mutex_t *lock) { memset(s_line_returned, 0, CMD_LINE_LENGTH); esp_linenoise_delete_instance(handle); close(socket_fd[0]); close(socket_fd[1]); // unlock the mutex for the next test pthread_mutex_destroy(lock); } typedef struct get_line_args { pthread_mutex_t *lock; TaskHandle_t parent_task; esp_linenoise_config_t *config; } get_line_args_t; static void get_line_task(void *args) { get_line_args_t *task_args = (get_line_args_t *)args; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(task_args->config, &s_linenoise_hdl)); TEST_ASSERT_NOT_NULL(s_linenoise_hdl); // wait for the instance to properly initialize before unlocking // the mutex so the test can run usleep(100000); // release the mutex so the test can start sending data pthread_mutex_unlock(task_args->lock); esp_err_t ret_val = esp_linenoise_get_line(s_linenoise_hdl, s_line_returned, CMD_LINE_LENGTH); TEST_ASSERT_EQUAL(ESP_OK, ret_val); xTaskNotifyGive(task_args->parent_task); vTaskDelete(NULL); } typedef struct get_line_task_args { esp_linenoise_handle_t handle; TaskHandle_t parent_task; pthread_mutex_t *lock; esp_err_t ret_val; char *buf; size_t buf_size; } get_line_task_args_t; static void get_line_task_w_args(void *args) { get_line_task_args_t *task_args = (get_line_task_args_t *)args; // wait for the instance to properly initialize before unlocking // the mutex so the test can run usleep(100000); // release the mutex so the test can start sending data pthread_mutex_unlock(task_args->lock); task_args->ret_val = esp_linenoise_get_line(task_args->handle, task_args->buf, task_args->buf_size); xTaskNotifyGive(task_args->parent_task); vTaskDelete(NULL); } TEST_CASE("esp_linenoise_get_line() returns line read from in_fd", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); const char *input_line = "unit test input"; test_send_characters(s_socket_fd_a[1], input_line); // Write newline to trigger prompt output + return from loop test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING(input_line, s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("custom prompt string appears on output", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); const char *custom_prompt = ">>> "; config.prompt = (char *)custom_prompt; // cast away const as config expects char* get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Write newline to trigger prompt output + return from loop test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Verify prompt string is found in output char full_cmd_line[32] = {0}; const ssize_t nread = read(s_socket_fd_a[1], full_cmd_line, 32); TEST_ASSERT_NOT_EQUAL(-1, nread); TEST_ASSERT_NOT_NULL(strstr(full_cmd_line, custom_prompt)); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("cursor left/right and insert edits input correctly", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Send chars and control sequences: // Step 1: insert 'a', 'b', 'c' => buffer: "abc", cursor at end test_send_characters(s_socket_fd_a[1], "abc"); // Step 2: CTRL-B (move cursor left once), cursor between 'b' and 'c' test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // Step 3: insert 'X' => buffer: "abXc" test_send_characters(s_socket_fd_a[1], "X"); // Step 4: CTRL-F (move cursor right), cursor after 'X' test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_F)); // Step 5: insert 'Y' => buffer: "abXcY" test_send_characters(s_socket_fd_a[1], "Y"); // Step 6: send newline to finish input test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // The line returned should be "abXcY" TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("abXcY", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-A moves cursor home, CTRL-E moves cursor end, inserts work correctly", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // ----- Test CTRL-A: move home ----- // Insert 'bcd' test_send_characters(s_socket_fd_a[1], "bcd"); // CTRL-A: move cursor to start test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_A)); // Insert 'a' at beginning → "abcd" test_send_characters(s_socket_fd_a[1], "a"); // CTRL-E to move to end test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_E)); // Insert 'e' at end → "abcde" test_send_characters(s_socket_fd_a[1], "e"); // send new line character test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("abcde", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("history navigation with CTRL-P / CTRL-N works correctly", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); wait_ms(100); // Add history entries in order: "first", "second", "third" TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "first")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "second")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "third")); wait_ms(100); // CTRL-P: get previous history command, should be "third" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_P)); // CTRL-P: get previous history command, should be "second" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_P)); // extend the "second" line to see if when we go back to it, // it will print the updated line or the former line test_send_characters(s_socket_fd_a[1], "second"); // CTRL-P: get previous history command, should be "first" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_P)); // CTRL-N: get previous history command, should be "second" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_N)); // Send newline to accept current line test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Expect "third" as final returned line after navigation TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("secondsecond", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("backspace erases the character before the cursor", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "abc" test_send_characters(s_socket_fd_a[1], "abc"); // BACKSPACE (127), buffer becomes "ab" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(BACKSPACE)); // CTRL-H (8), buffer becomes "a" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_H)); // Insert "aa", buffer becomes "aaa" test_send_characters(s_socket_fd_a[1], "aa"); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("aaa", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-D removes character at the right of the cursor", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "abcde" test_send_characters(s_socket_fd_a[1], "abcde"); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-D, buffer becomes "abde" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_D)); // CTRL-D, buffer becomes "abe" test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_D)); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("abe", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-T swaps character with previous", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "abcde" test_send_characters(s_socket_fd_a[1], "abcde"); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-T (swap characters) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_T)); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("abced", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-U deletes the whole line", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "abcde" test_send_characters(s_socket_fd_a[1], "abcde"); // CTRL-U (delete all line) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_U)); // Insert "fghij" test_send_characters(s_socket_fd_a[1], "fghij"); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("fghij", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-K deletes from character to end of line", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "abcde" test_send_characters(s_socket_fd_a[1], "abcde"); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-B (move cursor left once) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_B)); // CTRL-K (delete from current character on) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_K)); // Insert "abab" test_send_characters(s_socket_fd_a[1], "abab"); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("ababab", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-L clears the screen", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // CTRL-L (clear screen) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_L)); // we can't check that the screen is actually cleared but // we can check that the proper command was sent from // linenoise to the terminal // Verify prompt string is found in output wait_ms(100); char full_cmd_line[32] = {0}; const char *expect_string = "screen cleared"; const ssize_t nread = read(s_socket_fd_a[1], full_cmd_line, 32); TEST_ASSERT_NOT_EQUAL(-1, nread); TEST_ASSERT_NOT_NULL(strstr(full_cmd_line, expect_string)); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("CTRL-W removes the previous word", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "word_a " test_send_characters(s_socket_fd_a[1], "word_a"); test_send_characters(s_socket_fd_a[1], " "); // Insert "word_b" test_send_characters(s_socket_fd_a[1], "word_b"); // CTRL-W (removes previous work) test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(CTRL_W)); // Insert "word_c" test_send_characters(s_socket_fd_a[1], "word_c"); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_NOT_NULL(s_line_returned); TEST_ASSERT_EQUAL_STRING("word_a word_c", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("check completion, hint and free hint callback", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); config.completion_cb = custom_completion_cb; config.hints_cb = custom_hint_cb; config.free_hints_cb = custom_free_hint_cb; get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Insert "word_a" this should trigger the hint cb and free hints cb test_send_characters(s_socket_fd_a[1], "word_a"); // TAB: this should trigger the completions cb test_send_characters(s_socket_fd_a[1], COMPOUND_LITERAL(TAB)); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL(true, s_hint_called); TEST_ASSERT_EQUAL(true, s_completions_called); TEST_ASSERT_EQUAL(true, s_free_hint_called); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("check esp_linenoise_get_line return values", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config, &s_linenoise_hdl)); TEST_ASSERT_NOT_NULL(s_linenoise_hdl); const size_t buffer_size = 10; char buffer[buffer_size]; // pass NULL buffer, expect invalid arg error TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_linenoise_get_line(s_linenoise_hdl, NULL, buffer_size)); // pass 0 buffer size, expect invalid arg error TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_linenoise_get_line(s_linenoise_hdl, buffer, 0)); // pass buffer size bigger than max cmd line length, expect invalid arg error TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_linenoise_get_line(s_linenoise_hdl, buffer, config.max_cmd_line_length + 1)); // update the value of allow_empty_line to false and send an empty // line, expect esp_linenoise_get_line to return with ESP_FAIL TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_empty_line(s_linenoise_hdl, false)); get_line_task_args_t args = { .handle = s_linenoise_hdl, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock, .ret_val = ESP_OK, .buf = buffer, .buf_size = buffer_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // Newline (accept) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // check that esp_linenoise_get_line returned ESP_FAIL TEST_ASSERT_EQUAL(ESP_FAIL, args.ret_val); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } TEST_CASE("check cmd line is bigger than the buffer", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config, &s_linenoise_hdl)); TEST_ASSERT_NOT_NULL(s_linenoise_hdl); const size_t buffer_size = 10; char buffer[buffer_size]; // update the value of allow_empty_line to false and send an empty // line, expect esp_linenoise_get_line to return with ESP_FAIL TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_empty_line(s_linenoise_hdl, false)); get_line_task_args_t args = { .handle = s_linenoise_hdl, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock, .ret_val = ESP_OK, .buf = buffer, .buf_size = buffer_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // send more characters than the size of the buffer when linenoise // has dumb mode turned off test_send_characters(s_socket_fd_a[1], "aaaaaaaaaaa\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // check that esp_linenoise_get_line returned ESP_OK TEST_ASSERT_EQUAL(ESP_OK, args.ret_val); // linenoise should return when size of buffer - 1 is filled TEST_ASSERT_EQUAL(buffer_size - 1, strlen(buffer)); // reset the buffer and release the mutex pthread_mutex_unlock(&lock); memset(buffer, 0, buffer_size); // switch the dumb mode on TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_dumb_mode(s_linenoise_hdl, true)); // repeat the test xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args, 5, NULL); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&lock); // send more characters than the size of the buffer when linenoise // has dumb mode turned on test_send_characters(s_socket_fd_a[1], "aaaaaaaaaaa\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // check that esp_linenoise_get_line returned ESP_OK and the // number of char is equal to buffer_size - 1 TEST_ASSERT_EQUAL(ESP_OK, args.ret_val); TEST_ASSERT_EQUAL(buffer_size - 1, strlen(buffer)); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* Mappings from shortkey actions to escape sequences: - Up arrow : "\x1b[A" - Down arrow : "\x1b[B" - Right arrow : "\x1b[C" - Left arrow : "\x1b[D" - Home : "\x1b[H" or "\x1bOH" - End : "\x1b[F" or "\x1bOF" - Delete key : "\x1b[3~" */ TEST_CASE("cursor left/right edits work via escape sequences", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); pthread_mutex_lock(&lock); test_send_characters(s_socket_fd_a[1], "abc"); // step 1: insert abc test_send_characters(s_socket_fd_a[1], "\x1b[D"); // step 2: left arrow (cursor between b and c) test_send_characters(s_socket_fd_a[1], "X"); // step 3: insert X test_send_characters(s_socket_fd_a[1], "\x1b[C"); // step 4: right arrow test_send_characters(s_socket_fd_a[1], "Y"); // step 5: insert Y test_send_characters(s_socket_fd_a[1], "\n"); // finish input // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING("abXcY", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* Home and End via escape sequences */ TEST_CASE("Home and End keys work via escape sequences", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); pthread_mutex_lock(&lock); test_send_characters(s_socket_fd_a[1], "bcd"); // buffer: bcd test_send_characters(s_socket_fd_a[1], "\x1b[H"); // Home key test_send_characters(s_socket_fd_a[1], "a"); // -> abcd test_send_characters(s_socket_fd_a[1], "\x1b[F"); // End key test_send_characters(s_socket_fd_a[1], "e"); // -> abcde test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING("abcde", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* History navigation with Up/Down arrows */ TEST_CASE("history navigation works via arrow keys", "[esp_linenoise][history]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); pthread_mutex_lock(&lock); // add history TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "first")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "second")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(s_linenoise_hdl, "third")); // navigate test_send_characters(s_socket_fd_a[1], "\x1b[A"); // up -> third test_send_characters(s_socket_fd_a[1], "\x1b[A"); // up -> second test_send_characters(s_socket_fd_a[1], "second"); // append text test_send_characters(s_socket_fd_a[1], "\x1b[A"); // up -> first test_send_characters(s_socket_fd_a[1], "\x1b[B"); // down -> secondsecond test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING("secondsecond", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* Delete key via escape sequence */ TEST_CASE("Delete key works via escape sequence", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); pthread_mutex_lock(&lock); test_send_characters(s_socket_fd_a[1], "abcde"); test_send_characters(s_socket_fd_a[1], "\x1b[D"); // left test_send_characters(s_socket_fd_a[1], "\x1b[D"); // left test_send_characters(s_socket_fd_a[1], "\x1b[D"); // left test_send_characters(s_socket_fd_a[1], "\x1b[3~"); // delete (removes c) test_send_characters(s_socket_fd_a[1], "\x1b[3~"); // delete (removes d) test_send_characters(s_socket_fd_a[1], "\n"); // wait for the task to terminate to continue ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING("abe", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* Alternate Home/End sequences using ESC O form */ TEST_CASE("Home and End via ESC O form", "[esp_linenoise]") { esp_linenoise_config_t config; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock, &config); get_line_args_t args = { .lock = &lock, .parent_task = xTaskGetCurrentTaskHandle(), .config = &config }; xTaskCreate(get_line_task, "freertos_task", 2048, &args, 5, NULL); pthread_mutex_lock(&lock); test_send_characters(s_socket_fd_a[1], "bcd"); test_send_characters(s_socket_fd_a[1], "\x1bOH"); // ESC O H for home test_send_characters(s_socket_fd_a[1], "a"); test_send_characters(s_socket_fd_a[1], "\x1bOF"); // ESC O F for end test_send_characters(s_socket_fd_a[1], "e"); test_send_characters(s_socket_fd_a[1], "\n"); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING("abcde", s_line_returned); test_instance_teardown(s_socket_fd_a, s_linenoise_hdl, &lock); } /* test multi instances */ TEST_CASE("Create and use 2 esp_linenoise instances", "[esp_linenoise]") { esp_linenoise_config_t config_a; pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock_a, &config_a); esp_linenoise_handle_t linenoise_handle_a; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config_a, &linenoise_handle_a)); TEST_ASSERT_NOT_NULL(linenoise_handle_a); const size_t buffer_a_size = 32; char buffer_a[buffer_a_size]; memset(buffer_a, 0, buffer_a_size); get_line_task_args_t args_a = { .handle = linenoise_handle_a, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock_a, .ret_val = ESP_OK, .buf = buffer_a, .buf_size = buffer_a_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args_a, 5, NULL); pthread_mutex_lock(&lock_a); esp_linenoise_config_t config_b; pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_b, &lock_b, &config_b); esp_linenoise_handle_t linenoise_handle_b; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config_b, &linenoise_handle_b)); TEST_ASSERT_NOT_NULL(linenoise_handle_b); const size_t buffer_b_size = 32; char buffer_b[buffer_b_size]; memset(buffer_b, 0, buffer_b_size); get_line_task_args_t args_b = { .handle = linenoise_handle_b, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock_b, .ret_val = ESP_OK, .buf = buffer_b, .buf_size = buffer_b_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args_b, 5, NULL); pthread_mutex_lock(&lock_b); /* send different string to the instances and make sure each instances * get the correct string from the correct stream */ const char *test_msg_a = "test_msg_a"; const char *test_msg_b = "test_msg_b"; test_send_characters(s_socket_fd_a[1], test_msg_a); test_send_characters(s_socket_fd_a[1], "\n"); test_send_characters(s_socket_fd_b[1], test_msg_b); test_send_characters(s_socket_fd_b[1], "\n"); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); TEST_ASSERT_EQUAL_STRING(test_msg_a, args_a.buf); TEST_ASSERT_EQUAL_STRING(test_msg_b, args_b.buf); test_instance_teardown(s_socket_fd_a, linenoise_handle_a, &lock_a); test_instance_teardown(s_socket_fd_b, linenoise_handle_b, &lock_b); } TEST_CASE("tests that esp_linenoise_abort actually forces esp_linenoise_get_line to return", "[esp_linenoise]") { esp_linenoise_config_t config_a, config_b; pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER; test_instance_setup(s_socket_fd_a, &lock_a, &config_a); test_instance_setup(s_socket_fd_b, &lock_b, &config_b); /* make sure to use the default read function */ config_a.read_bytes_cb = NULL; config_b.read_bytes_cb = NULL; esp_linenoise_handle_t linenoise_handle_a; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config_a, &linenoise_handle_a)); TEST_ASSERT_NOT_NULL(linenoise_handle_a); const size_t buffer_a_size = 32; char buffer_a[buffer_a_size]; memset(buffer_a, 0, buffer_a_size); get_line_task_args_t args_a = { .handle = linenoise_handle_a, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock_a, .ret_val = ESP_OK, .buf = buffer_a, .buf_size = buffer_a_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args_a, 5, NULL); pthread_mutex_lock(&lock_a); esp_linenoise_handle_t linenoise_handle_b; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config_b, &linenoise_handle_b)); TEST_ASSERT_NOT_NULL(linenoise_handle_b); const size_t buffer_b_size = 32; char buffer_b[buffer_b_size]; memset(buffer_b, 0, buffer_b_size); get_line_task_args_t args_b = { .handle = linenoise_handle_b, .parent_task = xTaskGetCurrentTaskHandle(), .lock = &lock_b, .ret_val = ESP_OK, .buf = buffer_b, .buf_size = buffer_b_size }; xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args_b, 5, NULL); pthread_mutex_lock(&lock_b); /* send test message to instance */ const char dummy_message[] = "dummy_message"; test_send_characters(s_socket_fd_a[1], dummy_message); /* for the esp_linenoise to process the message */ vTaskDelay(pdMS_TO_TICKS(100)); /* call the esp_linenoise_abort on linenoise_handle_a to return from esp_linenoise_get_line */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_abort(linenoise_handle_a)); /* wait for the task running the linenoise instance A to return */ ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* check that the message was processed by the instance A */ TEST_ASSERT_EQUAL_STRING(dummy_message, args_a.buf); /* send dummy message to instance B, that should still be running */ test_send_characters(s_socket_fd_b[1], dummy_message); /* for the esp_linenoise to process the message */ vTaskDelay(pdMS_TO_TICKS(100)); /* call the esp_linenoise_abort on linenoise_handle_a to return from esp_linenoise_get_line */ TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_abort(linenoise_handle_b)); /* wait for the task running the linenoise instance A to return */ ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* check that the message was processed by the instance B */ TEST_ASSERT_EQUAL_STRING(dummy_message, args_b.buf); /* start instance A and repeat test to make sure it is possible to restart an instance * even after aborting it */ xTaskCreate(get_line_task_w_args, "freertos_task", 2048, &args_a, 5, NULL); pthread_mutex_lock(&lock_a); test_send_characters(s_socket_fd_a[1], dummy_message); vTaskDelay(pdMS_TO_TICKS(100)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_abort(linenoise_handle_a)); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); test_instance_teardown(s_socket_fd_a, linenoise_handle_a, &lock_a); test_instance_teardown(s_socket_fd_b, linenoise_handle_b, &lock_b); } ================================================ FILE: esp_linenoise/test_apps/main/test_esp_linenoise_get_set.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "unity.h" #include "esp_linenoise.h" #include "esp_err.h" static esp_linenoise_handle_t get_linenoise_instance_default_config(void) { esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); esp_linenoise_handle_t h; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config, &h)); TEST_ASSERT_NOT_NULL(h); return h; } TEST_CASE("set and get multi-line mode", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); bool is_multi_line = false; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_multi_line(h, true)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_multi_line(h, &is_multi_line)); TEST_ASSERT_TRUE(is_multi_line); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_multi_line(h, false)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_multi_line(h, &is_multi_line)); TEST_ASSERT_FALSE(is_multi_line); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("set and get dumb mode", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); bool is_dumb_mode = false; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_dumb_mode(h, true)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_dumb_mode(h, &is_dumb_mode)); TEST_ASSERT_TRUE(is_dumb_mode); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_dumb_mode(h, false)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_dumb_mode(h, &is_dumb_mode)); TEST_ASSERT_FALSE(is_dumb_mode); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("set and get empty line flag", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); bool is_empty_line = false; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_empty_line(h, true)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_empty_line(h, &is_empty_line)); TEST_ASSERT_TRUE(is_empty_line); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_empty_line(h, false)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_is_empty_line(h, &is_empty_line)); TEST_ASSERT_FALSE(is_empty_line); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("set and get prompt", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); const char *test_prompt = "test"; const char *ret_prompt = NULL; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_prompt(h, test_prompt)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_prompt(h, &ret_prompt)); TEST_ASSERT_TRUE(strcmp(test_prompt, ret_prompt) == 0); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("default max line length and max history length", "[esp_linenoise]") { esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); TEST_ASSERT_GREATER_THAN(0, config.max_cmd_line_length); TEST_ASSERT_GREATER_THAN(0, config.history_max_length); } TEST_CASE("set and get max command line length", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); size_t max_cmd_line_len = 0; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_set_max_cmd_line_length(h, 1024)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_max_cmd_line_length(h, &max_cmd_line_len)); TEST_ASSERT_EQUAL(1024, max_cmd_line_len); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("add and free history", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(h, "entry1")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_set_max_len(h, 5)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(h, "entry2")); esp_linenoise_history_free(h); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("save and load history to file", "[esp_linenoise]") { esp_linenoise_handle_t h = get_linenoise_instance_default_config(); const char *filename = "/tmp/test_esp_linenoise_history.txt"; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(h, "one")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_add(h, "two")); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_save(h, filename)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_free(h)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_history_load(h, filename)); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } TEST_CASE("get out_fd and in_fd", "[esp_linenoise]") { const int test_out_fd = 5; const int test_in_fd = 6; esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); config.out_fd = test_out_fd; config.in_fd = test_in_fd; esp_linenoise_handle_t h; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config, &h)); TEST_ASSERT_NOT_NULL(h); int in_fd = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_in_fd(h, &in_fd)); TEST_ASSERT_EQUAL(test_in_fd, in_fd); int out_fd = -1; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_out_fd(h, &out_fd)); TEST_ASSERT_EQUAL(test_out_fd, out_fd); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } static ssize_t test_read(int fd, void *buf, size_t count) { (void)fd; (void)buf; (void)count; return -1; } static ssize_t test_write(int fd, const void *buf, size_t count) { (void)fd; (void)buf; (void)count; return -1; } TEST_CASE("get read_func and write_func", "[esp_linenoise]") { esp_linenoise_config_t config; esp_linenoise_get_instance_config_default(&config); config.read_bytes_cb = test_read; config.write_bytes_cb = test_write; esp_linenoise_handle_t h; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&config, &h)); TEST_ASSERT_NOT_NULL(h); esp_linenoise_read_bytes_t read_ret; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_read(h, &read_ret)); TEST_ASSERT_EQUAL(test_read, read_ret); esp_linenoise_write_bytes_t write_ret; TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_get_write(h, &write_ret)); TEST_ASSERT_EQUAL(test_write, write_ret); TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(h)); } ================================================ FILE: esp_linenoise/test_apps/main/test_linenoise_legacy.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "fcntl.h" #include "unity.h" #include "linenoise/linenoise.h" #include "test_utils.h" static int s_socket_fd[2]; static int s_original_stdin_fd = -1; static int s_original_stdout_fd = -1; static char *s_returned_line = NULL; static pthread_mutex_t s_lock = PTHREAD_MUTEX_INITIALIZER; static bool s_completions_called = false; static bool s_hint_called = false; static bool s_free_hint_called = false; void custom_legacy_completion_cb(const char *str, linenoiseCompletions *lc) { // we just want to see that the callback is indeed called // so flip the s_completions_called to true if (!s_completions_called) { s_completions_called = true; } } char *custom_legacy_hint_cb(const char *str, int *color, int *bold) { // we just want to see that the callback is indeed called // so flip the s_hint_called to true if (!s_hint_called) { s_hint_called = true; } return "something"; } void custom_legacy_free_hint_cb(void *ptr) { // we just want to see that the callback is indeed called // so flip the s_free_hint_called to true if (!s_free_hint_called) { s_free_hint_called = true; } } ssize_t custom_legacy_write(int fd, const void *buf, size_t count) { // printf("writing : "); // for (size_t i = 0; i < count; i++) { // printf("(%x, %c) ", *((char*)buf + i), *((char*)buf + i)); // } // printf("\n"); // find the request in the list of commands and send the response for (size_t i = 0; i < commands_count; i++) { if (strstr(commands[i].request, buf) != NULL) { const char *response = commands[i].response; if (response != NULL) { const size_t size = strlen(commands[i].response); // write the expected response to the socket, so linenoise // can read the response const ssize_t nwrite = write(s_socket_fd[1], response, size); TEST_ASSERT_EQUAL(size, nwrite); } // return the count like a normal write would do // do not propagate to the socket to not pollute // the buffers return count; } } // otherwise just propagate the write return write(fd, buf, count); } static void test_setup(int socket_fd[2], pthread_mutex_t *s_lock) { // 2 fd are generated, simulating the full-duplex // communication between linenoise and the terminal socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd); // redirect stdin and stdout since legacy linenoise uses // stdin and stdout only as default streams s_original_stdin_fd = dup(STDIN_FILENO); s_original_stdout_fd = dup(STDOUT_FILENO); TEST_ASSERT(dup2(s_socket_fd[0], STDIN_FILENO) >= 0); TEST_ASSERT(dup2(s_socket_fd[0], STDOUT_FILENO) >= 0); close(s_socket_fd[0]); // assure that the read will be blocking int flags = fcntl(STDIN_FILENO, F_GETFL, 0); flags &= ~O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, flags); linenoiseSetCompletionCallback(custom_legacy_completion_cb); linenoiseSetHintsCallback(custom_legacy_hint_cb); linenoiseSetFreeHintsCallback(custom_legacy_free_hint_cb); /* don't set the custom read, since we don't need it. also it tests that the default * read is used by linenoise */ linenoiseSetWriteFunction(custom_legacy_write); const int is_dumb_mode = linenoiseProbe(); if (is_dumb_mode) { printf("running dumb mode\n"); linenoiseSetDumbMode(1); } else { printf("running normal mode\n"); linenoiseSetDumbMode(0); } // take the semaphore pthread_mutex_lock(s_lock); } static void test_teardown(int socket_fd[2]) { memset(s_returned_line, 0, CMD_LINE_LENGTH); // Restore the default streams dup2(s_original_stdin_fd, STDIN_FILENO); dup2(s_original_stdout_fd, STDOUT_FILENO); close(s_original_stdin_fd); close(s_original_stdout_fd); close(socket_fd[0]); close(socket_fd[1]); // unlock the mutex for the next test pthread_mutex_unlock(&s_lock); } static void *get_line_task(void *arg) { char *prompt = (char *)arg; // wait for the instance to properly initialize before unlocking // the mutex so the test can run usleep(100000); // release the mutex so the test can start sending data pthread_mutex_unlock(&s_lock); s_returned_line = linenoise(prompt); return NULL; } TEST_CASE("legacy linenoise() returns line read from stdin and writes to stdout", "[linenoise]") { test_setup(s_socket_fd, &s_lock); pthread_t thread_id; char *prompt = ">>>"; TEST_ASSERT_EQUAL(0, pthread_create(&thread_id, NULL, get_line_task, prompt)); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&s_lock); const char *input_line = "unit test input"; test_send_characters(s_socket_fd[1], input_line); // Write newline to trigger prompt output + return from loop test_send_characters(s_socket_fd[1], "\n"); TEST_ASSERT_EQUAL(0, pthread_join(thread_id, NULL)); /* verify that the string was processed properly by linenoise() */ TEST_ASSERT_NOT_NULL(s_returned_line); TEST_ASSERT_NOT_NULL(strstr(input_line, s_returned_line)); // Verify prompt string is found in output char full_cmd_line[32] = {0}; const ssize_t nread = read(s_socket_fd[1], full_cmd_line, 32); TEST_ASSERT_NOT_EQUAL(-1, nread); TEST_ASSERT_NOT_NULL(strstr(full_cmd_line, prompt)); test_teardown(s_socket_fd); } TEST_CASE("legacy check completion, hint and free hint callback", "[linenoise]") { test_setup(s_socket_fd, &s_lock); pthread_t thread_id; char *prompt = ">>>"; TEST_ASSERT_EQUAL(0, pthread_create(&thread_id, NULL, get_line_task, prompt)); // wait until the linenoise instance init is done, and the get line as started // before sending test content pthread_mutex_lock(&s_lock); // Insert "word_a" this should trigger the hint cb and free hints cb test_send_characters(s_socket_fd[1], "word_a"); // TAB: this should trigger the completions cb test_send_characters(s_socket_fd[1], COMPOUND_LITERAL(TAB)); // Newline (accept) test_send_characters(s_socket_fd[1], "\n"); // Wait for loop to finish TEST_ASSERT_EQUAL(0, pthread_join(thread_id, NULL)); TEST_ASSERT_EQUAL(true, s_hint_called); TEST_ASSERT_EQUAL(true, s_completions_called); TEST_ASSERT_EQUAL(true, s_free_hint_called); test_teardown(s_socket_fd); } ================================================ FILE: esp_linenoise/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running linenoise component tests\n"); unity_run_menu(); } ================================================ FILE: esp_linenoise/test_apps/main/test_utils.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "unity.h" #include "test_utils.h" // list of commands that the linenoise tests need // to intercept and potentially answer for linenoise to // behave as expected const command_t commands[] = { {"\x1b[5n", "\x1b[0n"}, // probe for escape sequence support {"\x1b[H\x1b[2J", "screen cleared"}, // clear screen request, the response will be analyzed in the concerned test {"\x1b[6n", "\x1b[10;50R"}, // request for rows, cols {"\x1b[999C", NULL}, // move the cursor right, no response needed }; const size_t commands_count = sizeof(commands) / sizeof(command_t); void test_send_characters(int socket_fd, const char *msg) { // wait to simulate that the user is doing the input // and prevent linenoise to detect the incoming character(s) // as pasted wait_ms(100); const size_t msg_len = strlen(msg); const int nwrite = write(socket_fd, msg, msg_len); TEST_ASSERT_EQUAL(msg_len, nwrite); } ================================================ FILE: esp_linenoise/test_apps/pytest_linenoise.py ================================================ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_esp_linenoise(dut: Dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: esp_linenoise/test_apps/sdkconfig.defaults ================================================ CONFIG_ESP_TASK_WDT_EN=n CONFIG_VFS_SUPPORT_IO=n ================================================ FILE: esp_schedule/.build-test-rules.yml ================================================ # ESP Schedule build test rules # This file can be empty - the component will be built with default rules ================================================ FILE: esp_schedule/CMakeLists.txt ================================================ set(component_srcs "src/esp_schedule.c" "src/esp_schedule_nvs.c") idf_component_register(SRCS "${component_srcs}" INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "src" PRIV_REQUIRES nvs_flash esp_netif) ================================================ FILE: esp_schedule/Kconfig ================================================ menu "ESP Schedule Configuration" config ESP_SCHEDULE_ENABLE_DAYLIGHT bool "Enable Daylight (Sunrise/Sunset) Schedules" default y help Enable support for sunrise and sunset based schedules. This feature allows scheduling based on astronomical calculations for sunrise and sunset times at specific geographical locations. Disabling this option will: - Remove sunrise/sunset schedule types from the API - Save memory by excluding solar calculation code - Reduce binary size (by about 11500 bytes) If disabled, ESP_SCHEDULE_TYPE_SUNRISE and ESP_SCHEDULE_TYPE_SUNSET will not be available. endmenu ================================================ FILE: esp_schedule/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_schedule/README.md ================================================ # ESP Scheduling [![Component Registry](https://components.espressif.com/components/espressif/esp_schedule/badge.svg)](https://components.espressif.com/components/espressif/esp_schedule) This component is used to implement scheduling for: - **One-shot events** with a relative time difference (e.g., 30 seconds into the future) - **Periodic events** based on a certain time[^1] on days of the week (e.g., every Monday or Wednesday) - **Periodic/one-shot events** on a certain time[^1] based on the date: - e.g., *(periodic)* every 23rd of January to April - e.g., *(one-shot)* 9th of August, 2026 - **Periodic events** at an offset from sunrise/sunset [^1]: By default, the time is w.r.t. UTC. If the timezone has been set, then the time is w.r.t. the specified timezone. ## Example Usage See the comprehensive example in [`examples/get_started/`](examples/get_started/) for a complete demonstration of all ESP Schedule features, including: - **Days of Week Scheduling** - Recurring events on specific weekdays - **Date-based Scheduling** - Monthly and yearly recurring events - **Relative Scheduling** - One-time delayed events - **Solar Scheduling** - Sunrise/sunset based events with location coordinates and day-of-week filtering - **Schedule Persistence** - NVS storage and recovery - **Callback Handling** - Trigger and timestamp callbacks - **Schedule Management** - Create, edit, enable, and disable schedules The example includes detailed documentation, build instructions, and demonstrates all schedule types with practical use cases. ================================================ FILE: esp_schedule/examples/get_started/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(esp_schedule_example) ================================================ FILE: esp_schedule/examples/get_started/README.md ================================================ # ESP Schedule Example ## Overview This example demonstrates how to use the ESP Schedule component to create different types of schedules for ESP32 applications. The example shows four main schedule types: 1. **Days of Week Schedule** - Triggers on specific days of the week at specified times 2. **Date Schedule** - Triggers on specific dates (day and month combinations) 3. **Relative Schedule** - Triggers after a specified number of seconds from creation 4. **Solar Schedule** - Triggers at sunrise or sunset with optional offset ## Schedule Types Demonstrated ### 1. Days of Week Schedule (`ESP_SCHEDULE_TYPE_DAYS_OF_WEEK`) - Triggers every Monday, Wednesday, and Friday at 14:30 (2:30 PM) - Useful for recurring weekly events like meetings, reminders, or automated tasks ### 2. Date Schedule (`ESP_SCHEDULE_TYPE_DATE`) - Triggers every month on the 15th at 09:00 (9:00 AM) - Useful for monthly recurring events like bill payments, reports, or maintenance tasks - Can be configured to repeat every year for annual events ### 3. Relative Schedule (`ESP_SCHEDULE_TYPE_RELATIVE`) - Triggers 60 seconds after creation (one minute timer) - Useful for one-time delayed actions like turning off a device after a timeout - Has a validity period (2 minutes in this example) ### 4. Solar Schedule (`ESP_SCHEDULE_TYPE_SUNRISE` / `ESP_SCHEDULE_TYPE_SUNSET`) - **Sunrise Schedule**: Triggers exactly at sunrise for a specific location (weekdays only) - **Sunset Schedule**: Triggers 30 minutes before sunset for a specific location (weekdays only) - Uses latitude/longitude coordinates to calculate solar times with day-of-week filtering - Perfect for automatic lighting control, irrigation systems, or other location-based automation - Supports both day-of-week patterns and specific date patterns for solar events - Requires `CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT=y` to be enabled ## Features Demonstrated - **Schedule Creation**: How to create schedules with different trigger types - **Schedule Persistence**: Schedules are stored in NVS and persist across reboots - **Network Provisioning**: Automatic WiFi provisioning via BLE or SoftAP for network connectivity with QR code display - **Time Synchronization**: Robust SNTP time sync with threshold validation for accurate real-world scheduling - **Callback Functions**: Both trigger callbacks (when schedule fires) and timestamp callbacks (when next trigger time updates) - **Schedule Management**: Enable, disable, edit, and delete schedules - **Schedule Recovery**: Automatically recovers and re-enables schedules after reboot ## How to Use Example ### Hardware Required * An ESP development board (ESP32, ESP32-S2, ESP32-S3, etc.) * USB cable for power supply and programming * WiFi network access for time synchronization and network provisioning ### Supported Targets This example requires Wi-Fi connectivity for network provisioning and time synchronization. **Supported Targets:** ESP32, ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-S2, ESP32-S3 ### Network Provisioning The example uses configurable network provisioning to connect to WiFi: 1. **Flash and run** the example on your ESP device 2. **A QR code will be displayed** in the serial output with provisioning data 3. **Use a BLE/SoftAP provisioning app** (like "ESP SoftAP Prov" on Android/iOS) to scan the QR code (or manually enter the QR code details) 4. **The device will automatically** connect to WiFi and synchronize time via NTP 5. **Schedules will then** operate with accurate real-world timing #### Configuration - `CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SCHEME` - provisioning transport type - `CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION` - provisioning security version: - **Version 0**: plaintext communication - **Version 1**: Proof of Possession (PoP) used, "12345678" - **Version 2**: Username: "wifiprov", Password: "abcd1234" ### Project Configuration To select the provisioning scheme: 1. Run `idf.py menuconfig` (or `idf.py set-target && idf.py menuconfig`) 2. Navigate to `Example Configuration` → `ESP Schedule Example Configuration` 3. Select your preferred `Network Provisioning Scheme`: - `BLE Provisioning` (default) - Uses Bluetooth LE - `SoftAP Provisioning` - Uses WiFi Access Point 4. Save and exit ### Build and Flash 1. Run `idf.py set-target ` to set the target chip (e.g., `esp32`, `esp32s3`) 2. Run `idf.py build` to build the project 3. Run `idf.py -p PORT flash monitor` to build, flash and monitor the project **Note**: The example includes network provisioning (BLE or SoftAP) and SNTP time synchronization which requires network connectivity. The example will automatically provision and connect to WiFi, then synchronize time from NTP servers. Ensure your device can connect to WiFi networks for proper operation. **Security Note**: The example uses a fixed Proof of Possession (PoP) value of "12345678" for secure provisioning. In production applications, use a unique, randomly generated PoP for enhanced security. **QR Code Note**: For BLE provisioning, a QR code is displayed in the serial output that can be scanned with the ESP RainMaker app. If the QR code is not visible, copy the provided URL and paste it in a browser to view the QR code. ### Expected Output The example will show logs like: ```text ESP Schedule example started. Schedules will trigger based on their configurations. I (Current time: Fri Oct 24 14:30:00 2025 ) I (Days of week schedule triggered! Data: Monday/Wednesday/Friday schedule ) ``` The schedules will trigger at their configured times: - **Days of week schedule**: Every Monday, Wednesday, Friday at 14:30 - **Date schedule**: Every 15th of the month at 09:00 - **Relative schedule**: 60 seconds after the program starts - **Solar schedules**: At sunrise (exactly) and sunset (30 minutes early) for San Francisco, CA (weekdays only) ## Code Structure ### Main Components - **`esp_schedule_example_main.c`**: Main application file demonstrating schedule creation and usage - **Callback Functions**: Separate callbacks for each schedule type showing how to handle triggers - **Schedule Configuration**: Examples of how to configure different schedule types ### Key Functions - `create_example_schedules()`: Creates the three example schedules - `edit_example_schedules()`: Demonstrates how to edit existing schedules - `days_of_week_callback()`, `date_callback()`, `relative_callback()`: Handle schedule triggers - `timestamp_callback()`: Handles schedule timestamp updates ## Customization ### Changing Schedule Times To modify the schedule times, edit the `esp_schedule_config_t` structures in `create_example_schedules()`: ```c // Days of week schedule - change time to 10:00 AM .trigger.hours = 10, .trigger.minutes = 0, // Date schedule - change to 20th of every month at 3:00 PM .trigger.date.day = 20, .trigger.hours = 15, .trigger.minutes = 0, // Relative schedule - change to 30 seconds from now .trigger.relative_seconds = 30, // Solar schedule - change location to New York, adjust timing, and change days .trigger.day.repeat_days = ESP_SCHEDULE_DAY_SATURDAY | ESP_SCHEDULE_DAY_SUNDAY, // Weekends only .trigger.solar.latitude = 40.7128, // New York latitude .trigger.solar.longitude = -74.0060, // New York longitude .trigger.solar.offset_minutes = 15, // 15 minutes after sunrise ``` ### Adding More Schedule Types The example demonstrates all the main schedule types including sunrise/sunset schedules. You can also use: - **Sunrise/Sunset Schedules** (already included in the example - see solar_callback): ```c .trigger.type = ESP_SCHEDULE_TYPE_SUNRISE, .trigger.solar.latitude = 37.7749, // San Francisco latitude .trigger.solar.longitude = -122.4194, // San Francisco longitude .trigger.solar.offset_minutes = 30, // 30 minutes after sunrise ``` ### Schedule Persistence Schedules are automatically saved to NVS (Non-Volatile Storage) and will persist across: - Device reboots - Power cycles - Firmware updates ### Schedule Management The example shows how to: - Create new schedules - Enable/disable schedules - Edit existing schedules - Handle schedule recovery after reboot ## Troubleshooting ### Schedule Not Triggering 1. **Check system time**: Ensure the ESP32 system time is set correctly 2. **Verify schedule configuration**: Double-check hours/minutes and trigger conditions 3. **Check logs**: Look for schedule creation and trigger messages in the serial output 4. **Solar schedules**: Ensure `CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT=y` is enabled and location coordinates are accurate 5. **Daylight saving time**: Solar schedules automatically adjust for DST changes ### NVS Issues If you encounter NVS-related errors: 1. The example includes automatic NVS initialization and recovery 2. Check if the NVS partition has enough space 3. Consider erasing NVS if corruption occurs: `idf.py erase-flash` ### Memory Issues If you run out of memory when creating many schedules: 1. Each schedule consumes some memory 2. Consider the validity periods to automatically clean up old schedules 3. Monitor heap usage with `ESP_LOGI(TAG, "Free heap: %d", esp_get_free_heap_size())` ## Further Reading - [ESP Schedule API Reference](../../include/esp_schedule.h) - [ESP Schedule README](../../README.md) ================================================ FILE: esp_schedule/examples/get_started/main/CMakeLists.txt ================================================ set(supported_targets "esp32" "esp32s2" "esp32s3" "esp32c2" "esp32c3" "esp32c5" "esp32c6" "esp32c61") # Redirect to a stub if target is not supported if(CONFIG_IDF_TARGET IN_LIST supported_targets) set(srcs "esp_schedule_example_main.c" "network/app_network.c" ) set(priv_include_dirs "network") else() message(WARNING "Target ${CONFIG_IDF_TARGET} is not supported. Redirecting to stub example.") set(srcs "esp_schedule_example_stub.c") set(priv_include_dirs "") endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS "." PRIV_INCLUDE_DIRS ${priv_include_dirs} PRIV_REQUIRES esp_schedule nvs_flash esp_netif esp_event esp_wifi) ================================================ FILE: esp_schedule/examples/get_started/main/Kconfig.projbuild ================================================ menu "esp_schedule Example Configuration" choice ESP_SCHEDULE_EXAMPLE_PROV_SCHEME prompt "Network Provisioning Scheme" default ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP help Select the network provisioning scheme for the ESP Schedule example. For BLE provisioning, please enable BT_ENABLED and BT_NIMBLE_ENABLED. config ESP_SCHEDULE_EXAMPLE_PROV_BLE bool "BLE Provisioning" depends on BT_ENABLED && BT_NIMBLE_ENABLED help Use Bluetooth Low Energy (BLE) for network provisioning. Requires a BLE-capable device and provisioning app. config ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP bool "SoftAP Provisioning" help Use WiFi Soft Access Point for network provisioning. Creates a WiFi hotspot that users can connect to for provisioning. endchoice choice ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION prompt "Provisioning security version" default ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_1 if ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 default ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_2 if ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 help Select the network provisioning security version. config ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_0 bool "Security Version 0" depends on ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 help Security version 0. No authentication is needed; plaintext communication. config ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_1 bool "Security Version 1" depends on ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 help Security version 1. The PoP is set to '12345678'. In production, please use a randomly generated PoP in a factory partition. config ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_2 bool "Security Version 2" depends on ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 help Security version 2. Username: 'wifiprov', Password: 'abcd1234' In production, please use a randomly generated username and password in a factory partition. endchoice endmenu ================================================ FILE: esp_schedule/examples/get_started/main/esp_schedule_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "esp_schedule.h" #include "nvs_flash.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_netif.h" #include "esp_event.h" #include "esp_sntp.h" #include "lwip/ip_addr.h" #include "network_provisioning/manager.h" #include "freertos/event_groups.h" #include "app_network.h" static const char *TAG = "esp_schedule_example"; /* Event group for network provisioning synchronization */ static EventGroupHandle_t s_network_event_group; // Callback functions for different schedule types static void days_of_week_callback(esp_schedule_handle_t handle, void *priv_data) { ESP_LOGI(TAG, "Days of week schedule triggered! Data: %s", (char *)priv_data); // Example: Toggle an LED, send a notification, etc. // Your application logic here } static void date_callback(esp_schedule_handle_t handle, void *priv_data) { ESP_LOGI(TAG, "Date schedule triggered! Data: %s", (char *)priv_data); // Example: Birthday reminder, anniversary notification, etc. // Your application logic here } static void relative_callback(esp_schedule_handle_t handle, void *priv_data) { ESP_LOGI(TAG, "Relative schedule triggered! Data: %s", (char *)priv_data); // Example: Timer expired, delayed action completed, etc. // Your application logic here } #ifdef CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT static void solar_callback(esp_schedule_handle_t handle, void *priv_data) { ESP_LOGI(TAG, "Solar schedule triggered! Data: %s", (char *)priv_data); // Example: Turn on lights at sunset, turn off lights at sunrise, etc. // Your application logic here } #endif // CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT static void timestamp_callback(esp_schedule_handle_t handle, uint32_t next_timestamp, void *priv_data) { time_t timestamp = (time_t)next_timestamp; char *time_str = ctime(×tamp); if (time_str) { ESP_LOGI(TAG, "Next schedule timestamp updated for %s: %s", (char *)priv_data, time_str); } else { ESP_LOGI(TAG, "Next schedule timestamp updated for %s: ", (char *)priv_data); } } // Private data for different schedules static char *days_of_week_data = "Monday/Wednesday/Friday schedule"; static char *date_data = "Monthly schedule"; static char *relative_data = "Timer schedule"; #ifdef CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT static char *solar_data = "Sunrise/Sunset schedule"; #endif // CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT /** * @brief Create example schedules * * This function creates the example schedules using the esp_schedule_create() API. * @note If NVS is enabled, then the names of the schedules are used as NVS keys, so they must be shorter than the NVS key length limit of 16 characters. */ static void create_example_schedules(void) { ESP_LOGI(TAG, "Creating example schedules..."); // Example 1: Days of week schedule // Triggers every Monday, Wednesday, and Friday at 14:30 esp_schedule_config_t days_schedule = { .name = "work_days", .trigger.type = ESP_SCHEDULE_TYPE_DAYS_OF_WEEK, .trigger.hours = 14, .trigger.minutes = 30, .trigger.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_WEDNESDAY | ESP_SCHEDULE_DAY_FRIDAY, .trigger_cb = days_of_week_callback, .timestamp_cb = timestamp_callback, .priv_data = days_of_week_data, .validity = { .start_time = 0, // Start immediately .end_time = 0 // No end time (run indefinitely) } }; esp_schedule_handle_t days_handle = esp_schedule_create(&days_schedule); if (days_handle) { ESP_LOGI(TAG, "Created days of week schedule successfully"); esp_schedule_enable(days_handle); } // Example 2: Date schedule // Triggers every month on the 15th at 09:00 esp_schedule_config_t date_schedule = { .name = "monthly_15", .trigger.type = ESP_SCHEDULE_TYPE_DATE, .trigger.hours = 9, .trigger.minutes = 0, .trigger.date.day = 15, .trigger.date.repeat_months = ESP_SCHEDULE_MONTH_ALL, // Every month .trigger.date.repeat_every_year = true, .trigger_cb = date_callback, .timestamp_cb = timestamp_callback, .priv_data = date_data, .validity = { .start_time = 0, // Start immediately .end_time = 0 // No end time } }; esp_schedule_handle_t date_handle = esp_schedule_create(&date_schedule); if (date_handle) { ESP_LOGI(TAG, "Created date schedule successfully"); esp_schedule_enable(date_handle); } // Example 3: Relative schedule // Triggers after 10 seconds from creation time_t current_time = time(NULL); esp_schedule_config_t relative_schedule = { .name = "10_sec", .trigger.type = ESP_SCHEDULE_TYPE_RELATIVE, .trigger.relative_seconds = 10, // 10 seconds from now .trigger_cb = relative_callback, .timestamp_cb = timestamp_callback, .priv_data = relative_data, .validity = { .start_time = current_time, .end_time = current_time + 120 // Valid for 2 minutes } }; esp_schedule_handle_t relative_handle = esp_schedule_create(&relative_schedule); if (relative_handle) { ESP_LOGI(TAG, "Created relative schedule successfully"); esp_schedule_enable(relative_handle); } // Example 4: Solar schedule (Sunrise/Sunset) with day-of-week filtering // Note: This requires CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT to be enabled // Triggers at sunrise and sunset for a specific location, but only on weekdays #ifdef CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT esp_schedule_config_t sunrise_schedule = { .name = "sunrise", .trigger.type = ESP_SCHEDULE_TYPE_SUNRISE, .trigger.hours = 0, // Hours/minutes are ignored for solar schedules .trigger.minutes = 0, .trigger.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_TUESDAY | ESP_SCHEDULE_DAY_WEDNESDAY | ESP_SCHEDULE_DAY_THURSDAY | ESP_SCHEDULE_DAY_FRIDAY, .trigger.solar.latitude = 37.7749, // San Francisco latitude .trigger.solar.longitude = -122.4194, // San Francisco longitude .trigger.solar.offset_minutes = 0, // Exactly at sunrise .trigger_cb = solar_callback, .timestamp_cb = timestamp_callback, .priv_data = solar_data, .validity = { .start_time = 0, // Start immediately .end_time = 0 // No end time } }; esp_schedule_handle_t sunrise_handle = esp_schedule_create(&sunrise_schedule); if (sunrise_handle) { ESP_LOGI(TAG, "Created sunrise schedule successfully"); esp_schedule_enable(sunrise_handle); } esp_schedule_config_t sunset_schedule = { .name = "sunset", .trigger.type = ESP_SCHEDULE_TYPE_SUNSET, .trigger.hours = 0, // Hours/minutes are ignored for solar schedules .trigger.minutes = 0, .trigger.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_TUESDAY | ESP_SCHEDULE_DAY_WEDNESDAY | ESP_SCHEDULE_DAY_THURSDAY | ESP_SCHEDULE_DAY_FRIDAY, .trigger.solar.latitude = 37.7749, // San Francisco latitude .trigger.solar.longitude = -122.4194, // San Francisco longitude .trigger.solar.offset_minutes = -30, // 30 minutes before sunset .trigger_cb = solar_callback, .timestamp_cb = timestamp_callback, .priv_data = solar_data, .validity = { .start_time = 0, // Start immediately .end_time = 0 // No end time } }; esp_schedule_handle_t sunset_handle = esp_schedule_create(&sunset_schedule); if (sunset_handle) { ESP_LOGI(TAG, "Created sunset schedule successfully"); esp_schedule_enable(sunset_handle); } #endif // CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT } void app_main(void) { // Initialize NVS (Non-Volatile Storage) esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); // Initialize Event Loop (Network Interface is initialized in app_network) ESP_ERROR_CHECK(esp_event_loop_create_default()); // Create event group for network provisioning synchronization s_network_event_group = xEventGroupCreate(); if (s_network_event_group == NULL) { ESP_LOGE(TAG, "Failed to create event group"); return; } // Initialize network provisioning (includes WiFi and network interfaces) ESP_ERROR_CHECK(app_network_init(s_network_event_group)); // Start network provisioning and wait for connection esp_err_t network_result = app_network_start(s_network_event_group, 300000); // 5 minutes timeout if (network_result != ESP_OK) { ESP_LOGE(TAG, "Network connection failed or timed out"); return; } // Start time synchronization app_network_start_time_sync(s_network_event_group); // Wait for time synchronization to complete esp_err_t time_result = app_network_wait_for_time_sync(s_network_event_group, 60000); // 1 minute timeout if (time_result != ESP_OK) { ESP_LOGW(TAG, "Time synchronization failed or timed out, continuing anyway"); } // Initialize ESP Schedule ESP_LOGI(TAG, "Initializing ESP Schedule..."); uint8_t schedule_count; esp_schedule_handle_t *schedule_list = esp_schedule_init(true, NULL, &schedule_count); if (schedule_list != NULL) { // If there are existing schedules in NVS, their handles will be available in this list. We don't use them in this example, so we free the array. free(schedule_list); } // Make all the schedules used create_example_schedules(); ESP_LOGI(TAG, "ESP Schedule example started. Schedules will trigger based on their configurations."); // The schedules will now trigger automatically based on their configurations // Monitor network status and handle disconnections while (1) { // Main application loop vTaskDelay(pdMS_TO_TICKS(1000)); // Delay 1 second // Print current time every 10 seconds for reference static int counter = 0; if (++counter >= 10) { time_t now = time(NULL); char *time_str = ctime(&now); if (time_str) { ESP_LOGI(TAG, "Current time: %s", time_str); } else { ESP_LOGI(TAG, "Current time: "); } counter = 0; } // Check for network disconnection and handle it EventBits_t network_bits = xEventGroupGetBits(s_network_event_group); if (network_bits & NETWORK_DISCONNECTED_BIT) { ESP_LOGW(TAG, "Network disconnected, attempting to reconnect..."); // For now, just log the issue - in a real application you might // want to restart provisioning or handle reconnection vTaskDelay(pdMS_TO_TICKS(5000)); // Wait 5 seconds before checking again } } // Cleanup (this won't be reached in the current infinite loop, but good practice) if (s_network_event_group) { vEventGroupDelete(s_network_event_group); } } ================================================ FILE: esp_schedule/examples/get_started/main/esp_schedule_example_stub.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" static const char *supported_targets[] = { "esp32", "esp32s2", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", }; void app_main(void) { // This is compiled only if CONFIG_IDF_TARGET is not in the list of supported targets. char supported_targets_str[256] = ""; for (int i = 0; i < sizeof(supported_targets) / sizeof(supported_targets[0]); i++) { char target_str[32]; snprintf(target_str, sizeof(target_str), "\n\t%s", supported_targets[i]); strcat(supported_targets_str, target_str); } ESP_LOGE("esp_schedule_example", "Target '%s' is not supported. Please try one of these supported targets:%s", CONFIG_IDF_TARGET, supported_targets_str); return; } ================================================ FILE: esp_schedule/examples/get_started/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: idf: version: ">=5.1" espressif/esp_schedule: version: "~1.3.1" override_path: "../../../." espressif/network_provisioning: version: "~1.2.0" espressif/qrcode: version: "~0.1.0" ================================================ FILE: esp_schedule/examples/get_started/main/network/app_network.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "app_network.h" #include "esp_log.h" #include "esp_event.h" #include "esp_sntp.h" #include "esp_netif.h" #include "esp_wifi.h" #include "esp_mac.h" #include "qrcode.h" #include "lwip/ip_addr.h" #include "network_provisioning/manager.h" #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_BLE) #include "network_provisioning/scheme_ble.h" #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP) #include "network_provisioning/scheme_softap.h" #endif #include "esp_wifi_types.h" static const char *TAG = "app_network"; /* Static variables for event group and provisioning scheme */ static EventGroupHandle_t s_event_group = NULL; static esp_netif_t *s_sta_netif = NULL; static uint8_t s_mac_addr[6]; #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_1) /* Proof of Possession for secure provisioning */ #define ESP_SCHEDULE_EXAMPLE_PROV_SEC1_POP "12345678" #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_2) /* Username and password for protocomm security 2 */ #define ESP_SCHEDULE_EXAMPLE_PROV_SEC2_USERNAME "wifiprov" #define ESP_SCHEDULE_EXAMPLE_PROV_SEC2_PWD "abcd1234" /* This salt,verifier has been generated for username = "wifiprov" and password = "abcd1234" * IMPORTANT NOTE: For production cases, this must be unique to every device * and should come from device manufacturing partition.*/ static const char sec2_salt[] = { 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 }; static const char sec2_verifier[] = { 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba }; #endif /* Event handler for network provisioning events */ static void network_prov_event_handler(void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == NETWORK_PROV_EVENT) { switch (event_id) { case NETWORK_PROV_START: ESP_LOGI(TAG, "Network provisioning started"); break; case NETWORK_PROV_WIFI_CRED_RECV: { ESP_LOGI(TAG, "WiFi credentials received"); wifi_config_t *wifi_config = (wifi_config_t *)event_data; ESP_LOGI(TAG, "SSID: %s", wifi_config->sta.ssid); break; } case NETWORK_PROV_WIFI_CRED_SUCCESS: ESP_LOGI(TAG, "Network provisioning credentials accepted"); if (s_event_group) { xEventGroupSetBits(s_event_group, PROVISIONING_SUCCESS_BIT); } break; case NETWORK_PROV_WIFI_CRED_FAIL: ESP_LOGE(TAG, "Network provisioning credentials failed"); if (s_event_group) { xEventGroupSetBits(s_event_group, PROVISIONING_FAILED_BIT); } break; case NETWORK_PROV_END: ESP_LOGI(TAG, "Network provisioning ended"); break; default: ESP_LOGD(TAG, "Unhandled network provisioning event: %ld", event_id); break; } } } /* Event handler for WiFi events */ static void wifi_event_handler(void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT) { switch (event_id) { case WIFI_EVENT_STA_START: ESP_LOGI(TAG, "WiFi station started"); esp_wifi_connect(); break; case WIFI_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "WiFi station connected"); break; case WIFI_EVENT_STA_DISCONNECTED: { ESP_LOGW(TAG, "WiFi station disconnected"); wifi_event_sta_disconnected_t *event = (wifi_event_sta_disconnected_t *)event_data; ESP_LOGW(TAG, "Disconnect reason: %d", event->reason); if (s_event_group) { xEventGroupSetBits(s_event_group, NETWORK_DISCONNECTED_BIT); } esp_wifi_connect(); break; } default: ESP_LOGD(TAG, "Unhandled WiFi event: %ld", event_id); break; } } } /* Event handler for IP events */ static void ip_event_handler(void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == IP_EVENT) { switch (event_id) { case IP_EVENT_STA_GOT_IP: { ESP_LOGI(TAG, "WiFi connected, got IP address"); ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "IP Address: " IPSTR, IP2STR(&event->ip_info.ip)); if (s_event_group) { xEventGroupSetBits(s_event_group, NETWORK_CONNECTED_BIT); } break; } case IP_EVENT_STA_LOST_IP: ESP_LOGW(TAG, "WiFi disconnected, lost IP address"); if (s_event_group) { xEventGroupSetBits(s_event_group, NETWORK_DISCONNECTED_BIT); } break; default: ESP_LOGD(TAG, "Unhandled IP event: %ld", event_id); break; } } } /* Initialize WiFi interfaces */ static esp_err_t wifi_init(void) { esp_err_t ret; /* Get MAC address for service name generation */ ret = esp_read_mac(s_mac_addr, ESP_MAC_BASE); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read MAC address: %s", esp_err_to_name(ret)); return ret; } /* Initialize TCP/IP */ ret = esp_netif_init(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize TCP/IP: %s", esp_err_to_name(ret)); return ret; } /* Create default WiFi station interface */ s_sta_netif = esp_netif_create_default_wifi_sta(); if (s_sta_netif == NULL) { ESP_LOGE(TAG, "Failed to create WiFi station interface"); return ESP_FAIL; } /* Initialize WiFi with default configuration */ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ret = esp_wifi_init(&cfg); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize WiFi: %s", esp_err_to_name(ret)); return ret; } /* Set WiFi storage to RAM (credentials will be managed by provisioning) */ ret = esp_wifi_set_storage(WIFI_STORAGE_RAM); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set WiFi storage: %s", esp_err_to_name(ret)); return ret; } /* Register event handlers */ ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register WiFi event handler: %s", esp_err_to_name(ret)); return ret; } ret = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register IP event handler: %s", esp_err_to_name(ret)); return ret; } ESP_LOGI(TAG, "WiFi interfaces initialized"); return ESP_OK; } /* Start WiFi station */ static esp_err_t wifi_start_sta(void) { esp_err_t ret; ret = esp_wifi_set_mode(WIFI_MODE_STA); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set WiFi mode to STA: %s", esp_err_to_name(ret)); return ret; } ret = esp_wifi_start(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start WiFi station: %s", esp_err_to_name(ret)); return ret; } return ESP_OK; } /* Generate QR code for provisioning data */ static void display_qr_code(const char *service_name) { if (!service_name) { ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing."); return; } #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_BLE) const char *transport = "ble"; #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP) const char *transport = "softap"; #else ESP_LOGE(TAG, "Unknown transport; cannot generate QR code."); return; #endif static const char *version = "v1"; char payload[150]; #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_1) snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\",\"pop\":\"%s\",\"transport\":\"%s\"}", version, service_name, ESP_SCHEDULE_EXAMPLE_PROV_SEC1_POP, transport); #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_2) snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\",\"username\":\"%s\",\"pop\":\"%s\",\"transport\":\"%s\"}", version, service_name, ESP_SCHEDULE_EXAMPLE_PROV_SEC2_USERNAME, ESP_SCHEDULE_EXAMPLE_PROV_SEC2_PWD, transport); #else snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\",\"transport\":\"%s\"}", version, service_name, transport); #endif ESP_LOGI(TAG, "Scan this QR code from the ESP RainMaker phone app for Provisioning."); esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); esp_qrcode_generate(&cfg, payload); ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\nhttps://espressif.github.io/esp-jumpstart/qrcode.html?data=%s", payload); } /* Initialize network provisioning with event group handling */ esp_err_t app_network_init(EventGroupHandle_t event_group) { esp_err_t ret; s_event_group = event_group; /* Initialize WiFi interfaces first */ ret = wifi_init(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize WiFi"); return ret; } /* Initialize network provisioning manager */ network_prov_mgr_config_t config = { #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_BLE) .scheme = network_prov_scheme_ble, .scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE, #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP) .scheme = network_prov_scheme_softap, .scheme_event_handler = NETWORK_PROV_EVENT_HANDLER_NONE, #endif }; ret = network_prov_mgr_init(config); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize network provisioning manager: %s", esp_err_to_name(ret)); return ret; } /* Register event handlers */ ret = esp_event_handler_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, &network_prov_event_handler, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register network provisioning event handler: %s", esp_err_to_name(ret)); return ret; } ret = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register IP event handler: %s", esp_err_to_name(ret)); return ret; } ESP_LOGI(TAG, "Network provisioning initialized successfully"); return ESP_OK; } /* Start network provisioning and wait for connection */ esp_err_t app_network_start(EventGroupHandle_t event_group, uint32_t timeout_ms) { esp_err_t ret; bool provisioned = false; /* Check if device is already provisioned */ ret = network_prov_mgr_is_wifi_provisioned(&provisioned); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to check provisioning status: %s", esp_err_to_name(ret)); return ret; } if (!provisioned) { ESP_LOGI(TAG, "Device not provisioned, starting provisioning..."); /* Generate service name */ char service_name[32]; snprintf(service_name, sizeof(service_name), "ESP-Schedule-%02x%02x%02x", s_mac_addr[3], s_mac_addr[4], s_mac_addr[5]); /* Display QR code for provisioning */ display_qr_code(service_name); #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SOFTAP) /* For SoftAP, create WiFi AP interface */ esp_netif_create_default_wifi_ap(); #endif #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_0) ret = network_prov_mgr_start_provisioning(NETWORK_PROV_SECURITY_0, NULL, service_name, NULL); #endif #if defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_1) ret = network_prov_mgr_start_provisioning(NETWORK_PROV_SECURITY_1, ESP_SCHEDULE_EXAMPLE_PROV_SEC1_POP, service_name, NULL); #elif defined(CONFIG_ESP_SCHEDULE_EXAMPLE_PROV_SECURITY_VERSION_2) const network_prov_security2_params_t sec2_params = { .salt = sec2_salt, .salt_len = sizeof(sec2_salt), .verifier = sec2_verifier, .verifier_len = sizeof(sec2_verifier), }; ret = network_prov_mgr_start_provisioning(NETWORK_PROV_SECURITY_2, &sec2_params, service_name, NULL); #endif if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start network provisioning: %s", esp_err_to_name(ret)); return ret; } } else { ESP_LOGI(TAG, "Device already provisioned, starting Wi-Fi STA"); /* Start WiFi station directly */ ret = wifi_start_sta(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start WiFi station: %s", esp_err_to_name(ret)); return ret; } /* Post provisioning end event since we're already provisioned */ esp_event_post(NETWORK_PROV_EVENT, NETWORK_PROV_END, NULL, 0, portMAX_DELAY); } /* Wait for network connection */ EventBits_t bits = xEventGroupWaitBits(event_group, NETWORK_CONNECTED_BIT, pdTRUE, // Clear bits on exit pdFALSE, // Wait for connection only pdMS_TO_TICKS(timeout_ms)); if (bits & NETWORK_CONNECTED_BIT) { ESP_LOGI(TAG, "Network connected successfully"); return ESP_OK; } else { ESP_LOGE(TAG, "Network connection timed out"); return ESP_ERR_TIMEOUT; } } /* Start time synchronization */ void app_network_start_time_sync(EventGroupHandle_t event_group) { ESP_LOGI(TAG, "Starting SNTP time synchronization..."); /* Configure SNTP */ esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); esp_sntp_setservername(0, "pool.ntp.org"); esp_sntp_setservername(1, "time.google.com"); esp_sntp_setservername(2, "time.cloudflare.com"); /* Start SNTP */ esp_sntp_init(); /* Set a flag to indicate time sync has started */ if (event_group) { xEventGroupSetBits(event_group, TIME_SYNC_SUCCESS_BIT); } } /* Wait for time synchronization to complete */ esp_err_t app_network_wait_for_time_sync(EventGroupHandle_t event_group, uint32_t timeout_ms) { time_t now = 0; int retry = 0; const int retry_count = timeout_ms / 2000; // Check every 2 seconds const time_t time_threshold = 1609459200; // January 1, 2021 - reasonable baseline ESP_LOGI(TAG, "Waiting for time synchronization..."); while (retry < retry_count) { time(&now); if (now >= time_threshold && sntp_get_sync_status() != SNTP_SYNC_STATUS_RESET) { ESP_LOGI(TAG, "Time synchronized successfully! Current time: %s", ctime(&now)); if (event_group) { xEventGroupSetBits(event_group, TIME_SYNC_SUCCESS_BIT); } return ESP_OK; } ESP_LOGD(TAG, "Time sync attempt %d/%d, time: %ld, status: %d", retry + 1, retry_count, (long)now, sntp_get_sync_status()); vTaskDelay(2000 / portTICK_PERIOD_MS); retry++; } ESP_LOGW(TAG, "Time synchronization may have failed or time is before threshold"); if (event_group) { xEventGroupSetBits(event_group, TIME_SYNC_FAILED_BIT); } return ESP_ERR_TIMEOUT; } ================================================ FILE: esp_schedule/examples/get_started/main/network/app_network.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "network_provisioning/manager.h" #ifdef __cplusplus extern "C" { #endif /* Event group bits for network provisioning states */ #define NETWORK_CONNECTED_BIT BIT0 #define NETWORK_DISCONNECTED_BIT BIT1 #define PROVISIONING_SUCCESS_BIT BIT2 #define PROVISIONING_FAILED_BIT BIT3 #define TIME_SYNC_SUCCESS_BIT BIT4 #define TIME_SYNC_FAILED_BIT BIT5 /** * @brief Initialize network provisioning with event group handling * * This function initializes the network provisioning system and sets up event handlers * for network provisioning and IP events. It uses either BLE or SoftAP provisioning * based on the Kconfig selection. * * @param event_group Pointer to the event group that will be used for synchronization * @return esp_err_t ESP_OK on success, or error code on failure */ esp_err_t app_network_init(EventGroupHandle_t event_group); /** * @brief Start network provisioning and wait for connection * * This function checks if the device is already provisioned. If provisioned, * it starts WiFi station and waits for network connection. If not provisioned, * it starts the provisioning sequence and waits for network connection. * * @param event_group The event group to wait on * @param timeout_ms Timeout in milliseconds * @return esp_err_t ESP_OK if network connected, ESP_FAIL if failed or timed out */ esp_err_t app_network_start(EventGroupHandle_t event_group, uint32_t timeout_ms); /** * @brief Start time synchronization * * This function starts SNTP time synchronization and waits for it to complete. * * @param event_group The event group to signal completion on */ void app_network_start_time_sync(EventGroupHandle_t event_group); /** * @brief Wait for time synchronization to complete * * This function waits for time synchronization to complete successfully. * * @param event_group The event group to wait on * @param timeout_ms Timeout in milliseconds * @return esp_err_t ESP_OK if time sync succeeded, ESP_FAIL if failed or timed out */ esp_err_t app_network_wait_for_time_sync(EventGroupHandle_t event_group, uint32_t timeout_ms); #ifdef __cplusplus } #endif ================================================ FILE: esp_schedule/examples/get_started/sdkconfig.defaults ================================================ # Example project configuration for ESP Schedule # Enable daylight (sunrise/sunset) schedules for the example CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT=y # Example can become pretty big, so enable larger partition CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y ================================================ FILE: esp_schedule/examples/get_started/sdkconfig.defaults.esp32c2 ================================================ # ESP32C2 specific CONFIG_IDF_TARGET="esp32c2" CONFIG_XTAL_FREQ_26=y ================================================ FILE: esp_schedule/idf_component.yml ================================================ ## IDF Component Manager Manifest File version: "1.3.2" description: Task scheduling based on periodic and one-time events url: https://github.com/espressif/idf-extra-components/tree/master/components/esp_schedule repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/esp-rainmaker/issues dependencies: idf: ">=5.1" espressif/esp_daylight: version: "~1.0.1" override_path: "../esp_daylight" ================================================ FILE: esp_schedule/include/esp_schedule.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #include "esp_err.h" #include "sdkconfig.h" #ifdef __cplusplus extern "C" { #endif /** Schedule Handle */ typedef void *esp_schedule_handle_t; /** Maximum length of the schedule name allowed. This value cannot be more than 16 as it is used for NVS key. */ #define MAX_SCHEDULE_NAME_LEN 16 /** Callback for schedule trigger * * This callback is called when the schedule is triggered. * * @param[in] handle Schedule handle. * @param[in] priv_data Pointer to the private data passed while creating/editing the schedule. */ typedef void (*esp_schedule_trigger_cb_t)(esp_schedule_handle_t handle, void *priv_data); /** Callback for schedule timestamp * * This callback is called when the next trigger timestamp of the schedule is changed. This might be useful to check if * one time schedules have already passed while the device was powered off. * * @param[in] handle Schedule handle. * @param[in] next_timestamp timestamp at which the schedule will trigger next. * @param[in] priv_data Pointer to the user data passed while creating/editing the schedule. */ typedef void (*esp_schedule_timestamp_cb_t)(esp_schedule_handle_t handle, uint32_t next_timestamp, void *priv_data); /** Schedule type */ typedef enum esp_schedule_type { ESP_SCHEDULE_TYPE_INVALID = 0, ESP_SCHEDULE_TYPE_DAYS_OF_WEEK, ESP_SCHEDULE_TYPE_DATE, ESP_SCHEDULE_TYPE_RELATIVE, #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT ESP_SCHEDULE_TYPE_SUNRISE, ESP_SCHEDULE_TYPE_SUNSET, #endif } esp_schedule_type_t; /** Schedule days. Used for ESP_SCHEDULE_TYPE_DAYS_OF_WEEK. */ typedef enum esp_schedule_days { ESP_SCHEDULE_DAY_ONCE = 0, ESP_SCHEDULE_DAY_EVERYDAY = 0b1111111, ESP_SCHEDULE_DAY_MONDAY = 1 << 0, ESP_SCHEDULE_DAY_TUESDAY = 1 << 1, ESP_SCHEDULE_DAY_WEDNESDAY = 1 << 2, ESP_SCHEDULE_DAY_THURSDAY = 1 << 3, ESP_SCHEDULE_DAY_FRIDAY = 1 << 4, ESP_SCHEDULE_DAY_SATURDAY = 1 << 5, ESP_SCHEDULE_DAY_SUNDAY = 1 << 6, } esp_schedule_days_t; /** Schedule months. Used for ESP_SCHEDULE_TYPE_DATE. */ typedef enum esp_schedule_months { ESP_SCHEDULE_MONTH_ONCE = 0, ESP_SCHEDULE_MONTH_ALL = 0b111111111111, ESP_SCHEDULE_MONTH_JANUARY = 1 << 0, ESP_SCHEDULE_MONTH_FEBRUARY = 1 << 1, ESP_SCHEDULE_MONTH_MARCH = 1 << 2, ESP_SCHEDULE_MONTH_APRIL = 1 << 3, ESP_SCHEDULE_MONTH_MAY = 1 << 4, ESP_SCHEDULE_MONTH_JUNE = 1 << 5, ESP_SCHEDULE_MONTH_JULY = 1 << 6, ESP_SCHEDULE_MONTH_AUGUST = 1 << 7, ESP_SCHEDULE_MONTH_SEPTEMBER = 1 << 8, ESP_SCHEDULE_MONTH_OCTOBER = 1 << 9, ESP_SCHEDULE_MONTH_NOVEMBER = 1 << 10, ESP_SCHEDULE_MONTH_DECEMBER = 1 << 11, } esp_schedule_months_t; /** Trigger details of the schedule */ typedef struct esp_schedule_trigger { /** Type of schedule */ esp_schedule_type_t type; /** Hours in 24 hour format. Accepted values: 0-23 */ uint8_t hours; /** Minutes in the given hour. Accepted values: 0-59. */ uint8_t minutes; /** For type ESP_SCHEDULE_TYPE_DAYS_OF_WEEK and solar schedules with day-of-week patterns */ struct { /** 'OR' list of esp_schedule_days_t */ uint8_t repeat_days; } day; /** For type ESP_SCHEDULE_TYPE_DATE and solar schedules with specific date patterns */ struct { /** Day of the month. Accepted values: 1-31. */ uint8_t day; /* 'OR' list of esp_schedule_months_t */ uint16_t repeat_months; /** Year */ uint16_t year; /** If the schedule is to be repeated every year. */ bool repeat_every_year; } date; #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT /** For type ESP_SCHEDULE_TYPE_SUNRISE and ESP_SCHEDULE_TYPE_SUNSET * Uses day.repeat_days for day-of-week patterns (if date.day == 0) * Uses date.* fields for specific date patterns (if date.day != 0) * If both are 0, treated as single-time schedule */ struct { /** Latitude in decimal degrees (-90 to +90, positive North) */ double latitude; /** Longitude in decimal degrees (-180 to +180, positive East) */ double longitude; /** Offset in minutes from sunrise/sunset (positive = after, negative = before) */ int offset_minutes; } solar; #endif /** For type ESP_SCHEDULE_TYPE_SECONDS */ int relative_seconds; /** Used for passing the next schedule timestamp for * ESP_SCHEDULE_TYPE_RELATIVE */ time_t next_scheduled_time_utc; } esp_schedule_trigger_t; /** Schedule Validity * Start and end time within which the schedule will be applicable. */ typedef struct esp_schedule_validity { /* Start time as UTC timestamp */ time_t start_time; /* End time as UTC timestamp */ time_t end_time; } esp_schedule_validity_t; /** Schedule config */ typedef struct esp_schedule_config { /** Name of the schedule. This is like a primary key for the schedule. This is required. +1 for NULL termination. */ char name[MAX_SCHEDULE_NAME_LEN + 1]; /** Trigger details */ esp_schedule_trigger_t trigger; /** Trigger callback */ esp_schedule_trigger_cb_t trigger_cb; /** Timestamp callback */ esp_schedule_timestamp_cb_t timestamp_cb; /** Private data associated with the schedule. This will be passed to callbacks. */ void *priv_data; /** Validity of schedules. */ esp_schedule_validity_t validity; } esp_schedule_config_t; /** Initialize ESP Schedule * * This initializes ESP Schedule. This must be called first before calling any of the other APIs. * This API also gets all the schedules from NVS (if it has been enabled). * * Note: After calling this API, the pointers to the callbacks should be updated for all the schedules by calling * esp_schedule_get() followed by esp_schedule_edit() with the correct callbacks. * * @param[in] enable_nvs If NVS is to be enabled or not. * @param[in] nvs_partition (Optional) The NVS partition to be used. If NULL is passed, the default partition is used. * @param[out] schedule_count Number of active schedules found in NVS. * * @return Array of schedule handles if any schedules have been found. * @return NULL if no schedule is found in NVS (or if NVS is not enabled). */ esp_schedule_handle_t *esp_schedule_init(bool enable_nvs, char *nvs_partition, uint8_t *schedule_count); /** Create Schedule * * This API can be used to create a new schedule. The schedule still needs to be enabled using * esp_schedule_enable(). * * @param[in] schedule_config Configuration of the schedule to be created. * * @return Schedule handle if successfully created. * @return NULL in case of error. */ esp_schedule_handle_t esp_schedule_create(esp_schedule_config_t *schedule_config); /** Remove Schedule * * This API can be used to remove an existing schedule. * * @param[in] handle Schedule handle for the schedule to be removed. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t esp_schedule_delete(esp_schedule_handle_t handle); /** Edit Schedule * * This API can be used to edit an existing schedule. * The schedule name should be same as when the schedule was created. The complete config must be provided * or the previously stored config might be over-written. * * Note: If a schedule is edited when it is on-going, the new changes will not be reflected. * You will need to disable the schedule, edit it, and then enable it again. * * @param[in] handle Schedule handle for the schedule to be edited. * @param[in] schedule_config Configuration of the schedule to be edited. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t esp_schedule_edit(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config); /** Enable Schedule * * This API can be used to enable an existing schedule. * It can be used to enable a schedule after it has been created using esp_schedule_create() * or if the schedule has been disabled using esp_schedule_disable(). * * @param[in] handle Schedule handle for the schedule to be enabled. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t esp_schedule_enable(esp_schedule_handle_t handle); /** Disable Schedule * * This API can be used to disable an on-going schedule. * It does not remove the schedule, just stops it. The schedule can be enabled again using * esp_schedule_enable(). * * @param[in] handle Schedule handle for the schedule to be disabled. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t esp_schedule_disable(esp_schedule_handle_t handle); /** Get Schedule * * This API can be used to get details of an existing schedule. * The schedule_config is populated with the schedule details. * * @param[in] handle Schedule handle. * @param[out] schedule_config Details of the schedule whose handle is passed. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t esp_schedule_get(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config); #ifdef __cplusplus } #endif ================================================ FILE: esp_schedule/src/esp_schedule.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "esp_sntp.h" #include "esp_daylight.h" #include "esp_schedule_internal.h" static const char *TAG = "esp_schedule"; #define SECONDS_TILL_2020 ((2020 - 1970) * 365 * 24 * 3600) #define SECONDS_IN_DAY (60 * 60 * 24) static bool init_done = false; static int esp_schedule_get_no_of_days(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time) { /* for day, monday = 0, sunday = 6. */ int next_day = 0; /* struct tm has tm_wday with sunday as 0. Whereas we have monday as 0. Converting struct tm to our format */ int today = ((current_time->tm_wday + 7 - 1) % 7); esp_schedule_days_t today_bit = 1 << today; uint8_t repeat_days = trigger->day.repeat_days; int current_seconds = (current_time->tm_hour * 60 + current_time->tm_min) * 60 + current_time->tm_sec; int schedule_seconds = (schedule_time->tm_hour * 60 + schedule_time->tm_min) * 60; /* Handling for one time schedule */ if (repeat_days == ESP_SCHEDULE_DAY_ONCE) { if (schedule_seconds > current_seconds) { /* The schedule is today and is yet to go off */ return 0; } else { /* The schedule is tomorrow */ return 1; } } /* Handling for repeating schedules */ /* Check if it is today */ if ((repeat_days & today_bit)) { if (schedule_seconds > current_seconds) { /* The schedule is today and is yet to go off. */ return 0; } } /* Check if it is this week or next week */ if ((repeat_days & (today_bit ^ 0xFF)) > today_bit) { /* Next schedule is yet to come in this week */ next_day = ffs(repeat_days & (0xFF << (today + 1))) - 1; return (next_day - today); } else { /* First scheduled day of the next week */ next_day = ffs(repeat_days) - 1; if (next_day == today) { /* Same day, next week */ return 7; } return (7 - today + next_day); } ESP_LOGE(TAG, "No of days could not be found. This should not happen."); return 0; } static uint8_t esp_schedule_get_next_month(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time) { int current_seconds = (current_time->tm_hour * 60 + current_time->tm_min) * 60 + current_time->tm_sec; int schedule_seconds = (schedule_time->tm_hour * 60 + schedule_time->tm_min) * 60; /* +1 is because struct tm has months starting from 0, whereas we have them starting from 1 */ uint8_t current_month = current_time->tm_mon + 1; /* -1 because month_bit starts from 0b1. So for January, it should be 1 << 0. And current_month starts from 1. */ uint16_t current_month_bit = 1 << (current_month - 1); uint8_t next_schedule_month = 0; uint16_t repeat_months = trigger->date.repeat_months; /* Check if month is not specified */ if (repeat_months == ESP_SCHEDULE_MONTH_ONCE) { if (trigger->date.day == current_time->tm_mday) { /* The schedule day is same. Check if time has already passed */ if (schedule_seconds > current_seconds) { /* The schedule is today and is yet to go off */ return current_month; } else { /* Today's time has passed */ return (current_month + 1); } } else if (trigger->date.day > current_time->tm_mday) { /* The day is yet to come in this month */ return current_month; } else { /* The day has passed in the current month */ return (current_month + 1); } } /* Check if schedule is not this year itself, it is in future. */ if (trigger->date.year > (current_time->tm_year + 1900)) { /* Find first schedule month of next year */ next_schedule_month = ffs(repeat_months); /* Year will be handled by the caller. So no need to add any additional months */ return next_schedule_month; } /* Check if schedule is this month and is yet to come */ if (current_month_bit & repeat_months) { if (trigger->date.day == current_time->tm_mday) { /* The schedule day is same. Check if time has already passed */ if (schedule_seconds > current_seconds) { /* The schedule is today and is yet to go off */ return current_month; } } if (trigger->date.day > current_time->tm_mday) { /* The day is yet to come in this month */ return current_month; } } /* Check if schedule is this year */ if ((repeat_months & (current_month_bit ^ 0xFFFF)) > current_month_bit) { /* Next schedule month is yet to come in this year */ next_schedule_month = ffs(repeat_months & (0xFFFF << (current_month))); return next_schedule_month; } /* Check if schedule is for this year and does not repeat */ if (!trigger->date.repeat_every_year) { /* For yearly repeating schedules with year=0, treat as repeating */ if (trigger->date.year != 0 && trigger->date.year <= (current_time->tm_year + 1900)) { ESP_LOGE(TAG, "Schedule does not repeat next year, but get_next_month has been called."); return 0; } } /* Schedule is not this year */ /* Find first schedule month of next year */ next_schedule_month = ffs(repeat_months); /* +12 because the schedule is next year */ return (next_schedule_month + 12); } static uint16_t esp_schedule_get_next_year(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time) { uint16_t current_year = current_time->tm_year + 1900; uint16_t schedule_year = trigger->date.year; if (schedule_year > current_year) { return schedule_year; } /* If the schedule is set to repeat_every_year, we return the current year */ /* If the schedule has already passed in this year, we still return current year, as the additional months will be handled in get_next_month */ return current_year; } #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT /* Helper function to calculate solar time for a specific date */ static time_t esp_schedule_calc_solar_time_for_date(const esp_schedule_trigger_t *trigger, int year, int month, int day, const char *schedule_name) { time_t sunrise_utc, sunset_utc; bool calc_ok = esp_daylight_calc_sunrise_sunset_utc( year, month, day, trigger->solar.latitude, trigger->solar.longitude, &sunrise_utc, &sunset_utc); if (!calc_ok) { ESP_LOGW(TAG, "Failed to calculate sunrise/sunset for date %04d-%02d-%02d for %s (likely polar night/day condition)", year, month, day, schedule_name); return 0; } time_t solar_time = (trigger->type == ESP_SCHEDULE_TYPE_SUNRISE) ? sunrise_utc : sunset_utc; return esp_daylight_apply_offset(solar_time, trigger->solar.offset_minutes); } /* Helper function to handle logging and timer calculation for solar schedules */ static int32_t esp_schedule_finalize_solar_time(time_t solar_time, time_t now, const esp_schedule_trigger_t *trigger, const char *schedule_name) { char time_str[64]; struct tm schedule_time; /* Convert solar time to local time for display and DST handling */ localtime_r(&solar_time, &schedule_time); /* Print schedule time */ memset(time_str, 0, sizeof(time_str)); strftime(time_str, sizeof(time_str), "%c %z[%Z]", &schedule_time); ESP_LOGI(TAG, "Schedule %s (%s%+d min) will be active on: %s. DST: %s", schedule_name, (trigger->type == ESP_SCHEDULE_TYPE_SUNRISE) ? "sunrise" : "sunset", trigger->solar.offset_minutes, time_str, schedule_time.tm_isdst ? "Yes" : "No"); /* Simple epoch-based timer calculation */ int32_t timer_seconds = (int32_t)difftime(solar_time, now); /* With proactive logic, this should not happen, but log if it does */ if (timer_seconds < 0) { ESP_LOGW(TAG, "Unexpected: Solar schedule time has passed (%" PRId32 " seconds ago). This should have been handled proactively.", -timer_seconds); /* Return the negative value to help debug - caller will handle this as error */ } return timer_seconds; } #endif /* CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT */ static uint32_t esp_schedule_get_next_schedule_time_diff(const char *schedule_name, esp_schedule_trigger_t *trigger) { struct tm current_time, schedule_time; time_t now; char time_str[64]; int32_t time_diff; /* Get current time */ time(&now); /* Handling ESP_SCHEDULE_TYPE_RELATIVE first since it doesn't require any * computation based on days, hours, minutes, etc. */ if (trigger->type == ESP_SCHEDULE_TYPE_RELATIVE) { /* If next scheduled time is already set, just compute the difference * between current time and next scheduled time and return that diff. */ time_t target; if (trigger->next_scheduled_time_utc > 0) { target = (time_t)trigger->next_scheduled_time_utc; time_diff = difftime(target, now); } else { target = now + (time_t)trigger->relative_seconds; time_diff = trigger->relative_seconds; } localtime_r(&target, &schedule_time); trigger->next_scheduled_time_utc = mktime(&schedule_time); /* Print schedule time */ memset(time_str, 0, sizeof(time_str)); strftime(time_str, sizeof(time_str), "%c %z[%Z]", &schedule_time); ESP_LOGI(TAG, "Schedule %s will be active on: %s. DST: %s", schedule_name, time_str, schedule_time.tm_isdst ? "Yes" : "No"); return time_diff; } /* Handle solar-based schedules (sunrise/sunset) */ #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT if (trigger->type == ESP_SCHEDULE_TYPE_SUNRISE || trigger->type == ESP_SCHEDULE_TYPE_SUNSET) { time_t solar_time = 0; /* Start with current local time for day calculations */ localtime_r(&now, ¤t_time); /* Determine schedule pattern using unified approach */ if (trigger->date.day != 0) { /* Date-based solar schedule - check if today's solar time + offset has passed */ struct tm schedule_time = current_time; schedule_time.tm_mday = trigger->date.day; /* For same day, check if solar time + offset has passed before deciding year */ if (trigger->date.day == current_time.tm_mday) { uint8_t current_month = current_time.tm_mon + 1; uint16_t repeat_months = trigger->date.repeat_months; uint16_t current_month_bit = 1 << (current_month - 1); /* Check if this month is in the repeat pattern */ if (current_month_bit & repeat_months) { /* Calculate today's solar time + offset */ time_t today_solar = esp_schedule_calc_solar_time_for_date(trigger, current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday, schedule_name); /* If today's solar time + offset hasn't passed, use today */ if (today_solar > 0 && today_solar > now) { schedule_time.tm_mon = current_time.tm_mon; schedule_time.tm_year = current_time.tm_year; } else { /* Solar time has passed, use next occurrence */ schedule_time.tm_mon = esp_schedule_get_next_month(trigger, ¤t_time, &schedule_time) - 1; schedule_time.tm_year = esp_schedule_get_next_year(trigger, ¤t_time, &schedule_time) - 1900; } } else { /* Not this month, use normal logic */ schedule_time.tm_mon = esp_schedule_get_next_month(trigger, ¤t_time, &schedule_time) - 1; schedule_time.tm_year = esp_schedule_get_next_year(trigger, ¤t_time, &schedule_time) - 1900; } } else { /* Different day, use normal logic */ schedule_time.tm_mon = esp_schedule_get_next_month(trigger, ¤t_time, &schedule_time) - 1; schedule_time.tm_year = esp_schedule_get_next_year(trigger, ¤t_time, &schedule_time) - 1900; } if (schedule_time.tm_mon < 0) { ESP_LOGE(TAG, "Invalid month found for solar schedule: %s", schedule_name); return 0; } if (schedule_time.tm_mon >= 12) { schedule_time.tm_year += schedule_time.tm_mon / 12; schedule_time.tm_mon = schedule_time.tm_mon % 12; } mktime(&schedule_time); /* Calculate solar time for the determined date */ solar_time = esp_schedule_calc_solar_time_for_date(trigger, schedule_time.tm_year + 1900, schedule_time.tm_mon + 1, schedule_time.tm_mday, schedule_name); } else if (trigger->day.repeat_days != 0) { /* Day-of-week solar schedule - check if today's solar time + offset has passed */ struct tm schedule_time = current_time; /* Check if today is one of the scheduled days */ int today = current_time.tm_wday == 0 ? 7 : current_time.tm_wday; /* Convert Sunday from 0 to 7 */ uint8_t today_bit = 1 << (today - 1); /* Monday=bit0, Tuesday=bit1, etc. */ if (trigger->day.repeat_days & today_bit) { /* Today is a scheduled day, check if solar time + offset has passed */ time_t today_solar = esp_schedule_calc_solar_time_for_date(trigger, current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday, schedule_name); /* If today's solar time + offset hasn't passed, use today */ if (today_solar > 0 && today_solar > now) { /* Use today */ solar_time = today_solar; } else { /* Solar time has passed, find next scheduled day */ int no_of_days = esp_schedule_get_no_of_days(trigger, ¤t_time, &schedule_time); schedule_time.tm_mday += no_of_days; mktime(&schedule_time); solar_time = esp_schedule_calc_solar_time_for_date(trigger, schedule_time.tm_year + 1900, schedule_time.tm_mon + 1, schedule_time.tm_mday, schedule_name); } } else { /* Today is not a scheduled day, use normal logic */ int no_of_days = esp_schedule_get_no_of_days(trigger, ¤t_time, &schedule_time); schedule_time.tm_mday += no_of_days; mktime(&schedule_time); solar_time = esp_schedule_calc_solar_time_for_date(trigger, schedule_time.tm_year + 1900, schedule_time.tm_mon + 1, schedule_time.tm_mday, schedule_name); } } else { /* Single-time solar schedule - use logic similar to regular schedules */ solar_time = esp_schedule_calc_solar_time_for_date(trigger, current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday, schedule_name); /* If time has passed today, calculate for tomorrow (like regular schedules) */ if (solar_time > 0 && solar_time <= now) { struct tm tomorrow_time; localtime_r(&now, &tomorrow_time); // Use fresh local time tomorrow_time.tm_mday += 1; mktime(&tomorrow_time); solar_time = esp_schedule_calc_solar_time_for_date(trigger, tomorrow_time.tm_year + 1900, tomorrow_time.tm_mon + 1, tomorrow_time.tm_mday, schedule_name); /* If tomorrow's time is still in the past (due to large negative offset), try day after tomorrow */ if (solar_time > 0 && solar_time <= now) { tomorrow_time.tm_mday += 1; mktime(&tomorrow_time); solar_time = esp_schedule_calc_solar_time_for_date(trigger, tomorrow_time.tm_year + 1900, tomorrow_time.tm_mon + 1, tomorrow_time.tm_mday, schedule_name); } } } /* Return error if solar calculation failed */ if (solar_time == 0) { ESP_LOGW(TAG, "Solar schedule %s cannot be calculated (no sunrise/sunset at this location/date)", schedule_name); return 0; } /* Calculate timer - should always be positive since we proactively handle past times */ time_diff = esp_schedule_finalize_solar_time(solar_time, now, trigger, schedule_name); if (time_diff < 0) { /* This should not happen with proactive logic, but handle gracefully */ ESP_LOGE(TAG, "Solar schedule time calculation error for %s (got %" PRId32 " seconds)", schedule_name, time_diff); return 0; } /* Store the final scheduled time - use raw UTC solar time for ts field (phone apps need UTC) */ trigger->next_scheduled_time_utc = solar_time; return time_diff; } #endif /* CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT */ localtime_r(&now, ¤t_time); /* Get schedule time */ localtime_r(&now, &schedule_time); schedule_time.tm_sec = 0; schedule_time.tm_min = trigger->minutes; schedule_time.tm_hour = trigger->hours; mktime(&schedule_time); /* Adjust schedule day */ if (trigger->type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) { int no_of_days = 0; no_of_days = esp_schedule_get_no_of_days(trigger, ¤t_time, &schedule_time); schedule_time.tm_sec += no_of_days * SECONDS_IN_DAY; } if (trigger->type == ESP_SCHEDULE_TYPE_DATE) { schedule_time.tm_mday = trigger->date.day; schedule_time.tm_mon = esp_schedule_get_next_month(trigger, ¤t_time, &schedule_time) - 1; schedule_time.tm_year = esp_schedule_get_next_year(trigger, ¤t_time, &schedule_time) - 1900; if (schedule_time.tm_mon < 0) { ESP_LOGE(TAG, "Invalid month found: %d. Setting it to next month.", schedule_time.tm_mon); schedule_time.tm_mon = current_time.tm_mon + 1; } if (schedule_time.tm_mon >= 12) { schedule_time.tm_year += schedule_time.tm_mon / 12; schedule_time.tm_mon = schedule_time.tm_mon % 12; } } mktime(&schedule_time); /* Adjust time according to DST */ time_t dst_adjust = 0; if (!current_time.tm_isdst && schedule_time.tm_isdst) { dst_adjust = -3600; } else if (current_time.tm_isdst && !schedule_time.tm_isdst) { dst_adjust = 3600; } ESP_LOGD(TAG, "DST adjust seconds: %lld", (long long) dst_adjust); schedule_time.tm_sec += dst_adjust; mktime(&schedule_time); /* Print schedule time */ memset(time_str, 0, sizeof(time_str)); strftime(time_str, sizeof(time_str), "%c %z[%Z]", &schedule_time); ESP_LOGI(TAG, "Schedule %s will be active on: %s. DST: %s", schedule_name, time_str, schedule_time.tm_isdst ? "Yes" : "No"); /* Calculate difference */ time_diff = difftime((mktime(&schedule_time)), mktime(¤t_time)); /* For one time schedules to check for expiry after a reboot. If NVS is enabled, this should be stored in NVS. */ trigger->next_scheduled_time_utc = mktime(&schedule_time); return time_diff; } static bool esp_schedule_is_expired(esp_schedule_trigger_t *trigger) { time_t current_timestamp = 0; struct tm current_time = {0}; time(¤t_timestamp); localtime_r(¤t_timestamp, ¤t_time); if (trigger->type == ESP_SCHEDULE_TYPE_RELATIVE) { if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) { /* Relative seconds based schedule has expired */ return true; } else if (trigger->next_scheduled_time_utc == 0) { /* Schedule has been disabled, so it is as good as expired. */ return true; } } else if (trigger->type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) { if (trigger->day.repeat_days == ESP_SCHEDULE_DAY_ONCE) { if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) { /* One time schedule has expired */ return true; } else if (trigger->next_scheduled_time_utc == 0) { /* Schedule has been disabled, so it is as good as expired. */ return true; } } #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT } else if (trigger->type == ESP_SCHEDULE_TYPE_SUNRISE || trigger->type == ESP_SCHEDULE_TYPE_SUNSET) { /* Check if this is a single-time solar schedule */ if (trigger->date.day == 0 && trigger->day.repeat_days == 0) { if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) { /* One time solar schedule has expired */ return true; } else if (trigger->next_scheduled_time_utc == 0) { /* Schedule has been disabled, so it is as good as expired. */ return true; } } /* Repeating solar schedules (day-of-week or date-based) never expire - they recalculate */ #endif } else if (trigger->type == ESP_SCHEDULE_TYPE_DATE) { if (trigger->date.repeat_months == 0) { if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) { /* One time schedule has expired */ return true; } else { return false; } } if (trigger->date.repeat_every_year == true) { return false; } struct tm schedule_time = {0}; localtime_r(¤t_timestamp, &schedule_time); schedule_time.tm_sec = 0; schedule_time.tm_min = trigger->minutes; schedule_time.tm_hour = trigger->hours; schedule_time.tm_mday = trigger->date.day; /* For expiry, just check the last month of the repeat_months. */ /* '-1' because struct tm has months starting from 0 and we have months starting from 1. */ schedule_time.tm_mon = fls(trigger->date.repeat_months) - 1; /* '-1900' because struct tm has number of years after 1900 */ schedule_time.tm_year = trigger->date.year - 1900; time_t schedule_timestamp = mktime(&schedule_time); if (schedule_timestamp < current_timestamp) { return true; } } else { /* Invalid type. Mark as expired */ return true; } return false; } static void esp_schedule_stop_timer(esp_schedule_t *schedule) { xTimerStop(schedule->timer, portMAX_DELAY); } static void esp_schedule_start_timer(esp_schedule_t *schedule) { time_t current_time = 0; time(¤t_time); if (current_time < SECONDS_TILL_2020) { ESP_LOGE(TAG, "Time is not updated"); return; } schedule->next_scheduled_time_diff = esp_schedule_get_next_schedule_time_diff(schedule->name, &schedule->trigger); /* Check if schedule calculation failed (returns 0) */ if (schedule->next_scheduled_time_diff == 0) { ESP_LOGW(TAG, "Schedule %s calculation failed or returned invalid time. Skipping timer creation.", schedule->name); /* Reset timestamp to indicate schedule is not active */ schedule->trigger.next_scheduled_time_utc = 0; return; } ESP_LOGI(TAG, "Starting a timer for %"PRIu32" seconds for schedule %s", schedule->next_scheduled_time_diff, schedule->name); if (schedule->timestamp_cb) { schedule->timestamp_cb((esp_schedule_handle_t)schedule, schedule->trigger.next_scheduled_time_utc, schedule->priv_data); } xTimerStop(schedule->timer, portMAX_DELAY); xTimerChangePeriod(schedule->timer, (schedule->next_scheduled_time_diff * 1000) / portTICK_PERIOD_MS, portMAX_DELAY); } static void esp_schedule_common_timer_cb(TimerHandle_t timer) { void *priv_data = pvTimerGetTimerID(timer); if (priv_data == NULL) { return; } esp_schedule_t *schedule = (esp_schedule_t *)priv_data; time_t now; time(&now); struct tm validity_time; char time_str[64] = {0}; if (schedule->validity.start_time != 0) { if (now < schedule->validity.start_time) { memset(time_str, 0, sizeof(time_str)); localtime_r(&schedule->validity.start_time, &validity_time); strftime(time_str, sizeof(time_str), "%c %z[%Z]", &validity_time); ESP_LOGW(TAG, "Schedule %s skipped. It will be active only after: %s. DST: %s.", schedule->name, time_str, validity_time.tm_isdst ? "Yes" : "No"); /* TODO: Start the timer such that the next time it triggers, it will be within the valid window. * Currently, it will just keep triggering and then get skipped if not in valid range. */ goto restart_schedule; } } if (schedule->validity.end_time != 0) { if (now > schedule->validity.end_time) { localtime_r(&schedule->validity.end_time, &validity_time); strftime(time_str, sizeof(time_str), "%c %z[%Z]", &validity_time); ESP_LOGW(TAG, "Schedule %s skipped. It can't be active after: %s. DST: %s.", schedule->name, time_str, validity_time.tm_isdst ? "Yes" : "No"); /* Return from here will ensure that the timer does not start again for this schedule */ return; } } ESP_LOGI(TAG, "Schedule %s triggered", schedule->name); if (schedule->trigger_cb) { schedule->trigger_cb((esp_schedule_handle_t)schedule, schedule->priv_data); } restart_schedule: if (esp_schedule_is_expired(&schedule->trigger)) { /* Not deleting the schedule here. Just not starting it again. */ return; } esp_schedule_start_timer(schedule); } static void esp_schedule_delete_timer(esp_schedule_t *schedule) { xTimerDelete(schedule->timer, portMAX_DELAY); } static void esp_schedule_create_timer(esp_schedule_t *schedule) { if (esp_schedule_nvs_is_enabled()) { /* This is just used for calculating next_scheduled_time_utc for ESP_SCHEDULE_DAY_ONCE (in case of ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) or for ESP_SCHEDULE_MONTH_ONCE (in case of ESP_SCHEDULE_TYPE_DATE), and only used when NVS is enabled. And if NVS is enabled, time will already be synced and the time will be correctly calculated. */ schedule->next_scheduled_time_diff = esp_schedule_get_next_schedule_time_diff(schedule->name, &schedule->trigger); } /* Temporarily setting the timer for 1 (anything greater than 0) tick. This will get changed when xTimerChangePeriod() is called. */ schedule->timer = xTimerCreate("schedule", 1, pdFALSE, (void *)schedule, esp_schedule_common_timer_cb); } esp_err_t esp_schedule_get(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config) { if (schedule_config == NULL) { return ESP_ERR_INVALID_ARG; } if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_schedule_t *schedule = (esp_schedule_t *)handle; strlcpy(schedule_config->name, schedule->name, sizeof(schedule_config->name)); schedule_config->trigger.type = schedule->trigger.type; schedule_config->trigger.hours = schedule->trigger.hours; schedule_config->trigger.minutes = schedule->trigger.minutes; if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) { schedule_config->trigger.day.repeat_days = schedule->trigger.day.repeat_days; } else if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DATE) { schedule_config->trigger.date.day = schedule->trigger.date.day; schedule_config->trigger.date.repeat_months = schedule->trigger.date.repeat_months; schedule_config->trigger.date.year = schedule->trigger.date.year; schedule_config->trigger.date.repeat_every_year = schedule->trigger.date.repeat_every_year; } schedule_config->trigger_cb = schedule->trigger_cb; schedule_config->timestamp_cb = schedule->timestamp_cb; schedule_config->priv_data = schedule->priv_data; schedule_config->validity = schedule->validity; return ESP_OK; } esp_err_t esp_schedule_enable(esp_schedule_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_schedule_t *schedule = (esp_schedule_t *)handle; esp_schedule_start_timer(schedule); return ESP_OK; } esp_err_t esp_schedule_disable(esp_schedule_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_schedule_t *schedule = (esp_schedule_t *)handle; esp_schedule_stop_timer(schedule); /* Disabling a schedule should also reset the next_scheduled_time. * It would be re-computed after enabling. */ schedule->trigger.next_scheduled_time_utc = 0; return ESP_OK; } static esp_err_t esp_schedule_set(esp_schedule_t *schedule, esp_schedule_config_t *schedule_config) { /* Setting everything apart from name. */ schedule->trigger.type = schedule_config->trigger.type; if (schedule->trigger.type == ESP_SCHEDULE_TYPE_RELATIVE) { schedule->trigger.relative_seconds = schedule_config->trigger.relative_seconds; schedule->trigger.next_scheduled_time_utc = schedule_config->trigger.next_scheduled_time_utc; } else { schedule->trigger.hours = schedule_config->trigger.hours; schedule->trigger.minutes = schedule_config->trigger.minutes; if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) { schedule->trigger.day.repeat_days = schedule_config->trigger.day.repeat_days; } else if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DATE) { schedule->trigger.date.day = schedule_config->trigger.date.day; schedule->trigger.date.repeat_months = schedule_config->trigger.date.repeat_months; schedule->trigger.date.year = schedule_config->trigger.date.year; schedule->trigger.date.repeat_every_year = schedule_config->trigger.date.repeat_every_year; #if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT } else if (schedule->trigger.type == ESP_SCHEDULE_TYPE_SUNRISE || schedule->trigger.type == ESP_SCHEDULE_TYPE_SUNSET) { schedule->trigger.solar.latitude = schedule_config->trigger.solar.latitude; schedule->trigger.solar.longitude = schedule_config->trigger.solar.longitude; schedule->trigger.solar.offset_minutes = schedule_config->trigger.solar.offset_minutes; /* Copy day and date fields for unified solar schedule approach */ schedule->trigger.day.repeat_days = schedule_config->trigger.day.repeat_days; schedule->trigger.date.day = schedule_config->trigger.date.day; schedule->trigger.date.repeat_months = schedule_config->trigger.date.repeat_months; schedule->trigger.date.year = schedule_config->trigger.date.year; schedule->trigger.date.repeat_every_year = schedule_config->trigger.date.repeat_every_year; #endif } } schedule->trigger_cb = schedule_config->trigger_cb; schedule->timestamp_cb = schedule_config->timestamp_cb; schedule->priv_data = schedule_config->priv_data; schedule->validity = schedule_config->validity; esp_schedule_nvs_add(schedule); return ESP_OK; } esp_err_t esp_schedule_edit(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config) { if (handle == NULL || schedule_config == NULL) { return ESP_ERR_INVALID_ARG; } esp_schedule_t *schedule = (esp_schedule_t *)handle; if (strncmp(schedule->name, schedule_config->name, sizeof(schedule->name)) != 0) { ESP_LOGE(TAG, "Schedule name mismatch. Expected: %s, Passed: %s", schedule->name, schedule_config->name); return ESP_FAIL; } /* Editing a schedule with relative time should also reset it. */ if (schedule->trigger.type == ESP_SCHEDULE_TYPE_RELATIVE) { schedule->trigger.next_scheduled_time_utc = 0; } esp_schedule_set(schedule, schedule_config); ESP_LOGD(TAG, "Schedule %s edited", schedule->name); return ESP_OK; } esp_err_t esp_schedule_delete(esp_schedule_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } esp_schedule_t *schedule = (esp_schedule_t *)handle; ESP_LOGI(TAG, "Deleting schedule %s", schedule->name); if (schedule->timer) { esp_schedule_stop_timer(schedule); esp_schedule_delete_timer(schedule); } esp_schedule_nvs_remove(schedule); free(schedule); return ESP_OK; } esp_schedule_handle_t esp_schedule_create(esp_schedule_config_t *schedule_config) { if (schedule_config == NULL) { return NULL; } if (strlen(schedule_config->name) <= 0) { ESP_LOGE(TAG, "Set schedule failed. Please enter a unique valid name for the schedule."); return NULL; } if (schedule_config->trigger.type == ESP_SCHEDULE_TYPE_INVALID) { ESP_LOGE(TAG, "Schedule type is invalid."); return NULL; } esp_schedule_t *schedule = (esp_schedule_t *)MEM_CALLOC_EXTRAM(1, sizeof(esp_schedule_t)); if (schedule == NULL) { ESP_LOGE(TAG, "Could not allocate handle"); return NULL; } strlcpy(schedule->name, schedule_config->name, sizeof(schedule->name)); esp_schedule_set(schedule, schedule_config); esp_schedule_create_timer(schedule); ESP_LOGD(TAG, "Schedule %s created", schedule->name); return (esp_schedule_handle_t)schedule; } esp_schedule_handle_t *esp_schedule_init(bool enable_nvs, char *nvs_partition, uint8_t *schedule_count) { if (!esp_sntp_enabled()) { ESP_LOGI(TAG, "Initializing SNTP"); esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); esp_sntp_setservername(0, "pool.ntp.org"); esp_sntp_init(); } if (!enable_nvs) { return NULL; } /* Wait for time to be updated here */ /* Below this is initialising schedules from NVS */ esp_schedule_nvs_init(nvs_partition); /* Get handle list from NVS */ esp_schedule_handle_t *handle_list = NULL; *schedule_count = 0; handle_list = esp_schedule_nvs_get_all(schedule_count); if (handle_list == NULL) { ESP_LOGI(TAG, "No schedules found in NVS"); return NULL; } ESP_LOGI(TAG, "Schedules found in NVS: %"PRIu8, *schedule_count); /* Start/Delete the schedules */ esp_schedule_t *schedule = NULL; for (size_t handle_count = 0; handle_count < *schedule_count; handle_count++) { schedule = (esp_schedule_t *)handle_list[handle_count]; schedule->trigger_cb = NULL; schedule->timer = NULL; /* Check for ONCE and expired schedules and delete them. */ if (esp_schedule_is_expired(&schedule->trigger)) { /* This schedule has already expired. */ ESP_LOGI(TAG, "Schedule %s does not repeat and has already expired. Deleting it.", schedule->name); esp_schedule_delete((esp_schedule_handle_t)schedule); /* Removing the schedule from the list */ handle_list[handle_count] = handle_list[*schedule_count - 1]; (*schedule_count)--; handle_count--; continue; } esp_schedule_create_timer(schedule); esp_schedule_start_timer(schedule); } init_done = true; return handle_list; } ================================================ FILE: esp_schedule/src/esp_schedule_internal.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "freertos/FreeRTOS.h" #include "freertos/timers.h" #include "esp_schedule.h" #include "esp_heap_caps.h" /** Memory allocation macros for external RAM */ #if ((CONFIG_SPIRAM || CONFIG_SPIRAM_SUPPORT) && \ (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC)) #define MEM_ALLOC_EXTRAM(size) heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL) #define MEM_CALLOC_EXTRAM(num, size) heap_caps_calloc_prefer(num, size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL) #define MEM_REALLOC_EXTRAM(ptr, size) heap_caps_realloc_prefer(ptr, size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL) #else #define MEM_ALLOC_EXTRAM(size) malloc(size) #define MEM_CALLOC_EXTRAM(num, size) calloc(num, size) #define MEM_REALLOC_EXTRAM(ptr, size) realloc(ptr, size) #endif typedef struct esp_schedule { char name[MAX_SCHEDULE_NAME_LEN + 1]; esp_schedule_trigger_t trigger; uint32_t next_scheduled_time_diff; TimerHandle_t timer; esp_schedule_trigger_cb_t trigger_cb; esp_schedule_timestamp_cb_t timestamp_cb; void *priv_data; esp_schedule_validity_t validity; } esp_schedule_t; esp_err_t esp_schedule_nvs_add(esp_schedule_t *schedule); esp_err_t esp_schedule_nvs_remove(esp_schedule_t *schedule); esp_schedule_handle_t *esp_schedule_nvs_get_all(uint8_t *schedule_count); bool esp_schedule_nvs_is_enabled(void); esp_err_t esp_schedule_nvs_init(char *nvs_partition); ================================================ FILE: esp_schedule/src/esp_schedule_nvs.c ================================================ // Copyright 2025 Espressif Systems (Shanghai) CO LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "esp_log.h" #include "nvs.h" #include "esp_schedule_internal.h" static const char *TAG = "esp_schedule_nvs"; #define ESP_SCHEDULE_NVS_NAMESPACE "schd" #define ESP_SCHEDULE_COUNT_KEY "schd_count" static char *esp_schedule_nvs_partition = NULL; static bool nvs_enabled = false; esp_err_t esp_schedule_nvs_add(esp_schedule_t *schedule) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not adding to NVS."); return ESP_ERR_INVALID_STATE; } nvs_handle_t nvs_handle; esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed with error %d", err); return err; } /* Check if this is new schedule or editing an existing schedule */ size_t buf_size; bool editing_schedule = true; err = nvs_get_blob(nvs_handle, schedule->name, NULL, &buf_size); if (err != ESP_OK) { if (err == ESP_ERR_NVS_NOT_FOUND) { editing_schedule = false; } else { ESP_LOGE(TAG, "NVS get failed with error %d", err); nvs_close(nvs_handle); return err; } } else { ESP_LOGI(TAG, "Updating the existing schedule %s", schedule->name); } err = nvs_set_blob(nvs_handle, schedule->name, schedule, sizeof(esp_schedule_t)); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS set failed with error %d", err); nvs_close(nvs_handle); return err; } if (editing_schedule == false) { uint8_t schedule_count; err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count); if (err != ESP_OK) { if (err == ESP_ERR_NVS_NOT_FOUND) { schedule_count = 0; } else { ESP_LOGE(TAG, "NVS get failed with error %d", err); nvs_close(nvs_handle); return err; } } schedule_count++; err = nvs_set_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, schedule_count); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS set failed for schedule count with error %d", err); nvs_close(nvs_handle); return err; } } nvs_commit(nvs_handle); nvs_close(nvs_handle); ESP_LOGI(TAG, "Schedule %s added in NVS", schedule->name); return ESP_OK; } esp_err_t esp_schedule_nvs_remove_all(void) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not removing from NVS."); return ESP_ERR_INVALID_STATE; } nvs_handle_t nvs_handle; esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed with error %d", err); return err; } err = nvs_erase_all(nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS erase all keys failed with error %d", err); nvs_close(nvs_handle); return err; } nvs_commit(nvs_handle); nvs_close(nvs_handle); ESP_LOGI(TAG, "All schedules removed from NVS"); return ESP_OK; } esp_err_t esp_schedule_nvs_remove(esp_schedule_t *schedule) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not removing from NVS."); return ESP_ERR_INVALID_STATE; } nvs_handle_t nvs_handle; esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed with error %d", err); return err; } err = nvs_erase_key(nvs_handle, schedule->name); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS erase key failed with error %d", err); nvs_close(nvs_handle); return err; } uint8_t schedule_count; err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS get failed for schedule count with error %d", err); nvs_close(nvs_handle); return err; } schedule_count--; err = nvs_set_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, schedule_count); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS set failed for schedule count with error %d", err); nvs_close(nvs_handle); return err; } nvs_commit(nvs_handle); nvs_close(nvs_handle); ESP_LOGI(TAG, "Schedule %s removed from NVS", schedule->name); return ESP_OK; } static uint8_t esp_schedule_nvs_get_count(void) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not getting count from NVS."); return 0; } nvs_handle_t nvs_handle; esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READONLY, &nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed with error %d", err); return 0; } uint8_t schedule_count; err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS get failed for schedule count with error %d", err); nvs_close(nvs_handle); return 0; } nvs_close(nvs_handle); ESP_LOGI(TAG, "Schedules in NVS: %d", schedule_count); return schedule_count; } static esp_schedule_handle_t esp_schedule_nvs_get(char *nvs_key) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not getting from NVS."); return NULL; } size_t buf_size; nvs_handle_t nvs_handle; esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READONLY, &nvs_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed with error %d", err); return NULL; } err = nvs_get_blob(nvs_handle, nvs_key, NULL, &buf_size); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS get failed with error %d", err); nvs_close(nvs_handle); return NULL; } esp_schedule_t *schedule = (esp_schedule_t *)malloc(buf_size); if (schedule == NULL) { ESP_LOGE(TAG, "Could not allocate handle"); nvs_close(nvs_handle); return NULL; } err = nvs_get_blob(nvs_handle, nvs_key, schedule, &buf_size); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS get failed with error %d", err); nvs_close(nvs_handle); free(schedule); return NULL; } nvs_close(nvs_handle); ESP_LOGI(TAG, "Schedule %s found in NVS", schedule->name); return (esp_schedule_handle_t) schedule; } esp_schedule_handle_t *esp_schedule_nvs_get_all(uint8_t *schedule_count) { if (!nvs_enabled) { ESP_LOGD(TAG, "NVS not enabled. Not Initialising NVS."); return NULL; } *schedule_count = esp_schedule_nvs_get_count(); if (*schedule_count == 0) { ESP_LOGI(TAG, "No Entries found in NVS"); return NULL; } esp_schedule_handle_t *handle_list = (esp_schedule_handle_t *)malloc(sizeof(esp_schedule_handle_t) * (*schedule_count)); if (handle_list == NULL) { ESP_LOGE(TAG, "Could not allocate schedule list"); *schedule_count = 0; return NULL; } int handle_count = 0; nvs_entry_info_t nvs_entry; nvs_iterator_t nvs_iterator = NULL; esp_err_t err = nvs_entry_find(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_TYPE_BLOB, &nvs_iterator); if (err != ESP_OK) { ESP_LOGE(TAG, "No entry found in NVS"); free(handle_list); return NULL; } while (err == ESP_OK) { nvs_entry_info(nvs_iterator, &nvs_entry); ESP_LOGI(TAG, "Found schedule in NVS with key: %s", nvs_entry.key); handle_list[handle_count] = esp_schedule_nvs_get(nvs_entry.key); if (handle_list[handle_count] != NULL) { /* Increase count only if nvs_get was successful */ handle_count++; } err = nvs_entry_next(&nvs_iterator); } nvs_release_iterator(nvs_iterator); *schedule_count = handle_count; ESP_LOGI(TAG, "Found %d schedules in NVS", *schedule_count); return handle_list; } bool esp_schedule_nvs_is_enabled(void) { return nvs_enabled; } esp_err_t esp_schedule_nvs_init(char *nvs_partition) { if (nvs_enabled) { ESP_LOGI(TAG, "NVS already enabled"); return ESP_OK; } if (nvs_partition) { esp_schedule_nvs_partition = strndup(nvs_partition, strlen(nvs_partition)); } else { esp_schedule_nvs_partition = strndup("nvs", strlen("nvs")); } if (esp_schedule_nvs_partition == NULL) { ESP_LOGE(TAG, "Could not allocate nvs_partition"); return ESP_ERR_NO_MEM; } nvs_enabled = true; return ESP_OK; } ================================================ FILE: esp_serial_slave_link/.build-test-rules.yml ================================================ ================================================ FILE: esp_serial_slave_link/CHANGELOG.md ================================================ ## 1.1.1 - Clean up the component dependency, don't depend on the `driver` component directly ## 1.1.0 - Supported communicating with ESP32C6 SDIO Slave ## 1.0.1 - Fixed build failure issue on chip which doesn't support SDMMC host - Remove dependency to `soc/host_reg.h` ## 1.0.0 - Initial version ================================================ FILE: esp_serial_slave_link/CMakeLists.txt ================================================ set(public_requires "sdmmc") if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3") list(APPEND public_requires "esp_driver_sdspi" "esp_driver_sdmmc") else() list(APPEND public_requires "driver") endif() idf_component_register(SRCS "essl.c" "essl_sdio.c" "essl_spi.c" "essl_sdio_defs.c" INCLUDE_DIRS "include" REQUIRES ${public_requires} PRIV_INCLUDE_DIRS "." "include/esp_serial_slave_link" ) ================================================ FILE: esp_serial_slave_link/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_serial_slave_link/README.md ================================================ # Espressif Serial Slave Link (ESSL) Library [![Component Registry](https://components.espressif.com/components/espressif/esp_serial_slave_link/badge.svg)](https://components.espressif.com/components/espressif/esp_serial_slave_link) It's used on the HOST, to communicate with ESP chips as SLAVE via SDIO or SPI slave HD mode. ## Documentation For detailed information about the Espressif Serial Slave Link component, including API reference and user guides, please visit: - **Programming Guide & API Reference**: [Espressif Serial Slave Link Documentation](https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/index.html) ================================================ FILE: esp_serial_slave_link/docs/Doxyfile ================================================ # Set this to the header file you want INPUT = ../include/esp_serial_slave_link/ # The output directory for the generated XML documentation OUTPUT_DIRECTORY = doxygen_output # Warning-related settings, it's recommended to keep them enabled WARN_IF_UNDOC_ENUM_VAL = YES WARN_AS_ERROR = YES # Other common settings FULL_PATH_NAMES = YES STRIP_FROM_PATH = ../ STRIP_FROM_INC_PATH = ../ ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES OPTIMIZE_OUTPUT_FOR_C = YES EXPAND_ONLY_PREDEF = YES EXTRACT_ALL = YES PREDEFINED = $(ENV_DOXYGEN_DEFINES) HAVE_DOT = NO GENERATE_XML = YES XML_OUTPUT = xml GENERATE_HTML = NO HAVE_DOT = NO GENERATE_LATEX = NO QUIET = YES MARKDOWN_SUPPORT = YES ================================================ FILE: esp_serial_slave_link/docs/book.toml ================================================ [book] title = "ESP Serial Slave Link Documentation" language = "en" [output.html] default-theme = "light" git-repository-url = "https://github.com/espressif/idf-extra-components/tree/master/esp_serial_slave_link" edit-url-template = "https://github.com/espressif/idf-extra-components/edit/master/esp_serial_slave_link/docs/{path}" ================================================ FILE: esp_serial_slave_link/docs/src/SUMMARY.md ================================================ # Summary --- # Programming Guide - [ESP Serial Slave Link](index.md) - [SDIO Slave Protocol](sdio_slave_protocol.md) - [SPI Slave HD Protocol](spi_slave_hd_protocol.md) --- # API Reference - [API Reference](api.md) ================================================ FILE: esp_serial_slave_link/docs/src/api.md ================================================ # API Reference
This file is automatically generated by esp-doxybook. DO NOT edit it manually.
================================================ FILE: esp_serial_slave_link/docs/src/index.md ================================================ # ESP Serial Slave Link ## Overview Espressif provides several chips that can work as slaves. These slave devices rely on some common buses, and have their own communication protocols over those buses. The `esp_serial_slave_link` component is designed for the **master** to communicate with ESP slave devices through those protocols over the bus drivers, although the name of the component implies it a `slave link`. After a slave device is initialized properly, the application can use it to communicate with the slave devices conveniently, as long as the slave complies with the protocols. The component provides a set of APIs to operate the slave device, including sending and receiving data, triggering interrupts, etc. ## Terminology - ESSL: Abbreviation for ESP Serial Slave Link, the component described by this document. - Master: The device running the `esp_serial_slave_link` component. - ESSL Device: A virtual device on the master associated with an ESP slave device. The device context has the knowledge of the slave protocol above the bus, relying on some bus drivers to communicate with the slave. - ESSL Device Handle: a handle to ESSL device context containing the configuration, status and data required by the ESSL component. The context stores the driver configurations, communication state, data shared by master and slave, etc. - The context should be initialized before it is used, and get de-initialized if not used any more. The master application operates on the ESSL device through this handle. - ESP Slave: the slave device connected to the bus, which ESSL component is designed to communicate with. - Bus: The bus over which the master and the slave communicate with each other. - Slave Protocol: The special communication protocol specified by Espressif HW/SW over the bus. - TX Buffer Num: a counter, which is on the slave and can be read by the master, indicates the accumulated buffer numbers that the slave has loaded to the hardware to receive data from the master. - RX Data Size: a counter, which is on the slave and can be read by the master, indicates the accumulated data size that the slave has loaded to the hardware to send to the master. ## About the Slave Communication Protocols For more details about the device communication protocols, please refer to the following documents: - [SDIO Slave Protocol](sdio_slave_protocol.md) - [SPI Slave Half Duplex Protocol](spi_slave_hd_protocol.md) ## Services Provided by ESP Slave There are some common services provided by the Espressif slaves: 1. Tohost Interrupts: The slave can inform the master about certain events by the interrupt line. (optional) 2. Frhost Interrupts: The master can inform the slave about certain events. 3. TX FIFO (master to slave): The slave can receive data from the master in units of receiving buffers. The slave updates the TX buffer num to inform the master how much data it can receive, and the master then read the TX buffer num, and take off the used buffer number to know how many buffers are remaining. 4. RX FIFO (slave to master): The slave can send data in stream to the master. The SDIO slave can also indicate it has new data to send to master by the interrupt line. The slave updates the RX data size to inform the master how much data it has prepared to send, and then the master read the data size, and take off the data length it has already received to know how many data is remaining. 5. Shared registers: The master can read some part of the registers on the slave, and also write these registers to let the slave read. ## Initialization of ESP Serial Slave Link ### ESP SDIO Slave The ESP SDIO slave link (ESSL SDIO) devices relies on the SDMMC component. It includes the usage of communicating with ESP SDIO Slave device via the SDMMC Host or SDSPI Host feature. The ESSL device should be initialized as follows: 1. Initialize a SDMMC card (see [Document of SDMMC driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/sdmmc.html)) structure. 2. Call `sdmmc_card_init` to initialize the card. 3. Initialize the ESSL device with `essl_sdio_config_t`. The `card` member should be the `sdmmc_card_t` got in step 2, and the `recv_buffer_size` member should be filled correctly according to pre-negotiated value. 4. Call [essl_init](api.md#function-essl_init) to do initialization of the SDIO part. 5. Call [essl_wait_for_ready](api.md#function-essl_wait_for_ready) to wait for the slave to be ready.
If you are communicating with the ESP SDIO Slave device through SPI interface, you should still choose this **SDIO interface**.
### ESP SPI Slave Has not been supported yet. ## Typical Usage of ESP Serial Slave Link After the initialization process above is performed, you can call the APIs below to make use of the services provided by the slave: ### Tohost Interrupts (Optional) 1. Call [essl_get_intr_ena](api.md#function-essl_get_intr_ena) to know which events will trigger the interrupts to the master. 2. Call [essl_set_intr_ena](api.md#function-essl_set_intr_ena) to set the events that should trigger interrupts to the master. 3. Call [essl_wait_int](api.md#function-essl_wait_int) to wait until interrupt from the slave, or timeout. 4. When interrupt is triggered, call [essl_get_intr](api.md#function-essl_get_intr) to know which events are active, and call [essl_clear_intr](api.md#function-essl_clear_intr) to clear them. ### Frhost Interrupts 1. Call [essl_send_slave_intr](api.md#function-essl_send_slave_intr) to trigger general purpose interrupt of the slave. ### TX FIFO 1. Call [essl_get_tx_buffer_num](api.md#function-essl_get_tx_buffer_num) to know how many buffers the slave has prepared to receive data from the master. This is optional. The master will poll `tx_buffer_num` when it tries to send packets to the slave, until the slave has enough buffer or timeout. 2. Call [essl_send_packet](api.md#function-essl_send_packet) to send data to the slave. ### RX FIFO 1. Call [essl_get_rx_data_size](api.md#function-essl_get_rx_data_size) to know how many data the slave has prepared to send to the master. This is optional. When the master tries to receive data from the slave, it updates the `rx_data_size` for once, if the current `rx_data_size` is shorter than the buffer size the master prepared to receive. And it may poll the `rx_data_size` if the `rx_data_size` keeps 0, until timeout. 2. Call [essl_get_packet](api.md#function-essl_get_packet) to receive data from the slave. ### Reset Counters (Optional) Call [essl_reset_cnt](api.md#function-essl_reset_cnt) to reset the internal counter if you find the slave has reset its counter. ================================================ FILE: esp_serial_slave_link/docs/src/sdio_slave_protocol.md ================================================ # SDIO Slave Protocol This document describes the process of initialization of an ESP SDIO Slave device and then provides details on the ESP SDIO Slave protocol - a non-standard protocol that allows an SDIO Host to communicate with an ESP SDIO slave. The ESP SDIO Slave protocol was created to implement the communication between SDIO host and slave, because the SDIO specification only shows how to access the custom region of a card (by sending CMD52 and CMD53 to functions 1-7) without any details regarding the underlying hardware implementation. ## SDIO Slave Capabilities of Espressif Chips The services provided by the SDIO Slave peripheral of the {IDF_TARGET_NAME} chip are listed in the table below: | ESP Target Chip | Tohost intr | Frhost intr | TX DMA | RX DMA | Shared registers | | --------------- | ----------- | ----------- | ------ | ------ | ---------------- | | ESP32 | 8 | 8 | Y | Y | 56\* | | ESP32-C5 | 8 | 8 | Y | Y | 56\* | | ESP32-C6 | 8 | 8 | Y | Y | 56\* | \* Not including the interrupt registers ## ESP SDIO Slave Initialization The host should initialize the SDIO slave according to the standard SDIO initialization process (Section 3.1.2 of [SDIO Simplified Specification](https://www.sdcard.org/downloads/pls/)). In this specification as well as below, the SDIO slave is called an SDIO/IO card. Here is a brief example of an ESP SDIO Slave initialization process: 1. SDIO reset > CMD52 (Write 0x6 = 0x8) 2. SD reset > CMD0 3. Check whether IO card (optional) > CMD8 4. Send SDIO op cond and wait for card ready > CMD5 arg = 0x00000000 > > CMD5 arg = 0x00ff8000 (according to the response above, poll until ready) > > **Example:** > > > Arg of R4 after first CMD5 (arg = 0x00000000) is 0xXXFFFF00. > > > > Keep sending CMD5 with arg = 0x00FFFF00 until the R4 shows card ready (arg bit 31 = 1). 5. Set address > CMD3 6. Select card > CMD7 (arg address according to CMD3 response) > > **Example:** > > > Arg of R6 after CMD3 is 0x0001xxxx. > > > > Arg of CMD7 should be 0x00010000. 7. Select 4-bit mode (optional) > CMD52 (Write 0x07 = 0x02) 8. Enable func1 > CMD52 (Write 0x02 = 0x02) 9. Enable SDIO interrupt (required if interrupt line (DAT1) is used) > CMD52 (Write 0x04 = 0x03) 10. Set Func0 block size (optional, default value is 512 (0x200)) > CMD52/53 (Read 0x10 ~ 0x11) > > CMD52/53 (Write 0x10 = 0x00) > > CMD52/53 (Write 0x11 = 0x02) > > CMD52/53 (Read 0x10 ~ 0x11, read to check the final value) 11. Set Func1 block size (optional, default value is 512 (0x200)) > CMD52/53 (Read 0x110 ~ 0x111) > > CMD52/53 (Write 0x110 = 0x00) > > CMD52/53 (Write 0x111 = 0x02) > > CMD52/53 (Read 0x110 ~ 0x111, read to check the final value) ## ESP SDIO Slave Protocol The ESP SDIO Slave protocol is based on the SDIO Specification's I/O Read/Write commands, i.e., CMD52 and CMD53. The protocol offers the following services: - Sending FIFO and receiving FIFO - 52 8-bit R/W registers shared by host and slave. For details, see `Technical Reference Manual > SDIO Slave Controller > Register Summary > SDIO SLC Host registers`. - 16 general purpose interrupt sources, 8 from host to slave and 8 from slave to host. To begin communication, the host needs to enable the I/O Function 1 in the slave and access its registers as described below. The `esp_serial_slave_link` component implements the logic of this protocol for ESP32 SDIO Host when communicating with an ESP32 SDIO slave. ### Slave Register Table #### 32-bit - 0x044 (TOKEN_RDATA): in which bit 27-16 holds the number of the receiving buffer. - 0x058 (INT_ST): holds the interrupt source bits from slave to host. - 0x060 (PKT_LEN): holds the accumulated data length (in bytes) already read by host plus the data copied to the buffer but yet to be read. - 0x0D4 (INT_CLR): write 1 to clear interrupt bits corresponding to INT_ST. - 0x0DC (INT_ENA): mask bits for interrupts from slave to host. #### 8-bit Shared general purpose registers: - 0x06C-0x077: R/W registers 0-11 shared by slave and host. - 0x07A-0x07B: R/W registers 14-15 shared by slave and host. - 0x07E-0x07F: R/W registers 18-19 shared by slave and host. - 0x088-0x08B: R/W registers 24-27 shared by slave and host. - 0x09C-0x0BB: R/W registers 32-63 shared by slave and host. Interrupt Registers: - 0x08D (SLAVE_INT): bits for host to interrupt slave. auto clear. #### FIFO (Sending and Receiving) 0x090 - 0x1F7FF are reserved for FIFOs. The address of CMD53 is related to the length requested to read from or write to the slave in a single transfer, as demonstrated by the equation below: _requested length = 0x1F800 - address_ The slave responds to data that has a length equal to the length field of CMD53. In cases where the data is longer than the **requested length**, the data will be zero filled (when sending) or discarded (when receiving). This includes both the block and the byte mode of CMD53.
The function number should be set to 1, and OP Code should be set to 1 (for CMD53). In order to achieve higher efficiency when accessing the FIFO by an arbitrary length, the block and byte modes of CMD53 can be used in combination. For example, given that the block size is set to 512 by default, you can write or get 1031 bytes of data from the FIFO by doing the following: 1. Send CMD53 in block mode, block count = 2 (1024 bytes) to address 0x1F3F9 = 0x1F800 - **1031**. 2. Then send CMD53 in byte mode, byte count = 8 (or 7 if your controller supports that) to address 0x1F7F9 = 0x1F800 - **7**.
### Interrupts SDIO interrupts are "level sensitive". For host interrupts, the slave sends an interrupt by pulling the DAT1 line down at a proper time. The host detects when the interrupt line is pulled down and reads the `INT_ST` register to determine the source of the interrupt. After that, the host can clear the interrupt bits by writing the `INT_CLR` register and process the interrupt. The host can also mask unneeded sources by clearing the bits in the `INT_ENA` register corresponding to the sources. If all the sources are cleared (or masked), the DAT1 line goes inactive. On ESP32, the corresponding `host_int` bits are: bit 0 to bit 7. For slave interrupts, the host sends a transfer to write the `SLAVE_INT` register. Once a bit is set to 1, the slave hardware and the driver will detect it and inform the application. ### Receiving FIFO To write to the slave's receiving FIFO, the host should complete the following steps: 1. **Read the TOKEN1 field (bits 27-16) of the register TOKEN_RDATA (0x044)**. The buffer number remaining is TOKEN1 minus the number of buffers used by host. 2. **Make sure the buffer number is sufficient** (_buffer_size_ x _buffer_num_ is greater than the data to write, _buffer_size_ is pre-defined between the host and the slave before the communication starts). Otherwise, keep returning to step 1 until the buffer size is sufficient. 3. **Write to the FIFO address with CMD53**. Note that the _requested length_ should not exceed the length calculated at step 2, and the FIFO address is related to _requested length_. 4. **Calculate used buffers**. Note that a partially-used buffer at the tail is counted as used. ### Sending FIFO To read the slave's sending FIFO, the host should complete the following steps: 1. **Wait for the interrupt line to become active** (optional, low by default). 2. **Read (poll) the interrupt bits in the INT_ST register** to monitor if new packets exist. 3. **If new packets are ready, read the PKT_LEN register**. Before reading the packets, determine the length of data to be read. As the host keeps the length of data already read from the slave, subtract this value from `PKT_LEN`, the result will be the maximum length of data available for reading. If no data has been added to the sending FIFO yet, wait and poll until the slave is ready and update `PKT_LEN`. 4. **Read from the FIFO using CMD53**. Note that the **requested length** should not be greater than calculated at Step 3, and the FIFO address is related to **requested length**. 5. **Update the read length**. ================================================ FILE: esp_serial_slave_link/docs/src/spi_slave_hd_protocol.md ================================================ # SPI Slave HD (Half Duplex) Protocol
ESP32 does not support this feature.
## SPI Slave Capabilities of Espressif Chips | ESP Target Chip | Tohost intr | Frhost intr | TX DMA | RX DMA | Shared registers | | --------------- | :---------: | :---------: | :----: | :----: | :--------------: | | ESP32-S2 | N | 2 | Y | Y | 72 | | ESP32-C3 | N | 2 | Y | Y | 64 | | ESP32-S3 | N | 2 | Y | Y | 64 | | ESP32-C2 | N | 2 | Y | Y | 64 | | ESP32-C6 | N | 2 | Y | Y | 64 | | ESP32-H2 | N | 2 | Y | Y | 64 | | ESP32-P4 | N | 2 | Y | Y | 64 | | ESP32-C5 | N | 2 | Y | Y | 64 | | ESP32-C61 | N | 2 | Y | Y | 64 | | ESP32-H21 | N | 2 | Y | Y | 64 | ## Introduction In the half duplex mode, the master has to use the protocol defined by the slave to communicate with the slave. Each transaction may consist of the following phases (listed by the order they should exist): - Command: 8-bit, master to slave > This phase determines the rest phases of the transactions. See [the supported commands](#supported-commands). - Address: 8-bit, master to slave, optional > For some commands (`WRBUF`, `RDBUF`), this phase specifies the address of the shared register to write to/read from. > > For other commands with this phase, they are meaningless but still have to exist in the transaction. - Dummy: 8-bit floating, optional > This phase is the turnaround time between the master and the slave on the bus, and also provides enough time for the slave to prepare the data to send to the master. - Data: variable length, the direction is also determined by the command. > This may be a data `OUT` phase, in which the direction is slave to master, or a data `IN` phase, in which the direction is master to slave. The **direction** means which side (master or slave) controls the MOSI, MISO, WP, and HD pins. ## Data IO Modes In some IO modes, more data wires can be used to send the data. As a result, the SPI clock cycles required for the same amount of data will be less than in the 1-bit mode. For example, in QIO mode, address and data (IN and OUT) should be sent on all 4 data wires (MOSI, MISO, WP, and HD). Here are the modes supported by the ESP32-S2 SPI slave and the wire number (WN) used in corresponding modes. | Mode | Command WN | Address WN | Dummy cycles | Data WN | | ----- | ---------- | ---------- | ------------ | ------- | | 1-bit | 1 | 1 | 1 | 1 | | DOUT | 1 | 1 | 4 | 2 | | DIO | 1 | 2 | 4 | 2 | | QOUT | 1 | 1 | 4 | 4 | | QIO | 1 | 4 | 4 | 4 | | QPI | 4 | 4 | 4 | 4 | Normally, which mode is used is determined by the command sent by the master (See [the Supported Commands](#supported-commands)), except the QPI mode. ### QPI Mode The QPI mode is a special state of the SPI Slave. The master can send the `ENQPI` command to put the slave into the QPI mode state. In the QPI mode, the command is also sent in 4-bit, thus it is not compatible with the normal modes. The master should only send QPI commands when the slave is in QPI mode. To exit from the QPI mode, master can send the EXQPI command. ## Supported Commands
The command name is in a master-oriented direction. For example, WRBUF means master writes the buffer of slave.
| Name | Description | Command | Address | Data | | -------- | ------------------- | ------- | -------- | -------------------------------------------------------- | | WRBUF | Write buffer | 0x01 | Buf addr | master to slave, no longer than buffer size | | RDBUF | Read buffer | 0x02 | Buf addr | slave to master, no longer than buffer size | | WRDMA | Write DMA | 0x03 | 8 bits | master to slave, no longer than length provided by slave | | RDDMA | Read DMA | 0x04 | 8 bits | slave to master, no longer than length provided by slave | | SEG_DONE | Segments done | 0x05 | | | | ENQPI | Enter QPI mode | 0x06 | | | | WR_DONE | Write segments done | 0x07 | | | | CMD8 | Interrupt | 0x08 | | | | CMD9 | Interrupt | 0x09 | | | | CMDA | Interrupt | 0x0A | | | | EXQPI | Exit QPI mode | 0xDD | | | Moreover, `WRBUF`, `RDBUF`, `WRDMA`, and `RDDMA` commands have their 2-bit and 4-bit version. To do transactions in 2-bit or 4-bit mode, send the original command ORed by the corresponding command mask below. For example, command 0xA1 means WRBUF in QIO mode. | Mode | Mask | | ----- | ---- | | 1-bit | 0x00 | | DOUT | 0x10 | | DIO | 0x50 | | QOUT | 0x20 | | QIO | 0xA0 | | QPI | 0xA0 | ## Segment Transaction Mode Segment transaction mode is the only mode supported by the SPI Slave HD driver for now. In this mode, for a transaction the slave loads onto the DMA, the master is allowed to read or write in segments. In this way, the master does not have to prepare a large buffer as the size of data provided by the slave. After the master finishes reading/writing a buffer, it has to send the corresponding termination command to the slave as a synchronization signal. The slave driver will update new data (if exist) onto the DMA upon seeing the termination command. The termination command is `WR_DONE` (0x07) for `WRDMA` and `CMD8` (0x08) for `RDDMA`. Here is an example for the flow the master read data from the slave DMA: 1. The slave loads 4092 bytes of data onto the RDDMA. 2. The master do seven `RDDMA` transactions, each of them is 512 bytes long, and reads the first 3584 bytes from the slave. 3. The master do the last `RDDMA` transaction of 512 bytes (equal, longer, or shorter than the total length loaded by the slave are all allowed). The first 508 bytes are valid data from the slave, while the last 4 bytes are meaningless bytes. 4. The master sends `CMD8` to the slave. 5. The slave loads another 4092 bytes of data onto the RDDMA. 6. The master can start new reading transactions after it sends the `CMD8`. ================================================ FILE: esp_serial_slave_link/essl.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "essl.h" #include "essl_internal.h" #define TIME_EXPIRED_SINCE_CORE(start, end, timeout, max) (bool)((end)>=(start)? \ ((end)-(start)>(timeout)) :\ ((max)-(timeout)>(start)-(end))) #define TIME_EXPIRED_SINCE(start, end, timeout) TIME_EXPIRED_SINCE_CORE(start, end, timeout, UINT32_MAX) #define MINUS_UNTIL_ZERO(a, b) ( ((a) > (b)) ? ((a)-(b)): 0) #define TIME_REMAIN_CORE(start, end, timeout, max) ((end)>=(start)?\ MINUS_UNTIL_ZERO(timeout, (end)-(start)):\ MINUS_UNTIL_ZERO((start)-(end), (max)-(timeout))) #define TIME_REMAIN(start, end, timeout) TIME_REMAIN_CORE(start, end, timeout, UINT32_MAX) #define ESSL_MIN(a, b) ((a) < (b) ? (a) : (b)) __attribute__((unused)) static const char TAG[] = "esp_serial_slave_link"; #define _CHECK_EXECUTE_CMD(DEV, CMD, STR, ...) do{ \ if ((DEV) == NULL) { \ return ESP_ERR_INVALID_ARG; \ } \ if ((DEV)->CMD) { \ return (DEV)->CMD((DEV)->args,##__VA_ARGS__); \ } else { \ ESP_LOGE(TAG, STR); \ return ESP_ERR_NOT_SUPPORTED; \ } } while(0) #define CHECK_EXECUTE_CMD(DEV, CMD, ...) _CHECK_EXECUTE_CMD(DEV, CMD, #CMD" not supported for the current device.",##__VA_ARGS__) esp_err_t essl_init(essl_handle_t handle, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, init, wait_ms); } esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, wait_for_ready, wait_ms); } esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms) { if (handle == NULL || start == NULL || length == 0) { return ESP_ERR_INVALID_ARG; } if (handle->send_packet == NULL) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t err; const uint32_t timeout_ticks = pdMS_TO_TICKS(wait_ms); uint32_t pre = xTaskGetTickCount(); uint32_t now; uint32_t remain_wait_ms = 0; do { now = xTaskGetTickCount(); remain_wait_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks)); err = handle->send_packet(handle->args, start, length, remain_wait_ms); if (err == ESP_OK) { break; } else if (err != ESP_ERR_NOT_FOUND) { return err; } // else ESP_ERR_NOT_FOUND //the slave is not ready, retry } while (remain_wait_ms > 0); return err; } esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } if (out_data == NULL || size == 0 || out_length == NULL) { return ESP_ERR_INVALID_ARG; } if (handle->get_packet == NULL || handle->update_rx_data_size == NULL || handle->get_rx_data_size == NULL) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t err; const uint32_t timeout_ticks = pdMS_TO_TICKS(wait_ms); uint32_t pre = xTaskGetTickCount(); uint32_t now = 3; uint32_t wait_remain_ms = 0; int data_available = handle->get_rx_data_size(handle->args); // if there is already enough data to read, skip the length update. if (data_available < size) { //loop until timeout, or there is at least one byte do { now = xTaskGetTickCount(); wait_remain_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks)); err = handle->update_rx_data_size(handle->args, wait_remain_ms); if (err != ESP_OK) { return err; } data_available = handle->get_rx_data_size(handle->args); if (data_available > 0) { break; } } while (wait_remain_ms > 0); } if (data_available == 0) { //the slave has no data to send return ESP_ERR_NOT_FOUND; } int len = ESSL_MIN(data_available, size); now = xTaskGetTickCount(); wait_remain_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks)); err = handle->get_packet(handle->args, out_data, len, wait_remain_ms); if (err != ESP_OK) { return err; } *out_length = len; if (len < data_available) { return ESP_ERR_NOT_FINISHED; } return ESP_OK; } esp_err_t essl_get_tx_buffer_num(essl_handle_t handle, uint32_t *out_tx_num, uint32_t wait_ms) { if (handle == NULL || out_tx_num == NULL) { return ESP_ERR_INVALID_ARG; } if (handle->update_tx_buffer_num == NULL || handle->get_tx_buffer_num == NULL) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t err = handle->update_tx_buffer_num(handle->args, wait_ms); if (err != ESP_OK) { return err; } *out_tx_num = handle->get_tx_buffer_num(handle->args); return ESP_OK; } esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uint32_t wait_ms) { if (handle == NULL || out_rx_size == NULL) { return ESP_ERR_INVALID_ARG; } if (handle->update_rx_data_size == NULL || handle->get_rx_data_size == NULL) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t err = handle->update_rx_data_size(handle->args, wait_ms); if (err != ESP_OK) { return err; } *out_rx_size = handle->get_rx_data_size(handle->args); return ESP_OK; } esp_err_t essl_write_reg(essl_handle_t handle, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, write_reg, addr, value, value_o, wait_ms); } esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, read_reg, add, value_o, wait_ms); } esp_err_t essl_wait_int(essl_handle_t handle, TickType_t wait_ms) { CHECK_EXECUTE_CMD(handle, wait_int, wait_ms); } esp_err_t essl_reset_cnt(essl_handle_t handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } if (handle->reset_cnt == NULL) { return ESP_ERR_NOT_SUPPORTED; } handle->reset_cnt(handle->args); return ESP_OK; } esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, clear_intr, intr_mask, wait_ms); } esp_err_t essl_get_intr(essl_handle_t handle, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms) { if (intr_raw == NULL && intr_st == NULL) { return ESP_ERR_INVALID_ARG; } CHECK_EXECUTE_CMD(handle, get_intr, intr_raw, intr_st, wait_ms); } esp_err_t essl_set_intr_ena(essl_handle_t handle, uint32_t ena_mask, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, set_intr_ena, ena_mask, wait_ms); } esp_err_t essl_get_intr_ena(essl_handle_t handle, uint32_t *ena_mask_o, uint32_t wait_ms) { if (ena_mask_o == NULL) { return ESP_ERR_INVALID_ARG; } CHECK_EXECUTE_CMD(handle, get_intr_ena, ena_mask_o, wait_ms); } esp_err_t essl_send_slave_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms) { CHECK_EXECUTE_CMD(handle, send_slave_intr, intr_mask, wait_ms); } ================================================ FILE: esp_serial_slave_link/essl_internal.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include /** Context used by the ``esp_serial_slave_link`` component. */ struct essl_dev_t { void *args; esp_err_t (*init)(void *ctx, uint32_t wait_ms); esp_err_t (*wait_for_ready)(void *ctx, uint32_t wait_ms); esp_err_t (*update_tx_buffer_num)(void *ctx, uint32_t wait_ms); esp_err_t (*update_rx_data_size)(void *ctx, uint32_t wait_ms); esp_err_t (*send_packet)(void *ctx, const void *start, size_t length, uint32_t wait_ms); esp_err_t (*get_packet)(void *ctx, void *out_data, size_t size, uint32_t wait_ms); esp_err_t (*write_reg)(void *ctx, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms); esp_err_t (*read_reg)(void *ctx, uint8_t add, uint8_t *value_o, uint32_t wait_ms); esp_err_t (*wait_int)(void *ctx, uint32_t wait_ms); esp_err_t (*clear_intr)(void *ctx, uint32_t intr_mask, uint32_t wait_ms); esp_err_t (*get_intr)(void *ctx, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms); esp_err_t (*set_intr_ena)(void *ctx, uint32_t ena_mask, uint32_t wait_ms); esp_err_t (*get_intr_ena)(void *ctx, uint32_t *ena_mask_o, uint32_t wait_ms); esp_err_t (*send_slave_intr)(void *ctx, uint32_t intr_mask, uint32_t wait_ms); uint32_t (*get_tx_buffer_num)(void *ctx); uint32_t (*get_rx_data_size)(void *ctx); void (*reset_cnt)(void *ctx); }; typedef struct essl_dev_t essl_dev_t; ================================================ FILE: esp_serial_slave_link/essl_sdio.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "soc/soc_caps.h" #include "esp_log.h" #include "sdmmc_cmd.h" #include "driver/sdmmc_defs.h" #include "essl_internal.h" #include "essl_sdio.h" #include "esp_heap_caps.h" static const char TAG[] = "essl_sdio"; #ifndef DR_REG_SLCHOST_BASE #define DR_REG_SLCHOST_BASE 0 //The SDIO slave only check the least significant 10 bits, this doesn't matter #endif //This should be consistent with the macro in soc/host_reg.h #define HOST_SLC0HOST_TOKEN_RDATA_REG (DR_REG_SLCHOST_BASE + 0x44) #define HOST_SLC0HOST_INT_RAW_REG (DR_REG_SLCHOST_BASE + 0x50) #define HOST_SLC0HOST_INT_ST_REG (DR_REG_SLCHOST_BASE + 0x58) #define HOST_SLCHOST_PKT_LEN_REG (DR_REG_SLCHOST_BASE + 0x60) #define HOST_SLCHOST_CONF_W0_REG (DR_REG_SLCHOST_BASE + 0x6C) #define HOST_SLCHOST_CONF_W7_REG (DR_REG_SLCHOST_BASE + 0x8C) #define HOST_SLC0HOST_INT_CLR_REG (DR_REG_SLCHOST_BASE + 0xD4) #define HOST_SLC0HOST_FUNC1_INT_ENA_REG (DR_REG_SLCHOST_BASE + 0xDC) #define HOST_SLCHOST_CONF_W_REG(pos) (HOST_SLCHOST_CONF_W0_REG+pos+(pos>23?4:0)+(pos>31?12:0)) #define ESSL_CMD53_END_ADDR 0x1f800 #define TX_BUFFER_MAX 0x1000 #define TX_BUFFER_MASK 0xFFF #define RX_BYTE_MAX 0x100000 #define RX_BYTE_MASK 0xFFFFF #define FUNC1_EN_MASK (BIT(1)) /** * Initialize ``void`` over SDIO by this macro. */ #define ESSL_SDIO_DEFAULT_CONTEXT() (essl_dev_t){\ .init = essl_sdio_init, \ .wait_for_ready = essl_sdio_wait_for_ready, \ .get_tx_buffer_num = essl_sdio_get_tx_buffer_num,\ .update_tx_buffer_num = essl_sdio_update_tx_buffer_num,\ .get_rx_data_size = essl_sdio_get_rx_data_size,\ .update_rx_data_size = essl_sdio_update_rx_data_size,\ .send_packet = essl_sdio_send_packet,\ .get_packet = essl_sdio_get_packet,\ .write_reg = essl_sdio_write_reg,\ .read_reg = essl_sdio_read_reg,\ .wait_int = essl_sdio_wait_int,\ .send_slave_intr = essl_sdio_send_slave_intr, \ .get_intr = essl_sdio_get_intr, \ .clear_intr = essl_sdio_clear_intr, \ .set_intr_ena = essl_sdio_set_intr_ena, \ .reset_cnt = essl_sdio_reset_cnt, \ } typedef struct { //common part uint16_t buffer_size; ///< All data that do not fully fill a buffer is still counted as one buffer. E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. ///< Buffer size of the slave pre-defined between host and slave before communication. size_t tx_sent_buffers; ///< Counter holding the amount of buffers already sent to ESP32 slave. Should be set to 0 when initialization. size_t tx_sent_buffers_latest; ///< The latest reading (from the slave) of counter holding the amount of buffers loaded. Should be set to 0 when initialization. size_t rx_got_bytes; ///< Counter holding the amount of bytes already received from ESP32 slave. Should be set to 0 when initialization. size_t rx_got_bytes_latest; ///< The latest reading (from the slave) of counter holding the amount of bytes to send. Should be set to 0 when initialization. sdmmc_card_t *card; ///< Initialized sdmmc_cmd card uint16_t block_size; ///< If this is too large, it takes time to send stuff bits; while if too small, intervals between blocks cost much. ///< Should be set according to length of data, and larger than ``TRANS_LEN_MAX/511``. ///< Block size of the SDIO function 1. After the initialization this will hold the value the slave really do. Valid value is 1-2048. } essl_sdio_context_t; esp_err_t essl_sdio_update_tx_buffer_num(void *arg, uint32_t wait_ms); esp_err_t essl_sdio_update_rx_data_size(void *arg, uint32_t wait_ms); static inline esp_err_t essl_sdio_write_byte(sdmmc_card_t *card, uint32_t addr, uint8_t val, uint8_t *val_o) { return sdmmc_io_write_byte(card, 1, addr & 0x3FF, val, val_o); } static inline esp_err_t essl_sdio_write_bytes(sdmmc_card_t *card, uint32_t addr, uint8_t *val, int len) { return sdmmc_io_write_bytes(card, 1, addr & 0x3FF, val, len); } static inline esp_err_t essl_sdio_read_byte(sdmmc_card_t *card, uint32_t addr, uint8_t *val_o) { return sdmmc_io_read_byte(card, 1, addr & 0x3FF, val_o); } static inline esp_err_t essl_sdio_read_bytes(sdmmc_card_t *card, uint32_t addr, uint8_t *val_o, int len) { return sdmmc_io_read_bytes(card, 1, addr & 0x3FF, val_o, len); } esp_err_t essl_sdio_init_dev(essl_handle_t *out_handle, const essl_sdio_config_t *config) { esp_err_t ret = ESP_OK; essl_sdio_context_t *arg = NULL; essl_dev_t *dev = NULL; arg = (essl_sdio_context_t *)heap_caps_malloc(sizeof(essl_sdio_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); dev = (essl_dev_t *)heap_caps_malloc(sizeof(essl_dev_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (arg == NULL || dev == NULL) { ret = ESP_ERR_NO_MEM; goto cleanup; } *dev = ESSL_SDIO_DEFAULT_CONTEXT(); dev->args = arg; *arg = (essl_sdio_context_t) { .card = config->card, .block_size = 0x200, .buffer_size = config->recv_buffer_size, .tx_sent_buffers = 0, .rx_got_bytes = 0, }; *out_handle = dev; return ESP_OK; cleanup: free(arg); free(dev); return ret; } esp_err_t essl_sdio_deinit_dev(essl_handle_t handle) { if (handle) { free (handle->args); } free(handle); return ESP_OK; } esp_err_t essl_sdio_init(void *arg, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; esp_err_t err; uint8_t ioe = 0; sdmmc_card_t *card = ctx->card; err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IOE: 0x%02"PRIx8, ioe); uint8_t ior = 0; err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IOR: 0x%02"PRIx8, ior); // enable function 1 ioe |= FUNC1_EN_MASK; err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, ioe, &ioe); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IOE: 0x%02"PRIx8, ioe); // wait for the card to become ready while ((ior & FUNC1_EN_MASK) == 0) { err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IOR: 0x%02"PRIx8, ior); } // get interrupt status uint8_t ie = 0; err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IE: 0x%02"PRIx8, ie); // enable interrupts for function 1&2 and master enable ie |= BIT(0) | FUNC1_EN_MASK; err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_INT_ENABLE, ie, &ie); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IE: 0x%02"PRIx8, ie); // get bus width register uint8_t bus_width = 0; err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BUS_WIDTH, &bus_width); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "BUS_WIDTH: 0x%02"PRIx8, bus_width); // enable continuous SPI interrupts bus_width |= CCCR_BUS_WIDTH_ECSI; err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_BUS_WIDTH, bus_width, &bus_width); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "BUS_WIDTH: 0x%02"PRIx8, bus_width); uint16_t bs = 512; const uint8_t *bs_u8 = (const uint8_t *) &bs; uint16_t bs_read = 0; uint8_t *bs_read_u8 = (uint8_t *) &bs_read; // Set block sizes for functions 0 to 512 bytes ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); ESP_LOGD(TAG, "Function 0 BS: %d", (unsigned int) bs_read); ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL)); ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL)); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); ESP_LOGD(TAG, "Function 0 BS: %d", (unsigned int) bs_read); // Set block sizes for functions 1 to given value (default value = 512). if (ctx->block_size > 0 && ctx->block_size <= 2048) { bs = ctx->block_size; } else { bs = 512; } size_t offset = SD_IO_FBR_START * 1; ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); ESP_LOGD(TAG, "Function 1 BS: %d", (unsigned int) bs_read); ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL)); ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL)); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); ESP_LOGD(TAG, "Function 1 BS: %d", (unsigned int) bs_read); if (bs_read != ctx->block_size) { ESP_LOGW(TAG, "Function1 block size %d different than set value %d", bs_read, ctx->block_size); ctx->block_size = bs_read; } return ESP_OK; } esp_err_t essl_sdio_wait_for_ready(void *arg, uint32_t wait_ms) { ESP_LOGV(TAG, "wait_for_ioready"); esp_err_t err; sdmmc_card_t *card = ((essl_sdio_context_t *)arg)->card; // wait for the card to become ready uint8_t ior = 0; while ((ior & FUNC1_EN_MASK) == 0) { err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); if (err != ESP_OK) { return err; } ESP_LOGD(TAG, "IOR: 0x%02x", ior); } return ESP_OK; } esp_err_t essl_sdio_send_packet(void *arg, const void *start, size_t length, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; uint16_t buffer_size = ctx->buffer_size; int buffer_used = (length + buffer_size - 1) / buffer_size; esp_err_t err; if (essl_sdio_get_tx_buffer_num(arg) < buffer_used) { //slave has no enough buffer, try update for once esp_err_t err = essl_sdio_update_tx_buffer_num(arg, wait_ms); if (err != ESP_OK) { return err; } if (essl_sdio_get_tx_buffer_num(arg) < buffer_used) { ESP_LOGV(TAG, "buffer is not enough: %d, %d required.", ctx->tx_sent_buffers_latest, ctx->tx_sent_buffers + buffer_used); return ESP_ERR_NOT_FOUND; } } ESP_LOGV(TAG, "send_packet: len: %d", length); uint8_t *start_ptr = (uint8_t *)start; uint32_t len_remain = length; do { const int block_size = 512; /* Though the driver supports to split packet of unaligned size into * length of 4x and 1~3, we still send aligned size of data to get * higher efficiency. The length is determined by the SDIO address, and * the remainning will be discard by the slave hardware. */ int block_n = len_remain / block_size; int len_to_send; if (block_n) { len_to_send = block_n * block_size; err = sdmmc_io_write_blocks(ctx->card, 1, ESSL_CMD53_END_ADDR - len_remain, start_ptr, len_to_send); } else { len_to_send = len_remain; err = sdmmc_io_write_bytes(ctx->card, 1, ESSL_CMD53_END_ADDR - len_remain, start_ptr, (len_to_send + 3) & (~3)); } if (err != ESP_OK) { return err; } start_ptr += len_to_send; len_remain -= len_to_send; } while (len_remain); ctx->tx_sent_buffers += buffer_used; return ESP_OK; } esp_err_t essl_sdio_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; esp_err_t err; ESP_LOGV(TAG, "get_packet: read size=%d", size); if (essl_sdio_get_rx_data_size(arg) < size) { err = essl_sdio_update_rx_data_size(arg, wait_ms); if (err != ESP_OK) { return err; } if (essl_sdio_get_rx_data_size(arg) < size) { return ESP_ERR_NOT_FOUND; } } uint8_t *start = out_data; uint32_t len_remain = size; do { const int block_size = 512; //currently our driver don't support block size other than 512 int len_to_send; int block_n = len_remain / block_size; if (block_n != 0) { len_to_send = block_n * block_size; err = sdmmc_io_read_blocks(ctx->card, 1, ESSL_CMD53_END_ADDR - len_remain, start, len_to_send); } else { len_to_send = len_remain; /* though the driver supports to split packet of unaligned size into length * of 4x and 1~3, we still get aligned size of data to get higher * efficiency. The length is determined by the SDIO address, and the * remainning will be ignored by the slave hardware. */ err = sdmmc_io_read_bytes(ctx->card, 1, ESSL_CMD53_END_ADDR - len_remain, start, (len_to_send + 3) & (~3)); } if (err != ESP_OK) { return err; } start += len_to_send; len_remain -= len_to_send; ctx->rx_got_bytes += len_to_send; } while (len_remain != 0); return err; } uint32_t essl_sdio_get_tx_buffer_num(void *arg) { essl_sdio_context_t *ctx = arg; ESP_LOGV(TAG, "tx latest: %d, sent: %d", ctx->tx_sent_buffers_latest, ctx->tx_sent_buffers); return (ctx->tx_sent_buffers_latest + TX_BUFFER_MAX - ctx->tx_sent_buffers) % TX_BUFFER_MAX; } esp_err_t essl_sdio_update_tx_buffer_num(void *arg, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; uint32_t len; esp_err_t err; err = essl_sdio_read_bytes(ctx->card, HOST_SLC0HOST_TOKEN_RDATA_REG, (uint8_t *) &len, 4); if (err != ESP_OK) { return err; } len = (len >> 16)&TX_BUFFER_MASK; ctx->tx_sent_buffers_latest = len; ESP_LOGV(TAG, "update_tx_buffer_num: %d", (unsigned int)len); return ESP_OK; } uint32_t essl_sdio_get_rx_data_size(void *arg) { essl_sdio_context_t *ctx = arg; ESP_LOGV(TAG, "rx latest: %d, read: %d", ctx->rx_got_bytes_latest, ctx->rx_got_bytes); return (ctx->rx_got_bytes_latest + RX_BYTE_MAX - ctx->rx_got_bytes) % RX_BYTE_MAX; } esp_err_t essl_sdio_update_rx_data_size(void *arg, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; uint32_t len; esp_err_t err; ESP_LOGV(TAG, "get_rx_data_size: got_bytes: %d", ctx->rx_got_bytes); err = essl_sdio_read_bytes(ctx->card, HOST_SLCHOST_PKT_LEN_REG, (uint8_t *) &len, 4); if (err != ESP_OK) { return err; } len &= RX_BYTE_MASK; ctx->rx_got_bytes_latest = len; return ESP_OK; } esp_err_t essl_sdio_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms) { ESP_LOGV(TAG, "write_reg: 0x%02"PRIX8, value); // address over range if (addr >= 60) { return ESP_ERR_INVALID_ARG; } //W7 is reserved for interrupts if (addr >= 28) { addr += 4; } return essl_sdio_write_byte(((essl_sdio_context_t *)arg)->card, HOST_SLCHOST_CONF_W_REG(addr), value, value_o); } esp_err_t essl_sdio_read_reg(void *arg, uint8_t add, uint8_t *value_o, uint32_t wait_ms) { ESP_LOGV(TAG, "read_reg"); // address over range if (add >= 60) { return ESP_ERR_INVALID_ARG; } //W7 is reserved for interrupts if (add >= 28) { add += 4; } esp_err_t ret = essl_sdio_read_byte(((essl_sdio_context_t *)arg)->card, HOST_SLCHOST_CONF_W_REG(add), value_o); ESP_LOGV(TAG, "reg: %02"PRIX8, *value_o); return ret; } esp_err_t essl_sdio_clear_intr(void *arg, uint32_t intr_mask, uint32_t wait_ms) { ESP_LOGV(TAG, "clear_intr: %08"PRIX32, intr_mask); return essl_sdio_write_bytes(((essl_sdio_context_t *) arg)->card, HOST_SLC0HOST_INT_CLR_REG, (uint8_t *) &intr_mask, 4); } esp_err_t essl_sdio_get_intr(void *arg, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms) { essl_sdio_context_t *ctx = arg; esp_err_t r; ESP_LOGV(TAG, "get_intr"); if (intr_raw == NULL && intr_st == NULL) { return ESP_ERR_INVALID_ARG; } if (intr_raw != NULL) { r = essl_sdio_read_bytes(ctx->card, HOST_SLC0HOST_INT_RAW_REG, (uint8_t *) intr_raw, 4); if (r != ESP_OK) { return r; } } if (intr_st != NULL) { r = essl_sdio_read_bytes(ctx->card, HOST_SLC0HOST_INT_ST_REG, (uint8_t *) intr_st, 4); if (r != ESP_OK) { return r; } } return ESP_OK; } esp_err_t essl_sdio_set_intr_ena(void *arg, uint32_t ena_mask, uint32_t wait_ms) { ESP_LOGV(TAG, "set_intr_ena: %08"PRIX32, ena_mask); return essl_sdio_write_bytes(((essl_sdio_context_t *)arg)->card, HOST_SLC0HOST_FUNC1_INT_ENA_REG, (uint8_t *) &ena_mask, 4); } esp_err_t essl_sdio_get_intr_ena(void *arg, uint32_t *ena_mask_o, uint32_t wait_ms) { ESP_LOGV(TAG, "get_intr_ena"); esp_err_t ret = essl_sdio_read_bytes(((essl_sdio_context_t *)arg)->card, HOST_SLC0HOST_FUNC1_INT_ENA_REG, (uint8_t *) ena_mask_o, 4); ESP_LOGV(TAG, "ena: %08"PRIX32, *ena_mask_o); return ret; } esp_err_t essl_sdio_send_slave_intr(void *arg, uint32_t intr_mask, uint32_t wait_ms) { //Only 8 bits available ESP_LOGV(TAG, "send_slave_intr: %02"PRIx8, (uint8_t)intr_mask); return essl_sdio_write_byte(((essl_sdio_context_t *)arg)->card, HOST_SLCHOST_CONF_W7_REG + 0, (uint8_t)intr_mask, NULL); } esp_err_t essl_sdio_wait_int(void *arg, uint32_t wait_ms) { return sdmmc_io_wait_int(((essl_sdio_context_t *)arg)->card, wait_ms); } void essl_sdio_reset_cnt(void *arg) { essl_sdio_context_t *ctx = arg; ctx->rx_got_bytes = 0; ctx->tx_sent_buffers = 0; } ================================================ FILE: esp_serial_slave_link/essl_sdio_defs.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ // Definitions of Espressif SDIO Slave hardware #include "essl_sdio.h" essl_sdio_def_t ESSL_SDIO_DEF_ESP32 = { .new_packet_intr_mask = BIT(23), }; essl_sdio_def_t ESSL_SDIO_DEF_ESP32C6 = { .new_packet_intr_mask = BIT(23), }; ================================================ FILE: esp_serial_slave_link/essl_spi.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "esp_check.h" #include "esp_memory_utils.h" #include "esp_private/periph_ctrl.h" #include "driver/spi_master.h" #include "hal/spi_types.h" #include "hal/spi_ll.h" #include "essl_internal.h" #include "essl_spi.h" /** * Initialise device function list of SPI by this macro. */ #define ESSL_SPI_DEFAULT_DEV_FUNC() (essl_dev_t) {\ .get_tx_buffer_num = essl_spi_get_tx_buffer_num,\ .update_tx_buffer_num = essl_spi_update_tx_buffer_num,\ .get_rx_data_size = essl_spi_get_rx_data_size,\ .update_rx_data_size = essl_spi_update_rx_data_size,\ .send_packet = essl_spi_send_packet,\ .get_packet = essl_spi_get_packet,\ .write_reg = essl_spi_write_reg,\ .read_reg = essl_spi_read_reg,\ } static const char TAG[] = "essl_spi"; typedef struct { spi_device_handle_t spi; // Pointer to SPI device handle. /* Master TX, Slave RX */ struct { size_t sent_buf_num; // Number of TX buffers that has been sent out by the master. size_t slave_rx_buf_num; // Number of RX buffers loaded by the slave. uint16_t tx_buffer_size; /* Buffer size for Master TX / Slave RX direction. * Data with length within this size will still be regarded as one buffer. * E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. */ uint8_t tx_sync_reg; // The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. } master_out; /* Master RX, Slave TX */ struct { size_t received_bytes; // Number of the RX bytes that has been received by the Master. size_t slave_tx_bytes; // Number of the TX bytes that has been loaded by the Slave uint8_t rx_sync_reg; // The pre-negotiated register ID for Master-RX-SLAVE-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. } master_in; } essl_spi_context_t; static uint16_t get_hd_command(spi_command_t cmd_t, uint32_t flags) { spi_line_mode_t line_mode = { .cmd_lines = 1, }; if (flags & SPI_TRANS_MODE_DIO) { line_mode.data_lines = 2; if (flags & SPI_TRANS_MODE_DIOQIO_ADDR) { line_mode.addr_lines = 2; } else { line_mode.addr_lines = 1; } } else if (flags & SPI_TRANS_MODE_QIO) { line_mode.data_lines = 4; if (flags & SPI_TRANS_MODE_DIOQIO_ADDR) { line_mode.addr_lines = 4; } else { line_mode.addr_lines = 1; } } else { line_mode.data_lines = 1; line_mode.addr_lines = 1; } return spi_ll_get_slave_hd_command(cmd_t, line_mode); } static int get_hd_dummy_bits(uint32_t flags) { spi_line_mode_t line_mode = {}; if (flags & SPI_TRANS_MODE_DIO) { line_mode.data_lines = 2; } else if (flags & SPI_TRANS_MODE_QIO) { line_mode.data_lines = 4; } else { line_mode.data_lines = 1; } return spi_ll_get_slave_hd_dummy_bits(line_mode); } esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_RDBUF, flags), .addr = addr % 72, .rxlength = len * 8, .rx_buffer = out_data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_rdbuf_polling(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_RDBUF, flags), .addr = addr % 72, .rxlength = len * 8, .rx_buffer = out_data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_polling_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_WRBUF, flags), .addr = addr % 72, .length = len * 8, .tx_buffer = data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_wrbuf_polling(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_WRBUF, flags), .addr = addr % 72, .length = len * 8, .tx_buffer = data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_polling_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_rddma_seg(spi_device_handle_t spi, uint8_t *out_data, int seg_len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_RDDMA, flags), .rxlength = seg_len * 8, .rx_buffer = out_data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags) { spi_transaction_t end_t = { .cmd = get_hd_command(SPI_CMD_HD_INT0, flags), .flags = flags, }; return spi_device_transmit(spi, &end_t); } esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, int seg_len, uint32_t flags) { if (!esp_ptr_dma_capable(out_data) || ((intptr_t)out_data % 4) != 0) { return ESP_ERR_INVALID_ARG; } seg_len = (seg_len > 0) ? seg_len : len; uint8_t *read_ptr = out_data; esp_err_t ret = ESP_OK; while (len > 0) { int send_len = MIN(seg_len, len); ret = essl_spi_rddma_seg(spi, read_ptr, send_len, flags); if (ret != ESP_OK) { return ret; } len -= send_len; read_ptr += send_len; } return essl_spi_rddma_done(spi, flags); } esp_err_t essl_spi_wrdma_seg(spi_device_handle_t spi, const uint8_t *data, int seg_len, uint32_t flags) { spi_transaction_ext_t t = { .base = { .cmd = get_hd_command(SPI_CMD_HD_WRDMA, flags), .length = seg_len * 8, .tx_buffer = data, .flags = flags | SPI_TRANS_VARIABLE_DUMMY, }, .dummy_bits = get_hd_dummy_bits(flags), }; return spi_device_transmit(spi, (spi_transaction_t *)&t); } esp_err_t essl_spi_wrdma_done(spi_device_handle_t spi, uint32_t flags) { spi_transaction_t end_t = { .cmd = get_hd_command(SPI_CMD_HD_WR_END, flags), .flags = flags, }; return spi_device_transmit(spi, &end_t); } esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, int seg_len, uint32_t flags) { if (!esp_ptr_dma_capable(data)) { return ESP_ERR_INVALID_ARG; } seg_len = (seg_len > 0) ? seg_len : len; while (len > 0) { int send_len = MIN(seg_len, len); esp_err_t ret = essl_spi_wrdma_seg(spi, data, send_len, flags); if (ret != ESP_OK) { return ret; } len -= send_len; data += send_len; } return essl_spi_wrdma_done(spi, flags); } esp_err_t essl_spi_int(spi_device_handle_t spi, int int_n, uint32_t flags) { spi_transaction_t end_t = { .cmd = get_hd_command(SPI_CMD_HD_INT0 + int_n, flags), .flags = flags, }; return spi_device_transmit(spi, &end_t); } //------------------------------------ APPEND MODE ----------------------------------// static uint32_t essl_spi_get_rx_data_size(void *arg); static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms); static uint32_t essl_spi_get_tx_buffer_num(void *arg); static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms); esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config) { ESP_RETURN_ON_FALSE(init_config->spi, ESP_ERR_INVALID_STATE, TAG, "Check SPI initialization first"); ESP_RETURN_ON_FALSE(init_config->tx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, ESP_ERR_INVALID_ARG, TAG, "GPSPI supports %d-byte-width internal registers", SOC_SPI_MAXIMUM_BUFFER_SIZE); ESP_RETURN_ON_FALSE(init_config->rx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, ESP_ERR_INVALID_ARG, TAG, "GPSPI supports %d-byte-width internal registers", SOC_SPI_MAXIMUM_BUFFER_SIZE); ESP_RETURN_ON_FALSE(init_config->tx_sync_reg != init_config->rx_sync_reg, ESP_ERR_INVALID_ARG, TAG, "Should use different word of registers for synchronization"); essl_spi_context_t *context = calloc(1, sizeof(essl_spi_context_t)); essl_dev_t *dev = calloc(1, sizeof(essl_dev_t)); if (!context || !dev) { free(context); free(dev); return ESP_ERR_NO_MEM; } *context = (essl_spi_context_t) { .spi = *init_config->spi, .master_out.tx_buffer_size = init_config->tx_buf_size, .master_out.tx_sync_reg = init_config->tx_sync_reg, .master_in.rx_sync_reg = init_config->rx_sync_reg }; *dev = ESSL_SPI_DEFAULT_DEV_FUNC(); dev->args = context; *out_handle = dev; return ESP_OK; } esp_err_t essl_spi_deinit_dev(essl_handle_t handle) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "ESSL SPI is not in use"); free(handle->args); free(handle); return ESP_OK; } void essl_spi_reset_cnt(void *arg) { essl_spi_context_t *ctx = arg; if (ctx) { ctx->master_out.sent_buf_num = 0; ctx->master_in.received_bytes = 0; } } //------------------------------------ RX ----------------------------------// esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms) { essl_spi_context_t *ctx = arg; ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first"); uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg; uint8_t reserved_1_tail = reserved_1_head + 3; uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg; uint8_t reserved_2_tail = reserved_2_head + 3; ESP_RETURN_ON_FALSE(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, ESP_ERR_INVALID_ARG, TAG, "Invalid address"); return essl_spi_rdbuf(ctx->spi, out_value, addr, sizeof(uint8_t), 0); } static uint32_t essl_spi_get_rx_data_size(void *arg) { essl_spi_context_t *ctx = arg; ESP_LOGV(TAG, "slave tx buffer: %d bytes, master has read: %d bytes", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes); return ctx->master_in.slave_tx_bytes - ctx->master_in.received_bytes; } static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms) { essl_spi_context_t *ctx = arg; uint32_t updated_size = 0; uint32_t previous_size = 0; esp_err_t ret; ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0); if (ret != ESP_OK) { return ret; } /** * Read until the last 2 reading result are same. Reason: * SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the * register value is changed by Slave at this time, Master may get wrong data. */ while (1) { ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0); if (ret != ESP_OK) { return ret; } if (updated_size == previous_size) { ctx->master_in.slave_tx_bytes = updated_size; ESP_LOGV(TAG, "updated: slave prepared tx buffer is: %d bytes", (unsigned int)updated_size); return ret; } previous_size = updated_size; } } esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms) { ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first"); if (!esp_ptr_dma_capable(out_data) || ((intptr_t)out_data % 4) != 0) { return ESP_ERR_INVALID_ARG; } essl_spi_context_t *ctx = arg; esp_err_t ret; if (essl_spi_get_rx_data_size(arg) < size) { /** * For realistic situation, usually there will be a large overhead (Slave will load large amount of data), * so here we only update the Slave's TX size when the last-updated size is smaller than what Master requires. */ ret = essl_spi_update_rx_data_size(arg, wait_ms); if (ret != ESP_OK) { return ret; } //Slave still did not load enough size of buffer if (essl_spi_get_rx_data_size(arg) < size) { ESP_LOGV(TAG, "slave buffer: %d is not enough, %d is required", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes + size); return ESP_ERR_NOT_FOUND; } } ESP_LOGV(TAG, "get_packet: size to read is: %d", size); ret = essl_spi_rddma_seg(ctx->spi, out_data, size, 0); if (ret != ESP_OK) { return ret; } ctx->master_in.received_bytes += size; return ESP_OK; } //------------------------------------ TX ----------------------------------// esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms) { essl_spi_context_t *ctx = arg; ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first"); uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg; uint8_t reserved_1_tail = reserved_1_head + 3; uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg; uint8_t reserved_2_tail = reserved_2_head + 3; ESP_RETURN_ON_FALSE(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, ESP_ERR_INVALID_ARG, TAG, "Invalid address"); ESP_RETURN_ON_FALSE(out_value == NULL, ESP_ERR_NOT_SUPPORTED, TAG, "This feature is not supported"); return essl_spi_wrbuf(ctx->spi, &value, addr, sizeof(uint8_t), 0); } static uint32_t essl_spi_get_tx_buffer_num(void *arg) { essl_spi_context_t *ctx = arg; ESP_LOGV(TAG, "slave rx buffer: %d, master has sent: %d", ctx->master_out.slave_rx_buf_num, ctx->master_out.sent_buf_num); return ctx->master_out.slave_rx_buf_num - ctx->master_out.sent_buf_num; } static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms) { essl_spi_context_t *ctx = arg; uint32_t updated_num = 0; uint32_t previous_size = 0; esp_err_t ret; ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0); if (ret != ESP_OK) { return ret; } /** * Read until the last 2 reading result are same. Reason: * SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the * register value is changed by Slave at this time, Master may get wrong data. */ while (1) { ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_num, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0); if (ret != ESP_OK) { return ret; } if (updated_num == previous_size) { ctx->master_out.slave_rx_buf_num = updated_num; ESP_LOGV(TAG, "updated: slave prepared rx buffer: %d", (unsigned int)updated_num); return ret; } previous_size = updated_num; } } esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms) { ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first"); if (!esp_ptr_dma_capable(data)) { return ESP_ERR_INVALID_ARG; } essl_spi_context_t *ctx = arg; esp_err_t ret; uint32_t buf_num_to_use = (size + ctx->master_out.tx_buffer_size - 1) / ctx->master_out.tx_buffer_size; if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) { /** * For realistic situation, usually there will be a large overhead (Slave will load enough number of RX buffers), * so here we only update the Slave's RX buffer number when the last-updated number is smaller than what Master requires. */ ret = essl_spi_update_tx_buffer_num(arg, wait_ms); if (ret != ESP_OK) { return ret; } //Slave still did not load a sufficient amount of buffers if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) { ESP_LOGV(TAG, "slave buffer: %"PRIu32" is not enough, %"PRIu32" is required", (uint32_t)ctx->master_out.slave_rx_buf_num, (uint32_t)ctx->master_out.sent_buf_num + buf_num_to_use); return ESP_ERR_NOT_FOUND; } } ESP_LOGV(TAG, "send_packet: size to write is: %zu", size); ret = essl_spi_wrdma_seg(ctx->spi, data, size, 0); if (ret != ESP_OK) { return ret; } ctx->master_out.sent_buf_num += buf_num_to_use; return essl_spi_wrdma_done(ctx->spi, 0); } ================================================ FILE: esp_serial_slave_link/idf_component.yml ================================================ version: "1.1.2" description: Espressif Serial Slave Link Library url: https://github.com/espressif/idf-extra-components/tree/master/esp_serial_slave_link repository: https://github.com/espressif/idf-extra-components.git documentation: https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/index.html issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.0" ================================================ FILE: esp_serial_slave_link/include/esp_serial_slave_link/essl.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif struct essl_dev_t; /// Handle of an ESSL device typedef struct essl_dev_t *essl_handle_t; /** * @brief Initialize the slave. * * @param handle Handle of an ESSL device. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * @return * - ESP_OK: If success * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - Other value returned from lower layer `init`. */ esp_err_t essl_init(essl_handle_t handle, uint32_t wait_ms); /** * @brief Wait for interrupt of an ESSL slave device. * * @param handle Handle of an ESSL device. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: If success * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms); /** * @brief Get buffer num for the host to send data to the slave. The buffers are size of ``buffer_size``. * * @param handle Handle of a ESSL device. * @param out_tx_num Output of buffer num that host can send data to ESSL slave. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_get_tx_buffer_num(essl_handle_t handle, uint32_t *out_tx_num, uint32_t wait_ms); /** * @brief Get the size, in bytes, of the data that the ESSL slave is ready to send * * @param handle Handle of an ESSL device. * @param out_rx_size Output of data size to read from slave, in bytes * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uint32_t wait_ms); /** * @brief Reset the counters of this component. Usually you don't need to do this unless you know the slave is reset. * * @param handle Handle of an ESSL device. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode * - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init. */ esp_err_t essl_reset_cnt(essl_handle_t handle); /** * @brief Send a packet to the ESSL Slave. The Slave receives the packet into buffers whose size is ``buffer_size`` (configured during initialization). * * @param handle Handle of an ESSL device. * @param start Start address of the packet to send * @param length Length of data to send, if the packet is over-size, the it will be divided into blocks and hold into different buffers automatically. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init or other argument is not valid. * - ESP_ERR_TIMEOUT: No buffer to use, or error ftrom SDMMC host controller. * - ESP_ERR_NOT_FOUND: Slave is not ready for receiving. * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode * - One of the error codes from SDMMC/SPI host controller. */ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms); /** * @brief Get a packet from ESSL slave. * * @param handle Handle of an ESSL device. * @param[out] out_data Data output address * @param size The size of the output buffer, if the buffer is smaller than the size of data to receive from slave, the driver returns ``ESP_ERR_NOT_FINISHED`` * @param[out] out_length Output of length the data actually received from slave. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK Success: All the data has been read from the slave. * - ESP_ERR_INVALID_ARG: Invalid argument, The handle is not initialized or the other arguments are invalid. * - ESP_ERR_NOT_FINISHED: Read was successful, but there is still data remaining. * - ESP_ERR_NOT_FOUND: Slave is not ready to send data. * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode * - One of the error codes from SDMMC/SPI host controller. */ esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms); /** * @brief Write general purpose R/W registers (8-bit) of ESSL slave. * * @param handle Handle of an ESSL device. * @param addr Address of register to write. For SDIO, valid address: 0-59. For SPI, see ``essl_spi.h`` * @param value Value to write to the register. * @param value_o Output of the returned written value. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @note sdio 28-31 are reserved, the lower API helps to skip. * * @return * - ESP_OK Success * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_write_reg(essl_handle_t handle, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms); /** * @brief Read general purpose R/W registers (8-bit) of ESSL slave. * * @param handle Handle of a ``essl`` device. * @param add Address of register to read. For SDIO, Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read). For SPI, see ``essl_spi.h`` * @param value_o Output value read from the register. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK Success * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uint32_t wait_ms); /** * @brief wait for an interrupt of the slave * * @param handle Handle of an ESSL device. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: If interrupt is triggered. * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - ESP_ERR_TIMEOUT: No interrupts before timeout. */ esp_err_t essl_wait_int(essl_handle_t handle, uint32_t wait_ms); /** * @brief Clear interrupt bits of ESSL slave. All the bits set in the mask will be cleared, while other bits will stay the same. * * @param handle Handle of an ESSL device. * @param intr_mask Mask of interrupt bits to clear. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms); /** * @brief Get interrupt bits of ESSL slave. * * @param handle Handle of an ESSL device. * @param intr_raw Output of the raw interrupt bits. Set to NULL if only masked bits are read. * @param intr_st Output of the masked interrupt bits. set to NULL if only raw bits are read. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_INVALID_ARG: If both ``intr_raw`` and ``intr_st`` are NULL. * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_get_intr(essl_handle_t handle, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms); /** * @brief Set interrupt enable bits of ESSL slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set. * * @param handle Handle of an ESSL device. * @param ena_mask Mask of the interrupt bits to enable. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_set_intr_ena(essl_handle_t handle, uint32_t ena_mask, uint32_t wait_ms); /** * @brief Get interrupt enable bits of ESSL slave. * * @param handle Handle of an ESSL device. * @param ena_mask_o Output of interrupt bit enable mask. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ esp_err_t essl_get_intr_ena(essl_handle_t handle, uint32_t *ena_mask_o, uint32_t wait_ms); /** * @brief Send interrupts to slave. Each bit of the interrupt will be triggered. * * @param handle Handle of an ESSL device. * @param intr_mask Mask of interrupt bits to send to slave. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK: Success * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_send_slave_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms); #ifdef __cplusplus } #endif ================================================ FILE: esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ // ESP SDIO slave link used by the ESP host to communicate with ESP SDIO slave. #pragma once #include "esp_err.h" #include "driver/sdmmc_types.h" #include "driver/sdmmc_host.h" #include "esp_serial_slave_link/essl.h" #include "essl_sdio_defs.h" #ifdef __cplusplus extern "C" { #endif /// Configuration for the ESSL SDIO device typedef struct { sdmmc_card_t *card; ///< The initialized sdmmc card pointer of the slave. int recv_buffer_size; ///< The pre-negotiated recv buffer size used by both the host and the slave. } essl_sdio_config_t; /** * @brief Initialize the ESSL SDIO device and get its handle. * * @param out_handle Output of the handle. * @param config Configuration for the ESSL SDIO device. * @return * - ESP_OK: on success * - ESP_ERR_NO_MEM: memory exhausted. */ esp_err_t essl_sdio_init_dev(essl_handle_t *out_handle, const essl_sdio_config_t *config); /** * @brief Deinitialize and free the space used by the ESSL SDIO device. * * @param handle Handle of the ESSL SDIO device to deinit. * @return * - ESP_OK: on success * - ESP_ERR_INVALID_ARG: wrong handle passed */ esp_err_t essl_sdio_deinit_dev(essl_handle_t handle); // Please call `essl_` functions without `sdio` instead of calling these functions directly. /** @cond */ /** * @brief SDIO Initialize process of an ESSL SDIO slave device. * * @param arg Context of the ``essl`` component. Send to other functions later. * @param wait_ms Time to wait before operation is done, in ms. * * @return * - ESP_OK if success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_init(void *arg, uint32_t wait_ms); /** * @brief Wait for interrupt of an ESSL SDIO slave device. * * @param arg Context of the ``essl`` component. * @param wait_ms Time to wait before operation is done, in ms. * * @return * - ESP_OK if success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_wait_for_ready(void *arg, uint32_t wait_ms); /** * @brief Get buffer num for the host to send data to the slave. The buffers are size of ``buffer_size``. * * @param arg Context of the component. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ uint32_t essl_sdio_get_tx_buffer_num(void *arg); /** * @brief Get amount of data the ESSL SDIO slave preparing to send to host. * * @param arg Context of the component. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ uint32_t essl_sdio_get_rx_data_size(void *arg); /** * @brief Send a packet to the ESSL SDIO slave. The slave receive the packet into buffers whose size is ``buffer_size`` in the arg. * * @param arg Context of the component. * @param start Start address of the packet to send * @param length Length of data to send, if the packet is over-size, the it will be divided into blocks and hold into different buffers automatically. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - ESP_ERR_TIMEOUT No buffer to use, or error ftrom SDMMC host controller * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_send_packet(void *arg, const void *start, size_t length, uint32_t wait_ms); /** * @brief Get a packet from an ESSL SDIO slave. * * @param arg Context of the component. * @param[out] out_data Data output address * @param size The size of the output buffer, if the buffer is smaller than the size of data to receive from slave, the driver returns ``ESP_ERR_NOT_FINISHED`` * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success, all the data are read from the slave. * - ESP_ERR_NOT_FINISHED Read success, while there're data remaining. * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms); /** * @brief Wait for the interrupt from the SDIO slave. * * @param arg Context of the component. * @param wait_ms Time to wait before timeout, in ms. * @return * - ESP_ERR_NOT_SUPPORTED: if the interrupt line is not initialized properly. * - ESP_OK: if interrupt happened * - ESP_ERR_TIMEOUT: if timeout before interrupt happened. * - or other values returned from the `io_int_wait` member of the `card->host` structure. */ esp_err_t essl_sdio_wait_int(void *arg, uint32_t wait_ms); /** * @brief Clear interrupt bits of an ESSL SDIO slave. All the bits set in the mask will be cleared, while other bits will stay the same. * * @param arg Context of the component. * @param intr_mask Mask of interrupt bits to clear. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_clear_intr(void *arg, uint32_t intr_mask, uint32_t wait_ms); /** * @brief Get interrupt bits of an ESSL SDIO slave. * * @param arg Context of the component. * @param intr_raw Output of the raw interrupt bits. Set to NULL if only masked bits are read. * @param intr_st Output of the masked interrupt bits. set to NULL if only raw bits are read. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - ESP_INVALID_ARG if both ``intr_raw`` and ``intr_st`` are NULL. * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_get_intr(void *arg, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms); /** * @brief Set interrupt enable bits of an ESSL SDIO slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set. * * @param arg Context of the component. * @param ena_mask Mask of the interrupt bits to enable. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_set_intr_ena(void *arg, uint32_t ena_mask, uint32_t wait_ms); /** * @brief Get interrupt enable bits of an ESSL SDIO slave. * * @param arg Context of the component. * @param ena_mask_o Output of interrupt bit enable mask. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_get_intr_ena(void *arg, uint32_t *ena_mask_o, uint32_t wait_ms); /** * @brief Write general purpose R/W registers (8-bit) of an ESSL SDIO slave. * * @param arg Context of the component. * @param addr Address of register to write. Valid address: 0-27, 32-63 (28-31 reserved). * @param value Value to write to the register. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Address not valid. * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms); /** * @brief Read general purpose R/W registers (8-bit) of an ESSL SDIO slave. * * @param arg Context of the component. * @param add Address of register to read. Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read). * @param value Output value read from the register. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Address not valid. * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_read_reg(void *arg, uint8_t add, uint8_t *value_o, uint32_t wait_ms); /** * @brief Send interrupts to slave. Each bit of the interrupt will be triggered. * * @param arg Context of the component. * @param intr_mask Mask of interrupt bits to send to slave. * @param wait_ms Time to wait before timeout, in ms. * * @return * - ESP_OK Success * - One of the error codes from SDMMC host controller */ esp_err_t essl_sdio_send_slave_intr(void *arg, uint32_t intr_mask, uint32_t wait_ms); /** * @brief Reset the counter on the host side. * * @note Only call when you know the slave has reset its counter, or there will be inconsistent between the master and the slave. * * @param arg Context of the component. */ void essl_sdio_reset_cnt(void *arg); /** @endcond */ #ifdef __cplusplus } #endif ================================================ FILE: esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio_defs.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once /** * This file contains SDIO Slave hardware specific requirements */ #include #ifdef __cplusplus extern "C" { #endif typedef struct { //interrupts uint32_t new_packet_intr_mask; } essl_sdio_def_t; /// Definitions of ESP32 SDIO Slave hardware extern essl_sdio_def_t ESSL_SDIO_DEF_ESP32; /// Definitions of ESP32C6 SDIO Slave hardware extern essl_sdio_def_t ESSL_SDIO_DEF_ESP32C6; #ifdef __cplusplus } #endif ================================================ FILE: esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_err.h" #include "driver/spi_master.h" #include "esp_serial_slave_link/essl.h" #ifdef __cplusplus extern "C" { #endif /// Configuration of ESSL SPI device typedef struct { spi_device_handle_t *spi; ///< Pointer to SPI device handle. uint32_t tx_buf_size; ///< The pre-negotiated Master TX buffer size used by both the host and the slave. uint8_t tx_sync_reg; ///< The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. uint8_t rx_sync_reg; ///< The pre-negotiated register ID for Master-RX-Slave-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. } essl_spi_config_t; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // APIs for DMA Append Mode // This mode has a better performance for continuous Half Duplex SPI transactions. // // * You can use the ``essl_spi_init_dev`` and ``essl_spi_deinit_dev`` together with APIs in ``essl.h`` to communicate // with ESP SPI Slaves in Half Duplex DMA Append Mode. See example for SPI SLAVE HALFDUPLEX APPEND MODE. // * You can also use the following APIs to create your own logic. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize the ESSL SPI device function list and get its handle * * @param[out] out_handle Output of the handle * @param init_config Configuration for the ESSL SPI device * @return * - ESP_OK: On success * - ESP_ERR_NO_MEM: Memory exhausted * - ESP_ERR_INVALID_STATE: SPI driver is not initialized * - ESP_ERR_INVALID_ARG: Wrong register ID */ esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config); /** * @brief Deinitialize the ESSL SPI device and free the memory used by the device * * @param handle Handle of the ESSL SPI device * @return * - ESP_OK: On success * - ESP_ERR_INVALID_STATE: ESSL SPI is not in use */ esp_err_t essl_spi_deinit_dev(essl_handle_t handle); /** * @brief Read from the shared registers * * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `rx_sync_reg` in `essl_spi_config_t`) * * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) * @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1). * @param[out] out_value Read buffer for the shared registers. * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). * @return * - ESP_OK: success * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. * - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1. * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms); /** * @brief Get a packet from Slave * * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) * @param[out] out_data Output data address * @param size The size of the output data. * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). * @return * - ESP_OK: On Success * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. * - ESP_ERR_INVALID_ARG: The output data address is neither DMA capable nor 4 byte-aligned * - ESP_ERR_INVALID_SIZE: Master requires ``size`` bytes of data but Slave did not load enough bytes. */ esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms); /** * @brief Write to the shared registers * * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `tx_sync_reg` in `essl_spi_config_t`) * @note Feature of checking the actual written value (``out_value``) is not supported. * * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) * @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1) * @param value Buffer for data to send, should be align to 4. * @param[out] out_value Not supported, should be set to NULL. * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). * @return * - ESP_OK: success * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. * - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1. * - ESP_ERR_NOT_SUPPORTED: Should set ``out_value`` to NULL. See note 2. * - or other return value from :cpp:func:`spi_device_transmit`. * */ esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms); /** * @brief Send a packet to Slave * * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) * @param data Address of the data to send * @param size Size of the data to send. * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). * @return * - ESP_OK: On success * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. * - ESP_ERR_INVALID_ARG: The data address is not DMA capable * - ESP_ERR_INVALID_SIZE: Master will send ``size`` bytes of data but Slave did not load enough RX buffer */ esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms); /** * @brief Reset the counter in Master context * * @note Shall only be called if the slave has reset its counter. Else, Slave and Master would be desynchronized * * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) */ void essl_spi_reset_cnt(void *arg); //////////////////////////////////////////////////////////////////////////////// // Basic commands to communicate with the SPI Slave HD on ESP32-S2 //////////////////////////////////////////////////////////////////////////////// /** * @brief Read the shared buffer from the slave in ISR way * * @note The slave's HW doesn't guarantee the data in one SPI transaction is consistent. It sends data in unit of byte. * In other words, if the slave SW attempts to update the shared register when a rdbuf SPI transaction is in-flight, * the data got by the master will be the combination of bytes of different writes of slave SW. * * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words * by the DMA. When a byte is written, the remaining bytes in the same word will also be * overwritten, even the ``len`` is shorter than a word. * * @param spi SPI device handle representing the slave * @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned to 4 * @param addr Address of the slave shared buffer * @param len Length to read * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: on success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags); /** * @brief Read the shared buffer from the slave in polling way * * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words * by the DMA. When a byte is written, the remaining bytes in the same word will also be * overwritten, even the ``len`` is shorter than a word. * * @param spi SPI device handle representing the slave * @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned to 4 * @param addr Address of the slave shared buffer * @param len Length to read * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: on success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_rdbuf_polling(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags); /** * @brief Write the shared buffer of the slave in ISR way * * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words * by the DMA. When a byte is written, the remaining bytes in the same word will also be * overwritten, even the ``len`` is shorter than a word. * * @param spi SPI device handle representing the slave * @param data Buffer for data to send, strongly suggested to be in the DRAM * @param addr Address of the slave shared buffer, * @param len Length to write * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags); /** * @brief Write the shared buffer of the slave in polling way * * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words * by the DMA. When a byte is written, the remaining bytes in the same word will also be * overwritten, even the ``len`` is shorter than a word. * * @param spi SPI device handle representing the slave * @param data Buffer for data to send, strongly suggested to be in the DRAM * @param addr Address of the slave shared buffer, * @param len Length to write * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_polling_transmit`. */ esp_err_t essl_spi_wrbuf_polling(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags); /** * @brief Receive long buffer in segments from the slave through its DMA. * * @note This function combines several :cpp:func:`essl_spi_rddma_seg` and one * :cpp:func:`essl_spi_rddma_done` at the end. Used when the slave is working in segment mode. * * @param spi SPI device handle representing the slave * @param[out] out_data Buffer to hold the received data, strongly suggested to be in the DRAM and aligned to 4 * @param len Total length of data to receive. * @param seg_len Length of each segment, which is not larger than the maximum transaction length * allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send * all data in one segment (the ``rddma_done`` will still be sent.) * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, int seg_len, uint32_t flags); /** * @brief Read one data segment from the slave through its DMA. * * @note To read long buffer, call :cpp:func:`essl_spi_rddma` instead. * * @param spi SPI device handle representing the slave * @param[out] out_data Buffer to hold the received data. strongly suggested to be in the DRAM and aligned to 4 * @param seg_len Length of this segment * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_rddma_seg(spi_device_handle_t spi, uint8_t *out_data, int seg_len, uint32_t flags); /** * @brief Send the ``rddma_done`` command to the slave. Upon receiving this command, the slave will * stop sending the current buffer even there are data unsent, and maybe prepare the next buffer to * send. * * @note This is required only when the slave is working in segment mode. * * @param spi SPI device handle representing the slave * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags); /** * @brief Send long buffer in segments to the slave through its DMA. * * @note This function combines several :cpp:func:`essl_spi_wrdma_seg` and one * :cpp:func:`essl_spi_wrdma_done` at the end. Used when the slave is working in segment mode. * * @param spi SPI device handle representing the slave * @param data Buffer for data to send, strongly suggested to be in the DRAM * @param len Total length of data to send. * @param seg_len Length of each segment, which is not larger than the maximum transaction length * allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send * all data in one segment (the ``wrdma_done`` will still be sent.) * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, int seg_len, uint32_t flags); /** * @brief Send one data segment to the slave through its DMA. * * @note To send long buffer, call :cpp:func:`essl_spi_wrdma` instead. * * @param spi SPI device handle representing the slave * @param data Buffer for data to send, strongly suggested to be in the DRAM * @param seg_len Length of this segment * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_wrdma_seg(spi_device_handle_t spi, const uint8_t *data, int seg_len, uint32_t flags); /** * @brief Send the ``wrdma_done`` command to the slave. Upon receiving this command, the slave will * stop receiving, process the received data, and maybe prepare the next buffer to receive. * * @note This is required only when the slave is working in segment mode. * * @param spi SPI device handle representing the slave * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return * - ESP_OK: success * - or other return value from :cpp:func:`spi_device_transmit`. */ esp_err_t essl_spi_wrdma_done(spi_device_handle_t spi, uint32_t flags); #ifdef __cplusplus } #endif ================================================ FILE: esp_serial_slave_link/include/essl_spi/esp32c2_defs.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once // NOTE: From the view of master #define CMD_HD_WRBUF_REG 0x01 #define CMD_HD_RDBUF_REG 0x02 #define CMD_HD_WRDMA_REG 0x03 #define CMD_HD_RDDMA_REG 0x04 #define CMD_HD_ONEBIT_MODE 0x00 #define CMD_HD_DOUT_MODE 0x10 #define CMD_HD_QOUT_MODE 0x20 #define CMD_HD_DIO_MODE 0x50 #define CMD_HD_QIO_MODE 0xA0 #define CMD_HD_SEG_END_REG 0x05 #define CMD_HD_EN_QPI_REG 0x06 #define CMD_HD_WR_END_REG 0x07 #define CMD_HD_INT0_REG 0x08 #define CMD_HD_INT1_REG 0x09 #define CMD_HD_INT2_REG 0x0A #define CMD_HD_EX_QPI_REG 0xDD #define SPI_SLAVE_HD_BUFFER_SIZE 64 ================================================ FILE: esp_serial_slave_link/include/essl_spi/esp32c3_defs.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once // NOTE: From the view of master #define CMD_HD_WRBUF_REG 0x01 #define CMD_HD_RDBUF_REG 0x02 #define CMD_HD_WRDMA_REG 0x03 #define CMD_HD_RDDMA_REG 0x04 #define CMD_HD_ONEBIT_MODE 0x00 #define CMD_HD_DOUT_MODE 0x10 #define CMD_HD_QOUT_MODE 0x20 #define CMD_HD_DIO_MODE 0x50 #define CMD_HD_QIO_MODE 0xA0 #define CMD_HD_SEG_END_REG 0x05 #define CMD_HD_EN_QPI_REG 0x06 #define CMD_HD_WR_END_REG 0x07 #define CMD_HD_INT0_REG 0x08 #define CMD_HD_INT1_REG 0x09 #define CMD_HD_INT2_REG 0x0A #define CMD_HD_EX_QPI_REG 0xDD #define SPI_SLAVE_HD_BUFFER_SIZE 64 ================================================ FILE: esp_serial_slave_link/include/essl_spi/esp32s2_defs.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once // NOTE: From the view of master #define CMD_HD_WRBUF_REG 0x01 #define CMD_HD_RDBUF_REG 0x02 #define CMD_HD_WRDMA_REG 0x03 #define CMD_HD_RDDMA_REG 0x04 #define CMD_HD_ONEBIT_MODE 0x00 #define CMD_HD_DOUT_MODE 0x10 #define CMD_HD_QOUT_MODE 0x20 #define CMD_HD_DIO_MODE 0x50 #define CMD_HD_QIO_MODE 0xA0 #define CMD_HD_SEG_END_REG 0x05 #define CMD_HD_EN_QPI_REG 0x06 #define CMD_HD_WR_END_REG 0x07 #define CMD_HD_INT0_REG 0x08 #define CMD_HD_INT1_REG 0x09 #define CMD_HD_INT2_REG 0x0A #define CMD_HD_EX_QPI_REG 0xDD #define SPI_SLAVE_HD_BUFFER_SIZE 72 ================================================ FILE: esp_serial_slave_link/include/essl_spi/esp32s3_defs.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once // NOTE: From the view of master #define CMD_HD_WRBUF_REG 0x01 #define CMD_HD_RDBUF_REG 0x02 #define CMD_HD_WRDMA_REG 0x03 #define CMD_HD_RDDMA_REG 0x04 #define CMD_HD_ONEBIT_MODE 0x00 #define CMD_HD_DOUT_MODE 0x10 #define CMD_HD_QOUT_MODE 0x20 #define CMD_HD_DIO_MODE 0x50 #define CMD_HD_QIO_MODE 0xA0 #define CMD_HD_SEG_END_REG 0x05 #define CMD_HD_EN_QPI_REG 0x06 #define CMD_HD_WR_END_REG 0x07 #define CMD_HD_INT0_REG 0x08 #define CMD_HD_INT1_REG 0x09 #define CMD_HD_INT2_REG 0x0A #define CMD_HD_EX_QPI_REG 0xDD #define SPI_SLAVE_HD_BUFFER_SIZE 64 ================================================ FILE: esp_serial_slave_link/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(essl_test) ================================================ FILE: esp_serial_slave_link/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "essl_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: esp_serial_slave_link/test_apps/main/essl_test.c ================================================ #include void app_main(void) { } ================================================ FILE: esp_serial_slave_link/test_apps/main/idf_component.yml ================================================ dependencies: espressif/esp_serial_slave_link: version: "*" override_path: "../.." ================================================ FILE: esp_sysview/.build-test-rules.yml ================================================ ================================================ FILE: esp_sysview/CMakeLists.txt ================================================ if(CONFIG_ESP_TRACE_LIB_EXTERNAL) set(include_dirs "") set(srcs "") list(APPEND include_dirs src/Config src/SEGGER src/Sample/FreeRTOSV10.4 src/include) list(APPEND srcs src/SEGGER/SEGGER_SYSVIEW.c src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c src/Sample/FreeRTOSV10.4/SEGGER_SYSVIEW_FreeRTOS.c src/esp/SEGGER_RTT_esp.c src/esp/SEGGER_SYSVIEW_esp.c src/esp/adapter_encoder_sysview.c src/ext/logging.c) if(CONFIG_HEAP_TRACING_TOHOST) list(APPEND srcs src/ext/heap_trace_module.c src/ext/heap_trace_tohost.c) if(CONFIG_IDF_TARGET_ARCH_XTENSA) set_source_files_properties(src/ext/heap_trace_tohost.c PROPERTIES COMPILE_FLAGS -Wno-frame-address) endif() endif() configure_file(linker.lf.in ${CMAKE_CURRENT_BINARY_DIR}/linker.lf) idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" REQUIRES esp_trace LDFRAGMENTS ${CMAKE_CURRENT_BINARY_DIR}/linker.lf WHOLE_ARCHIVE TRUE # Link all symbols so self-registering adapters (ESP_TRACE_REGISTER_*) are not discarded ) # Trick to include the SEGGER header into freertos component # This allows freertos component to find esp_trace_freertos_impl.h idf_component_get_property(freertos_lib freertos COMPONENT_LIB) target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) else() idf_component_register(REQUIRES "esp_trace") endif() ================================================ FILE: esp_sysview/Kconfig ================================================ menu "SEGGER SystemView Configuration" depends on ESP_TRACE_LIB_EXTERNAL choice SEGGER_SYSVIEW_DEST_CPU prompt "CPU to trace" depends on ESP_TRACE_TRANSPORT_APPTRACE && APPTRACE_DEST_UART && !ESP_SYSTEM_SINGLE_CORE_MODE default SEGGER_SYSVIEW_DEST_CPU_0 help Define the CPU to trace. config SEGGER_SYSVIEW_DEST_CPU_0 bool "CPU0" help Send tracing data for Pro CPU. config SEGGER_SYSVIEW_DEST_CPU_1 bool "CPU1" help Send tracing data for App CPU. endchoice config SEGGER_SYSVIEW_MAX_TASKS int "Maximum supported tasks" range 1 64 default 16 help Configures maximum supported tasks in sysview debug config SEGGER_SYSVIEW_BUF_WAIT_TMO int "Trace buffer wait timeout" default 500 help Configures timeout (in us) to wait for free space in trace buffer. Set to -1 to wait forever and avoid lost events. config SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE bool "Trace Buffer Overflow Event" default y help Enables "Trace Buffer Overflow" event. config SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE bool "ISR Enter Event" default y help Enables "ISR Enter" event. config SEGGER_SYSVIEW_EVT_ISR_EXIT_ENABLE bool "ISR Exit Event" default y help Enables "ISR Exit" event. config SEGGER_SYSVIEW_EVT_ISR_TO_SCHED_ENABLE bool "ISR Exit to Scheduler Event" default y help Enables "ISR to Scheduler" event. config SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE bool "Task Start Execution Event" default y help Enables "Task Start Execution" event. config SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE bool "Task Stop Execution Event" default y help Enables "Task Stop Execution" event. config SEGGER_SYSVIEW_EVT_TASK_START_READY_ENABLE bool "Task Start Ready State Event" default y help Enables "Task Start Ready State" event. config SEGGER_SYSVIEW_EVT_TASK_STOP_READY_ENABLE bool "Task Stop Ready State Event" default y help Enables "Task Stop Ready State" event. config SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE bool "Task Create Event" default y help Enables "Task Create" event. config SEGGER_SYSVIEW_EVT_TASK_TERMINATE_ENABLE bool "Task Terminate Event" default y help Enables "Task Terminate" event. config SEGGER_SYSVIEW_EVT_IDLE_ENABLE bool "System Idle Event" default y help Enables "System Idle" event. config SEGGER_SYSVIEW_EVT_TIMER_ENTER_ENABLE bool "Timer Enter Event" default y help Enables "Timer Enter" event. config SEGGER_SYSVIEW_EVT_TIMER_EXIT_ENABLE bool "Timer Exit Event" default y help Enables "Timer Exit" event. endmenu ================================================ FILE: esp_sysview/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: esp_sysview/README.md ================================================ # SEGGER SystemView for ESP-IDF [![Component Registry](https://components.espressif.com/components/espressif/esp_sysview/badge.svg)](https://components.espressif.com/components/espressif/esp_sysview) This component integrates SEGGER SystemView with ESP-IDF and is distributed as a managed component. ## Install (managed component) Add a dependency in your project's `idf_component.yml`: ```yaml dependencies: espressif/esp_sysview: ^1 ``` Configure SystemView tracing in `idf.py menuconfig`: - Enable tracing: `CONFIG_ESP_TRACING_ENABLE` - Select trace library: Component config > ESP Trace Configuration > Trace library > External library from component registry `CONFIG_ESP_TRACE_LIB_EXTERNAL` - Select timestamp source: Component config > ESP Trace Configuration > Trace timestamp source - Configure event filters: Component config > SEGGER SystemView Configuration ## Documentation and examples - ESP-IDF examples: - `examples/system/sysview_tracing/` (basic SystemView tracing) - `examples/system/sysview_tracing_heap_log/README.md` (heap tracing) - SEGGER SystemView documentation for host-side tooling. ## License Apache 2.0. See `LICENSE` file. ================================================ FILE: esp_sysview/idf_component.yml ================================================ version: 1.0.2 description: SEGGER SystemView component for ESP-IDF url: https://github.com/espressif/idf-extra-components/tree/master/esp_sysview issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git dependencies: idf: ">=6.0" sbom: manifests: - path: sbom_segger.yml dest: src/SEGGER ================================================ FILE: esp_sysview/linker.lf.in ================================================ [mapping:esp_sysview] archive: lib${COMPONENT_NAME}.a entries: # Place all the symbols in the IRAM to allow tracing when flash cache is disabled SEGGER_SYSVIEW (noflash) SEGGER_RTT_esp (noflash) SEGGER_SYSVIEW_esp (noflash) SEGGER_SYSVIEW_Config_FreeRTOS (noflash) SEGGER_SYSVIEW_FreeRTOS (noflash) adapter_encoder_sysview (noflash) if HEAP_TRACING_TOHOST = y: heap_trace_module (noflash) ================================================ FILE: esp_sysview/sbom_segger.yml ================================================ name: 'SystemView' version: '3.56' cpe: cpe:2.3:a:segger:systemview:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' originator: 'Organization: SEGGER Microcontroller GmbH' description: Real-time recording and visualization tool for embedded systems. ================================================ FILE: esp_sysview/sdkconfig.rename ================================================ # sdkconfig replacement configurations for deprecated options formatted as # CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION CONFIG_SYSVIEW_MAX_TASKS CONFIG_SEGGER_SYSVIEW_MAX_TASKS CONFIG_SYSVIEW_BUF_WAIT_TMO CONFIG_SEGGER_SYSVIEW_BUF_WAIT_TMO CONFIG_SYSVIEW_EVT_OVERFLOW_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE CONFIG_SYSVIEW_EVT_ISR_ENTER_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE CONFIG_SYSVIEW_EVT_ISR_EXIT_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_ISR_EXIT_ENABLE CONFIG_SYSVIEW_EVT_ISR_TO_SCHEDULER_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_ISR_TO_SCHED_ENABLE CONFIG_SYSVIEW_EVT_TASK_START_EXEC_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE CONFIG_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE CONFIG_SYSVIEW_EVT_TASK_START_READY_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_READY_ENABLE CONFIG_SYSVIEW_EVT_TASK_STOP_READY_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_READY_ENABLE CONFIG_SYSVIEW_EVT_TASK_CREATE_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE CONFIG_SYSVIEW_EVT_TASK_TERMINATE_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TASK_TERMINATE_ENABLE CONFIG_SYSVIEW_EVT_IDLE_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_IDLE_ENABLE CONFIG_SYSVIEW_EVT_TIMER_ENTER_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TIMER_ENTER_ENABLE CONFIG_SYSVIEW_EVT_TIMER_EXIT_ENABLE CONFIG_SEGGER_SYSVIEW_EVT_TIMER_EXIT_ENABLE ================================================ FILE: esp_sysview/src/Config/Global.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** ---------------------------------------------------------------------- File : Global.h Purpose : Global types In case your application already has a Global.h, you should merge the files. In order to use Segger code, the types U8, U16, U32, I8, I16, I32 need to be defined in Global.h; additional definitions do not hurt. Revision: $Rev: 12501 $ ---------------------------END-OF-HEADER------------------------------ */ #ifndef GLOBAL_H // Guard against multiple inclusion #define GLOBAL_H #define U8 unsigned char #define I8 signed char #define U16 unsigned short #define I16 signed short #ifdef __x86_64__ #define U32 unsigned #define I32 int #else #define U32 unsigned long #define I32 signed long #endif // // CC_NO_LONG_SUPPORT can be defined to compile test // without long support for compilers that do not // support C99 and its long type. // #ifdef CC_NO_LONG_SUPPORT #define PTR_ADDR U32 #else // Supports long type. #if defined(_WIN32) && !defined(__clang__) && !defined(__MINGW32__) // // Microsoft VC6 compiler related // #define U64 unsigned __int64 #define U128 unsigned __int128 #define I64 __int64 #define I128 __int128 #if _MSC_VER <= 1200 #define U64_C(x) x##UI64 #else #define U64_C(x) x##ULL #endif #else // // C99 compliant compiler // #define U64 unsigned long long #define I64 signed long long #define U64_C(x) x##ULL #endif #if (defined(_WIN64) || defined(__LP64__)) // 64-bit symbols used by Visual Studio and GCC, maybe others as well. #define PTR_ADDR U64 #else #define PTR_ADDR U32 #endif #endif // Supports long type. #endif // Avoid multiple inclusion /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/Config/SEGGER_RTT_Conf.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** ---------------------------END-OF-HEADER------------------------------ File : SEGGER_RTT_Conf.h Purpose : Implementation of SEGGER real-time transfer (RTT) which allows real-time communication on targets which support debugger memory accesses while the CPU is running. Revision: $Rev: 24316 $ */ #ifndef SEGGER_RTT_CONF_H #define SEGGER_RTT_CONF_H #ifdef __IAR_SYSTEMS_ICC__ #include #endif /********************************************************************* * * Defines, configurable * ********************************************************************** */ // // Take in and set to correct values for Cortex-A systems with CPU cache // //#define SEGGER_RTT_CPU_CACHE_LINE_SIZE (32) // Largest cache line size (in bytes) in the current system //#define SEGGER_RTT_UNCACHED_OFF (0xFB000000) // Address alias where RTT CB and buffers can be accessed uncached // // Most common case: // Up-channel 0: RTT // Up-channel 1: SystemView // #ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS #define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3) // Max. number of up-buffers (T->H) available on this target (Default: 3) #endif // // Most common case: // Down-channel 0: RTT // Down-channel 1: SystemView // #ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3) // Max. number of down-buffers (H->T) available on this target (Default: 3) #endif #ifndef BUFFER_SIZE_UP #define BUFFER_SIZE_UP (1024) // Size of the buffer for terminal output of target, up to host (Default: 1k) #endif #ifndef BUFFER_SIZE_DOWN #define BUFFER_SIZE_DOWN (16) // Size of the buffer for terminal input to target from host (Usually keyboard input) (Default: 16) #endif #ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64u) // Size of buffer for RTT printf to bulk-send chars via RTT (Default: 64) #endif #ifndef SEGGER_RTT_MODE_DEFAULT #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP // Mode for pre-initialized terminal channel (buffer 0) #endif /********************************************************************* * * RTT memcpy configuration * * memcpy() is good for large amounts of data, * but the overhead is big for small amounts, which are usually stored via RTT. * With SEGGER_RTT_MEMCPY_USE_BYTELOOP a simple byte loop can be used instead. * * SEGGER_RTT_MEMCPY() can be used to replace standard memcpy() in RTT functions. * This is may be required with memory access restrictions, * such as on Cortex-A devices with MMU. */ #ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 // 0: Use memcpy/SEGGER_RTT_MEMCPY, 1: Use a simple byte-loop #endif // // Example definition of SEGGER_RTT_MEMCPY to external memcpy with GCC toolchains and Cortex-A targets // //#if ((defined __SES_ARM) || (defined __CROSSWORKS_ARM) || (defined __GNUC__)) && (defined (__ARM_ARCH_7A__)) // #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) SEGGER_memcpy((pDest), (pSrc), (NumBytes)) //#endif // // Target is not allowed to perform other RTT operations while string still has not been stored completely. // Otherwise we would probably end up with a mixed string in the buffer. // If using RTT from within interrupts, multiple tasks or multi processors, define the SEGGER_RTT_LOCK() and SEGGER_RTT_UNLOCK() function here. // // SEGGER_RTT_MAX_INTERRUPT_PRIORITY can be used in the sample lock routines on Cortex-M3/4. // Make sure to mask all interrupts which can send RTT data, i.e. generate SystemView events, or cause task switches. // When high-priority interrupts must not be masked while sending RTT data, SEGGER_RTT_MAX_INTERRUPT_PRIORITY needs to be adjusted accordingly. // (Higher priority = lower priority number) // Default value for embOS: 128u // Default configuration in FreeRTOS: configMAX_SYSCALL_INTERRUPT_PRIORITY: ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) // In case of doubt mask all interrupts: 1 << (8 - BASEPRI_PRIO_BITS) i.e. 1 << 5 when 3 bits are implemented in NVIC // or define SEGGER_RTT_LOCK() to completely disable interrupts. // #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) // Interrupt priority to lock on SEGGER_RTT_LOCK on Cortex-M3/4 (Default: 0x20) #endif /********************************************************************* * * RTT lock configuration for SEGGER Embedded Studio, * Rowley CrossStudio and GCC */ #if ((defined(__SES_ARM) || defined(__SES_RISCV) || defined(__CROSSWORKS_ARM) || defined(__GNUC__) || defined(__clang__)) && !defined (__CC_ARM) && !defined(WIN32)) #if (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__)) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ __asm volatile ("mrs %0, primask \n\t" \ "movs r1, #1 \n\t" \ "msr primask, r1 \n\t" \ : "=r" (_SEGGER_RTT__LockState) \ : \ : "r1", "cc" \ ); #define SEGGER_RTT_UNLOCK() __asm volatile ("msr primask, %0 \n\t" \ : \ : "r" (_SEGGER_RTT__LockState) \ : \ ); \ } #elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__)) #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) #endif #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ __asm volatile ("mrs %0, basepri \n\t" \ "mov r1, %1 \n\t" \ "msr basepri, r1 \n\t" \ : "=r" (_SEGGER_RTT__LockState) \ : "i"(SEGGER_RTT_MAX_INTERRUPT_PRIORITY) \ : "r1", "cc" \ ); #define SEGGER_RTT_UNLOCK() __asm volatile ("msr basepri, %0 \n\t" \ : \ : "r" (_SEGGER_RTT__LockState) \ : \ ); \ } #elif (defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__)) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ __asm volatile ("mrs r1, CPSR \n\t" \ "mov %0, r1 \n\t" \ "orr r1, r1, #0xC0 \n\t" \ "msr CPSR_c, r1 \n\t" \ : "=r" (_SEGGER_RTT__LockState) \ : \ : "r1", "cc" \ ); #define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \ "mrs r1, CPSR \n\t" \ "bic r1, r1, #0xC0 \n\t" \ "and r0, r0, #0xC0 \n\t" \ "orr r1, r1, r0 \n\t" \ "msr CPSR_c, r1 \n\t" \ : \ : "r" (_SEGGER_RTT__LockState) \ : "r0", "r1", "cc" \ ); \ } #elif defined(__riscv) || defined(__riscv_xlen) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ __asm volatile ("csrr %0, mstatus \n\t" \ "csrci mstatus, 8 \n\t" \ "andi %0, %0, 8 \n\t" \ : "=r" (_SEGGER_RTT__LockState) \ : \ : \ ); #define SEGGER_RTT_UNLOCK() __asm volatile ("csrr a1, mstatus \n\t" \ "or %0, %0, a1 \n\t" \ "csrs mstatus, %0 \n\t" \ : \ : "r" (_SEGGER_RTT__LockState) \ : "a1" \ ); \ } #else #define SEGGER_RTT_LOCK() #define SEGGER_RTT_UNLOCK() #endif #endif /********************************************************************* * * RTT lock configuration for IAR EWARM */ #ifdef __ICCARM__ #if (defined (__ARM6M__) && (__CORE__ == __ARM6M__)) || \ (defined (__ARM8M_BASELINE__) && (__CORE__ == __ARM8M_BASELINE__)) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = __get_PRIMASK(); \ __set_PRIMASK(1); #define SEGGER_RTT_UNLOCK() __set_PRIMASK(_SEGGER_RTT__LockState); \ } #elif (defined (__ARM7EM__) && (__CORE__ == __ARM7EM__)) || \ (defined (__ARM7M__) && (__CORE__ == __ARM7M__)) || \ (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) || \ (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) #endif #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = __get_BASEPRI(); \ __set_BASEPRI(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); #define SEGGER_RTT_UNLOCK() __set_BASEPRI(_SEGGER_RTT__LockState); \ } #elif (defined (__ARM7A__) && (__CORE__ == __ARM7A__)) || \ (defined (__ARM7R__) && (__CORE__ == __ARM7R__)) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ __asm volatile ("mrs r1, CPSR \n\t" \ "mov %0, r1 \n\t" \ "orr r1, r1, #0xC0 \n\t" \ "msr CPSR_c, r1 \n\t" \ : "=r" (_SEGGER_RTT__LockState) \ : \ : "r1", "cc" \ ); #define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \ "mrs r1, CPSR \n\t" \ "bic r1, r1, #0xC0 \n\t" \ "and r0, r0, #0xC0 \n\t" \ "orr r1, r1, r0 \n\t" \ "msr CPSR_c, r1 \n\t" \ : \ : "r" (_SEGGER_RTT__LockState) \ : "r0", "r1", "cc" \ ); \ } #endif #endif /********************************************************************* * * RTT lock configuration for IAR RX */ #ifdef __ICCRX__ #define SEGGER_RTT_LOCK() { \ unsigned long _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = __get_interrupt_state(); \ __disable_interrupt(); #define SEGGER_RTT_UNLOCK() __set_interrupt_state(_SEGGER_RTT__LockState); \ } #endif /********************************************************************* * * RTT lock configuration for IAR RL78 */ #ifdef __ICCRL78__ #define SEGGER_RTT_LOCK() { \ __istate_t _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = __get_interrupt_state(); \ __disable_interrupt(); #define SEGGER_RTT_UNLOCK() __set_interrupt_state(_SEGGER_RTT__LockState); \ } #endif /********************************************************************* * * RTT lock configuration for KEIL ARM */ #ifdef __CC_ARM #if (defined __TARGET_ARCH_6S_M) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ register unsigned char _SEGGER_RTT__PRIMASK __asm( "primask"); \ _SEGGER_RTT__LockState = _SEGGER_RTT__PRIMASK; \ _SEGGER_RTT__PRIMASK = 1u; \ __schedule_barrier(); #define SEGGER_RTT_UNLOCK() _SEGGER_RTT__PRIMASK = _SEGGER_RTT__LockState; \ __schedule_barrier(); \ } #elif (defined(__TARGET_ARCH_7_M) || defined(__TARGET_ARCH_7E_M)) #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) #endif #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ register unsigned char BASEPRI __asm( "basepri"); \ _SEGGER_RTT__LockState = BASEPRI; \ BASEPRI = SEGGER_RTT_MAX_INTERRUPT_PRIORITY; \ __schedule_barrier(); #define SEGGER_RTT_UNLOCK() BASEPRI = _SEGGER_RTT__LockState; \ __schedule_barrier(); \ } #endif #endif /********************************************************************* * * RTT lock configuration for TI ARM */ #ifdef __TI_ARM__ #if defined (__TI_ARM_V6M0__) #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = __get_PRIMASK(); \ __set_PRIMASK(1); #define SEGGER_RTT_UNLOCK() __set_PRIMASK(_SEGGER_RTT__LockState); \ } #elif (defined (__TI_ARM_V7M3__) || defined (__TI_ARM_V7M4__)) #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) #endif #define SEGGER_RTT_LOCK() { \ unsigned int _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = _set_interrupt_priority(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); #define SEGGER_RTT_UNLOCK() _set_interrupt_priority(_SEGGER_RTT__LockState); \ } #endif #endif /********************************************************************* * * RTT lock configuration for CCRX */ #ifdef __RX #include #define SEGGER_RTT_LOCK() { \ unsigned long _SEGGER_RTT__LockState; \ _SEGGER_RTT__LockState = get_psw() & 0x010000; \ clrpsw_i(); #define SEGGER_RTT_UNLOCK() set_psw(get_psw() | _SEGGER_RTT__LockState); \ } #endif /********************************************************************* * * RTT lock configuration for embOS Simulation on Windows * (Can also be used for generic RTT locking with embOS) */ #if defined(WIN32) || defined(SEGGER_RTT_LOCK_EMBOS) void OS_SIM_EnterCriticalSection(void); void OS_SIM_LeaveCriticalSection(void); #define SEGGER_RTT_LOCK() { \ OS_SIM_EnterCriticalSection(); #define SEGGER_RTT_UNLOCK() OS_SIM_LeaveCriticalSection(); \ } #endif /********************************************************************* * * RTT lock configuration fallback */ #ifndef SEGGER_RTT_LOCK #define SEGGER_RTT_LOCK() // Lock RTT (nestable) (i.e. disable interrupts) #endif #ifndef SEGGER_RTT_UNLOCK #define SEGGER_RTT_UNLOCK() // Unlock RTT (nestable) (i.e. enable previous interrupt lock state) #endif #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/Config/SEGGER_SYSVIEW_Conf.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_Conf.h Purpose : SEGGER SystemView configuration file. Set defines which deviate from the defaults (see SEGGER_SYSVIEW_ConfDefaults.h) here. Revision: $Rev: 21292 $ Additional information: Required defines which must be set are: SEGGER_SYSVIEW_GET_TIMESTAMP SEGGER_SYSVIEW_GET_INTERRUPT_ID For known compilers and cores, these might be set to good defaults in SEGGER_SYSVIEW_ConfDefaults.h. SystemView needs a (nestable) locking mechanism. If not defined, the RTT locking mechanism is used, which then needs to be properly configured. */ #ifndef SEGGER_SYSVIEW_CONF_H #define SEGGER_SYSVIEW_CONF_H /********************************************************************* * * Defines, configurable * ********************************************************************** */ /********************************************************************* * TODO: Add your defines here. * ********************************************************************** */ #endif // SEGGER_SYSVIEW_CONF_H /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** ---------------------------------------------------------------------- File : SEGGER.h Purpose : Global types etc & general purpose utility functions Revision: $Rev: 18102 $ ---------------------------END-OF-HEADER------------------------------ */ #ifndef SEGGER_H // Guard against multiple inclusion #define SEGGER_H #include // For va_list. #include "Global.h" // Type definitions: U8, U16, U32, I8, I16, I32 #if defined(__cplusplus) extern "C" { /* Make sure we have C-declarations in C++ programs */ #endif /********************************************************************* * * Keywords/specifiers * ********************************************************************** */ #ifndef INLINE #if (defined(__ICCARM__) || defined(__RX) || defined(__ICCRX__)) // // Other known compilers. // #define INLINE inline #else #if (defined(_WIN32) && !defined(__clang__)) // // Microsoft VC6 and newer. // Force inlining without cost checking. // #define INLINE __forceinline #elif defined(__GNUC__) || defined(__clang__) // // Force inlining with GCC + clang // #define INLINE inline __attribute__((always_inline)) #elif (defined(__CC_ARM)) // // Force inlining with ARMCC (Keil) // #define INLINE __inline #else // // Unknown compilers. // #define INLINE #endif #endif #endif /********************************************************************* * * Function-like macros * ********************************************************************** */ #define SEGGER_COUNTOF(a) (sizeof((a))/sizeof((a)[0])) #define SEGGER_MIN(a,b) (((a) < (b)) ? (a) : (b)) #define SEGGER_MAX(a,b) (((a) > (b)) ? (a) : (b)) #ifndef SEGGER_USE_PARA // Some compiler complain about unused parameters. #define SEGGER_USE_PARA(Para) (void)Para // This works for most compilers. #endif #define SEGGER_ADDR2PTR(Type, Addr) (/*lint -e(923) -e(9078)*/((Type*)((PTR_ADDR)(Addr)))) // Allow cast from address to pointer. #define SEGGER_PTR2ADDR(p) (/*lint -e(923) -e(9078)*/((PTR_ADDR)(p))) // Allow cast from pointer to address. #define SEGGER_PTR2PTR(Type, p) (/*lint -e(740) -e(826) -e(9079) -e(9087)*/((Type*)(p))) // Allow cast from one pointer type to another (ignore different size). #define SEGGER_PTR_DISTANCE(p0, p1) (SEGGER_PTR2ADDR(p0) - SEGGER_PTR2ADDR(p1)) /********************************************************************* * * Defines * ********************************************************************** */ #define SEGGER_PRINTF_FLAG_ADJLEFT (1 << 0) #define SEGGER_PRINTF_FLAG_SIGNFORCE (1 << 1) #define SEGGER_PRINTF_FLAG_SIGNSPACE (1 << 2) #define SEGGER_PRINTF_FLAG_PRECEED (1 << 3) #define SEGGER_PRINTF_FLAG_ZEROPAD (1 << 4) #define SEGGER_PRINTF_FLAG_NEGATIVE (1 << 5) /********************************************************************* * * Types * ********************************************************************** */ typedef struct { char *pBuffer; int BufferSize; int Cnt; } SEGGER_BUFFER_DESC; typedef struct { unsigned int CacheLineSize; // 0: No Cache. Most Systems such as ARM9 use a 32 bytes cache line size. void (*pfDMB) (void); // Optional DMB function for Data Memory Barrier to make sure all memory operations are completed. void (*pfClean) (void *p, unsigned long NumBytes); // Optional clean function for cached memory. void (*pfInvalidate)(void *p, unsigned long NumBytes); // Optional invalidate function for cached memory. } SEGGER_CACHE_CONFIG; typedef struct SEGGER_SNPRINTF_CONTEXT_struct SEGGER_SNPRINTF_CONTEXT; struct SEGGER_SNPRINTF_CONTEXT_struct { void *pContext; // Application specific context. SEGGER_BUFFER_DESC *pBufferDesc; // Buffer descriptor to use for output. void (*pfFlush)(SEGGER_SNPRINTF_CONTEXT *pContext); // Callback executed once the buffer is full. Callback decides if the buffer gets cleared to store more or not. }; typedef struct { void (*pfStoreChar) (SEGGER_BUFFER_DESC *pBufferDesc, SEGGER_SNPRINTF_CONTEXT *pContext, char c); int (*pfPrintUnsigned) (SEGGER_BUFFER_DESC *pBufferDesc, SEGGER_SNPRINTF_CONTEXT *pContext, U32 v, unsigned Base, char Flags, int Width, int Precision); int (*pfPrintInt) (SEGGER_BUFFER_DESC *pBufferDesc, SEGGER_SNPRINTF_CONTEXT *pContext, I32 v, unsigned Base, char Flags, int Width, int Precision); } SEGGER_PRINTF_API; typedef void (*SEGGER_pFormatter)(SEGGER_BUFFER_DESC *pBufferDesc, SEGGER_SNPRINTF_CONTEXT *pContext, const SEGGER_PRINTF_API *pApi, va_list *pParamList, char Lead, int Width, int Precision); typedef struct SEGGER_PRINTF_FORMATTER { struct SEGGER_PRINTF_FORMATTER *pNext; // Pointer to next formatter. SEGGER_pFormatter pfFormatter; // Formatter function. char Specifier; // Format specifier. } SEGGER_PRINTF_FORMATTER; typedef struct { U32 (*pfGetHPTimestamp)(void); // Mandatory, pfGetHPTimestamp int (*pfGetUID) (U8 abUID[16]); // Optional, pfGetUID } SEGGER_BSP_API; /********************************************************************* * * Utility functions * ********************************************************************** */ // // Memory operations. // void SEGGER_ARM_memcpy(void *pDest, const void *pSrc, int NumBytes); void SEGGER_memcpy (void *pDest, const void *pSrc, unsigned NumBytes); void SEGGER_memxor (void *pDest, const void *pSrc, unsigned NumBytes); // // String functions. // int SEGGER_atoi (const char *s); int SEGGER_isalnum (int c); int SEGGER_isalpha (int c); unsigned SEGGER_strlen (const char *s); int SEGGER_tolower (int c); int SEGGER_strcasecmp (const char *sText1, const char *sText2); int SEGGER_strncasecmp(const char *sText1, const char *sText2, unsigned Count); // // Buffer/printf related. // void SEGGER_StoreChar (SEGGER_BUFFER_DESC *pBufferDesc, char c); void SEGGER_PrintUnsigned(SEGGER_BUFFER_DESC *pBufferDesc, U32 v, unsigned Base, int Precision); void SEGGER_PrintInt (SEGGER_BUFFER_DESC *pBufferDesc, I32 v, unsigned Base, int Precision); int SEGGER_snprintf (char *pBuffer, int BufferSize, const char *sFormat, ...); int SEGGER_vsnprintf (char *pBuffer, int BufferSize, const char *sFormat, va_list ParamList); int SEGGER_vsnprintfEx (SEGGER_SNPRINTF_CONTEXT *pContext, const char *sFormat, va_list ParamList); int SEGGER_PRINTF_AddFormatter (SEGGER_PRINTF_FORMATTER *pFormatter, SEGGER_pFormatter pfFormatter, char c); void SEGGER_PRINTF_AddDoubleFormatter (void); void SEGGER_PRINTF_AddIPFormatter (void); void SEGGER_PRINTF_AddBLUEFormatter (void); void SEGGER_PRINTF_AddCONNECTFormatter(void); void SEGGER_PRINTF_AddSSLFormatter (void); void SEGGER_PRINTF_AddSSHFormatter (void); void SEGGER_PRINTF_AddHTMLFormatter (void); // // BSP abstraction API. // int SEGGER_BSP_GetUID (U8 abUID[16]); int SEGGER_BSP_GetUID32(U32 *pUID); void SEGGER_BSP_SetAPI (const SEGGER_BSP_API *pAPI); void SEGGER_BSP_SeedUID (void); // // Other API. // void SEGGER_VERSION_GetString(char acText[8], unsigned Version); #if defined(__cplusplus) } /* Make sure we have C-declarations in C++ programs */ #endif #endif // Avoid multiple inclusion /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER_RTT.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** ---------------------------END-OF-HEADER------------------------------ File : SEGGER_RTT.h Purpose : Implementation of SEGGER real-time transfer which allows real-time communication on targets which support debugger memory accesses while the CPU is running. Revision: $Rev: 25842 $ ---------------------------------------------------------------------- */ #ifndef SEGGER_RTT_H #define SEGGER_RTT_H #include "../Config/SEGGER_RTT_Conf.h" /********************************************************************* * * Defines, defaults * ********************************************************************** */ #ifndef RTT_USE_ASM // // Some cores support out-of-order memory accesses (reordering of memory accesses in the core) // For such cores, we need to define a memory barrier to guarantee the order of certain accesses to the RTT ring buffers. // Needed for: // Cortex-M7 (ARMv7-M) // Cortex-M23 (ARM-v8M) // Cortex-M33 (ARM-v8M) // Cortex-A/R (ARM-v7A/R) // // We do not explicitly check for "Embedded Studio" as the compiler in use determines what we support. // You can use an external toolchain like IAR inside ES. So there is no point in checking for "Embedded Studio" // #if (defined __CROSSWORKS_ARM) // Rowley Crossworks #define _CC_HAS_RTT_ASM_SUPPORT 1 #if (defined __ARM_ARCH_7M__) // Cortex-M3 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #elif (defined __ARM_ARCH_7EM__) // Cortex-M4/M7 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_BASE__) // Cortex-M23 #define _CORE_HAS_RTT_ASM_SUPPORT 0 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined(__ARM_ARCH_8_1M_MAIN__)) // Cortex-M85 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #else #define _CORE_HAS_RTT_ASM_SUPPORT 0 #endif #elif (defined __ARMCC_VERSION) // // ARM compiler // ARM compiler V6.0 and later is clang based. // Our ASM part is compatible to clang. // #if (__ARMCC_VERSION >= 6000000) #define _CC_HAS_RTT_ASM_SUPPORT 1 #else #define _CC_HAS_RTT_ASM_SUPPORT 0 #endif #if (defined __ARM_ARCH_6M__) // Cortex-M0 / M1 #define _CORE_HAS_RTT_ASM_SUPPORT 0 // No ASM support for this architecture #elif (defined __ARM_ARCH_7M__) // Cortex-M3 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #elif (defined __ARM_ARCH_7EM__) // Cortex-M4/M7 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_BASE__) // Cortex-M23 #define _CORE_HAS_RTT_ASM_SUPPORT 0 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8_1M_MAIN__) // Cortex-M85 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif ((defined __ARM_ARCH_7A__) || (defined __ARM_ARCH_7R__)) // Cortex-A/R 32-bit ARMv7-A/R #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #else #define _CORE_HAS_RTT_ASM_SUPPORT 0 #endif #elif ((defined __GNUC__) || (defined __clang__)) // // GCC / Clang // #define _CC_HAS_RTT_ASM_SUPPORT 1 // ARM 7/9: __ARM_ARCH_5__ / __ARM_ARCH_5E__ / __ARM_ARCH_5T__ / __ARM_ARCH_5T__ / __ARM_ARCH_5TE__ #if (defined __ARM_ARCH_7M__) // Cortex-M3 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #elif (defined __ARM_ARCH_7EM__) // Cortex-M4/M7 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 // Only Cortex-M7 needs a DMB but we cannot distinguish M4 and M7 here... #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_BASE__) // Cortex-M23 #define _CORE_HAS_RTT_ASM_SUPPORT 0 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif (defined __ARM_ARCH_8_1M_MAIN__) // Cortex-M85 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #elif ((defined __ARM_ARCH_7A__) || (defined __ARM_ARCH_7R__)) // Cortex-A/R 32-bit ARMv7-A/R #define _CORE_NEEDS_DMB 1 #define RTT__DMB() __asm volatile ("dmb\n" : : :); #else #define _CORE_HAS_RTT_ASM_SUPPORT 0 #endif #elif ((defined __IASMARM__) || (defined __ICCARM__)) // // IAR assembler/compiler // #define _CC_HAS_RTT_ASM_SUPPORT 1 #if (__VER__ < 6300000) #define VOLATILE #else #define VOLATILE volatile #endif #if (defined __ARM7M__) // Needed for old versions that do not know the define yet #if (__CORE__ == __ARM7M__) // Cortex-M3 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #endif #endif #if (defined __ARM7EM__) #if (__CORE__ == __ARM7EM__) // Cortex-M4/M7 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif #if (defined __ARM8M_BASELINE__) #if (__CORE__ == __ARM8M_BASELINE__) // Cortex-M23 #define _CORE_HAS_RTT_ASM_SUPPORT 0 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif #if (defined __ARM8M_MAINLINE__) #if (__CORE__ == __ARM8M_MAINLINE__) // Cortex-M33 #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif #if (defined __ARM8EM_MAINLINE__) #if (__CORE__ == __ARM8EM_MAINLINE__) // Cortex-??? #define _CORE_HAS_RTT_ASM_SUPPORT 1 #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif #if (defined __ARM7A__) #if (__CORE__ == __ARM7A__) // Cortex-A 32-bit ARMv7-A #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif #if (defined __ARM7R__) #if (__CORE__ == __ARM7R__) // Cortex-R 32-bit ARMv7-R #define _CORE_NEEDS_DMB 1 #define RTT__DMB() asm VOLATILE ("DMB"); #endif #endif // TBD: __ARM8A__ => Cortex-A 64-bit ARMv8-A // TBD: __ARM8R__ => Cortex-R 64-bit ARMv8-R #else // // Other compilers // #define _CC_HAS_RTT_ASM_SUPPORT 0 #define _CORE_HAS_RTT_ASM_SUPPORT 0 #endif // // If IDE and core support the ASM version, enable ASM version by default // #ifndef _CORE_HAS_RTT_ASM_SUPPORT #define _CORE_HAS_RTT_ASM_SUPPORT 0 // Default for unknown cores #endif #if (_CC_HAS_RTT_ASM_SUPPORT && _CORE_HAS_RTT_ASM_SUPPORT) #define RTT_USE_ASM (1) #else #define RTT_USE_ASM (0) #endif #endif #ifndef _CORE_NEEDS_DMB #define _CORE_NEEDS_DMB 0 #endif #ifndef RTT__DMB #if _CORE_NEEDS_DMB #error "Don't know how to place inline assembly for DMB" #else #define RTT__DMB() #endif #endif #ifndef SEGGER_RTT_CPU_CACHE_LINE_SIZE #define SEGGER_RTT_CPU_CACHE_LINE_SIZE (0) // On most target systems where RTT is used, we do not have a CPU cache, therefore 0 is a good default here #endif #ifndef SEGGER_RTT_UNCACHED_OFF #if SEGGER_RTT_CPU_CACHE_LINE_SIZE #error "SEGGER_RTT_UNCACHED_OFF must be defined when setting SEGGER_RTT_CPU_CACHE_LINE_SIZE != 0" #else #define SEGGER_RTT_UNCACHED_OFF (0) #endif #endif #if RTT_USE_ASM #if SEGGER_RTT_CPU_CACHE_LINE_SIZE #error "RTT_USE_ASM is not available if SEGGER_RTT_CPU_CACHE_LINE_SIZE != 0" #endif #endif #ifndef SEGGER_RTT_ASM // defined when SEGGER_RTT.h is included from assembly file #include #include #include /********************************************************************* * * Defines, fixed * ********************************************************************** */ // // Determine how much we must pad the control block to make it a multiple of a cache line in size // Assuming: U8 = 1B // U16 = 2B // U32 = 4B // U8/U16/U32* = 4B // #if SEGGER_RTT_CPU_CACHE_LINE_SIZE // Avoid division by zero in case we do not have any cache #define SEGGER_RTT__ROUND_UP_2_CACHE_LINE_SIZE(NumBytes) (((NumBytes + SEGGER_RTT_CPU_CACHE_LINE_SIZE - 1) / SEGGER_RTT_CPU_CACHE_LINE_SIZE) * SEGGER_RTT_CPU_CACHE_LINE_SIZE) #else #define SEGGER_RTT__ROUND_UP_2_CACHE_LINE_SIZE(NumBytes) (NumBytes) #endif #define SEGGER_RTT__CB_SIZE (16 + 4 + 4 + (SEGGER_RTT_MAX_NUM_UP_BUFFERS * 24) + (SEGGER_RTT_MAX_NUM_DOWN_BUFFERS * 24)) #define SEGGER_RTT__CB_PADDING (SEGGER_RTT__ROUND_UP_2_CACHE_LINE_SIZE(SEGGER_RTT__CB_SIZE) - SEGGER_RTT__CB_SIZE) /********************************************************************* * * Types * ********************************************************************** */ // // Description for a circular buffer (also called "ring buffer") // which is used as up-buffer (T->H) // typedef struct { const char *sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" char *pBuffer; // Pointer to start of buffer unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. unsigned WrOff; // Position of next item to be written by either target. volatile unsigned RdOff; // Position of next item to be read by host. Must be volatile since it may be modified by host. unsigned Flags; // Contains configuration flags. Flags[31:24] are used for validity check and must be zero. Flags[23:2] are reserved for future use. Flags[1:0] = RTT operating mode. } SEGGER_RTT_BUFFER_UP; // // Description for a circular buffer (also called "ring buffer") // which is used as down-buffer (H->T) // typedef struct { const char *sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" char *pBuffer; // Pointer to start of buffer unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. volatile unsigned WrOff; // Position of next item to be written by host. Must be volatile since it may be modified by host. unsigned RdOff; // Position of next item to be read by target (down-buffer). unsigned Flags; // Contains configuration flags. Flags[31:24] are used for validity check and must be zero. Flags[23:2] are reserved for future use. Flags[1:0] = RTT operating mode. } SEGGER_RTT_BUFFER_DOWN; // // RTT control block which describes the number of buffers available // as well as the configuration for each buffer // // typedef struct { char acID[16]; // Initialized to "SEGGER RTT" int MaxNumUpBuffers; // Initialized to SEGGER_RTT_MAX_NUM_UP_BUFFERS (type. 2) int MaxNumDownBuffers; // Initialized to SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (type. 2) SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS]; // Up buffers, transferring information up from target via debug probe to host SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS]; // Down buffers, transferring information down from host via debug probe to target #if SEGGER_RTT__CB_PADDING unsigned char aDummy[SEGGER_RTT__CB_PADDING]; #endif } SEGGER_RTT_CB; /********************************************************************* * * Global data * ********************************************************************** */ extern SEGGER_RTT_CB _SEGGER_RTT; /********************************************************************* * * RTT API functions * ********************************************************************** */ #ifdef __cplusplus extern "C" { #endif int SEGGER_RTT_AllocDownBuffer (const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags); int SEGGER_RTT_AllocUpBuffer (const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags); int SEGGER_RTT_ConfigUpBuffer (unsigned BufferIndex, const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags); int SEGGER_RTT_ConfigDownBuffer (unsigned BufferIndex, const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags); int SEGGER_RTT_GetKey (void); unsigned SEGGER_RTT_HasData (unsigned BufferIndex); int SEGGER_RTT_HasKey (void); unsigned SEGGER_RTT_HasDataUp (unsigned BufferIndex); void SEGGER_RTT_Init (void); unsigned SEGGER_RTT_Read (unsigned BufferIndex, void *pBuffer, unsigned BufferSize); unsigned SEGGER_RTT_ReadNoLock (unsigned BufferIndex, void *pData, unsigned BufferSize); int SEGGER_RTT_SetNameDownBuffer (unsigned BufferIndex, const char *sName); int SEGGER_RTT_SetNameUpBuffer (unsigned BufferIndex, const char *sName); int SEGGER_RTT_SetFlagsDownBuffer (unsigned BufferIndex, unsigned Flags); int SEGGER_RTT_SetFlagsUpBuffer (unsigned BufferIndex, unsigned Flags); int SEGGER_RTT_WaitKey (void); unsigned SEGGER_RTT_Write (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_WriteNoLock (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_WriteSkipNoLock (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_ASM_WriteSkipNoLock (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_WriteString (unsigned BufferIndex, const char *s); void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_PutChar (unsigned BufferIndex, char c); unsigned SEGGER_RTT_PutCharSkip (unsigned BufferIndex, char c); unsigned SEGGER_RTT_PutCharSkipNoLock (unsigned BufferIndex, char c); unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex); unsigned SEGGER_RTT_GetBytesInBuffer (unsigned BufferIndex); // Espressif specific functions void SEGGER_RTT_ESP_FlushNoLock (void); void SEGGER_RTT_ESP_Flush (void); // // Function macro for performance optimization // // @AGv: This macro is used inside SEGGER SystemView code. // For ESP32 we use our own implementation of RTT, so this macro should not check SEGGER's RTT buffer state. #define SEGGER_RTT_HASDATA(n) (1) #if RTT_USE_ASM #define SEGGER_RTT_WriteSkipNoLock SEGGER_RTT_ASM_WriteSkipNoLock #endif /********************************************************************* * * RTT transfer functions to send RTT data via other channels. * ********************************************************************** */ unsigned SEGGER_RTT_ReadUpBuffer (unsigned BufferIndex, void *pBuffer, unsigned BufferSize); unsigned SEGGER_RTT_ReadUpBufferNoLock (unsigned BufferIndex, void *pData, unsigned BufferSize); unsigned SEGGER_RTT_WriteDownBuffer (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); unsigned SEGGER_RTT_WriteDownBufferNoLock (unsigned BufferIndex, const void *pBuffer, unsigned NumBytes); #define SEGGER_RTT_HASDATA_UP(n) (((SEGGER_RTT_BUFFER_UP*)((uintptr_t)&_SEGGER_RTT.aUp[n] + SEGGER_RTT_UNCACHED_OFF))->WrOff - ((SEGGER_RTT_BUFFER_UP*)((uintptr_t)&_SEGGER_RTT.aUp[n] + SEGGER_RTT_UNCACHED_OFF))->RdOff) // Access uncached to make sure we see changes made by the J-Link side and all of our changes go into HW directly /********************************************************************* * * RTT "Terminal" API functions * ********************************************************************** */ int SEGGER_RTT_SetTerminal (unsigned char TerminalId); int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char *s); /********************************************************************* * * RTT printf functions (require SEGGER_RTT_printf.c) * ********************************************************************** */ int SEGGER_RTT_printf(unsigned BufferIndex, const char *sFormat, ...); int SEGGER_RTT_vprintf(unsigned BufferIndex, const char *sFormat, va_list *pParamList); #ifdef __cplusplus } #endif #endif // ifndef(SEGGER_RTT_ASM) /********************************************************************* * * Defines * ********************************************************************** */ // // Operating modes. Define behavior if buffer is full (not enough space for entire message) // #define SEGGER_RTT_MODE_NO_BLOCK_SKIP (0) // Skip. Do not block, output nothing. (Default) #define SEGGER_RTT_MODE_NO_BLOCK_TRIM (1) // Trim: Do not block, output as much as fits. #define SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL (2) // Block: Wait until there is space in the buffer. #define SEGGER_RTT_MODE_MASK (3) // // Control sequences, based on ANSI. // Can be used to control color, and clear the screen // #define RTT_CTRL_RESET "\x1B[0m" // Reset to default colors #define RTT_CTRL_CLEAR "\x1B[2J" // Clear screen, reposition cursor to top left #define RTT_CTRL_TEXT_BLACK "\x1B[2;30m" #define RTT_CTRL_TEXT_RED "\x1B[2;31m" #define RTT_CTRL_TEXT_GREEN "\x1B[2;32m" #define RTT_CTRL_TEXT_YELLOW "\x1B[2;33m" #define RTT_CTRL_TEXT_BLUE "\x1B[2;34m" #define RTT_CTRL_TEXT_MAGENTA "\x1B[2;35m" #define RTT_CTRL_TEXT_CYAN "\x1B[2;36m" #define RTT_CTRL_TEXT_WHITE "\x1B[2;37m" #define RTT_CTRL_TEXT_BRIGHT_BLACK "\x1B[1;30m" #define RTT_CTRL_TEXT_BRIGHT_RED "\x1B[1;31m" #define RTT_CTRL_TEXT_BRIGHT_GREEN "\x1B[1;32m" #define RTT_CTRL_TEXT_BRIGHT_YELLOW "\x1B[1;33m" #define RTT_CTRL_TEXT_BRIGHT_BLUE "\x1B[1;34m" #define RTT_CTRL_TEXT_BRIGHT_MAGENTA "\x1B[1;35m" #define RTT_CTRL_TEXT_BRIGHT_CYAN "\x1B[1;36m" #define RTT_CTRL_TEXT_BRIGHT_WHITE "\x1B[1;37m" #define RTT_CTRL_BG_BLACK "\x1B[24;40m" #define RTT_CTRL_BG_RED "\x1B[24;41m" #define RTT_CTRL_BG_GREEN "\x1B[24;42m" #define RTT_CTRL_BG_YELLOW "\x1B[24;43m" #define RTT_CTRL_BG_BLUE "\x1B[24;44m" #define RTT_CTRL_BG_MAGENTA "\x1B[24;45m" #define RTT_CTRL_BG_CYAN "\x1B[24;46m" #define RTT_CTRL_BG_WHITE "\x1B[24;47m" #define RTT_CTRL_BG_BRIGHT_BLACK "\x1B[4;40m" #define RTT_CTRL_BG_BRIGHT_RED "\x1B[4;41m" #define RTT_CTRL_BG_BRIGHT_GREEN "\x1B[4;42m" #define RTT_CTRL_BG_BRIGHT_YELLOW "\x1B[4;43m" #define RTT_CTRL_BG_BRIGHT_BLUE "\x1B[4;44m" #define RTT_CTRL_BG_BRIGHT_MAGENTA "\x1B[4;45m" #define RTT_CTRL_BG_BRIGHT_CYAN "\x1B[4;46m" #define RTT_CTRL_BG_BRIGHT_WHITE "\x1B[4;47m" #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW.c Purpose : System visualization API implementation. Revision: $Rev: 29105 $ Additional information: Packet format: Packets with IDs 0..23 are standard packets with known structure. For efficiency, they do *NOT* contain a length field. Packets with IDs 24..31 are standard packets with extendible structure and contain a length field. Packet ID 31 is used for SystemView extended events. Packets with IDs >= 32 always contain a length field. Packet IDs: 0.. 31 : Standard packets, known by SystemView. 32..1023 : OS-definable packets, described in a SystemView description file. 1024..2047 : User-definable packets, described in a SystemView description file. 2048..32767: Undefined. Data encoding: Basic types (int, short, char, ...): Basic types are encoded little endian with most-significant bit variant encoding. Each encoded byte contains 7 data bits [6:0] and the MSB continuation bit. The continuation bit indicates whether the next byte belongs to the data (bit set) or this is the last byte (bit clear). The most significant bits of data are encoded first, proceeding to the least significant bits in the final byte (little endian). Example encoding: Data: 0x1F4 (500) Encoded: 0xF4 (First 7 data bits 74 | Continuation bit) 0x03 (Second 7 data bits 03, no continuation) Data: 0xFFFFFFFF Encoded: 0xFF 0xFF 0xFF 0xFF 0x0F Data: 0xA2 (162), 0x03 (3), 0x7000 Encoded: 0xA2 0x01 0x03 0x80 0xE0 0x01 Byte arrays and strings: Byte arrays and strings are encoded as followed by the raw data. NumBytes is encoded as a basic type with a theoretical maximum of 4G. Example encoding: Data: "Hello World\0" (0x48 0x65 0x6C 0x6C 0x6F 0x20 0x57 0x6F 0x72 0x6C 0x64 0x00) Encoded: 0x0B 0x48 0x65 0x6C 0x6C 0x6F 0x20 0x57 0x6F 0x72 0x6C 0x64 Examples packets: 01 F4 03 80 80 10 // Overflow packet. Data is a single U32. This packet means: 500 packets lost, Timestamp is 0x40000 02 0F 50 // ISR(15) Enter. Timestamp 80 (0x50) 03 20 // ISR Exit. Timestamp 32 (0x20) (Shortest possible packet.) Sample code for user defined Packets: #define MY_ID 0x400 // Any value between 0x400 and 0x7FF void SendMyPacket(unsigned Para0, unsigned Para1, const char* s) { U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + MAX_STR_LEN + 1]; U8* pPayload; // pPayload = SEGGER_SYSVIEW_PPREPARE_PACKET(aPacket); // Prepare the packet for SystemView pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, Para0); // Add the first parameter to the packet pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, Para1); // Add the second parameter to the packet pPayload = SEGGER_SYSVIEW_EncodeString(pPayload, s, MAX_STR_LEN); // Add the string to the packet // SEGGER_SYSVIEW_SendPacket(&aPacket[0], pPayload, MY_ID); // Send the packet with EventId = MY_ID } #define MY_ID_1 0x401 void SendOnePara(unsigned Para0) { SEGGER_SYSVIEW_RecordU32(MY_ID_1, Para0); } */ /********************************************************************* * * #include section * ********************************************************************** */ #define SEGGER_SYSVIEW_C // For EXTERN statements in SEGGER_SYSVIEW.h #include #include #include #include #include "SEGGER_SYSVIEW_Int.h" #include "SEGGER_RTT.h" /********************************************************************* * * Defines, fixed * ********************************************************************** */ #if SEGGER_SYSVIEW_ID_SHIFT #define SHRINK_ID(Id) (((Id) - _SYSVIEW_Globals.RAMBaseAddress) >> SEGGER_SYSVIEW_ID_SHIFT) #else #define SHRINK_ID(Id) ((Id) - _SYSVIEW_Globals.RAMBaseAddress) #endif #if SEGGER_SYSVIEW_RTT_CHANNEL > 0 #define CHANNEL_ID_UP SEGGER_SYSVIEW_RTT_CHANNEL #define CHANNEL_ID_DOWN SEGGER_SYSVIEW_RTT_CHANNEL #else #define CHANNEL_ID_UP _SYSVIEW_Globals.UpChannel #define CHANNEL_ID_DOWN _SYSVIEW_Globals.DownChannel #endif #if SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE #if (SEGGER_SYSVIEW_RTT_BUFFER_SIZE % SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE) #error "SEGGER_SYSVIEW_RTT_BUFFER_SIZE must be a multiple of SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE" #endif #endif /********************************************************************* * * Defines, configurable * ********************************************************************** */ // Timestamps may be less than full 32-bits, in which case we need to zero // the unused bits to properly handle overflows. // Note that this is a quite common scenario, as a 32-bit time such as // SysTick might be scaled down to reduce bandwidth // or a 16-bit hardware time might be used. #if SEGGER_SYSVIEW_TIMESTAMP_BITS < 32 // Eliminate unused bits in case hardware timestamps are less than 32 bits #define MAKE_DELTA_32BIT(Delta) Delta <<= 32 - SEGGER_SYSVIEW_TIMESTAMP_BITS; \ Delta >>= 32 - SEGGER_SYSVIEW_TIMESTAMP_BITS; #else #define MAKE_DELTA_32BIT(Delta) #endif #if SEGGER_SYSVIEW_SUPPORT_LONG_ID #define _MAX_ID_BYTES 5u #else #define _MAX_ID_BYTES 2u #endif #if SEGGER_SYSVIEW_SUPPORT_LONG_DATA #define _MAX_DATA_BYTES 5u #else #define _MAX_DATA_BYTES 2u #endif /********************************************************************* * * Defines, fixed * ********************************************************************** */ #define ENABLE_STATE_OFF 0 #define ENABLE_STATE_ON 1 #define ENABLE_STATE_DROPPING 2 #define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0) #define FORMAT_FLAG_PAD_ZERO (1u << 1) #define FORMAT_FLAG_PRINT_SIGN (1u << 2) #define FORMAT_FLAG_ALTERNATE (1u << 3) #define MODULE_EVENT_OFFSET (512) /********************************************************************* * * Types, local * ********************************************************************** */ typedef struct { U8 *pBuffer; U8 *pPayload; U8 *pPayloadStart; U32 Options; unsigned Cnt; } SEGGER_SYSVIEW_PRINTF_DESC; typedef struct { U8 EnableState; // 0: Disabled, 1: Enabled, (2: Dropping) U8 UpChannel; U8 RecursionCnt; U32 SysFreq; U32 CPUFreq; U32 LastTxTimeStamp; U32 RAMBaseAddress; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) U32 PacketCount; #else U32 DropCount; U8 DownChannel; #endif U32 DisabledEvents; const SEGGER_SYSVIEW_OS_API *pOSAPI; SEGGER_SYSVIEW_SEND_SYS_DESC_FUNC *pfSendSysDesc; } SEGGER_SYSVIEW_GLOBALS; /********************************************************************* * * Function prototypes, required * ********************************************************************** */ static void _SendPacket(U8 *pStartPacket, U8 *pEndPacket, unsigned int EventId); /********************************************************************* * * Static data * ********************************************************************** */ static const U8 _abSync[10] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; #if SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE #ifdef SEGGER_SYSVIEW_SECTION // // Alignment + special section required // #if (defined __GNUC__) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE))) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE))) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __ICCARM__) || (defined __ICCRX__) #pragma location=SEGGER_SYSVIEW_SECTION #pragma data_alignment=SEGGER_RTT_CPU_CACHE_LINE_SIZE static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) #pragma location=SEGGER_SYSVIEW_SECTION #pragma data_alignment=SEGGER_RTT_CPU_CACHE_LINE_SIZE static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __CC_ARM) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE), zero_init)) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE), zero_init)) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #else #error "Do not know how to place SystemView buffers in specific section" #endif #else // // Only alignment required // #if (defined __GNUC__) __attribute__ ((aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE))) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE))) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __ICCARM__) || (defined __ICCRX__) #pragma data_alignment=SEGGER_RTT_CPU_CACHE_LINE_SIZE static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) #pragma data_alignment=SEGGER_RTT_CPU_CACHE_LINE_SIZE static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __CC_ARM) __attribute__ ((aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE), zero_init)) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((aligned (SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE), zero_init)) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #else #error "Do not know how to align SystemView buffers to cache line size" #endif #endif #else #ifdef SEGGER_SYSVIEW_SECTION // // Only special section required // #if (defined __GNUC__) __attribute__ ((section (SEGGER_SYSVIEW_SECTION))) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((section (SEGGER_SYSVIEW_SECTION))) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __ICCARM__) || (defined __ICCRX__) #pragma location=SEGGER_SYSVIEW_SECTION static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) #pragma location=SEGGER_SYSVIEW_SECTION static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #elif (defined __CC_ARM) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), zero_init)) static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) __attribute__ ((section (SEGGER_SYSVIEW_SECTION), zero_init)) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #else #error "Do not know how to place SystemView buffers in specific section" #endif #else // // Neither special section nor alignment required // static char _UpBuffer [SEGGER_SYSVIEW_RTT_BUFFER_SIZE]; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) static char _DownBuffer[8]; // Small, fixed-size buffer, for back-channel comms #endif #endif #endif static SEGGER_SYSVIEW_GLOBALS _SYSVIEW_Globals; static SEGGER_SYSVIEW_MODULE *_pFirstModule; static U8 _NumModules; /********************************************************************* * * Static code * ********************************************************************** */ #define ENCODE_U32(pDest, Value) { \ U8* pSysviewPointer; \ U32 SysViewData; \ pSysviewPointer = pDest; \ SysViewData = Value; \ while(SysViewData > 0x7F) { \ *pSysviewPointer++ = (U8)(SysViewData | 0x80); \ SysViewData >>= 7; \ }; \ *pSysviewPointer++ = (U8)SysViewData; \ pDest = pSysviewPointer; \ }; #if (SEGGER_SYSVIEW_USE_STATIC_BUFFER == 1) static U8 _aPacket[SEGGER_SYSVIEW_MAX_PACKET_SIZE]; #define RECORD_START(PacketSize) SEGGER_SYSVIEW_LOCK(); \ pPayloadStart = _PreparePacket(_aPacket); #define RECORD_END() SEGGER_SYSVIEW_UNLOCK() #else #define RECORD_START(PacketSize) U8 aPacket[(PacketSize)]; \ pPayloadStart = _PreparePacket(aPacket); \ #define RECORD_END() #endif /********************************************************************* * * _EncodeData() * * Function description * Encode a byte buffer in variable-length format. * * Parameters * pPayload - Pointer to where string will be encoded. * pSrc - Pointer to data buffer to be encoded. * NumBytes - Number of bytes in the buffer to be encoded. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. * * Additional information * The data is encoded as a count byte followed by the contents * of the data buffer. * Make sure NumBytes + 1 bytes are free for the payload. */ static U8 *_EncodeData(U8 *pPayload, const char *pSrc, unsigned int NumBytes) { unsigned int n; const U8 *p; // Espressif doesn't support larger packages yet. Encode data length must be less than 255. assert(NumBytes < 255); // n = 0; p = (const U8 *)pSrc; // // Write Len // if (NumBytes < 255) { *pPayload++ = (U8)NumBytes; } else { *pPayload++ = 255; *pPayload++ = ((NumBytes >> 8) & 255); *pPayload++ = (NumBytes & 255); } while (n < NumBytes) { *pPayload++ = *p++; n++; } return pPayload; } /********************************************************************* * * _EncodeFloat() * * Function description * Encode a float value in variable-length format. * * Parameters * pPayload - Pointer to where value will be encoded. * Value - Value to be encoded. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. */ static U8 *_EncodeFloat(U8 *pPayload, float Value) { float Val = Value; U8 *pSysviewPointer; U32 *SysViewData; pSysviewPointer = pPayload; SysViewData = (U32 *)&Val; while ((*SysViewData) > 0x7F) { *pSysviewPointer++ = (U8)((*SysViewData) | 0x80); (*SysViewData) >>= 7; }; *pSysviewPointer++ = (U8)(*SysViewData); pPayload = pSysviewPointer; return pPayload; } /********************************************************************* * * _EncodeStr() * * Function description * Encode a string in variable-length format. * * Parameters * pPayload - Pointer to where string will be encoded. * pText - String to encode. * Limit - Maximum number of characters to encode from string. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. * * Additional information * The string is encoded as a count byte followed by the contents * of the string. * No more than 1 + Limit bytes will be encoded to the payload. */ static U8 *_EncodeStr(U8 *pPayload, const char *pText, unsigned int Limit) { U8 *pLen; const char *sStart; if (pText == NULL) { *pPayload++ = (U8)0; } else { sStart = pText; // Remember start of string. // // Save space to store count byte(s). // pLen = pPayload++; #if (SEGGER_SYSVIEW_MAX_STRING_LEN >= 255) // Length always encodes in 3 bytes pPayload += 2; #endif // // Limit string to maximum length and copy into payload buffer. // if (Limit > SEGGER_SYSVIEW_MAX_STRING_LEN) { Limit = SEGGER_SYSVIEW_MAX_STRING_LEN; } while ((Limit-- > 0) && (*pText != '\0')) { *pPayload++ = *pText++; } // // Save string length to buffer. // #if (SEGGER_SYSVIEW_MAX_STRING_LEN >= 255) // Length always encodes in 3 bytes Limit = (unsigned int)(pText - sStart); *pLen++ = (U8)255; *pLen++ = (U8)((Limit >> 8) & 255); *pLen++ = (U8)(Limit & 255); #else // Length always encodes in 1 byte *pLen = (U8)(pText - sStart); #endif } // return pPayload; } /********************************************************************* * * _PreparePacket() * * Function description * Prepare a SystemView event packet header. * * Parameters * pPacket - Pointer to start of packet to initialize. * * Return value * Pointer to first byte of packet payload. * * Additional information * The payload length and evnetId are not initialized. * PreparePacket only reserves space for them and they are * computed and filled in by the sending function. */ static U8 *_PreparePacket(U8 *pPacket) { return pPacket + _MAX_ID_BYTES + _MAX_DATA_BYTES; } /********************************************************************* * * _HandleIncomingPacket() * * Function description * Read an incoming command from the down channel and process it. * * Additional information * This function is called each time after sending a packet. * Processing incoming packets is done asynchronous. SystemView might * already have sent event packets after the host has sent a command. */ #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) static void _HandleIncomingPacket(void) { U8 Cmd; unsigned int Status; // Status = SEGGER_RTT_ReadNoLock(CHANNEL_ID_DOWN, &Cmd, 1); if (Status > 0) { switch (Cmd) { case SEGGER_SYSVIEW_COMMAND_ID_START: SEGGER_SYSVIEW_Start(); break; case SEGGER_SYSVIEW_COMMAND_ID_STOP: SEGGER_SYSVIEW_Stop(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_SYSTIME: SEGGER_SYSVIEW_RecordSystime(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_TASKLIST: SEGGER_SYSVIEW_SendTaskList(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_SYSDESC: SEGGER_SYSVIEW_GetSysDesc(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_NUMMODULES: SEGGER_SYSVIEW_SendNumModules(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_MODULEDESC: SEGGER_SYSVIEW_SendModuleDescription(); break; case SEGGER_SYSVIEW_COMMAND_ID_GET_MODULE: Status = SEGGER_RTT_ReadNoLock(CHANNEL_ID_DOWN, &Cmd, 1); if (Status > 0) { SEGGER_SYSVIEW_SendModule(Cmd); } break; case SEGGER_SYSVIEW_COMMAND_ID_HEARTBEAT: break; default: if (Cmd >= 128) { // Unknown extended command. Dummy read its parameter. SEGGER_RTT_ReadNoLock(CHANNEL_ID_DOWN, &Cmd, 1); } break; } } } #endif // (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) /********************************************************************* * * _TrySendOverflowPacket() * * Function description * Try to transmit an SystemView Overflow packet containing the * number of dropped packets. * * Additional information * Format as follows: * 01 Max. packet len is 1 + 5 + 5 = 11 * * Example packets sent * 01 20 40 * * Return value * !=0: Success, Message sent (stored in RTT-Buffer) * ==0: Buffer full, Message *NOT* stored * */ #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) static int _TrySendOverflowPacket(void) { U32 TimeStamp; I32 Delta; int Status; U8 aPacket[11]; U8 *pPayload; aPacket[0] = SYSVIEW_EVTID_OVERFLOW; // 1 pPayload = &aPacket[1]; ENCODE_U32(pPayload, _SYSVIEW_Globals.DropCount); // // Compute time stamp delta and append it to packet. // TimeStamp = SEGGER_SYSVIEW_GET_TIMESTAMP(); Delta = TimeStamp - _SYSVIEW_Globals.LastTxTimeStamp; MAKE_DELTA_32BIT(Delta); ENCODE_U32(pPayload, Delta); // // Try to store packet in RTT buffer and update time stamp when this was successful // Status = (int)SEGGER_RTT_WriteSkipNoLock(CHANNEL_ID_UP, aPacket, (unsigned int)(pPayload - aPacket)); SEGGER_SYSVIEW_ON_EVENT_RECORDED(pPayload - aPacket); if (Status) { _SYSVIEW_Globals.LastTxTimeStamp = TimeStamp; _SYSVIEW_Globals.EnableState--; // EnableState has been 2, will be 1. Always. } else { _SYSVIEW_Globals.DropCount++; } // return Status; } #endif // (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) /********************************************************************* * * _SendSyncInfo() * * Function description * Send SystemView sync packet and system information in * post mortem mode. * * Additional information * Sync is 10 * 0x00 without timestamp */ #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) static void _SendSyncInfo(void) { // // Add sync packet ( 10 * 0x00) // Send system description // Send system time // Send task list // Send module description // Send module information // SEGGER_RTT_WriteWithOverwriteNoLock(CHANNEL_ID_UP, _abSync, 10); SEGGER_SYSVIEW_ON_EVENT_RECORDED(10); SEGGER_SYSVIEW_RecordVoid(SYSVIEW_EVTID_TRACE_START); { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 4 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, _SYSVIEW_Globals.SysFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.CPUFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.RAMBaseAddress); ENCODE_U32(pPayload, SEGGER_SYSVIEW_ID_SHIFT); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_INIT); RECORD_END(); } if (_SYSVIEW_Globals.pfSendSysDesc) { _SYSVIEW_Globals.pfSendSysDesc(); } SEGGER_SYSVIEW_RecordSystime(); SEGGER_SYSVIEW_SendTaskList(); if (_NumModules > 0) { int n; SEGGER_SYSVIEW_SendNumModules(); for (n = 0; n < _NumModules; n++) { SEGGER_SYSVIEW_SendModule(n); } } } #endif // (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) /********************************************************************* * * _SendPacket() * * Function description * Send a SystemView packet over RTT. RTT channel and mode are * configured by macros when the SystemView component is initialized. * This function takes care of maintaining the packet drop count * and sending overflow packets when necessary. * The packet must be passed without Id and Length because this * function prepends it to the packet before transmission. * * Parameters * pStartPacket - Pointer to start of packet payload. * There must be at least 4 bytes free to prepend Id and Length. * pEndPacket - Pointer to end of packet payload. * EventId - Id of the event to send. * */ static void _SendPacket(U8 *pStartPacket, U8 *pEndPacket, unsigned int EventId) { unsigned int NumBytes; U32 TimeStamp; U32 Delta; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) unsigned int Status; #endif #if (SEGGER_SYSVIEW_USE_STATIC_BUFFER == 0) SEGGER_SYSVIEW_LOCK(); #endif #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) if (_SYSVIEW_Globals.EnableState == 0) { goto SendDone; } #else if (_SYSVIEW_Globals.EnableState == 1) { // Enabled, no dropped packets remaining goto Send; } if (_SYSVIEW_Globals.EnableState == 0) { goto SendDone; } // // Handle buffer full situations: // Have packets been dropped before because buffer was full? // In this case try to send and overflow packet. // if (_SYSVIEW_Globals.EnableState == 2) { _TrySendOverflowPacket(); if (_SYSVIEW_Globals.EnableState != 1) { goto SendDone; } } Send: #endif // // Check if event is disabled from being recorded. // if (EventId < 32) { if (_SYSVIEW_Globals.DisabledEvents & ((U32)1u << EventId)) { goto SendDone; } } // // Prepare actual packet. // If it is a known packet, prepend eventId only, // otherwise prepend packet length and eventId. // if (EventId < 24) { *--pStartPacket = (U8)EventId; } else { // // Get data length and prepend it. // NumBytes = (unsigned int)(pEndPacket - pStartPacket); #if SEGGER_SYSVIEW_SUPPORT_LONG_DATA if (NumBytes < 127) { #error "Seems EventId should be changed to NumBytes in the next line. Please check." *--pStartPacket = EventId; } else { // // Backwards U32 encode EventId. // if (NumBytes < (1ul << 14)) { // Encodes in 2 bytes *--pStartPacket = (U8)(NumBytes >> 7); *--pStartPacket = (U8)(NumBytes | 0x80); } else if (NumBytes < (1ul << 21)) { // Encodes in 3 bytes *--pStartPacket = (U8)(NumBytes >> 14); *--pStartPacket = (U8)((NumBytes >> 7) | 0x80); *--pStartPacket = (U8)(NumBytes | 0x80); } else if (NumBytes < (1ul << 28)) { // Encodes in 4 bytes *--pStartPacket = (U8)(NumBytes >> 21); *--pStartPacket = (U8)((NumBytes >> 14) | 0x80); *--pStartPacket = (U8)((NumBytes >> 7) | 0x80); *--pStartPacket = (U8)(NumBytes | 0x80); } else { // Encodes in 5 bytes *--pStartPacket = (U8)(NumBytes >> 28); *--pStartPacket = (U8)((NumBytes >> 21) | 0x80); *--pStartPacket = (U8)((NumBytes >> 14) | 0x80); *--pStartPacket = (U8)((NumBytes >> 7) | 0x80); *--pStartPacket = (U8)(NumBytes | 0x80); } } #else if (NumBytes > 127) { *--pStartPacket = (U8)(NumBytes >> 7); *--pStartPacket = (U8)(NumBytes | 0x80); } else { *--pStartPacket = (U8)NumBytes; } #endif // // Prepend EventId. // #if SEGGER_SYSVIEW_SUPPORT_LONG_ID if (EventId < 127) { *--pStartPacket = (U8)EventId; } else { // // Backwards U32 encode EventId. // if (EventId < (1u << 14)) { // Encodes in 2 bytes *--pStartPacket = (U8)(EventId >> 7); *--pStartPacket = (U8)(EventId | 0x80); } else if (EventId < (1ul << 21)) { // Encodes in 3 bytes *--pStartPacket = (U8)(EventId >> 14); *--pStartPacket = (U8)((EventId >> 7) | 0x80); *--pStartPacket = (U8)(EventId | 0x80); } else if (EventId < (1ul << 28)) { // Encodes in 4 bytes *--pStartPacket = (U8)(EventId >> 21); *--pStartPacket = (U8)((EventId >> 14) | 0x80); *--pStartPacket = (U8)((EventId >> 7) | 0x80); *--pStartPacket = (U8)(EventId | 0x80); } else { // Encodes in 5 bytes *--pStartPacket = (U8)(EventId >> 28); *--pStartPacket = (U8)((EventId >> 21) | 0x80); *--pStartPacket = (U8)((EventId >> 14) | 0x80); *--pStartPacket = (U8)((EventId >> 7) | 0x80); *--pStartPacket = (U8)(EventId | 0x80); } } #else if (EventId > 127) { *--pStartPacket = (U8)(EventId >> 7); *--pStartPacket = (U8)(EventId | 0x80); } else { *--pStartPacket = (U8)EventId; } #endif } // // Compute time stamp delta and append it to packet. // TimeStamp = SEGGER_SYSVIEW_GET_TIMESTAMP(); Delta = TimeStamp - _SYSVIEW_Globals.LastTxTimeStamp; MAKE_DELTA_32BIT(Delta); ENCODE_U32(pEndPacket, Delta); #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) // // Store packet in RTT buffer by overwriting old data and update time stamp // SEGGER_RTT_WriteWithOverwriteNoLock(CHANNEL_ID_UP, pStartPacket, pEndPacket - pStartPacket); SEGGER_SYSVIEW_ON_EVENT_RECORDED(pEndPacket - pStartPacket); _SYSVIEW_Globals.LastTxTimeStamp = TimeStamp; #else // // Try to store packet in RTT buffer and update time stamp when this was successful // Status = SEGGER_RTT_WriteSkipNoLock(CHANNEL_ID_UP, pStartPacket, (unsigned int)(pEndPacket - pStartPacket)); SEGGER_SYSVIEW_ON_EVENT_RECORDED(pEndPacket - pStartPacket); if (Status) { _SYSVIEW_Globals.LastTxTimeStamp = TimeStamp; } else { _SYSVIEW_Globals.EnableState++; // EnableState has been 1, will be 2. Always. } #endif #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) // // Add sync and system information periodically if we are in post mortem mode // if (_SYSVIEW_Globals.RecursionCnt == 0) { // Avoid uncontrolled nesting. This way, this routine can call itself once, but no more often than that. _SYSVIEW_Globals.RecursionCnt = 1; if (_SYSVIEW_Globals.PacketCount++ & (1 << SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT)) { _SendSyncInfo(); _SYSVIEW_Globals.PacketCount = 0; } _SYSVIEW_Globals.RecursionCnt = 0; } SendDone: ; // Avoid "label at end of compound statement" error when using static buffer #else SendDone: // // Check if host is sending data which needs to be processed. // Note that since this code is called for every packet, it is very time critical, so we do // only what is really needed here, which is checking if there is any data // if (SEGGER_RTT_HASDATA(CHANNEL_ID_DOWN)) { if (_SYSVIEW_Globals.RecursionCnt == 0) { // Avoid uncontrolled nesting. This way, this routine can call itself once, but no more often than that. _SYSVIEW_Globals.RecursionCnt = 1; _HandleIncomingPacket(); _SYSVIEW_Globals.RecursionCnt = 0; } } #endif // #if (SEGGER_SYSVIEW_USE_STATIC_BUFFER == 0) SEGGER_SYSVIEW_UNLOCK(); // We are done. Unlock and return #endif } #ifndef SEGGER_SYSVIEW_EXCLUDE_PRINTF // Define in project to avoid warnings about variable parameter list /********************************************************************* * * _VPrintHost() * * Function description * Send a format string and its parameters to the host. * * Parameters * s Pointer to format string. * Options Options to be sent to the host. * pParamList Pointer to the list of arguments for the format string. */ static int _VPrintHost(const char *s, U32 Options, va_list *pParamList) { U32 aParas[SEGGER_SYSVIEW_MAX_ARGUMENTS]; U32 *pParas; U32 NumArguments; const char *p; char c; U8 *pPayload; U8 *pPayloadStart; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT U8 HasNonScalar; HasNonScalar = 0; #endif // // Count number of arguments by counting '%' characters in string. // If enabled, check for non-scalar modifier flags to format string on the target. // p = s; NumArguments = 0; for (;;) { c = *p++; if (c == 0) { break; } if (c == '%') { c = *p; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT == 0 aParas[NumArguments++] = (U32)(va_arg(*pParamList, int)); if (NumArguments == SEGGER_SYSVIEW_MAX_ARGUMENTS) { break; } #else if (c == 's') { HasNonScalar = 1; break; } else { aParas[NumArguments++] = (U32)(va_arg(*pParamList, int)); if (NumArguments == SEGGER_SYSVIEW_MAX_ARGUMENTS) { break; } } #endif } } #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT if (HasNonScalar) { return -1; } #endif // // Send string and parameters to host // { RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_MAX_STRING_LEN + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_ARGUMENTS * SEGGER_SYSVIEW_QUANTA_U32); pPayload = _EncodeStr(pPayloadStart, s, SEGGER_SYSVIEW_MAX_STRING_LEN); ENCODE_U32(pPayload, Options); ENCODE_U32(pPayload, NumArguments); pParas = aParas; while (NumArguments--) { ENCODE_U32(pPayload, (*pParas)); pParas++; } _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); RECORD_END(); } return 0; } /********************************************************************* * * _StoreChar() * * Function description * Stores a character in the printf-buffer and sends the buffer when * it is filled. * * Parameters * p Pointer to the buffer description. * c Character to be printed. */ static void _StoreChar(SEGGER_SYSVIEW_PRINTF_DESC *p, char c) { unsigned int Cnt; U8 *pPayload; U32 Options; Cnt = p->Cnt; if ((Cnt + 1u) <= SEGGER_SYSVIEW_MAX_STRING_LEN) { *(p->pPayload++) = (U8)c; p->Cnt = Cnt + 1u; } // // Write part of string, when the buffer is full // if (p->Cnt == SEGGER_SYSVIEW_MAX_STRING_LEN) { *(p->pPayloadStart) = (U8)p->Cnt; pPayload = p->pPayload; Options = p->Options; ENCODE_U32(pPayload, Options); ENCODE_U32(pPayload, 0); _SendPacket(p->pPayloadStart, pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); p->pPayloadStart = _PreparePacket(p->pBuffer); p->pPayload = p->pPayloadStart + 1u; p->Cnt = 0u; } } /********************************************************************* * * _PrintUnsigned() * * Function description * Print an unsigned integer with the given formatting into the * formatted string. * * Parameters * pBufferDesc Pointer to the buffer description. * v Value to be printed. * Base Base of the value. * NumDigits Number of digits to be printed. * FieldWidth Width of the printed field. * FormatFlags Flags for formatting the value. */ static void _PrintUnsigned(SEGGER_SYSVIEW_PRINTF_DESC *pBufferDesc, unsigned int v, unsigned int Base, unsigned int NumDigits, unsigned int FieldWidth, unsigned int FormatFlags) { static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; unsigned int Div; unsigned int Digit; unsigned int Number; unsigned int Width; char c; Number = v; Digit = 1u; // // Get actual field width // Width = 1u; while (Number >= Base) { Number = (Number / Base); Width++; } if (NumDigits > Width) { Width = NumDigits; } // // Print leading chars if necessary // if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) { if (FieldWidth != 0u) { if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u)) { c = '0'; } else { c = ' '; } while ((FieldWidth != 0u) && (Width < FieldWidth)) { FieldWidth--; _StoreChar(pBufferDesc, c); } } } // // Compute Digit. // Loop until Digit has the value of the highest digit required. // Example: If the output is 345 (Base 10), loop 2 times until Digit is 100. // while (1) { if (NumDigits > 1u) { // User specified a min number of digits to print? => Make sure we loop at least that often, before checking anything else (> 1 check avoids problems with NumDigits being signed / unsigned) NumDigits--; } else { Div = v / Digit; if (Div < Base) { // Is our divider big enough to extract the highest digit from value? => Done break; } } Digit *= Base; } // // Output digits // do { Div = v / Digit; v -= Div * Digit; _StoreChar(pBufferDesc, _aV2C[Div]); Digit /= Base; } while (Digit); // // Print trailing spaces if necessary // if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY) { if (FieldWidth != 0u) { while ((FieldWidth != 0u) && (Width < FieldWidth)) { FieldWidth--; _StoreChar(pBufferDesc, ' '); } } } } /********************************************************************* * * _PrintInt() * * Function description * Print a signed integer with the given formatting into the * formatted string. * * Parameters * pBufferDesc Pointer to the buffer description. * v Value to be printed. * Base Base of the value. * NumDigits Number of digits to be printed. * FieldWidth Width of the printed field. * FormatFlags Flags for formatting the value. */ static void _PrintInt(SEGGER_SYSVIEW_PRINTF_DESC *pBufferDesc, int v, unsigned int Base, unsigned int NumDigits, unsigned int FieldWidth, unsigned int FormatFlags) { unsigned int Width; int Number; Number = (v < 0) ? -v : v; // // Get actual field width // Width = 1u; while (Number >= (int)Base) { Number = (Number / (int)Base); Width++; } if (NumDigits > Width) { Width = NumDigits; } if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN))) { FieldWidth--; } // // Print leading spaces if necessary // if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)) { if (FieldWidth != 0u) { while ((FieldWidth != 0u) && (Width < FieldWidth)) { FieldWidth--; _StoreChar(pBufferDesc, ' '); } } } // // Print sign if necessary // if (v < 0) { v = -v; _StoreChar(pBufferDesc, '-'); } else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN) { _StoreChar(pBufferDesc, '+'); } else { } // // Print leading zeros if necessary // if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u)) { if (FieldWidth != 0u) { while ((FieldWidth != 0u) && (Width < FieldWidth)) { FieldWidth--; _StoreChar(pBufferDesc, '0'); } } } // // Print number without sign // _PrintUnsigned(pBufferDesc, (unsigned int)v, Base, NumDigits, FieldWidth, FormatFlags); } /********************************************************************* * * _VPrintTarget() * * Function description * Stores a formatted string. * This data is read by the host. * * Parameters * sFormat Pointer to format string. * Options Options to be sent to the host. * pParamList Pointer to the list of arguments for the format string. */ static void _VPrintTarget(const char *sFormat, U32 Options, va_list *pParamList) { SEGGER_SYSVIEW_PRINTF_DESC BufferDesc; char c; int v; unsigned int NumDigits; unsigned int FormatFlags; unsigned int FieldWidth; U8 *pPayloadStart; const char *s; #if SEGGER_SYSVIEW_USE_STATIC_BUFFER == 0 RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_MAX_STRING_LEN + 1 + 2 * SEGGER_SYSVIEW_QUANTA_U32); SEGGER_SYSVIEW_LOCK(); #else RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_MAX_STRING_LEN + 1 + 2 * SEGGER_SYSVIEW_QUANTA_U32); #endif #if SEGGER_SYSVIEW_USE_STATIC_BUFFER == 0 BufferDesc.pBuffer = aPacket; #else BufferDesc.pBuffer = _aPacket; #endif BufferDesc.Cnt = 0u; BufferDesc.pPayloadStart = pPayloadStart; BufferDesc.pPayload = BufferDesc.pPayloadStart + 1u; BufferDesc.Options = Options; do { c = *sFormat; sFormat++; if (c == 0u) { break; } if (c == '%') { // // Filter out flags // FormatFlags = 0u; v = 1; do { c = *sFormat; switch (c) { case '-': FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY; sFormat++; break; case '0': FormatFlags |= FORMAT_FLAG_PAD_ZERO; sFormat++; break; case '+': FormatFlags |= FORMAT_FLAG_PRINT_SIGN; sFormat++; break; case '#': FormatFlags |= FORMAT_FLAG_ALTERNATE; sFormat++; break; default: v = 0; break; } } while (v); // // filter out field with // FieldWidth = 0u; do { c = *sFormat; if ((c < '0') || (c > '9')) { break; } sFormat++; FieldWidth = (FieldWidth * 10u) + ((unsigned int)c - '0'); } while (1); // // Filter out precision (number of digits to display) // NumDigits = 0u; c = *sFormat; if (c == '.') { sFormat++; do { c = *sFormat; if ((c < '0') || (c > '9')) { break; } sFormat++; NumDigits = NumDigits * 10u + ((unsigned int)c - '0'); } while (1); } // // Filter out length modifier // c = *sFormat; do { if ((c == 'l') || (c == 'h')) { c = *sFormat; sFormat++; } else { break; } } while (1); // // Handle specifiers // switch (c) { case 'c': { char c0; v = va_arg(*pParamList, int); c0 = (char)v; _StoreChar(&BufferDesc, c0); break; } case 'd': v = va_arg(*pParamList, int); _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags); break; case 'u': v = va_arg(*pParamList, int); _PrintUnsigned(&BufferDesc, (unsigned int)v, 10u, NumDigits, FieldWidth, FormatFlags); break; case 'x': case 'X': v = va_arg(*pParamList, int); _PrintUnsigned(&BufferDesc, (unsigned int)v, 16u, NumDigits, FieldWidth, FormatFlags); break; case 's': s = va_arg(*pParamList, const char *); if (s == NULL) { s = "(null)"; } do { c = *s; s++; if (c == '\0') { break; } _StoreChar(&BufferDesc, c); } while (BufferDesc.Cnt < SEGGER_SYSVIEW_MAX_STRING_LEN); break; case 'p': v = va_arg(*pParamList, int); _PrintUnsigned(&BufferDesc, (unsigned int)v, 16u, 8u, 8u, 0u); break; case '%': _StoreChar(&BufferDesc, '%'); break; default: break; } sFormat++; } else { _StoreChar(&BufferDesc, c); } } while (*sFormat); // // Write remaining data, if any // if (BufferDesc.Cnt != 0u) { *(BufferDesc.pPayloadStart) = (U8)BufferDesc.Cnt; ENCODE_U32(BufferDesc.pPayload, BufferDesc.Options); ENCODE_U32(BufferDesc.pPayload, 0); _SendPacket(BufferDesc.pPayloadStart, BufferDesc.pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); } #if SEGGER_SYSVIEW_USE_STATIC_BUFFER == 0 SEGGER_SYSVIEW_UNLOCK(); RECORD_END(); #else RECORD_END(); #endif } #endif // SEGGER_SYSVIEW_EXCLUDE_PRINTF /********************************************************************* * * Public code * ********************************************************************** */ /********************************************************************* * * SEGGER_SYSVIEW_Init() * * Function description * Initializes the SYSVIEW module. * Must be called before the SystemView Application connects to * the system. * * Parameters * SysFreq - Frequency of timestamp, usually CPU core clock frequency. * CPUFreq - CPU core clock frequency. * pOSAPI - Pointer to the API structure for OS-specific functions. * pfSendSysDesc - Pointer to record system description callback function. * * Additional information * This function initializes the RTT channel used to transport * SEGGER SystemView packets. * The channel is assigned the label "SysView" for client software * to identify the SystemView channel. * * The channel is configured with the macro SEGGER_SYSVIEW_RTT_CHANNEL. */ void SEGGER_SYSVIEW_Init(U32 SysFreq, U32 CPUFreq, const SEGGER_SYSVIEW_OS_API *pOSAPI, SEGGER_SYSVIEW_SEND_SYS_DESC_FUNC pfSendSysDesc) { #ifdef SEGGER_RTT_SECTION // // Explicitly initialize the RTT Control Block if it is in its dedicated section. // SEGGER_RTT_Init(); #endif #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) #if SEGGER_SYSVIEW_RTT_CHANNEL > 0 SEGGER_RTT_ConfigUpBuffer(SEGGER_SYSVIEW_RTT_CHANNEL, "SysView", &_UpBuffer[0], sizeof(_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); #else _SYSVIEW_Globals.UpChannel = (U8)SEGGER_RTT_AllocUpBuffer ("SysView", &_UpBuffer[0], sizeof(_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); #endif _SYSVIEW_Globals.RAMBaseAddress = SEGGER_SYSVIEW_ID_BASE; _SYSVIEW_Globals.LastTxTimeStamp = SEGGER_SYSVIEW_GET_TIMESTAMP(); _SYSVIEW_Globals.pOSAPI = pOSAPI; _SYSVIEW_Globals.SysFreq = SysFreq; _SYSVIEW_Globals.CPUFreq = CPUFreq; _SYSVIEW_Globals.pfSendSysDesc = pfSendSysDesc; _SYSVIEW_Globals.EnableState = 0; _SYSVIEW_Globals.PacketCount = 0; #else // (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) #if SEGGER_SYSVIEW_RTT_CHANNEL > 0 SEGGER_RTT_ConfigUpBuffer (SEGGER_SYSVIEW_RTT_CHANNEL, "SysView", &_UpBuffer[0], sizeof(_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); SEGGER_RTT_ConfigDownBuffer (SEGGER_SYSVIEW_RTT_CHANNEL, "SysView", &_DownBuffer[0], sizeof(_DownBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); #else _SYSVIEW_Globals.UpChannel = (U8)SEGGER_RTT_AllocUpBuffer ("SysView", &_UpBuffer[0], sizeof(_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); _SYSVIEW_Globals.DownChannel = _SYSVIEW_Globals.UpChannel; SEGGER_RTT_ConfigDownBuffer (_SYSVIEW_Globals.DownChannel, "SysView", &_DownBuffer[0], sizeof(_DownBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP); #endif _SYSVIEW_Globals.RAMBaseAddress = SEGGER_SYSVIEW_ID_BASE; _SYSVIEW_Globals.LastTxTimeStamp = SEGGER_SYSVIEW_GET_TIMESTAMP(); _SYSVIEW_Globals.pOSAPI = pOSAPI; _SYSVIEW_Globals.SysFreq = SysFreq; _SYSVIEW_Globals.CPUFreq = CPUFreq; _SYSVIEW_Globals.pfSendSysDesc = pfSendSysDesc; _SYSVIEW_Globals.EnableState = 0; #endif // (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) } /********************************************************************* * * SEGGER_SYSVIEW_SetRAMBase() * * Function description * Sets the RAM base address, which is subtracted from IDs in order * to save bandwidth. * * Parameters * RAMBaseAddress - Lowest RAM Address. (i.e. 0x20000000 on most Cortex-M) */ void SEGGER_SYSVIEW_SetRAMBase(U32 RAMBaseAddress) { _SYSVIEW_Globals.RAMBaseAddress = RAMBaseAddress; } /********************************************************************* * * SEGGER_SYSVIEW_RecordVoid() * * Function description * Formats and sends a SystemView packet with an empty payload. * * Parameters * EventID - SystemView event ID. */ void SEGGER_SYSVIEW_RecordVoid(unsigned int EventID) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32() * * Function description * Formats and sends a SystemView packet containing a single U32 * parameter payload. * * Parameters * EventID - SystemView event ID. * Value - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32(unsigned int EventID, U32 Value) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Value); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x2() * * Function description * Formats and sends a SystemView packet containing 2 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x2(unsigned int EventID, U32 Para0, U32 Para1) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x3() * * Function description * Formats and sends a SystemView packet containing 3 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x3(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 3 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x4() * * Function description * Formats and sends a SystemView packet containing 4 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x4(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 4 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x5() * * Function description * Formats and sends a SystemView packet containing 5 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x5(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 5 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x6() * * Function description * Formats and sends a SystemView packet containing 6 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. * Para5 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x6(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 6 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); ENCODE_U32(pPayload, Para5); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x7() * * Function description * Formats and sends a SystemView packet containing 7 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. * Para5 - The 32-bit parameter encoded to SystemView packet payload. * Para6 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x7(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 7 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); ENCODE_U32(pPayload, Para5); ENCODE_U32(pPayload, Para6); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x8() * * Function description * Formats and sends a SystemView packet containing 8 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. * Para5 - The 32-bit parameter encoded to SystemView packet payload. * Para6 - The 32-bit parameter encoded to SystemView packet payload. * Para7 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x8(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 8 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); ENCODE_U32(pPayload, Para5); ENCODE_U32(pPayload, Para6); ENCODE_U32(pPayload, Para7); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x9() * * Function description * Formats and sends a SystemView packet containing 9 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. * Para5 - The 32-bit parameter encoded to SystemView packet payload. * Para6 - The 32-bit parameter encoded to SystemView packet payload. * Para7 - The 32-bit parameter encoded to SystemView packet payload. * Para8 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x9(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7, U32 Para8) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 9 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); ENCODE_U32(pPayload, Para5); ENCODE_U32(pPayload, Para6); ENCODE_U32(pPayload, Para7); ENCODE_U32(pPayload, Para8); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordU32x10() * * Function description * Formats and sends a SystemView packet containing 10 U32 parameter payload. * * Parameters * EventID - SystemView event ID. * Para0 - The 32-bit parameter encoded to SystemView packet payload. * Para1 - The 32-bit parameter encoded to SystemView packet payload. * Para2 - The 32-bit parameter encoded to SystemView packet payload. * Para3 - The 32-bit parameter encoded to SystemView packet payload. * Para4 - The 32-bit parameter encoded to SystemView packet payload. * Para5 - The 32-bit parameter encoded to SystemView packet payload. * Para6 - The 32-bit parameter encoded to SystemView packet payload. * Para7 - The 32-bit parameter encoded to SystemView packet payload. * Para8 - The 32-bit parameter encoded to SystemView packet payload. * Para9 - The 32-bit parameter encoded to SystemView packet payload. */ void SEGGER_SYSVIEW_RecordU32x10(unsigned int EventID, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7, U32 Para8, U32 Para9) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 10 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, Para0); ENCODE_U32(pPayload, Para1); ENCODE_U32(pPayload, Para2); ENCODE_U32(pPayload, Para3); ENCODE_U32(pPayload, Para4); ENCODE_U32(pPayload, Para5); ENCODE_U32(pPayload, Para6); ENCODE_U32(pPayload, Para7); ENCODE_U32(pPayload, Para8); ENCODE_U32(pPayload, Para9); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordString() * * Function description * Formats and sends a SystemView packet containing a string. * * Parameters * EventID - SystemView event ID. * pString - The string to be sent in the SystemView packet payload. * * Additional information * The string is encoded as a count byte followed by the contents * of the string. * No more than SEGGER_SYSVIEW_MAX_STRING_LEN bytes will be encoded to the payload. */ void SEGGER_SYSVIEW_RecordString(unsigned int EventID, const char *pString) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = _EncodeStr(pPayloadStart, pString, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, EventID); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_Start() * * Function description * Start recording SystemView events. * * This function is triggered by the SystemView Application on connect. * For single-shot or post-mortem mode recording, it needs to be called * by the application. * * Additional information * This function enables transmission of SystemView packets recorded * by subsequent trace calls and records a SystemView Start event. * * As part of start, a SystemView Init packet is sent, containing the system * frequency. The list of current tasks, the current system time and the * system description string is sent, too. * * Notes * SEGGER_SYSVIEW_Start and SEGGER_SYSVIEW_Stop do not nest. * When SEGGER_SYSVIEW_CAN_RESTART is 1, each received start command * records the system information. This is required to enable restart * of recordings when SystemView unexpectedly disconnects without sending * a stop command before. */ void SEGGER_SYSVIEW_Start(void) { #if (SEGGER_SYSVIEW_CAN_RESTART == 0) if (_SYSVIEW_Globals.EnableState == 0) { #endif _SYSVIEW_Globals.EnableState = 1; #if (SEGGER_SYSVIEW_POST_MORTEM_MODE == 1) _SendSyncInfo(); #else SEGGER_SYSVIEW_LOCK(); SEGGER_RTT_WriteSkipNoLock(CHANNEL_ID_UP, _abSync, 10); SEGGER_SYSVIEW_UNLOCK(); SEGGER_SYSVIEW_ON_EVENT_RECORDED(10); SEGGER_SYSVIEW_RecordVoid(SYSVIEW_EVTID_TRACE_START); { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 4 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, _SYSVIEW_Globals.SysFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.CPUFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.RAMBaseAddress); ENCODE_U32(pPayload, SEGGER_SYSVIEW_ID_SHIFT); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_INIT); RECORD_END(); } if (_SYSVIEW_Globals.pfSendSysDesc) { _SYSVIEW_Globals.pfSendSysDesc(); } SEGGER_SYSVIEW_RecordSystime(); SEGGER_SYSVIEW_SendTaskList(); SEGGER_SYSVIEW_SendNumModules(); #endif #if (SEGGER_SYSVIEW_CAN_RESTART == 0) } #endif } /********************************************************************* * * SEGGER_SYSVIEW_Stop() * * Function description * Stop recording SystemView events. * * This function is triggered by the SystemView Application on disconnect. * For single-shot or postmortem mode recording, it can be called * by the application. * * Additional information * This function disables transmission of SystemView packets recorded * by subsequent trace calls. If transmission is enabled when * this function is called, a single SystemView Stop event is recorded * to the trace, send, and then trace transmission is halted. */ void SEGGER_SYSVIEW_Stop(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // We should send answer for the Stop command in any case. _SYSVIEW_Globals.EnableState = 1; if (_SYSVIEW_Globals.EnableState) { _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_TRACE_STOP); _SYSVIEW_Globals.EnableState = 0; } RECORD_END(); } U8 SEGGER_SYSVIEW_Started(void) { return _SYSVIEW_Globals.EnableState; } /********************************************************************* * * SEGGER_SYSVIEW_GetChannelID() * * Function description * Returns the RTT / channel ID used by SystemView. */ int SEGGER_SYSVIEW_GetChannelID(void) { return CHANNEL_ID_UP; } /********************************************************************* * * SEGGER_SYSVIEW_GetSysDesc() * * Function description * Triggers a send of the system information and description. * */ void SEGGER_SYSVIEW_GetSysDesc(void) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_STRING_LEN + 3/*1 or 3 bytes for string length*/); // pPayload = pPayloadStart; ENCODE_U32(pPayload, _SYSVIEW_Globals.SysFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.CPUFreq); ENCODE_U32(pPayload, _SYSVIEW_Globals.RAMBaseAddress); ENCODE_U32(pPayload, SEGGER_SYSVIEW_ID_SHIFT); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_INIT); RECORD_END(); if (_SYSVIEW_Globals.pfSendSysDesc) { _SYSVIEW_Globals.pfSendSysDesc(); } } /********************************************************************* * * SEGGER_SYSVIEW_SendTaskInfo() * * Function description * Send a Task Info Packet, containing TaskId for identification, * task priority and task name. * * Parameters * pInfo - Pointer to task information to send. */ void SEGGER_SYSVIEW_SendTaskInfo(const SEGGER_SYSVIEW_TASKINFO *pInfo) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32 + 1 + 32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SHRINK_ID(pInfo->TaskID)); ENCODE_U32(pPayload, pInfo->Prio); pPayload = _EncodeStr(pPayload, pInfo->sName, 32); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_INFO); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SHRINK_ID(pInfo->TaskID)); ENCODE_U32(pPayload, pInfo->StackBase); ENCODE_U32(pPayload, pInfo->StackSize); ENCODE_U32(pPayload, pInfo->StackUsage); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_STACK_INFO); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_SendStackInfo() * * Function description * Send a Stack Info Packet, containing TaskId for identification, * stack base, stack size and stack usage. * * * Parameters * pInfo - Pointer to stack information to send. */ void SEGGER_SYSVIEW_SendStackInfo(const SEGGER_SYSVIEW_STACKINFO *pInfo) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 4 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SHRINK_ID(pInfo->TaskID)); ENCODE_U32(pPayload, pInfo->StackBase); ENCODE_U32(pPayload, pInfo->StackSize); ENCODE_U32(pPayload, pInfo->StackUsage); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_SampleData() * * Function description * Send a Data Sample Packet, containing the data Id and the value. * * * Parameters * pInfo - Pointer to data sample struct to send. */ void SEGGER_SYSVIEW_SampleData(const SEGGER_SYSVIEW_DATA_SAMPLE *pInfo) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, pInfo->ID); pPayload = _EncodeFloat(pPayload, *(pInfo->pFloat_Value)); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_DATA_SAMPLE); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_SendTaskList() * * Function description * Send all tasks descriptors to the host. */ void SEGGER_SYSVIEW_SendTaskList(void) { if (_SYSVIEW_Globals.pOSAPI && _SYSVIEW_Globals.pOSAPI->pfSendTaskList) { _SYSVIEW_Globals.pOSAPI->pfSendTaskList(); } } /********************************************************************* * * SEGGER_SYSVIEW_SendSysDesc() * * Function description * Send the system description string to the host. * The system description is used by the SystemView Application * to identify the current application and handle events accordingly. * * The system description is usually called by the system description * callback, to ensure it is only sent when the SystemView Application * is connected. * * Parameters * sSysDesc - Pointer to the 0-terminated system description string. * * Additional information * One system description string may not exceed SEGGER_SYSVIEW_MAX_STRING_LEN characters. * Multiple description strings can be recorded. * * The Following items can be described in a system description string. * Each item is identified by its identifier, followed by '=' and the value. * Items are separated by ','. */ void SEGGER_SYSVIEW_SendSysDesc(const char *sSysDesc) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = _EncodeStr(pPayloadStart, sSysDesc, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_SYSDESC); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordSystime() * * Function description * Formats and sends a SystemView Systime containing a single U64 or U32 * parameter payload. */ void SEGGER_SYSVIEW_RecordSystime(void) { U64 Systime; // This code requeued because SystemView expect the answer from the device. // If there is no answer, then communication will be broken. U8 old_en = _SYSVIEW_Globals.EnableState; _SYSVIEW_Globals.EnableState = 1; if (_SYSVIEW_Globals.pOSAPI && _SYSVIEW_Globals.pOSAPI->pfGetTime) { Systime = _SYSVIEW_Globals.pOSAPI->pfGetTime(); SEGGER_SYSVIEW_RecordU32x2(SYSVIEW_EVTID_SYSTIME_US, (U32)(Systime), (U32)(Systime >> 32)); } else { SEGGER_SYSVIEW_RecordU32(SYSVIEW_EVTID_SYSTIME_CYCLES, SEGGER_SYSVIEW_GET_TIMESTAMP()); } _SYSVIEW_Globals.EnableState = old_en; } /********************************************************************* * * SEGGER_SYSVIEW_RecordEnterISR() * * Function description * Format and send an ISR entry event. * * Additional information * Example packets sent * 02 0F 50 // ISR(15) Enter. Timestamp is 80 (0x50) */ void SEGGER_SYSVIEW_RecordEnterISR(U32 IrqId) { unsigned v; U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; v = IrqId; // SEGGER_SYSVIEW_GET_INTERRUPT_ID(); ENCODE_U32(pPayload, v); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_ISR_ENTER); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordExitISR() * * Function description * Format and send an ISR exit event. * * Additional information * Format as follows: * 03 // Max. packet len is 6 * * Example packets sent * 03 20 // ISR Exit. Timestamp is 32 (0x20) */ void SEGGER_SYSVIEW_RecordExitISR(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_ISR_EXIT); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordExitISRToScheduler() * * Function description * Format and send an ISR exit into scheduler event. * * Additional information * Format as follows: * 18 // Max. packet len is 6 * * Example packets sent * 18 20 // ISR Exit to Scheduler. Timestamp is 32 (0x20) */ void SEGGER_SYSVIEW_RecordExitISRToScheduler(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_ISR_TO_SCHEDULER); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordEnterTimer() * * Function description * Format and send a Timer entry event. * * Parameters * TimerId - Id of the timer which starts. */ void SEGGER_SYSVIEW_RecordEnterTimer(U32 TimerId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SHRINK_ID(TimerId)); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TIMER_ENTER); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordExitTimer() * * Function description * Format and send a Timer exit event. */ void SEGGER_SYSVIEW_RecordExitTimer(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_TIMER_EXIT); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordEndCall() * * Function description * Format and send an End API Call event without return value. * * Parameters * EventID - Id of API function which ends. */ void SEGGER_SYSVIEW_RecordEndCall(unsigned int EventID) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, EventID); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_END_CALL); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordEndCallU32() * * Function description * Format and send an End API Call event with return value. * * Parameters * EventID - Id of API function which ends. * Para0 - Return value which will be returned by the API function. */ void SEGGER_SYSVIEW_RecordEndCallU32(unsigned int EventID, U32 Para0) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, EventID); ENCODE_U32(pPayload, Para0); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_END_CALL); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnIdle() * * Function description * Record an Idle event. */ void SEGGER_SYSVIEW_OnIdle(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_IDLE); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskCreate() * * Function description * Record a Task Create event. The Task Create event corresponds * to creating a task in the OS. * * Parameters * TaskId - Task ID of created task. */ void SEGGER_SYSVIEW_OnTaskCreate(U32 TaskId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; TaskId = SHRINK_ID(TaskId); ENCODE_U32(pPayload, TaskId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_CREATE); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskTerminate() * * Function description * Record a Task termination event. * The Task termination event corresponds to terminating a task in * the OS. If the TaskId is the currently active task, * SEGGER_SYSVIEW_OnTaskStopExec may be used, either. * * Parameters * TaskId - Task ID of terminated task. */ void SEGGER_SYSVIEW_OnTaskTerminate(U32 TaskId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; TaskId = SHRINK_ID(TaskId); ENCODE_U32(pPayload, TaskId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_TERMINATE); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskStartExec() * * Function description * Record a Task Start Execution event. The Task Start event * corresponds to when a task has started to execute rather than * when it is ready to execute. * * Parameters * TaskId - Task ID of task that started to execute. */ void SEGGER_SYSVIEW_OnTaskStartExec(U32 TaskId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; TaskId = SHRINK_ID(TaskId); ENCODE_U32(pPayload, TaskId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_START_EXEC); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskStopExec() * * Function description * Record a Task Stop Execution event. The Task Stop event * corresponds to when a task stops executing and terminates. */ void SEGGER_SYSVIEW_OnTaskStopExec(void) { U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // _SendPacket(pPayloadStart, pPayloadStart, SYSVIEW_EVTID_TASK_STOP_EXEC); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskStartReady() * * Function description * Record a Task Start Ready event. * * Parameters * TaskId - Task ID of task that started to execute. */ void SEGGER_SYSVIEW_OnTaskStartReady(U32 TaskId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; TaskId = SHRINK_ID(TaskId); ENCODE_U32(pPayload, TaskId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_START_READY); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_OnTaskStopReady() * * Function description * Record a Task Stop Ready event. * * Parameters * TaskId - Task ID of task that completed execution. * Cause - Reason for task to stop (i.e. Idle/Sleep) */ void SEGGER_SYSVIEW_OnTaskStopReady(U32 TaskId, unsigned int Cause) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; TaskId = SHRINK_ID(TaskId); ENCODE_U32(pPayload, TaskId); ENCODE_U32(pPayload, Cause); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_TASK_STOP_READY); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_MarkStart() * * Function description * Record a Performance Marker Start event to start measuring runtime. * * Parameters * MarkerId - User defined ID for the marker. */ void SEGGER_SYSVIEW_MarkStart(unsigned MarkerId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, MarkerId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_MARK_START); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_MarkStop() * * Function description * Record a Performance Marker Stop event to stop measuring runtime. * * Parameters * MarkerId - User defined ID for the marker. */ void SEGGER_SYSVIEW_MarkStop(unsigned MarkerId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, MarkerId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_MARK_STOP); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_Mark() * * Function description * Record a Performance Marker intermediate event. * * Parameters * MarkerId - User defined ID for the marker. */ void SEGGER_SYSVIEW_Mark(unsigned int MarkerId) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_MARK); ENCODE_U32(pPayload, MarkerId); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_NameMarker() * * Function description * Send the name of a Performance Marker to be displayed in SystemView. * * Marker names are usually set in the system description * callback, to ensure it is only sent when the SystemView Application * is connected. * * Parameters * MarkerId - User defined ID for the marker. * sName - Pointer to the marker name. (Max. SEGGER_SYSVIEW_MAX_STRING_LEN Bytes) */ void SEGGER_SYSVIEW_NameMarker(unsigned int MarkerId, const char *sName) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_NAME_MARKER); ENCODE_U32(pPayload, MarkerId); pPayload = _EncodeStr(pPayload, sName, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_NameResource() * * Function description * Send the name of a resource to be displayed in SystemView. * * Marker names are usually set in the system description * callback, to ensure it is only sent when the SystemView Application * is connected. * * Parameters * ResourceId - Id of the resource to be named. i.e. its address. * sName - Pointer to the resource name. (Max. SEGGER_SYSVIEW_MAX_STRING_LEN Bytes) */ void SEGGER_SYSVIEW_NameResource(U32 ResourceId, const char *sName) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32 + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SHRINK_ID(ResourceId)); pPayload = _EncodeStr(pPayload, sName, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_NAME_RESOURCE); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_RegisterData() * * Function description * Register data to sample the values via SystemView. * * Register functions are usually set in the system description * callback, to ensure it is only sent when the SystemView Application * is connected. * * Parameters * pInfo - Struct containing all possible properties that can be sent via this registration event. */ void SEGGER_SYSVIEW_RegisterData(SEGGER_SYSVIEW_DATA_REGISTER *pInfo) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 8 * SEGGER_SYSVIEW_QUANTA_U32 + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_REGISTER_DATA); ENCODE_U32(pPayload, pInfo->ID); pPayload = _EncodeStr(pPayload, pInfo->sName, SEGGER_SYSVIEW_MAX_STRING_LEN); if (pInfo->sName != 0) { ENCODE_U32(pPayload, pInfo->DataType); ENCODE_U32(pPayload, pInfo->Offset); ENCODE_U32(pPayload, pInfo->RangeMin); ENCODE_U32(pPayload, pInfo->RangeMax); pPayload = _EncodeFloat(pPayload, pInfo->ScalingFactor); pPayload = _EncodeStr(pPayload, pInfo->sUnit, SEGGER_SYSVIEW_MAX_STRING_LEN); } else if (pInfo->ScalingFactor != 0) { ENCODE_U32(pPayload, pInfo->DataType); ENCODE_U32(pPayload, pInfo->Offset); ENCODE_U32(pPayload, pInfo->RangeMin); ENCODE_U32(pPayload, pInfo->RangeMax); pPayload = _EncodeFloat(pPayload, pInfo->ScalingFactor); } else if (pInfo->RangeMax != 0) { ENCODE_U32(pPayload, pInfo->DataType); ENCODE_U32(pPayload, pInfo->Offset); ENCODE_U32(pPayload, pInfo->RangeMin); ENCODE_U32(pPayload, pInfo->RangeMax); } else if (pInfo->RangeMin != 0) { ENCODE_U32(pPayload, pInfo->DataType); ENCODE_U32(pPayload, pInfo->Offset); ENCODE_U32(pPayload, pInfo->RangeMin); } else if (pInfo->Offset != 0) { ENCODE_U32(pPayload, pInfo->DataType); ENCODE_U32(pPayload, pInfo->Offset); } else if (pInfo->DataType != 0) { ENCODE_U32(pPayload, pInfo->DataType); } _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_HeapDefine() * * Function description * Define heap. * * Parameters * pHeap - Pointer to heap control structure. * pBase - Pointer to managed heap memory. * HeapSize - Size of managed heap memory in bytes. * MetadataSize - Size of metadata associated with each heap allocation. * * Additional information * SystemView can track allocations across multiple heaps. * * HeapSize must be a multiple of the natural alignment unit of the * target. This size is subject to compression, controlled by the * specific setting of SEGGER_SYSVIEW_ID_SHIFT. * * MetadataSize defines the size of the per-allocation metadata. * For many heap implementations, the metadata size is a multiple of * the word size of the machine and typically contains the size * of the allocated block (used upon deallocation), optional * pointers to the preceding and/or following blocks, and optionally * a tag identifying the owner of the block. Note that MetadataSize * is not compressed within the SystemView packet and is not * required to be a multiple of 1<> SEGGER_SYSVIEW_ID_SHIFT); ENCODE_U32(pPayload, MetadataSize); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_HeapAlloc() * * Function description * Record a system-heap allocation event. * * Parameters * pHeap - Pointer to heap where allocation was made. * pUserData - Pointer to allocated user data. * UserDataLen - Size of block allocated to hold user data, excluding any metadata. * * Additional information * The user data must be correctly aligned for the architecture, which * typically requires that the alignment is at least the alignment * of a double or a long long. pUserData is, therefore, compressed by * shrinking as IDs are compressed, controlled by the specific setting * of SEGGER_SYSVIEW_ID_SHIFT. * * In the same way, UserDataLen must reflect the size of the allocated * block, not the allocation size requested by the application. This * size is also subject to compression, controlled by the specific setting * of SEGGER_SYSVIEW_ID_SHIFT. * * As an example, assume the allocator is running on a Cortex-M device * with SEGGER_SYSVIEW_ID_SHIFT set to 2 (the word alignment of the device). * If a user requests an allocation of 5 bytes, a hypothetical heap * allocator could allocate a block with size 32 bytes for this. The value * of UserDataLen sent to SystemView for recording should be 32, not 5, * and the 32 is compressed by shifting by two bits, the configured value * of SEGGER_SYSVIEW_ID_SHIFT, and describes the number of bytes that are * consumed from managed memory from which SystemView can calculate * accurate heap metrics. */ void SEGGER_SYSVIEW_HeapAlloc(void *pHeap, void *pUserData, unsigned int UserDataLen) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 3 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_HEAP_ALLOC); ENCODE_U32(pPayload, SHRINK_ID((U32)pHeap)); ENCODE_U32(pPayload, SHRINK_ID((U32)pUserData)); ENCODE_U32(pPayload, UserDataLen >> SEGGER_SYSVIEW_ID_SHIFT); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_HeapAllocEx() * * Function description * Record a per-heap allocation event. * * Parameters * pHeap - Pointer to heap where allocation was made. * pUserData - Pointer to allocated user data. * UserDataLen - Size of block allocated to hold user data, excluding any metadata. * Tag - Block tag, typically used to identify the owner of the block. * * Additional information * The user data must be correctly aligned for the architecture, which * typically requires that the alignment is at least the alignment * of a double or a long long. pUserData is, therefore, compressed by * shrinking as IDs are compressed, controlled by the specific setting * of SEGGER_SYSVIEW_ID_SHIFT. * * In the same way, UserDataLen must reflect the size of the allocated * block, not the allocation size requested by the application. This * size is also subject to compression, controlled by the specific setting * of SEGGER_SYSVIEW_ID_SHIFT. * * As an example, assume the allocator is running on a Cortex-M device * with SEGGER_SYSVIEW_ID_SHIFT set to 2 (the word alignment of the device). * If a user requests an allocation of 5 bytes, a hypothetical heap * allocator could allocate a block with size 32 bytes for this. The value * of UserDataLen sent to SystemView for recording should be 32, not 5, * and the 32 is compressed by shifting by two bits, the configured value * of SEGGER_SYSVIEW_ID_SHIFT, and describes the number of bytes that are * consumed from managed memory from which SystemView can calculate * accurate heap metrics. * * See also * SEGGER_SYSVIEW_HeapAlloc(). */ void SEGGER_SYSVIEW_HeapAllocEx(void *pHeap, void *pUserData, unsigned int UserDataLen, unsigned int Tag) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 5 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_HEAP_ALLOC_EX); ENCODE_U32(pPayload, SHRINK_ID((U32)pHeap)); ENCODE_U32(pPayload, SHRINK_ID((U32)pUserData)); ENCODE_U32(pPayload, UserDataLen >> SEGGER_SYSVIEW_ID_SHIFT); ENCODE_U32(pPayload, Tag); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_HeapFree() * * Function description * Record a heap deallocation event. * * Parameters * pHeap - Pointer to heap where allocation was made. * pUserData - Pointer to allocated user data. * * Additional information * SystemViews track allocations and knows the size of the * allocated data. */ void SEGGER_SYSVIEW_HeapFree(void *pHeap, void *pUserData) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); // pPayload = pPayloadStart; ENCODE_U32(pPayload, SYSVIEW_EVTID_EX_HEAP_FREE); ENCODE_U32(pPayload, SHRINK_ID((U32)pHeap)); ENCODE_U32(pPayload, SHRINK_ID((U32)pUserData)); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_EX); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_SendPacket() * * Function description * Send an event packet. * * Parameters * pPacket - Pointer to the start of the packet. * pPayloadEnd - Pointer to the end of the payload. * Make sure there are at least 5 bytes free after the payload. * EventId - Id of the event packet. * * Return value * !=0: Success, Message sent. * ==0: Buffer full, Message *NOT* sent. */ int SEGGER_SYSVIEW_SendPacket(U8 *pPacket, U8 *pPayloadEnd, unsigned int EventId) { #if (SEGGER_SYSVIEW_USE_STATIC_BUFFER == 1) SEGGER_SYSVIEW_LOCK(); #endif _SendPacket(pPacket + 4, pPayloadEnd, EventId); #if (SEGGER_SYSVIEW_USE_STATIC_BUFFER == 1) SEGGER_SYSVIEW_UNLOCK(); #endif return 0; } /********************************************************************* * * SEGGER_SYSVIEW_EncodeU32() * * Function description * Encode a U32 in variable-length format. * * Parameters * pPayload - Pointer to where U32 will be encoded. * Value - The 32-bit value to be encoded. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. */ U8 *SEGGER_SYSVIEW_EncodeU32(U8 *pPayload, U32 Value) { ENCODE_U32(pPayload, Value); return pPayload; } /********************************************************************* * * SEGGER_SYSVIEW_EncodeString() * * Function description * Encode a string in variable-length format. * * Parameters * pPayload - Pointer to where string will be encoded. * s - String to encode. * MaxLen - Maximum number of characters to encode from string. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. * * Additional information * The string is encoded as a count byte followed by the contents * of the string. * No more than 1 + MaxLen bytes will be encoded to the payload. */ U8 *SEGGER_SYSVIEW_EncodeString(U8 *pPayload, const char *s, unsigned int MaxLen) { return _EncodeStr(pPayload, s, MaxLen); } /********************************************************************* * * SEGGER_SYSVIEW_EncodeData() * * Function description * Encode a byte buffer in variable-length format. * * Parameters * pPayload - Pointer to where string will be encoded. * pSrc - Pointer to data buffer to be encoded. * NumBytes - Number of bytes in the buffer to be encoded. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. * * Additional information * The data is encoded as a count byte followed by the contents * of the data buffer. * Make sure NumBytes + 1 bytes are free for the payload. */ U8 *SEGGER_SYSVIEW_EncodeData(U8 *pPayload, const char *pSrc, unsigned int NumBytes) { return _EncodeData(pPayload, pSrc, NumBytes); } /********************************************************************* * * SEGGER_SYSVIEW_EncodeId() * * Function description * Encode a 32-bit Id in shrunken variable-length format. * * Parameters * pPayload - Pointer to where the Id will be encoded. * Id - The 32-bit value to be encoded. * * Return value * Pointer to the byte following the value, i.e. the first free * byte in the payload and the next position to store payload * content. * * Additional information * The parameters to shrink an Id can be configured in * SEGGER_SYSVIEW_Conf.h and via SEGGER_SYSVIEW_SetRAMBase(). * SEGGER_SYSVIEW_ID_BASE: Lowest Id reported by the application. * (i.e. 0x20000000 when all Ids are an address in this RAM) * SEGGER_SYSVIEW_ID_SHIFT: Number of bits to shift the Id to * save bandwidth. (i.e. 2 when Ids are 4 byte aligned) */ U8 *SEGGER_SYSVIEW_EncodeId(U8 *pPayload, U32 Id) { Id = SHRINK_ID(Id); ENCODE_U32(pPayload, Id); return pPayload; } /********************************************************************* * * SEGGER_SYSVIEW_ShrinkId() * * Function description * Get the shrunken value of an Id for further processing like in * SEGGER_SYSVIEW_NameResource(). * * Parameters * Id - The 32-bit value to be shrunken. * * Return value * Shrunken Id. * * Additional information * The parameters to shrink an Id can be configured in * SEGGER_SYSVIEW_Conf.h and via SEGGER_SYSVIEW_SetRAMBase(). * SEGGER_SYSVIEW_ID_BASE: Lowest Id reported by the application. * (i.e. 0x20000000 when all Ids are an address in this RAM) * SEGGER_SYSVIEW_ID_SHIFT: Number of bits to shift the Id to * save bandwidth. (i.e. 2 when Ids are 4 byte aligned) */ U32 SEGGER_SYSVIEW_ShrinkId(U32 Id) { return SHRINK_ID(Id); } /********************************************************************* * * SEGGER_SYSVIEW_RegisterModule() * * Function description * Register a middleware module for recording its events. * * Parameters * pModule - The middleware module information. * * Additional information * SEGGER_SYSVIEW_MODULE elements: * sDescription - Pointer to a string containing the module name and optionally the module event description. * NumEvents - Number of events the module wants to register. * EventOffset - Offset to be added to the event Ids. Out parameter, set by this function. Do not modify after calling this function. * pfSendModuleDesc - Callback function pointer to send more detailed module description to SystemView Application. * pNext - Pointer to next registered module. Out parameter, set by this function. Do not modify after calling this function. */ void SEGGER_SYSVIEW_RegisterModule(SEGGER_SYSVIEW_MODULE *pModule) { SEGGER_SYSVIEW_LOCK(); if (_pFirstModule == 0) { // // No module registered, yet. // Start list with new module. // EventOffset is the base offset for modules // pModule->EventOffset = MODULE_EVENT_OFFSET; pModule->pNext = 0; _pFirstModule = pModule; _NumModules = 1; } else { // // Registreded module(s) present. // Prepend new module in list. // EventOffset set from number of events and offset of previous module. // pModule->EventOffset = _pFirstModule->EventOffset + _pFirstModule->NumEvents; pModule->pNext = _pFirstModule; _pFirstModule = pModule; _NumModules++; } SEGGER_SYSVIEW_SendModule(0); SEGGER_SYSVIEW_UNLOCK(); } /********************************************************************* * * SEGGER_SYSVIEW_RecordModuleDescription() * * Function description * Sends detailed information of a registered module to the host. * * Parameters * pModule - Pointer to the described module. * sDescription - Pointer to a description string. */ void SEGGER_SYSVIEW_RecordModuleDescription(const SEGGER_SYSVIEW_MODULE *pModule, const char *sDescription) { U8 ModuleId; SEGGER_SYSVIEW_MODULE *p; p = _pFirstModule; ModuleId = 0; do { if (p == pModule) { break; } ModuleId++; p = p->pNext; } while (p); { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = pPayloadStart; // // Send module description // Send event offset and number of events // ENCODE_U32(pPayload, ModuleId); ENCODE_U32(pPayload, (pModule->EventOffset)); pPayload = _EncodeStr(pPayload, sDescription, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_MODULEDESC); RECORD_END(); } } /********************************************************************* * * SEGGER_SYSVIEW_SendModule() * * Function description * Sends the information of a registered module to the host. * * Parameters * ModuleId - Id of the requested module. */ void SEGGER_SYSVIEW_SendModule(U8 ModuleId) { SEGGER_SYSVIEW_MODULE *pModule; U32 n; if (_pFirstModule != 0) { pModule = _pFirstModule; for (n = 0; n < ModuleId; n++) { pModule = pModule->pNext; if (pModule == 0) { break; } } if (pModule != 0) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + 1 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = pPayloadStart; // // Send module description // Send event offset and number of events // ENCODE_U32(pPayload, ModuleId); ENCODE_U32(pPayload, (pModule->EventOffset)); pPayload = _EncodeStr(pPayload, pModule->sModule, SEGGER_SYSVIEW_MAX_STRING_LEN); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_MODULEDESC); RECORD_END(); } if (pModule && pModule->pfSendModuleDesc) { pModule->pfSendModuleDesc(); } } } /********************************************************************* * * SEGGER_SYSVIEW_SendModuleDescription() * * Function description * Triggers a send of the registered module descriptions. * */ void SEGGER_SYSVIEW_SendModuleDescription(void) { SEGGER_SYSVIEW_MODULE *pModule; if (_pFirstModule != 0) { pModule = _pFirstModule; do { if (pModule->pfSendModuleDesc) { pModule->pfSendModuleDesc(); } pModule = pModule->pNext; } while (pModule); } } /********************************************************************* * * SEGGER_SYSVIEW_SendNumModules() * * Function description * Send the number of registered modules to the host. */ void SEGGER_SYSVIEW_SendNumModules(void) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32); pPayload = pPayloadStart; ENCODE_U32(pPayload, _NumModules); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_NUMMODULES); RECORD_END(); } #ifndef SEGGER_SYSVIEW_EXCLUDE_PRINTF // Define in project to avoid warnings about variable parameter list /********************************************************************* * * SEGGER_SYSVIEW_PrintfHostEx() * * Function description * Print a string which is formatted on the host by the SystemView Application * with Additional information. * * Parameters * s - String to be formatted. * Options - Options for the string. i.e. Log level. * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_PrintfHostEx(const char *s, U32 Options, ...) { va_list ParamList; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_start(ParamList, Options); r = _VPrintHost(s, Options, &ParamList); va_end(ParamList); if (r == -1) { va_start(ParamList, Options); _VPrintTarget(s, Options, &ParamList); va_end(ParamList); } #else va_start(ParamList, Options); _VPrintHost(s, Options, &ParamList); va_end(ParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_VPrintfHostEx() * * Function description * Print a string which is formatted on the host by the SystemView Application * with Additional information. * * Parameters * s - String to be formatted. * Options - Options for the string. i.e. Log level. * pParamList - Pointer to the list of arguments for the format string * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_VPrintfHostEx(const char *s, U32 Options, va_list *pParamList) { #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_list ParamListCopy; va_copy(ParamListCopy, *pParamList); r = _VPrintHost(s, Options, pParamList); if (r == -1) { _VPrintTarget(s, Options, &ParamListCopy); } va_end(ParamListCopy); #else _VPrintHost(s, Options, pParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_PrintfHost() * * Function description * Print a string which is formatted on the host by the SystemView Application. * * Parameters * s - String to be formatted. * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_PrintfHost(const char *s, ...) { va_list ParamList; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_start(ParamList, s); r = _VPrintHost(s, SEGGER_SYSVIEW_LOG, &ParamList); va_end(ParamList); if (r == -1) { va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_LOG, &ParamList); va_end(ParamList); } #else va_start(ParamList, s); _VPrintHost(s, SEGGER_SYSVIEW_LOG, &ParamList); va_end(ParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_VPrintfHost() * * Function description * Print a string which is formatted on the host by the SystemView Application. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_VPrintfHost(const char *s, va_list *pParamList) { #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_list ParamListCopy; va_copy(ParamListCopy, *pParamList); r = _VPrintHost(s, SEGGER_SYSVIEW_LOG, pParamList); if (r == -1) { _VPrintTarget(s, SEGGER_SYSVIEW_LOG, &ParamListCopy); } va_end(ParamListCopy); #else _VPrintHost(s, SEGGER_SYSVIEW_LOG, pParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_WarnfHost() * * Function description * Print a warning string which is formatted on the host by * the SystemView Application. * * Parameters * s - String to be formatted. * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_WarnfHost(const char *s, ...) { va_list ParamList; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_start(ParamList, s); r = _VPrintHost(s, SEGGER_SYSVIEW_WARNING, &ParamList); va_end(ParamList); if (r == -1) { va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_WARNING, &ParamList); va_end(ParamList); } #else va_start(ParamList, s); _VPrintHost(s, SEGGER_SYSVIEW_WARNING, &ParamList); va_end(ParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_VWarnfHost() * * Function description * Print a warning string which is formatted on the host by * the SystemView Application. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_VWarnfHost(const char *s, va_list *pParamList) { #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_list ParamListCopy; va_copy(ParamListCopy, *pParamList); r = _VPrintHost(s, SEGGER_SYSVIEW_WARNING, pParamList); if (r == -1) { _VPrintTarget(s, SEGGER_SYSVIEW_WARNING, &ParamListCopy); } va_end(ParamListCopy); #else _VPrintHost(s, SEGGER_SYSVIEW_WARNING, pParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_ErrorfHost() * * Function description * Print an error string which is formatted on the host by * the SystemView Application. * * Parameters * s - String to be formatted. * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_ErrorfHost(const char *s, ...) { va_list ParamList; #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_start(ParamList, s); r = _VPrintHost(s, SEGGER_SYSVIEW_ERROR, &ParamList); va_end(ParamList); if (r == -1) { va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_ERROR, &ParamList); va_end(ParamList); } #else va_start(ParamList, s); _VPrintHost(s, SEGGER_SYSVIEW_ERROR, &ParamList); va_end(ParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_VErrorfHost() * * Function description * Print a warning string which is formatted on the host by * the SystemView Application. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string * * Additional information * All format arguments are treated as 32-bit scalar values. */ void SEGGER_SYSVIEW_VErrorfHost(const char *s, va_list *pParamList) { #if SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT int r; va_list ParamListCopy; va_copy(ParamListCopy, *pParamList); r = _VPrintHost(s, SEGGER_SYSVIEW_ERROR, pParamList); if (r == -1) { _VPrintTarget(s, SEGGER_SYSVIEW_ERROR, &ParamListCopy); } va_end(ParamListCopy); #else _VPrintHost(s, SEGGER_SYSVIEW_ERROR, pParamList); #endif } /********************************************************************* * * SEGGER_SYSVIEW_PrintfTargetEx() * * Function description * Print a string which is formatted on the target before sent to * the host with Additional information. * * Parameters * s - String to be formatted. * Options - Options for the string. i.e. Log level. */ void SEGGER_SYSVIEW_PrintfTargetEx(const char *s, U32 Options, ...) { va_list ParamList; va_start(ParamList, Options); _VPrintTarget(s, Options, &ParamList); va_end(ParamList); } /********************************************************************* * * SEGGER_SYSVIEW_VPrintfTargetEx() * * Function description * Print a string which is formatted on the target before sent to * the host with Additional information. * * Parameters * s - String to be formatted. * Options - Options for the string. i.e. Log level. * pParamList - Pointer to the list of arguments for the format string */ void SEGGER_SYSVIEW_VPrintfTargetEx(const char *s, U32 Options, va_list *pParamList) { _VPrintTarget(s, Options, pParamList); } /********************************************************************* * * SEGGER_SYSVIEW_PrintfTarget() * * Function description * Print a string which is formatted on the target before sent to * the host. * * Parameters * s - String to be formatted. */ void SEGGER_SYSVIEW_PrintfTarget(const char *s, ...) { va_list ParamList; va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_LOG, &ParamList); va_end(ParamList); } /********************************************************************* * * SEGGER_SYSVIEW_VPrintfTarget() * * Function description * Print a string which is formatted on the target before sent to * the host. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string */ void SEGGER_SYSVIEW_VPrintfTarget(const char *s, va_list *pParamList) { _VPrintTarget(s, SEGGER_SYSVIEW_LOG, pParamList); } /********************************************************************* * * SEGGER_SYSVIEW_WarnfTarget() * * Function description * Print a warning string which is formatted on the target before * sent to the host. * * Parameters * s - String to be formatted. */ void SEGGER_SYSVIEW_WarnfTarget(const char *s, ...) { va_list ParamList; va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_WARNING, &ParamList); va_end(ParamList); } /********************************************************************* * * SEGGER_SYSVIEW_VWarnfTarget() * * Function description * Print a warning string which is formatted on the target before * sent to the host. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string */ void SEGGER_SYSVIEW_VWarnfTarget(const char *s, va_list *pParamList) { _VPrintTarget(s, SEGGER_SYSVIEW_WARNING, pParamList); } /********************************************************************* * * SEGGER_SYSVIEW_ErrorfTarget() * * Function description * Print an error string which is formatted on the target before * sent to the host. * * Parameters * s - String to be formatted. */ void SEGGER_SYSVIEW_ErrorfTarget(const char *s, ...) { va_list ParamList; va_start(ParamList, s); _VPrintTarget(s, SEGGER_SYSVIEW_ERROR, &ParamList); va_end(ParamList); } /********************************************************************* * * SEGGER_SYSVIEW_VErrorfTarget() * * Function description * Print an error string which is formatted on the target before * sent to the host. * * Parameters * s - String to be formatted. * pParamList - Pointer to the list of arguments for the format string */ void SEGGER_SYSVIEW_VErrorfTarget(const char *s, va_list *pParamList) { _VPrintTarget(s, SEGGER_SYSVIEW_ERROR, pParamList); } #endif // SEGGER_SYSVIEW_EXCLUDE_PRINTF /********************************************************************* * * SEGGER_SYSVIEW_Print() * * Function description * Print a string to the host. * * Parameters * s - String to sent. */ void SEGGER_SYSVIEW_Print(const char *s) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = _EncodeStr(pPayloadStart, s, SEGGER_SYSVIEW_MAX_STRING_LEN); ENCODE_U32(pPayload, SEGGER_SYSVIEW_LOG); ENCODE_U32(pPayload, 0); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_Warn() * * Function description * Print a warning string to the host. * * Parameters * s - String to sent. */ void SEGGER_SYSVIEW_Warn(const char *s) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = _EncodeStr(pPayloadStart, s, SEGGER_SYSVIEW_MAX_STRING_LEN); ENCODE_U32(pPayload, SEGGER_SYSVIEW_WARNING); ENCODE_U32(pPayload, 0); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_Error() * * Function description * Print an error string to the host. * * Parameters * s - String to sent. */ void SEGGER_SYSVIEW_Error(const char *s) { U8 *pPayload; U8 *pPayloadStart; RECORD_START(SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_STRING_LEN); // pPayload = _EncodeStr(pPayloadStart, s, SEGGER_SYSVIEW_MAX_STRING_LEN); ENCODE_U32(pPayload, SEGGER_SYSVIEW_ERROR); ENCODE_U32(pPayload, 0); _SendPacket(pPayloadStart, pPayload, SYSVIEW_EVTID_PRINT_FORMATTED); RECORD_END(); } /********************************************************************* * * SEGGER_SYSVIEW_EnableEvents() * * Function description * Enable standard SystemView events to be generated. * * Parameters * EnableMask - Events to be enabled. */ void SEGGER_SYSVIEW_EnableEvents(U32 EnableMask) { _SYSVIEW_Globals.DisabledEvents &= ~EnableMask; } /********************************************************************* * * SEGGER_SYSVIEW_DisableEvents() * * Function description * Disable standard SystemView events to not be generated. * * Parameters * DisableMask - Events to be disabled. */ void SEGGER_SYSVIEW_DisableEvents(U32 DisableMask) { _SYSVIEW_Globals.DisabledEvents |= DisableMask; } /********************************************************************* * * SEGGER_SYSVIEW_IsStarted() * * Function description * Handle incoming packets if any and check if recording is started. * * Return value * 0: Recording not started. * > 0: Recording started. */ int SEGGER_SYSVIEW_IsStarted(void) { #if (SEGGER_SYSVIEW_POST_MORTEM_MODE != 1) // // Check if host is sending data which needs to be processed. // if (SEGGER_RTT_HASDATA(CHANNEL_ID_DOWN)) { if (_SYSVIEW_Globals.RecursionCnt == 0) { // Avoid uncontrolled nesting. This way, this routine can call itself once, but no more often than that. _SYSVIEW_Globals.RecursionCnt = 1; _HandleIncomingPacket(); _SYSVIEW_Globals.RecursionCnt = 0; } } #endif return _SYSVIEW_Globals.EnableState; } /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW.h Purpose : System visualization API. Revision: $Rev: 28768 $ */ #ifndef SEGGER_SYSVIEW_H #define SEGGER_SYSVIEW_H /********************************************************************* * * #include Section * ********************************************************************** */ #include "SEGGER.h" #include "SEGGER_SYSVIEW_ConfDefaults.h" #ifdef __cplusplus extern "C" { #endif /********************************************************************* * * Defines, fixed * ********************************************************************** */ #define SEGGER_SYSVIEW_MAJOR 3 #define SEGGER_SYSVIEW_MINOR 32 #define SEGGER_SYSVIEW_REV 0 #define SEGGER_SYSVIEW_VERSION ((SEGGER_SYSVIEW_MAJOR * 10000) + (SEGGER_SYSVIEW_MINOR * 100) + SEGGER_SYSVIEW_REV) #define SEGGER_SYSVIEW_INFO_SIZE 9 // Minimum size, which has to be reserved for a packet. 1-2 byte of message type, 0-2 byte of payload length, 1-5 bytes of timestamp. #define SEGGER_SYSVIEW_QUANTA_U32 5 // Maximum number of bytes to encode a U32, should be reserved for each 32-bit value in a packet. #define SEGGER_SYSVIEW_LOG (0u) #define SEGGER_SYSVIEW_WARNING (1u) #define SEGGER_SYSVIEW_ERROR (2u) #define SEGGER_SYSVIEW_FLAG_APPEND (1u << 6) #define SEGGER_SYSVIEW_PREPARE_PACKET(p) (p) + 4 // // SystemView events. First 32 IDs from 0 .. 31 are reserved for these // #define SYSVIEW_EVTID_NOP 0 // Dummy packet. #define SYSVIEW_EVTID_OVERFLOW 1 #define SYSVIEW_EVTID_ISR_ENTER 2 #define SYSVIEW_EVTID_ISR_EXIT 3 #define SYSVIEW_EVTID_TASK_START_EXEC 4 #define SYSVIEW_EVTID_TASK_STOP_EXEC 5 #define SYSVIEW_EVTID_TASK_START_READY 6 #define SYSVIEW_EVTID_TASK_STOP_READY 7 #define SYSVIEW_EVTID_TASK_CREATE 8 #define SYSVIEW_EVTID_TASK_INFO 9 #define SYSVIEW_EVTID_TRACE_START 10 #define SYSVIEW_EVTID_TRACE_STOP 11 #define SYSVIEW_EVTID_SYSTIME_CYCLES 12 #define SYSVIEW_EVTID_SYSTIME_US 13 #define SYSVIEW_EVTID_SYSDESC 14 #define SYSVIEW_EVTID_MARK_START 15 #define SYSVIEW_EVTID_MARK_STOP 16 #define SYSVIEW_EVTID_IDLE 17 #define SYSVIEW_EVTID_ISR_TO_SCHEDULER 18 #define SYSVIEW_EVTID_TIMER_ENTER 19 #define SYSVIEW_EVTID_TIMER_EXIT 20 #define SYSVIEW_EVTID_STACK_INFO 21 #define SYSVIEW_EVTID_MODULEDESC 22 #define SYSVIEW_EVTID_DATA_SAMPLE 23 #define SYSVIEW_EVTID_INIT 24 #define SYSVIEW_EVTID_NAME_RESOURCE 25 #define SYSVIEW_EVTID_PRINT_FORMATTED 26 #define SYSVIEW_EVTID_NUMMODULES 27 #define SYSVIEW_EVTID_END_CALL 28 #define SYSVIEW_EVTID_TASK_TERMINATE 29 #define SYSVIEW_EVTID_EX 31 // // SystemView extended events. Sent with ID 31. // #define SYSVIEW_EVTID_EX_MARK 0 #define SYSVIEW_EVTID_EX_NAME_MARKER 1 #define SYSVIEW_EVTID_EX_HEAP_DEFINE 2 #define SYSVIEW_EVTID_EX_HEAP_ALLOC 3 #define SYSVIEW_EVTID_EX_HEAP_ALLOC_EX 4 #define SYSVIEW_EVTID_EX_HEAP_FREE 5 #define SYSVIEW_EVTID_EX_REGISTER_DATA 6 // // Event masks to disable/enable events // #define SYSVIEW_EVTMASK_NOP (1 << SYSVIEW_EVTID_NOP) #define SYSVIEW_EVTMASK_OVERFLOW (1 << SYSVIEW_EVTID_OVERFLOW) #define SYSVIEW_EVTMASK_ISR_ENTER (1 << SYSVIEW_EVTID_ISR_ENTER) #define SYSVIEW_EVTMASK_ISR_EXIT (1 << SYSVIEW_EVTID_ISR_EXIT) #define SYSVIEW_EVTMASK_TASK_START_EXEC (1 << SYSVIEW_EVTID_TASK_START_EXEC) #define SYSVIEW_EVTMASK_TASK_STOP_EXEC (1 << SYSVIEW_EVTID_TASK_STOP_EXEC) #define SYSVIEW_EVTMASK_TASK_START_READY (1 << SYSVIEW_EVTID_TASK_START_READY) #define SYSVIEW_EVTMASK_TASK_STOP_READY (1 << SYSVIEW_EVTID_TASK_STOP_READY) #define SYSVIEW_EVTMASK_TASK_CREATE (1 << SYSVIEW_EVTID_TASK_CREATE) #define SYSVIEW_EVTMASK_TASK_INFO (1 << SYSVIEW_EVTID_TASK_INFO) #define SYSVIEW_EVTMASK_TRACE_START (1 << SYSVIEW_EVTID_TRACE_START) #define SYSVIEW_EVTMASK_TRACE_STOP (1 << SYSVIEW_EVTID_TRACE_STOP) #define SYSVIEW_EVTMASK_SYSTIME_CYCLES (1 << SYSVIEW_EVTID_SYSTIME_CYCLES) #define SYSVIEW_EVTMASK_SYSTIME_US (1 << SYSVIEW_EVTID_SYSTIME_US) #define SYSVIEW_EVTMASK_SYSDESC (1 << SYSVIEW_EVTID_SYSDESC) #define SYSVIEW_EVTMASK_USER_START (1 << SYSVIEW_EVTID_USER_START) #define SYSVIEW_EVTMASK_USER_STOP (1 << SYSVIEW_EVTID_USER_STOP) #define SYSVIEW_EVTMASK_IDLE (1 << SYSVIEW_EVTID_IDLE) #define SYSVIEW_EVTMASK_ISR_TO_SCHEDULER (1 << SYSVIEW_EVTID_ISR_TO_SCHEDULER) #define SYSVIEW_EVTMASK_TIMER_ENTER (1 << SYSVIEW_EVTID_TIMER_ENTER) #define SYSVIEW_EVTMASK_TIMER_EXIT (1 << SYSVIEW_EVTID_TIMER_EXIT) #define SYSVIEW_EVTMASK_STACK_INFO (1 << SYSVIEW_EVTID_STACK_INFO) #define SYSVIEW_EVTMASK_MODULEDESC (1 << SYSVIEW_EVTID_MODULEDESC) #define SYSVIEW_EVTMASK_DATA_SAMPLE (1 << SYSVIEW_EVTID_DATA_SAMPLE) #define SYSVIEW_EVTMASK_INIT (1 << SYSVIEW_EVTID_INIT) #define SYSVIEW_EVTMASK_NAME_RESOURCE (1 << SYSVIEW_EVTID_NAME_RESOURCE) #define SYSVIEW_EVTMASK_PRINT_FORMATTED (1 << SYSVIEW_EVTID_PRINT_FORMATTED) #define SYSVIEW_EVTMASK_NUMMODULES (1 << SYSVIEW_EVTID_NUMMODULES) #define SYSVIEW_EVTMASK_END_CALL (1 << SYSVIEW_EVTID_END_CALL) #define SYSVIEW_EVTMASK_TASK_TERMINATE (1 << SYSVIEW_EVTID_TASK_TERMINATE) #define SYSVIEW_EVTMASK_EX (1 << SYSVIEW_EVTID_EX) #define SYSVIEW_EVTMASK_ALL_INTERRUPTS ( SYSVIEW_EVTMASK_ISR_ENTER \ | SYSVIEW_EVTMASK_ISR_EXIT \ | SYSVIEW_EVTMASK_ISR_TO_SCHEDULER) #define SYSVIEW_EVTMASK_ALL_TASKS ( SYSVIEW_EVTMASK_TASK_START_EXEC \ | SYSVIEW_EVTMASK_TASK_STOP_EXEC \ | SYSVIEW_EVTMASK_TASK_START_READY \ | SYSVIEW_EVTMASK_TASK_STOP_READY \ | SYSVIEW_EVTMASK_TASK_CREATE \ | SYSVIEW_EVTMASK_TASK_INFO \ | SYSVIEW_EVTMASK_STACK_INFO \ | SYSVIEW_EVTMASK_TASK_TERMINATE) /********************************************************************* * * Structures * ********************************************************************** */ typedef struct { U32 TaskID; const char *sName; U32 Prio; U32 StackBase; U32 StackSize; U32 StackUsage; } SEGGER_SYSVIEW_TASKINFO; typedef struct { U32 TaskID; U32 StackBase; U32 StackSize; U32 StackUsage; } SEGGER_SYSVIEW_STACKINFO; typedef struct { U32 ID; union { U32 *pU32_Value; I32 *pI32_Value; float *pFloat_Value; }; } SEGGER_SYSVIEW_DATA_SAMPLE; typedef enum { SEGGER_SYSVIEW_TYPE_U32 = 0, SEGGER_SYSVIEW_TYPE_I32 = 1, SEGGER_SYSVIEW_TYPE_FLOAT = 2 } SEGGER_SYSVIEW_DATA_TYPE; typedef struct { U32 ID; SEGGER_SYSVIEW_DATA_TYPE DataType; I32 Offset; I32 RangeMin; I32 RangeMax; float ScalingFactor; const char *sName; const char *sUnit; } SEGGER_SYSVIEW_DATA_REGISTER; typedef struct SEGGER_SYSVIEW_MODULE_STRUCT SEGGER_SYSVIEW_MODULE; struct SEGGER_SYSVIEW_MODULE_STRUCT { const char *sModule; U32 NumEvents; U32 EventOffset; void (*pfSendModuleDesc)(void); SEGGER_SYSVIEW_MODULE *pNext; }; typedef void (SEGGER_SYSVIEW_SEND_SYS_DESC_FUNC)(void); /********************************************************************* * * Global data * ********************************************************************** */ #ifdef EXTERN #undef EXTERN #endif #ifndef SEGGER_SYSVIEW_C // Defined in SEGGER_SYSVIEW.c which includes this header beside other C-files #define EXTERN extern #else #define EXTERN #endif EXTERN unsigned int SEGGER_SYSVIEW_TickCnt; EXTERN unsigned int SEGGER_SYSVIEW_InterruptId; #undef EXTERN /********************************************************************* * * API functions * ********************************************************************** */ typedef struct { U64 (*pfGetTime) (void); void (*pfSendTaskList) (void); } SEGGER_SYSVIEW_OS_API; /********************************************************************* * * Control and initialization functions */ void SEGGER_SYSVIEW_Init (U32 SysFreq, U32 CPUFreq, const SEGGER_SYSVIEW_OS_API *pOSAPI, SEGGER_SYSVIEW_SEND_SYS_DESC_FUNC pfSendSysDesc); void SEGGER_SYSVIEW_SetRAMBase (U32 RAMBaseAddress); void SEGGER_SYSVIEW_Start (void); void SEGGER_SYSVIEW_Stop (void); void SEGGER_SYSVIEW_GetSysDesc (void); void SEGGER_SYSVIEW_SendTaskList (void); void SEGGER_SYSVIEW_SendTaskInfo (const SEGGER_SYSVIEW_TASKINFO *pInfo); void SEGGER_SYSVIEW_SendStackInfo (const SEGGER_SYSVIEW_STACKINFO *pInfo); void SEGGER_SYSVIEW_SendSysDesc (const char *sSysDesc); int SEGGER_SYSVIEW_IsStarted (void); int SEGGER_SYSVIEW_GetChannelID (void); void SEGGER_SYSVIEW_SampleData (const SEGGER_SYSVIEW_DATA_SAMPLE *pInfo); // Checks whether tracing has been started U8 SEGGER_SYSVIEW_Started(void); /********************************************************************* * * Event recording functions */ void SEGGER_SYSVIEW_RecordVoid (unsigned int EventId); void SEGGER_SYSVIEW_RecordU32 (unsigned int EventId, U32 Para0); void SEGGER_SYSVIEW_RecordU32x2 (unsigned int EventId, U32 Para0, U32 Para1); void SEGGER_SYSVIEW_RecordU32x3 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2); void SEGGER_SYSVIEW_RecordU32x4 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3); void SEGGER_SYSVIEW_RecordU32x5 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4); void SEGGER_SYSVIEW_RecordU32x6 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5); void SEGGER_SYSVIEW_RecordU32x7 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6); void SEGGER_SYSVIEW_RecordU32x8 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7); void SEGGER_SYSVIEW_RecordU32x9 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7, U32 Para8); void SEGGER_SYSVIEW_RecordU32x10 (unsigned int EventId, U32 Para0, U32 Para1, U32 Para2, U32 Para3, U32 Para4, U32 Para5, U32 Para6, U32 Para7, U32 Para8, U32 Para9); void SEGGER_SYSVIEW_RecordString (unsigned int EventId, const char *pString); void SEGGER_SYSVIEW_RecordSystime (void); void SEGGER_SYSVIEW_RecordEnterISR (U32 IrqId); void SEGGER_SYSVIEW_RecordExitISR (void); void SEGGER_SYSVIEW_RecordExitISRToScheduler (void); void SEGGER_SYSVIEW_RecordEnterTimer (U32 TimerId); void SEGGER_SYSVIEW_RecordExitTimer (void); void SEGGER_SYSVIEW_RecordEndCall (unsigned int EventID); void SEGGER_SYSVIEW_RecordEndCallU32 (unsigned int EventID, U32 Para0); void SEGGER_SYSVIEW_OnIdle (void); void SEGGER_SYSVIEW_OnTaskCreate (U32 TaskId); void SEGGER_SYSVIEW_OnTaskTerminate (U32 TaskId); void SEGGER_SYSVIEW_OnTaskStartExec (U32 TaskId); void SEGGER_SYSVIEW_OnTaskStopExec (void); void SEGGER_SYSVIEW_OnTaskStartReady (U32 TaskId); void SEGGER_SYSVIEW_OnTaskStopReady (U32 TaskId, unsigned int Cause); void SEGGER_SYSVIEW_MarkStart (unsigned int MarkerId); void SEGGER_SYSVIEW_MarkStop (unsigned int MarkerId); void SEGGER_SYSVIEW_Mark (unsigned int MarkerId); void SEGGER_SYSVIEW_NameMarker (unsigned int MarkerId, const char *sName); void SEGGER_SYSVIEW_HeapDefine (void *pHeap, void *pBase, unsigned int HeapSize, unsigned int MetadataSize); void SEGGER_SYSVIEW_HeapAlloc (void *pHeap, void *pUserData, unsigned int UserDataLen); void SEGGER_SYSVIEW_HeapAllocEx (void *pHeap, void *pUserData, unsigned int UserDataLen, unsigned int Tag); void SEGGER_SYSVIEW_HeapFree (void *pHeap, void *pUserData); void SEGGER_SYSVIEW_NameResource (U32 ResourceId, const char *sName); void SEGGER_SYSVIEW_RegisterData ( SEGGER_SYSVIEW_DATA_REGISTER *pInfo); int SEGGER_SYSVIEW_SendPacket (U8 *pPacket, U8 *pPayloadEnd, unsigned int EventId); /********************************************************************* * * Event parameter encoding functions */ U8 *SEGGER_SYSVIEW_EncodeU32 (U8 *pPayload, U32 Value); U8 *SEGGER_SYSVIEW_EncodeData (U8 *pPayload, const char *pSrc, unsigned int Len); U8 *SEGGER_SYSVIEW_EncodeString (U8 *pPayload, const char *s, unsigned int MaxLen); U8 *SEGGER_SYSVIEW_EncodeId (U8 *pPayload, U32 Id); U32 SEGGER_SYSVIEW_ShrinkId (U32 Id); /********************************************************************* * * Middleware module registration */ void SEGGER_SYSVIEW_RegisterModule (SEGGER_SYSVIEW_MODULE *pModule); void SEGGER_SYSVIEW_RecordModuleDescription (const SEGGER_SYSVIEW_MODULE *pModule, const char *sDescription); void SEGGER_SYSVIEW_SendModule (U8 ModuleId); void SEGGER_SYSVIEW_SendModuleDescription (void); void SEGGER_SYSVIEW_SendNumModules (void); /********************************************************************* * * printf-Style functions */ #ifndef SEGGER_SYSVIEW_EXCLUDE_PRINTF // Define in project to avoid warnings about variable parameter list void SEGGER_SYSVIEW_PrintfHostEx (const char *s, U32 Options, ...); void SEGGER_SYSVIEW_VPrintfHostEx (const char *s, U32 Options, va_list *pParamList); void SEGGER_SYSVIEW_PrintfTargetEx (const char *s, U32 Options, ...); void SEGGER_SYSVIEW_VPrintfTargetEx (const char *s, U32 Options, va_list *pParamList); void SEGGER_SYSVIEW_PrintfHost (const char *s, ...); void SEGGER_SYSVIEW_VPrintfHost (const char *s, va_list *pParamList); void SEGGER_SYSVIEW_PrintfTarget (const char *s, ...); void SEGGER_SYSVIEW_VPrintfTarget (const char *s, va_list *pParamList); void SEGGER_SYSVIEW_WarnfHost (const char *s, ...); void SEGGER_SYSVIEW_VWarnfHost (const char *s, va_list *pParamList); void SEGGER_SYSVIEW_WarnfTarget (const char *s, ...); void SEGGER_SYSVIEW_VWarnfTarget (const char *s, va_list *pParamList); void SEGGER_SYSVIEW_ErrorfHost (const char *s, ...); void SEGGER_SYSVIEW_VErrorfHost (const char *s, va_list *pParamList); void SEGGER_SYSVIEW_ErrorfTarget (const char *s, ...); void SEGGER_SYSVIEW_VErrorfTarget (const char *s, va_list *pParamList); #endif void SEGGER_SYSVIEW_Print (const char *s); void SEGGER_SYSVIEW_Warn (const char *s); void SEGGER_SYSVIEW_Error (const char *s); /********************************************************************* * * Run-time configuration functions */ void SEGGER_SYSVIEW_EnableEvents (U32 EnableMask); void SEGGER_SYSVIEW_DisableEvents (U32 DisableMask); /********************************************************************* * * Application-provided functions */ void SEGGER_SYSVIEW_Conf (void); U32 SEGGER_SYSVIEW_X_GetTimestamp (void); U32 SEGGER_SYSVIEW_X_GetInterruptId (void); void SEGGER_SYSVIEW_X_StartComm (void); void SEGGER_SYSVIEW_X_OnEventRecorded (unsigned NumBytes); /********************************************************************* * * Espressif specific functions */ int SEGGER_SYSVIEW_ESP_SetEncoder(void *encoder); void *SEGGER_SYSVIEW_ESP_GetEncoder(void); int SEGGER_SYSVIEW_ESP_GetDestCpu(void); #ifdef __cplusplus } #endif /********************************************************************* * * Compatibility API defines */ #define SEGGER_SYSVIEW_OnUserStart SEGGER_SYSVIEW_MarkStart #define SEGGER_SYSVIEW_OnUserStop SEGGER_SYSVIEW_MarkStop #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER_SYSVIEW_ConfDefaults.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_ConfDefaults.h Purpose : Defines defaults for configurable defines used in SEGGER SystemView. Revision: $Rev: 26230 $ */ #ifndef SEGGER_SYSVIEW_CONFDEFAULTS_H #define SEGGER_SYSVIEW_CONFDEFAULTS_H /********************************************************************* * * #include Section * ********************************************************************** */ #include "SEGGER_SYSVIEW_Conf.h" #include "SEGGER_RTT_Conf.h" #include "esp_assert.h" #ifdef __cplusplus extern "C" { #endif /********************************************************************* * * Defines, fixed * ********************************************************************** */ // // Use auto-detection for SEGGER_SYSVIEW_CORE define // based on compiler-/toolchain-specific defines // to define SEGGER_SYSVIEW_GET_INTERRUPT_ID and SEGGER_SYSVIEW_GET_TIMESTAMP // #define SEGGER_SYSVIEW_CORE_OTHER 0 #define SEGGER_SYSVIEW_CORE_CM0 1 // Cortex-M0/M0+/M1 #define SEGGER_SYSVIEW_CORE_CM3 2 // Cortex-M3/M4/M7 #define SEGGER_SYSVIEW_CORE_RX 3 // Renesas RX #ifndef SEGGER_SYSVIEW_CORE #if (defined __SES_ARM) || (defined __CROSSWORKS_ARM) || (defined __SEGGER_CC__) || (defined __GNUC__) || (defined __clang__) #if (defined __ARM_ARCH_6M__) || (defined __ARM_ARCH_8M_BASE__) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM0 #elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM3 #endif #elif defined(__ICCARM__) #if (defined (__ARM6M__) && (__CORE__ == __ARM6M__)) \ || (defined (__ARM8M_BASELINE__) && (__CORE__ == __ARM8M_BASELINE__)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM0 #elif (defined (__ARM7EM__) && (__CORE__ == __ARM7EM__)) \ || (defined (__ARM7M__) && (__CORE__ == __ARM7M__)) \ || (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) \ || (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM3 #endif #elif defined(__CC_ARM) #if (defined(__TARGET_ARCH_6S_M)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM0 #elif (defined(__TARGET_ARCH_7_M) || defined(__TARGET_ARCH_7E_M)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM3 #endif #elif defined(__TI_ARM__) #ifdef __TI_ARM_V6M0__ #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM0 #elif (defined(__TI_ARM_V7M3__) || defined(__TI_ARM_V7M4__)) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_CM3 #endif #elif defined(__ICCRX__) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_RX #elif defined(__RX) #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_RX #endif #ifndef SEGGER_SYSVIEW_CORE #define SEGGER_SYSVIEW_CORE SEGGER_SYSVIEW_CORE_OTHER #endif #endif /********************************************************************* * * Defines, defaults * ********************************************************************** */ /********************************************************************* * * Define: SEGGER_SYSVIEW_APP_NAME * * Description * The application name to be displayed in SystemView. * Default * "SystemView-enabled Application" * Notes * Convenience define to be used for SEGGER_SYSVIEW_SendSysDesc(). */ #ifndef SEGGER_SYSVIEW_APP_NAME #define SEGGER_SYSVIEW_APP_NAME "SystemView-enabled Application" #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_DEVICE_NAME * * Description * The target device name to be displayed in SystemView. * Default * "undefined device" * Notes * Convenience define to be used for SEGGER_SYSVIEW_SendSysDesc(). */ #ifndef SEGGER_SYSVIEW_DEVICE_NAME #define SEGGER_SYSVIEW_DEVICE_NAME "undefined device" #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_GET_INTERRUPT_ID() * * Description * Function macro to retrieve the Id of the currently active * interrupt. * Default * Call user-supplied function SEGGER_SYSVIEW_X_GetInterruptId(). * Notes * For some known compilers and cores, a ready-to-use, core-specific * default is set. * ARMv7M: Read ICSR[8:0] (active vector) * ARMv6M: Read ICSR[5:0] (active vector) */ #ifndef SEGGER_SYSVIEW_GET_INTERRUPT_ID #if SEGGER_SYSVIEW_CORE == SEGGER_SYSVIEW_CORE_CM3 #define SEGGER_SYSVIEW_GET_INTERRUPT_ID() ((*(U32*)(0xE000ED04)) & 0x1FF) // Get the currently active interrupt Id. (i.e. read Cortex-M ICSR[8:0] = active vector) #elif SEGGER_SYSVIEW_CORE == SEGGER_SYSVIEW_CORE_CM0 #if defined(__ICCARM__) #if (__VER__ > 6010000) #define SEGGER_SYSVIEW_GET_INTERRUPT_ID() (__get_IPSR()) // Workaround for IAR, which might do a byte-access to 0xE000ED04. Read IPSR instead. #else #define SEGGER_SYSVIEW_GET_INTERRUPT_ID() ((*(U32*)(0xE000ED04)) & 0x3F) // Older versions of IAR do not include __get_IPSR, but might also not optimize to byte-access. #endif #else #define SEGGER_SYSVIEW_GET_INTERRUPT_ID() ((*(U32*)(0xE000ED04)) & 0x3F) // Get the currently active interrupt Id. (i.e. read Cortex-M ICSR[5:0] = active vector) #endif #else #define SEGGER_SYSVIEW_GET_INTERRUPT_ID() SEGGER_SYSVIEW_X_GetInterruptId() // Get the currently active interrupt Id from the user-provided function. #endif #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_GET_TIMESTAMP() * * Description * Function macro to retrieve a system timestamp for SYSVIEW events. * Default * Call user-supplied function SEGGER_SYSVIEW_X_GetTimestamp(). * Notes * For some known compilers and cores, a ready-to-use, core-specific * default is set. * ARMv7M: Read Cortex-M Cycle Count register. * * The system timestamp clock frequency has to be passed in * SEGGER_SYSVIEW_Init(). */ #ifndef SEGGER_SYSVIEW_GET_TIMESTAMP #if defined (SEGGER_SYSVIEW_CORE) && (SEGGER_SYSVIEW_CORE == SEGGER_SYSVIEW_CORE_CM3) #define SEGGER_SYSVIEW_GET_TIMESTAMP() (*(U32 *)(0xE0001004)) // Retrieve a system timestamp. Cortex-M cycle counter. #else #define SEGGER_SYSVIEW_GET_TIMESTAMP() SEGGER_SYSVIEW_X_GetTimestamp() // Retrieve a system timestamp via user-defined function #endif #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_TIMESTAMP_BITS * * Description * Number of valid (low-order) bits delivered in system timestamp. * Default * 32 * Notes * Value has to match system timestamp clock source. */ // Define number of valid bits low-order delivered by clock source. #ifndef SEGGER_SYSVIEW_TIMESTAMP_BITS #define SEGGER_SYSVIEW_TIMESTAMP_BITS 32 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_RTT_CHANNEL * * Description * The RTT channel that SystemView will use. * Default * 0: Auto selection. * Notes * Value has to be lower than SEGGER_RTT_MAX_NUM_UP_BUFFERS. */ #ifndef SEGGER_SYSVIEW_RTT_CHANNEL #define SEGGER_SYSVIEW_RTT_CHANNEL 1 #endif // Sanity check of RTT channel #if (SEGGER_SYSVIEW_RTT_CHANNEL == 0) && (SEGGER_RTT_MAX_NUM_UP_BUFFERS < 2) #error "SEGGER_RTT_MAX_NUM_UP_BUFFERS in SEGGER_RTT_Conf.h has to be > 1!" #elif (SEGGER_SYSVIEW_RTT_CHANNEL >= SEGGER_RTT_MAX_NUM_UP_BUFFERS) #error "SEGGER_RTT_MAX_NUM_UP_BUFFERS in SEGGER_RTT_Conf.h has to be > SEGGER_SYSVIEW_RTT_CHANNEL!" #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_RTT_BUFFER_SIZE * * Description * Number of bytes that SystemView uses for the RTT buffer. * Default * 1024 */ #ifndef SEGGER_SYSVIEW_RTT_BUFFER_SIZE #define SEGGER_SYSVIEW_RTT_BUFFER_SIZE 1024 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_SECTION * * Description * Section to place the SystemView RTT Buffer into. * Default * undefined: Do not place into a specific section. * Notes * If SEGGER_RTT_SECTION is defined, the default changes to use * this section for the SystemView RTT Buffer, too. */ #if !(defined SEGGER_SYSVIEW_SECTION) && (defined SEGGER_RTT_SECTION) #define SEGGER_SYSVIEW_SECTION SEGGER_RTT_SECTION #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE * * Description * Largest cache line size (in bytes) in the target system. * Default * 0 * Notes * Required in systems with caches to make sure that the SystemView * RTT buffer can be aligned accordingly. */ #ifndef SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE #define SEGGER_SYSVIEW_CPU_CACHE_LINE_SIZE 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_ID_BASE * * Description * Lowest Id reported by the application. * Default * 0 * Notes * Value is usually subtracted from mailboxes, semaphores, tasks, * .... addresses, to compress event parameters. * Should be the lowest RAM address of the system. */ #ifndef SEGGER_SYSVIEW_ID_BASE #define SEGGER_SYSVIEW_ID_BASE 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_ID_SHIFT * * Description * Number of bits to shift Ids. * Default * 0 * Notes * Ids are shifted to compress event parameters. * Should match the alignment of Ids (addresses), * e.g. 2 when Ids are 4 byte aligned. */ #ifndef SEGGER_SYSVIEW_ID_SHIFT #define SEGGER_SYSVIEW_ID_SHIFT 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_MAX_ARGUMENTS * * Description * Maximum number of arguments which are handled with SystemView * print routines or may be encoded in one recording function. * routines. * Default * 16 */ #ifndef SEGGER_SYSVIEW_MAX_ARGUMENTS #define SEGGER_SYSVIEW_MAX_ARGUMENTS 16 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_MAX_STRING_LEN * * Description * Maximum string length which can be used in SystemView print and * system description routines. * Default * 128 */ #ifndef SEGGER_SYSVIEW_MAX_STRING_LEN #define SEGGER_SYSVIEW_MAX_STRING_LEN 128 #endif ESP_STATIC_ASSERT(SEGGER_SYSVIEW_MAX_STRING_LEN < 255, "SEGGER Sysview string length must be less than 255."); /********************************************************************* * * Define: SEGGER_SYSVIEW_SUPPORT_LONG_ID * * Description * It set, support encoding Evend Ids longer than 14 bit. * Default * 1 */ #ifndef SEGGER_SYSVIEW_SUPPORT_LONG_ID #define SEGGER_SYSVIEW_SUPPORT_LONG_ID 1 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_SUPPORT_LONG_DATA * * Description * It set, support encoding event data longer than 14 bit. * Default * 0 */ #ifndef SEGGER_SYSVIEW_SUPPORT_LONG_DATA #define SEGGER_SYSVIEW_SUPPORT_LONG_DATA 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT * * Description * If enabled, on SEGGER_SYSVIEW_PrintHost, check the format string * and if it includes unsupported formatters, use formatting on the * target instead. * Default * 0: Disabled. */ #ifndef SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT #define SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_USE_INTERNAL_RECORDER * * Description * If set, an internal recorder, such as UART or IP is used. * Default * 0: Disabled. * Notes * Convenience define to be used by SEGGER_SYSVIEW_Conf(), * such as in embOS configuration to enable Cortex-M cycle counter. */ #ifndef SEGGER_SYSVIEW_USE_INTERNAL_RECORDER #define SEGGER_SYSVIEW_USE_INTERNAL_RECORDER 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_CAN_RESTART * * Description * If enabled, send the SystemView start sequence on every start * command, not just on the first one. * Enables restart when SystemView disconnected unexpectedly. * Default * 1: Enabled */ #ifndef SEGGER_SYSVIEW_CAN_RESTART #define SEGGER_SYSVIEW_CAN_RESTART 1 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_START_ON_INIT * * Description * Enable calling SEGGER_SYSVIEW_Start() after initialization. * Default * 0: Disabled. * Notes * Convenience define to be used by SEGGER_SYSVIEW_Conf(), * such as in embOS configuration. */ #ifndef SEGGER_SYSVIEW_START_ON_INIT #define SEGGER_SYSVIEW_START_ON_INIT 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_USE_STATIC_BUFFER * * Description * If enabled, use a static buffer instead of a buffer on the stack * for SystemView event packets. * Default * 1: Enabled. * Notes * If enabled, the static memory use by SystemView is increased by * the maximum packet size. SystemView is locked on entry of a * recording function. * If disabled, the stack usage by SystemView recording functions * might be increased by up to the maximum packet size. SystemView * is locked when writing the packet to the RTT buffer. */ #ifndef SEGGER_SYSVIEW_USE_STATIC_BUFFER #define SEGGER_SYSVIEW_USE_STATIC_BUFFER 1 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_MAX_PACKET_SIZE * * Description * Maximum packet size for a SystemView event. * Default * Automatically calculated. * Notes * The maximum packet size is mainly defined by the maximum string * length and the maximum number of arguments. */ #ifndef SEGGER_SYSVIEW_MAX_PACKET_SIZE #define SEGGER_SYSVIEW_MAX_PACKET_SIZE (SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_MAX_STRING_LEN + 2 * SEGGER_SYSVIEW_QUANTA_U32 + SEGGER_SYSVIEW_MAX_ARGUMENTS * SEGGER_SYSVIEW_QUANTA_U32) #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_POST_MORTEM_MODE * * Description * If enabled, SystemView records for post-mortem analysis instead * of real-time analysis. * Default * 0: Disabled. * Notes * For more information refer to * https://www.segger.com/products/development-tools/systemview/technology/post-mortem-mode */ #ifndef SEGGER_SYSVIEW_POST_MORTEM_MODE #define SEGGER_SYSVIEW_POST_MORTEM_MODE 0 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT * * Description * Configure how frequently synchronization is sent in post-mortem * mode. * Default * 8: (1 << 8) = Every 256 Events. * Notes * In post-mortem mode, at least one sync has to be in the RTT buffer. * Recommended sync frequency: Buffer Size / 16 * For more information refer to * https://www.segger.com/products/development-tools/systemview/technology/post-mortem-mode */ #ifndef SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT #define SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT 8 #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_ON_EVENT_RECORDED() * * Description * Function macro to notify recorder about a new event in buffer. * Default * undefined: Do not notify recorder. * Notes * Used for non-J-Link recorder, * such as to enable transmission via UART or notify IP task. */ #ifndef SEGGER_SYSVIEW_ON_EVENT_RECORDED #define SEGGER_SYSVIEW_ON_EVENT_RECORDED(NumBytes) #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_LOCK() * * Description * Function macro to (nestable) lock SystemView recording. * Default * Use RTT Locking mechanism (defined by SEGGER_RTT_LOCK()). * Notes * If SystemView recording is not locked, recording events from * interrupts and tasks may lead to unpredictable, undefined, event * data. */ #ifndef SEGGER_SYSVIEW_LOCK unsigned SEGGER_SYSVIEW_X_SysView_Lock(void); #define SEGGER_SYSVIEW_LOCK() unsigned _SYSVIEW_int_state = SEGGER_SYSVIEW_X_SysView_Lock() #endif /********************************************************************* * * Define: SEGGER_SYSVIEW_UNLOCK * * Description * Function macro to unlock SystemView recording. * Default * Use RTT Unlocking mechanism (defined by SEGGER_RTT_UNLOCK()). */ #ifndef SEGGER_SYSVIEW_UNLOCK void SEGGER_SYSVIEW_X_SysView_Unlock(unsigned int_state); #define SEGGER_SYSVIEW_UNLOCK() SEGGER_SYSVIEW_X_SysView_Unlock(_SYSVIEW_int_state) #endif #ifdef __cplusplus } #endif #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/SEGGER/SEGGER_SYSVIEW_Int.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2024 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.56 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_Int.h Purpose : SEGGER SystemView internal header. Revision: $Rev: 21281 $ */ #ifndef SEGGER_SYSVIEW_INT_H #define SEGGER_SYSVIEW_INT_H /********************************************************************* * * #include Section * ********************************************************************** */ #include "SEGGER_SYSVIEW.h" #ifdef __cplusplus extern "C" { #endif /********************************************************************* * * Private data types * ********************************************************************** */ // // Commands that Host can send to target // typedef enum { SEGGER_SYSVIEW_COMMAND_ID_START = 1, SEGGER_SYSVIEW_COMMAND_ID_STOP, SEGGER_SYSVIEW_COMMAND_ID_GET_SYSTIME, SEGGER_SYSVIEW_COMMAND_ID_GET_TASKLIST, SEGGER_SYSVIEW_COMMAND_ID_GET_SYSDESC, SEGGER_SYSVIEW_COMMAND_ID_GET_NUMMODULES, SEGGER_SYSVIEW_COMMAND_ID_GET_MODULEDESC, SEGGER_SYSVIEW_COMMAND_ID_HEARTBEAT = 127, // Extended commands: Commands >= 128 have a second parameter SEGGER_SYSVIEW_COMMAND_ID_GET_MODULE = 128 } SEGGER_SYSVIEW_COMMAND_ID; #ifdef __cplusplus } #endif #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2017-2025 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_Config_FreeRTOS.c Purpose : Sample setup configuration of SystemView with FreeRTOS. Revision: $Rev: 7745 $ */ #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "SEGGER_SYSVIEW.h" #include "esp_intr_alloc.h" #include "soc/soc.h" #include "soc/interrupts.h" #include "esp_trace_port_encoder.h" #include "esp_trace_port_transport.h" #include "esp_trace_util.h" extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; /********************************************************************* * * Defines, configurable * ********************************************************************** */ // The application name to be displayed in SystemViewer #define SYSVIEW_APP_NAME "FreeRTOS Application" // The target device name #define SYSVIEW_DEVICE_NAME CONFIG_IDF_TARGET // The target core name #define SYSVIEW_CORE_NAME "core0" // In dual core, this will be renamed by OpenOCD as core1 // The lowest RAM address used for IDs (pointers) #define SYSVIEW_RAM_BASE (SOC_DROM_LOW) #ifdef CONFIG_FREERTOS_TICK_SUPPORT_CORETIMER #if CONFIG_FREERTOS_CORETIMER_0 #define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER0_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF) #endif #if CONFIG_FREERTOS_CORETIMER_1 #define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER1_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF) #endif #elif CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER #define SYSTICK_INTR_ID (ETS_SYSTIMER_TARGET0_INTR_SOURCE) #endif // CONFIG_FREERTOS_TICK_SUPPORT_CORETIMER // SystemView is single core specific: it implies that SEGGER_SYSVIEW_LOCK() // disables IRQs (disables rescheduling globally). So we can not use finite timeouts for locks and return error // in case of expiration, because error will not be handled and SEGGER's code will go further implying that // everything is fine, so for multi-core env we have to wait on underlying lock forever #define SEGGER_LOCK_WAIT_TMO ESP_TRACE_TMO_INFINITE /********************************************************************* * * _cbSendSystemDesc() * * Function description * Sends SystemView description strings. */ static void _cbSendSystemDesc(void) { char irq_str[32] = "I#"; SEGGER_SYSVIEW_SendSysDesc("N="SYSVIEW_APP_NAME",D="SYSVIEW_DEVICE_NAME",C="SYSVIEW_CORE_NAME",O=FreeRTOS"); strcat(itoa(SYSTICK_INTR_ID, irq_str + 2, 10), "=SysTick"); SEGGER_SYSVIEW_SendSysDesc(irq_str); size_t isr_count = sizeof(esp_isr_names) / sizeof(esp_isr_names[0]); for (size_t i = 0; i < isr_count; ++i) { if (esp_isr_names[i] == NULL || (ETS_INTERNAL_INTR_SOURCE_OFF + i) == SYSTICK_INTR_ID) { continue; } strcat(itoa(ETS_INTERNAL_INTR_SOURCE_OFF + i, irq_str + 2, 10), "="); strncat(irq_str, esp_isr_names[i], sizeof(irq_str) - strlen(irq_str) - 1); SEGGER_SYSVIEW_SendSysDesc(irq_str); } } /********************************************************************* * * Global functions * ********************************************************************** */ void SEGGER_SYSVIEW_Conf(void) { U32 disable_evts = 0; int timestamp_freq = esp_trace_timestamp_init(); SEGGER_SYSVIEW_Init(timestamp_freq, esp_trace_cpu_freq_get(), &SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc); SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE); #if !CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE disable_evts |= SYSVIEW_EVTMASK_OVERFLOW; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE disable_evts |= SYSVIEW_EVTMASK_ISR_ENTER; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_ISR_EXIT_ENABLE disable_evts |= SYSVIEW_EVTMASK_ISR_EXIT; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_START_EXEC; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_STOP_EXEC; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_READY_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_START_READY; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_READY_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_STOP_READY; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_CREATE; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TASK_TERMINATE_ENABLE disable_evts |= SYSVIEW_EVTMASK_TASK_TERMINATE; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_IDLE_ENABLE disable_evts |= SYSVIEW_EVTMASK_IDLE; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_ISR_TO_SCHED_ENABLE disable_evts |= SYSVIEW_EVTMASK_ISR_TO_SCHEDULER; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TIMER_ENTER_ENABLE disable_evts |= SYSVIEW_EVTMASK_TIMER_ENTER; #endif #if !CONFIG_SEGGER_SYSVIEW_EVT_TIMER_EXIT_ENABLE disable_evts |= SYSVIEW_EVTMASK_TIMER_EXIT; #endif SEGGER_SYSVIEW_DisableEvents(disable_evts); } U32 SEGGER_SYSVIEW_X_GetTimestamp(void) { return esp_trace_timestamp_get(); } void SEGGER_SYSVIEW_X_RTT_Lock(void) { } void SEGGER_SYSVIEW_X_RTT_Unlock(void) { } unsigned SEGGER_SYSVIEW_X_SysView_Lock(void) { esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (encoder) { // Use encoder-level lock return encoder->vt->take_lock(encoder, SEGGER_LOCK_WAIT_TMO); } return 0; } void SEGGER_SYSVIEW_X_SysView_Unlock(unsigned int_state) { esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (encoder) { // Use encoder-level unlock encoder->vt->give_lock(encoder, int_state); } } /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/Sample/FreeRTOSV10.4/SEGGER_SYSVIEW_FreeRTOS.c ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_FreeRTOS.c Purpose : Interface between FreeRTOS and SystemView. Revision: $Rev: 7947 $ */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "SEGGER_SYSVIEW.h" #include "SEGGER_SYSVIEW_FreeRTOS.h" #include "string.h" // Required for memset typedef struct SYSVIEW_FREERTOS_TASK_STATUS SYSVIEW_FREERTOS_TASK_STATUS; struct SYSVIEW_FREERTOS_TASK_STATUS { U32 xHandle; const char *pcTaskName; unsigned uxCurrentPriority; U32 pxStack; unsigned uStackHighWaterMark; }; static SYSVIEW_FREERTOS_TASK_STATUS _aTasks[SYSVIEW_FREERTOS_MAX_NOF_TASKS]; static unsigned _NumTasks; /********************************************************************* * * _cbSendTaskList() * * Function description * This function is part of the link between FreeRTOS and SYSVIEW. * Called from SystemView when asked by the host, it uses SYSVIEW * functions to send the entire task list to the host. */ static void _cbSendTaskList(void) { unsigned n; for (n = 0; n < _NumTasks; n++) { #if INCLUDE_uxTaskGetStackHighWaterMark // Report Task Stack High Watermark _aTasks[n].uStackHighWaterMark = uxTaskGetStackHighWaterMark((TaskHandle_t)_aTasks[n].xHandle); #endif SYSVIEW_SendTaskInfo((U32)_aTasks[n].xHandle, _aTasks[n].pcTaskName, (unsigned)_aTasks[n].uxCurrentPriority, (U32)_aTasks[n].pxStack, (unsigned)_aTasks[n].uStackHighWaterMark); } } /********************************************************************* * * _cbGetTime() * * Function description * This function is part of the link between FreeRTOS and SYSVIEW. * Called from SystemView when asked by the host, returns the * current system time in micro seconds. */ __attribute__((unused)) static U64 _cbGetTime(void) { U64 Time; Time = xTaskGetTickCountFromISR(); Time *= portTICK_PERIOD_MS; Time *= 1000; return Time; } /********************************************************************* * * Global functions * ********************************************************************** */ /********************************************************************* * * SYSVIEW_AddTask() * * Function description * Add a task to the internal list and record its information. */ void SYSVIEW_AddTask(U32 xHandle, const char *pcTaskName, unsigned uxCurrentPriority, U32 pxStack, unsigned uStackHighWaterMark) { /* On multi-core we have several idle tasks with 'IDLEx' names Not best solution, because we can filter out user tasks starting with 'IDLE'. But we can not use 'xTaskGetIdleTaskHandle' because at the moment when this function is called array of idle tasks handles are not initialized yet. */ if (memcmp(pcTaskName, "IDLE", 4) == 0) { return; } if (_NumTasks >= SYSVIEW_FREERTOS_MAX_NOF_TASKS) { SEGGER_SYSVIEW_Warn("SYSTEMVIEW: Could not record task information. Maximum number of tasks reached."); return; } _aTasks[_NumTasks].xHandle = xHandle; _aTasks[_NumTasks].pcTaskName = pcTaskName; _aTasks[_NumTasks].uxCurrentPriority = uxCurrentPriority; _aTasks[_NumTasks].pxStack = pxStack; _aTasks[_NumTasks].uStackHighWaterMark = uStackHighWaterMark; _NumTasks++; SYSVIEW_SendTaskInfo(xHandle, pcTaskName, uxCurrentPriority, pxStack, uStackHighWaterMark); } /********************************************************************* * * SYSVIEW_UpdateTask() * * Function description * Update a task in the internal list and record its information. */ void SYSVIEW_UpdateTask(U32 xHandle, const char *pcTaskName, unsigned uxCurrentPriority, U32 pxStack, unsigned uStackHighWaterMark) { unsigned n; /* On multi-core we have several idle tasks with 'IDLEx' names Not best solution, because we can filter out user tasks starting with 'IDLE'. But we can not use 'xTaskGetIdleTaskHandle' because at the moment when this function is called array of idle tasks handles are not initialized yet. */ if (memcmp(pcTaskName, "IDLE", 4) == 0) { return; } for (n = 0; n < _NumTasks; n++) { if (_aTasks[n].xHandle == xHandle) { break; } } if (n < _NumTasks) { _aTasks[n].pcTaskName = pcTaskName; _aTasks[n].uxCurrentPriority = uxCurrentPriority; _aTasks[n].pxStack = pxStack; _aTasks[n].uStackHighWaterMark = uStackHighWaterMark; SYSVIEW_SendTaskInfo(xHandle, pcTaskName, uxCurrentPriority, pxStack, uStackHighWaterMark); } else { SYSVIEW_AddTask(xHandle, pcTaskName, uxCurrentPriority, pxStack, uStackHighWaterMark); } } /********************************************************************* * * SYSVIEW_DeleteTask() * * Function description * Delete a task from the internal list. */ void SYSVIEW_DeleteTask(U32 xHandle) { unsigned n; if (_NumTasks == 0) { return; // Early out } for (n = 0; n < _NumTasks; n++) { if (_aTasks[n].xHandle == xHandle) { break; } } if (n == (_NumTasks - 1)) { // // Task is last item in list. // Simply zero the item and decrement number of tasks. // memset(&_aTasks[n], 0, sizeof(_aTasks[n])); _NumTasks--; } else if (n < _NumTasks) { // // Task is in the middle of the list. // Move last item to current position and decrement number of tasks. // Order of tasks does not really matter, so no need to move all following items. // _aTasks[n].xHandle = _aTasks[_NumTasks - 1].xHandle; _aTasks[n].pcTaskName = _aTasks[_NumTasks - 1].pcTaskName; _aTasks[n].uxCurrentPriority = _aTasks[_NumTasks - 1].uxCurrentPriority; _aTasks[n].pxStack = _aTasks[_NumTasks - 1].pxStack; _aTasks[n].uStackHighWaterMark = _aTasks[_NumTasks - 1].uStackHighWaterMark; memset(&_aTasks[_NumTasks - 1], 0, sizeof(_aTasks[_NumTasks - 1])); _NumTasks--; } } /********************************************************************* * * SYSVIEW_SendTaskInfo() * * Function description * Record task information. */ void SYSVIEW_SendTaskInfo(U32 TaskID, const char *sName, unsigned Prio, U32 StackBase, unsigned StackSize) { SEGGER_SYSVIEW_TASKINFO TaskInfo; memset(&TaskInfo, 0, sizeof(TaskInfo)); // Fill all elements with 0 to allow extending the structure in future version without breaking the code TaskInfo.TaskID = TaskID; TaskInfo.sName = sName; TaskInfo.Prio = Prio; TaskInfo.StackBase = StackBase; TaskInfo.StackSize = StackSize; SEGGER_SYSVIEW_SendTaskInfo(&TaskInfo); } /********************************************************************* * * Public API structures * ********************************************************************** */ // Callbacks provided to SYSTEMVIEW by FreeRTOS const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI = { /* Callback _cbGetTime locks xKernelLock inside xTaskGetTickCountFromISR, this can cause deadlock on multi-core. To prevent deadlock, always lock xKernelLock before s_sys_view_lock. Omitting the callback here results in sending SYSVIEW_EVTID_SYSTIME_CYCLES events instead of SYSVIEW_EVTID_SYSTIME_US */ NULL, _cbSendTaskList, }; /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/Sample/FreeRTOSV10.4/SEGGER_SYSVIEW_FreeRTOS.h ================================================ /* * SPDX-FileCopyrightText: 1995-2021 SEGGER Microcontroller GmbH * * SPDX-License-Identifier: BSD-1-Clause * * SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD */ /********************************************************************* * SEGGER Microcontroller GmbH * * The Embedded Experts * ********************************************************************** * * * (c) 1995 - 2021 SEGGER Microcontroller GmbH * * * * www.segger.com Support: support@segger.com * * * ********************************************************************** * * * SEGGER SystemView * Real-time application analysis * * * ********************************************************************** * * * All rights reserved. * * * * SEGGER strongly recommends to not make any changes * * to or modify the source code of this software in order to stay * * compatible with the SystemView and RTT protocol, and J-Link. * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * condition is met: * * * * o Redistributions of source code must retain the above copyright * * notice, this condition and the following disclaimer. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * * DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * * DAMAGE. * * * ********************************************************************** * * * SystemView version: 3.42 * * * ********************************************************************** -------------------------- END-OF-HEADER ----------------------------- File : SEGGER_SYSVIEW_FreeRTOS.h Purpose : Interface between FreeRTOS and SystemView. Tested with FreeRTOS V10.4.3 Revision: $Rev: 7745 $ Notes: (1) Include this file at the end of FreeRTOSConfig.h */ #ifndef SYSVIEW_FREERTOS_H #define SYSVIEW_FREERTOS_H #include "SEGGER_SYSVIEW.h" /********************************************************************* * * Defines, configurable * ********************************************************************** */ #define SYSVIEW_FREERTOS_MAX_NOF_TASKS CONFIG_SEGGER_SYSVIEW_MAX_TASKS /********************************************************************* * * Defines, fixed * ********************************************************************** */ // for dual-core targets we use event ID to keep core ID bit (0 or 1) // use the highest - 1 bit of event ID to indicate core ID // the highest bit can not be used due to event ID encoding method // this reduces supported ID range to [0..63] (for 1 byte IDs) plus [128..16383] (for 2 bytes IDs) // so original continuous event IDs range is split into two sub-ranges for 1-bytes IDs and 2-bytes ones // events which use apiFastID_OFFSET will have 1 byte ID, // so for the sake of bandwidth economy events which are generated more frequently should use this ID offset // currently all used events fall into this range #define apiFastID_OFFSET (32u) #define apiID_VTASKDELETE (1u) #define apiID_VTASKDELAY (2u) #define apiID_VTASKDELAYUNTIL (3u) #define apiID_VTASKSUSPEND (4u) #define apiID_ULTASKNOTIFYTAKE (5u) #define apiID_VTASKNOTIFYGIVEFROMISR (6u) #define apiID_VTASKPRIORITYINHERIT (7u) #define apiID_VTASKRESUME (8u) #define apiID_VTASKSTEPTICK (9u) #define apiID_XTASKPRIORITYDISINHERIT (10u) #define apiID_XTASKRESUMEFROMISR (11u) #define apiID_XTASKGENERICNOTIFY (12u) #define apiID_XTASKGENERICNOTIFYFROMISR (13u) #define apiID_XTASKNOTIFYWAIT (14u) #define apiID_XQUEUEGENERICCREATE (15u) #define apiID_VQUEUEDELETE (16u) #define apiID_XQUEUEGENERICRECEIVE (17u) #define apiID_XQUEUEPEEKFROMISR (18u) #define apiID_XQUEUERECEIVEFROMISR (19u) #define apiID_VQUEUEADDTOREGISTRY (20u) #define apiID_XQUEUEGENERICSEND (21u) #define apiID_XQUEUEGENERICSENDFROMISR (22u) #define apiID_VTASKPRIORITYSET (23u) #define apiID_UXTASKPRIORITYGETFROMISR (24u) #define apiID_XTASKGETTICKCOUNTFROMISR (25u) #define apiID_XEVENTGROUPCLEARBITSFROMISR (26u) #define apiID_XEVENTGROUPSETBITSFROMISR (27u) #define apiID_XEVENTGROUPGETBITSFROMISR (28u) #define apiID_XQUEUEGIVEFROMISR (29u) #define apiID_XQUEUEISQUEUEEMPTYFROMISR (30u) #define apiID_XQUEUEISQUEUEFULLFROMISR (31u) // the maximum allowed apiID for the first ID range // events which use apiSlowID_OFFSET will have 2-bytes ID #define apiSlowID_OFFSET (127u) #define apiID_VTASKALLOCATEMPUREGIONS (1u) #define apiID_UXTASKPRIORITYGET (2u) #define apiID_ETASKGETSTATE (3u) #define apiID_VTASKSTARTSCHEDULER (4u) #define apiID_VTASKENDSCHEDULER (5u) #define apiID_VTASKSUSPENDALL (6u) #define apiID_XTASKRESUMEALL (7u) #define apiID_XTASKGETTICKCOUNT (8u) #define apiID_UXTASKGETNUMBEROFTASKS (9u) #define apiID_PCTASKGETTASKNAME (10u) #define apiID_UXTASKGETSTACKHIGHWATERMARK (11u) #define apiID_VTASKSETAPPLICATIONTASKTAG (12u) #define apiID_XTASKGETAPPLICATIONTASKTAG (13u) #define apiID_VTASKSETTHREADLOCALSTORAGEPOINTER (14u) #define apiID_PVTASKGETTHREADLOCALSTORAGEPOINTER (15u) #define apiID_XTASKCALLAPPLICATIONTASKHOOK (16u) #define apiID_XTASKGETIDLETASKHANDLE (17u) #define apiID_UXTASKGETSYSTEMSTATE (18u) #define apiID_VTASKLIST (19u) #define apiID_VTASKGETRUNTIMESTATS (20u) #define apiID_XTASKNOTIFYSTATECLEAR (21u) #define apiID_XTASKGETCURRENTTASKHANDLE (22u) #define apiID_VTASKSETTIMEOUTSTATE (23u) #define apiID_XTASKCHECKFORTIMEOUT (24u) #define apiID_VTASKMISSEDYIELD (25u) #define apiID_XTASKGETSCHEDULERSTATE (26u) #define apiID_XTASKGENERICCREATE (27u) #define apiID_UXTASKGETTASKNUMBER (28u) #define apiID_VTASKSETTASKNUMBER (29u) #define apiID_ETASKCONFIRMSLEEPMODESTATUS (30u) #define apiID_XTIMERCREATE (31u) #define apiID_PVTIMERGETTIMERID (32u) #define apiID_VTIMERSETTIMERID (33u) #define apiID_XTIMERISTIMERACTIVE (34u) #define apiID_XTIMERGETTIMERDAEMONTASKHANDLE (35u) #define apiID_XTIMERPENDFUNCTIONCALLFROMISR (36u) #define apiID_XTIMERPENDFUNCTIONCALL (37u) #define apiID_PCTIMERGETTIMERNAME (38u) #define apiID_XTIMERCREATETIMERTASK (39u) #define apiID_XTIMERGENERICCOMMAND (40u) #define apiID_UXQUEUEMESSAGESWAITING (41u) #define apiID_UXQUEUESPACESAVAILABLE (42u) #define apiID_UXQUEUEMESSAGESWAITINGFROMISR (43u) #define apiID_XQUEUEALTGENERICSEND (44u) #define apiID_XQUEUEALTGENERICRECEIVE (45u) #define apiID_XQUEUECRSENDFROMISR (46u) #define apiID_XQUEUECRRECEIVEFROMISR (47u) #define apiID_XQUEUECRSEND (48u) #define apiID_XQUEUECRRECEIVE (49u) #define apiID_XQUEUECREATEMUTEX (50u) #define apiID_XQUEUECREATECOUNTINGSEMAPHORE (51u) #define apiID_XQUEUEGETMUTEXHOLDER (52u) #define apiID_XQUEUETAKEMUTEXRECURSIVE (53u) #define apiID_XQUEUEGIVEMUTEXRECURSIVE (54u) #define apiID_VQUEUEUNREGISTERQUEUE (55u) #define apiID_XQUEUECREATESET (56u) #define apiID_XQUEUEADDTOSET (57u) #define apiID_XQUEUEREMOVEFROMSET (58u) #define apiID_XQUEUESELECTFROMSET (59u) #define apiID_XQUEUESELECTFROMSETFROMISR (60u) #define apiID_XQUEUEGENERICRESET (61u) #define apiID_VLISTINITIALISE (62u) #define apiID_VLISTINITIALISEITEM (63u) #define apiID_VLISTINSERT (64u) #define apiID_VLISTINSERTEND (65u) #define apiID_UXLISTREMOVE (66u) #define apiID_XEVENTGROUPCREATE (67u) #define apiID_XEVENTGROUPWAITBITS (68u) #define apiID_XEVENTGROUPCLEARBITS (69u) #define apiID_XEVENTGROUPSETBITS (70u) #define apiID_XEVENTGROUPSYNC (71u) #define apiID_VEVENTGROUPDELETE (72u) #define apiID_UXEVENTGROUPGETNUMBER (73u) #define apiID_XSTREAMBUFFERCREATE (74u) #define apiID_VSTREAMBUFFERDELETE (75u) #define apiID_XSTREAMBUFFERRESET (76u) #define apiID_XSTREAMBUFFERSEND (77u) #define apiID_XSTREAMBUFFERSENDFROMISR (78u) #define apiID_XSTREAMBUFFERRECEIVE (79u) #define apiID_XSTREAMBUFFERRECEIVEFROMISR (80u) #ifdef CONFIG_FREERTOS_SMP #define traceQUEUE_SEND( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSEND, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), 0u, 0u, 0u) #else // CONFIG_FREERTOS_SMP #if ( configUSE_QUEUE_SETS != 1 ) #define traceQUEUE_SEND( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSEND, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pvItemToQueue, xTicksToWait, xCopyPosition) #else #define traceQUEUE_SEND( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSEND, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), 0u, 0u, 0u) #endif #endif // CONFIG_FREERTOS_SMP #define traceSTART() SEGGER_SYSVIEW_Conf() #define traceTASK_NOTIFY_TAKE(uxIndexToWait) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_ULTASKNOTIFYTAKE, xClearCountOnExit, xTicksToWait) #define traceTASK_DELAY() SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VTASKDELAY, xTicksToDelay) #define traceTASK_DELAY_UNTIL(xTimeToWake) SEGGER_SYSVIEW_RecordVoid (apiFastID_OFFSET + apiID_VTASKDELAYUNTIL) #define traceTASK_NOTIFY_GIVE_FROM_ISR(uxIndexToNotify) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_VTASKNOTIFYGIVEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB), (U32)pxHigherPriorityTaskWoken) #define traceTASK_PRIORITY_INHERIT( pxTCB, uxPriority ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VTASKPRIORITYINHERIT, (U32)pxMutexHolder) #define traceTASK_RESUME( pxTCB ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VTASKRESUME, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB)) #define traceINCREASE_TICK_COUNT( xTicksToJump ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VTASKSTEPTICK, xTicksToJump) #define traceTASK_SUSPEND( pxTCB ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VTASKSUSPEND, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB)) #define traceTASK_PRIORITY_DISINHERIT( pxTCB, uxBasePriority ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_XTASKPRIORITYDISINHERIT, (U32)pxMutexHolder) #define traceTASK_RESUME_FROM_ISR( pxTCB ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_XTASKRESUMEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB)) #define traceTASK_NOTIFY(uxIndexToNotify) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XTASKGENERICNOTIFY, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB), ulValue, eAction, (U32)pulPreviousNotificationValue) #define traceTASK_NOTIFY_FROM_ISR(uxIndexToWait) SEGGER_SYSVIEW_RecordU32x5(apiFastID_OFFSET + apiID_XTASKGENERICNOTIFYFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB), ulValue, eAction, (U32)pulPreviousNotificationValue, (U32)pxHigherPriorityTaskWoken) #define traceTASK_NOTIFY_WAIT(uxIndexToWait) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XTASKNOTIFYWAIT, ulBitsToClearOnEntry, ulBitsToClearOnExit, (U32)pulNotificationValue, xTicksToWait) #define traceQUEUE_CREATE( pxNewQueue ) SEGGER_SYSVIEW_RecordU32x3(apiFastID_OFFSET + apiID_XQUEUEGENERICCREATE, uxQueueLength, uxItemSize, ucQueueType) #define traceQUEUE_DELETE( pxQueue ) SEGGER_SYSVIEW_RecordU32 (apiFastID_OFFSET + apiID_VQUEUEDELETE, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue)) #define traceQUEUE_PEEK( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICRECEIVE, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)pvBuffer), xTicksToWait, 1) #define traceQUEUE_PEEK_FROM_ISR( pxQueue ) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_XQUEUEPEEKFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)pvBuffer)) #define traceQUEUE_PEEK_FROM_ISR_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_XQUEUEPEEKFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)pvBuffer)) #define traceQUEUE_RECEIVE( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICRECEIVE, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)0), xTicksToWait, 1) #define traceQUEUE_RECEIVE_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICRECEIVE, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)0), xTicksToWait, 1) #define traceQUEUE_SEMAPHORE_RECEIVE( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICRECEIVE, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)0), xTicksToWait, 0) #define traceQUEUE_RECEIVE_FROM_ISR( pxQueue ) SEGGER_SYSVIEW_RecordU32x3(apiFastID_OFFSET + apiID_XQUEUERECEIVEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)pvBuffer), (U32)pxHigherPriorityTaskWoken) #define traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x3(apiFastID_OFFSET + apiID_XQUEUERECEIVEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), SEGGER_SYSVIEW_ShrinkId((U32)pvBuffer), (U32)pxHigherPriorityTaskWoken) #define traceQUEUE_REGISTRY_ADD( xQueue, pcQueueName ) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_VQUEUEADDTOREGISTRY, SEGGER_SYSVIEW_ShrinkId((U32)xQueue), (U32)pcQueueName) #define traceQUEUE_SEND_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSEND, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pvItemToQueue, xTicksToWait, xCopyPosition) #define traceQUEUE_SEND_FROM_ISR( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSENDFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pvItemToQueue, (U32)pxHigherPriorityTaskWoken, xCopyPosition) #define traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x4(apiFastID_OFFSET + apiID_XQUEUEGENERICSENDFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pvItemToQueue, (U32)pxHigherPriorityTaskWoken, xCopyPosition) #define traceQUEUE_GIVE_FROM_ISR( pxQueue ) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_XQUEUEGIVEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pxHigherPriorityTaskWoken) #define traceQUEUE_GIVE_FROM_ISR_FAILED( pxQueue ) SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET + apiID_XQUEUEGIVEFROMISR, SEGGER_SYSVIEW_ShrinkId((U32)pxQueue), (U32)pxHigherPriorityTaskWoken) #define traceSTREAM_BUFFER_CREATE( pxStreamBuffer, xIsMessageBuffer ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERCREATE, (U32)xIsMessageBuffer, (U32)pxStreamBuffer) #define traceSTREAM_BUFFER_CREATE_FAILED( xIsMessageBuffer ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERCREATE, (U32)xIsMessageBuffer, 0u) #define traceSTREAM_BUFFER_DELETE( xStreamBuffer ) SEGGER_SYSVIEW_RecordU32 (apiSlowID_OFFSET + apiID_VSTREAMBUFFERDELETE, (U32)xStreamBuffer) #define traceSTREAM_BUFFER_RESET( xStreamBuffer ) SEGGER_SYSVIEW_RecordU32 (apiSlowID_OFFSET + apiID_XSTREAMBUFFERRESET, (U32)xStreamBuffer) #define traceSTREAM_BUFFER_SEND( xStreamBuffer, xBytesSent ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERSEND, (U32)xStreamBuffer, (U32)xBytesSent) #define traceSTREAM_BUFFER_SEND_FAILED( xStreamBuffer ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERSEND, (U32)xStreamBuffer, 0u) #define traceSTREAM_BUFFER_SEND_FROM_ISR( xStreamBuffer, xBytesSent ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERSENDFROMISR, (U32)xStreamBuffer, (U32)xBytesSent) #define traceSTREAM_BUFFER_RECEIVE( xStreamBuffer, xReceivedLength ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERRECEIVE, (U32)xStreamBuffer, (U32)xReceivedLength) #define traceSTREAM_BUFFER_RECEIVE_FAILED( xStreamBuffer ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERRECEIVE, (U32)xStreamBuffer, 0u) #define traceSTREAM_BUFFER_RECEIVE_FROM_ISR( xStreamBuffer, xReceivedLength ) SEGGER_SYSVIEW_RecordU32x2(apiSlowID_OFFSET + apiID_XSTREAMBUFFERRECEIVEFROMISR, (U32)xStreamBuffer, (U32)xReceivedLength) #define traceTASK_DELETE( pxTCB ) do { \ SEGGER_SYSVIEW_RecordU32(apiFastID_OFFSET + apiID_VTASKDELETE, SEGGER_SYSVIEW_ShrinkId((U32)pxTCB)); \ SYSVIEW_DeleteTask((U32)pxTCB); \ } while(0) #if( portSTACK_GROWTH < 0 ) #define traceTASK_CREATE(pxNewTCB) if (pxNewTCB != NULL) { \ SEGGER_SYSVIEW_OnTaskCreate((U32)pxNewTCB); \ SYSVIEW_AddTask((U32)pxNewTCB, \ &(pxNewTCB->pcTaskName[0]), \ pxNewTCB->uxPriority, \ (U32)pxNewTCB->pxStack, \ ((U32)pxNewTCB->pxTopOfStack - (U32)pxNewTCB->pxStack) \ ); \ } #else #define traceTASK_CREATE(pxNewTCB) if (pxNewTCB != NULL) { \ SEGGER_SYSVIEW_OnTaskCreate((U32)pxNewTCB); \ SYSVIEW_AddTask((U32)pxNewTCB, \ &(pxNewTCB->pcTaskName[0]), \ pxNewTCB->uxPriority, \ (U32)pxNewTCB->pxStack, \ (U32)(pxNewTCB->pxStack-pxNewTCB->pxTopOfStack) \ ); \ } #endif #define traceTASK_PRIORITY_SET(pxTask, uxNewPriority) { \ SEGGER_SYSVIEW_RecordU32x2(apiFastID_OFFSET+apiID_VTASKPRIORITYSET, \ SEGGER_SYSVIEW_ShrinkId((U32)pxTCB), \ uxNewPriority \ ); \ SYSVIEW_UpdateTask((U32)pxTask, \ &(pxTask->pcTaskName[0]), \ uxNewPriority, \ (U32)pxTask->pxStack, \ 0 \ ); \ } // // Define INCLUDE_xTaskGetIdleTaskHandle as 1 in FreeRTOSConfig.h to allow identification of Idle state. // // SMP FreeRTOS uses unpinned IDLE tasks, so sometimes IDEL0 runs on CPU1, IDLE1 runs on CPU0 and so on. // So IDLE state detection based on 'xTaskGetIdleTaskHandle' does not work for SMP kernel. // We could compare current task handle with every element of the array returned by 'xTaskGetIdleTaskHandle', // but it deos not seem to be efficient enough to be worth of making code more complex and less readabl. // So always use task name comparison mechanism for SMP kernel. #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 && !defined(CONFIG_FREERTOS_SMP)) #define traceTASK_SWITCHED_IN() if(prvGetTCBFromHandle(NULL) == xTaskGetIdleTaskHandle()) { \ SEGGER_SYSVIEW_OnIdle(); \ } else { \ SEGGER_SYSVIEW_OnTaskStartExec((U32)prvGetTCBFromHandle(NULL)); \ } #else #define traceTASK_SWITCHED_IN() { \ if (memcmp(prvGetTCBFromHandle(NULL)->pcTaskName, "IDLE", 4) != 0) { \ SEGGER_SYSVIEW_OnTaskStartExec((U32)prvGetTCBFromHandle(NULL)); \ } else { \ SEGGER_SYSVIEW_OnIdle(); \ } \ } #endif #define traceMOVED_TASK_TO_READY_STATE(pxTCB) SEGGER_SYSVIEW_OnTaskStartReady((U32)pxTCB) #define traceREADDED_TASK_TO_READY_STATE(pxTCB) #define traceMOVED_TASK_TO_DELAYED_LIST() SEGGER_SYSVIEW_OnTaskStopReady((U32)prvGetTCBFromHandle(NULL), (1u << 2)) #define traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST() SEGGER_SYSVIEW_OnTaskStopReady((U32)prvGetTCBFromHandle(NULL), (1u << 2)) #define traceMOVED_TASK_TO_SUSPENDED_LIST(pxTCB) SEGGER_SYSVIEW_OnTaskStopReady((U32)pxTCB, ((3u << 3) | 3)) #define traceISR_EXIT_TO_SCHEDULER() SEGGER_SYSVIEW_RecordExitISRToScheduler() #define traceISR_EXIT() SEGGER_SYSVIEW_RecordExitISR() #define traceISR_ENTER(n) SEGGER_SYSVIEW_RecordEnterISR(n) /********************************************************************* * * API functions * ********************************************************************** */ #ifdef __cplusplus extern "C" { #endif void SYSVIEW_AddTask (U32 xHandle, const char *pcTaskName, unsigned uxCurrentPriority, U32 pxStack, unsigned uStackHighWaterMark); void SYSVIEW_UpdateTask (U32 xHandle, const char *pcTaskName, unsigned uxCurrentPriority, U32 pxStack, unsigned uStackHighWaterMark); void SYSVIEW_DeleteTask (U32 xHandle); void SYSVIEW_SendTaskInfo (U32 TaskID, const char *sName, unsigned Prio, U32 StackBase, unsigned StackSize); #ifdef __cplusplus } #endif #endif /*************************** End of file ****************************/ ================================================ FILE: esp_sysview/src/esp/SEGGER_RTT_esp.c ================================================ /* * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "string.h" #include "freertos/FreeRTOS.h" #include "SEGGER_RTT.h" #include "SEGGER_SYSVIEW.h" #include "SEGGER_SYSVIEW_Conf.h" #include "esp_log.h" #include "esp_cpu.h" #include "esp_trace_port_transport.h" #include "esp_trace_port_encoder.h" #include "esp_trace_types.h" #include "esp_private/startup_internal.h" const static char *TAG = "segger_rtt"; #define SYSVIEW_EVENTS_BUF_SZ 255U // size of down channel data buf #define SYSVIEW_DOWN_BUF_SIZE 32 #if CONFIG_SEGGER_SYSVIEW_BUF_WAIT_TMO == -1 #define SEGGER_HOST_WAIT_TMO ESP_TRACE_TMO_INFINITE #else #define SEGGER_HOST_WAIT_TMO CONFIG_SEGGER_SYSVIEW_BUF_WAIT_TMO #endif static uint8_t s_events_buf[SYSVIEW_EVENTS_BUF_SZ]; static uint16_t s_events_buf_filled; static uint8_t s_down_buf[SYSVIEW_DOWN_BUF_SIZE]; /********************************************************************* * * Public code * ********************************************************************** */ /********************************************************************* * * SEGGER_RTT_ESP_FlushNoLock() * * Function description * Flushes buffered events. * * Parameters * min_sz Threshold for flushing data. If current filling level is above this value, data will be flushed. JTAG destinations only. * tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinitely. * * Return value * None. */ void SEGGER_RTT_ESP_FlushNoLock(void) { esp_err_t res; esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (!encoder) { ESP_LOGE(TAG, "Encoder not initialized"); return; } esp_trace_transport_t *tp = encoder->tp; if (s_events_buf_filled > 0) { res = tp->vt->write(tp, s_events_buf, s_events_buf_filled, SEGGER_HOST_WAIT_TMO); if (res != ESP_OK) { ESP_LOGE(TAG, "Failed to write buffered events (%d)!", res); } } // flush even if we failed to write buffered events, because no new events will be sent after STOP res = tp->vt->flush_nolock(tp); if (res != ESP_OK) { ESP_LOGE(TAG, "Failed to flush buffered events (%d)!", res); } s_events_buf_filled = 0; } /********************************************************************* * * SEGGER_RTT_ESP_Flush() * * Function description * Flushes buffered events. * * * Return value * None. */ void SEGGER_RTT_ESP_Flush(void) { SEGGER_SYSVIEW_LOCK(); SEGGER_RTT_ESP_FlushNoLock(); SEGGER_SYSVIEW_UNLOCK(); } /********************************************************************* * * SEGGER_RTT_ReadNoLock() * * Function description * Reads characters from SEGGER real-time-terminal control block * which have been previously stored by the host. * Do not lock against interrupts and multiple access. * * Parameters * BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). * pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. * BufferSize Size of the target application buffer. * * Return value * Number of bytes that have been read. */ unsigned SEGGER_RTT_ReadNoLock(unsigned BufferIndex, void *pData, unsigned BufferSize) { esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (!encoder) { return 0; } size_t size = BufferSize; esp_err_t res = encoder->tp->vt->read(encoder->tp, pData, &size, 0); return res != ESP_OK ? 0 : size; } /********************************************************************* * * SEGGER_RTT_WriteSkipNoLock * * Function description * Stores a specified number of characters in SEGGER RTT * control block which is then read by the host. * SEGGER_RTT_WriteSkipNoLock does not lock the application and * skips all data, if the data does not fit into the buffer. * * Parameters * BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). * pBuffer Pointer to character array. Does not need to point to a \0 terminated string. * NumBytes Number of bytes to be stored in the SEGGER RTT control block. * * Return value * Number of bytes which have been stored in the "Up"-buffer. * * Notes * (1) If there is not enough space in the "Up"-buffer, all data is dropped. * (2) For performance reasons this function does not call Init() * and may only be called after RTT has been initialized. * Either by calling SEGGER_RTT_Init() or calling another RTT API function first. */ unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void *pBuffer, unsigned NumBytes) { esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (!encoder) { return 0; // Encoder is not initialized } esp_trace_transport_t *tp = encoder->tp; uint8_t *pbuf = (uint8_t *)pBuffer; uint8_t event_id = *pbuf; esp_trace_link_types_t link_type = tp->vt->get_link_type(tp); if (link_type != ESP_TRACE_LINK_DEBUG_PROBE) { // Uart and USB Serial JTAG are handled separately int dest_cpu = SEGGER_SYSVIEW_ESP_GetDestCpu(); if ( (dest_cpu != esp_cpu_get_core_id()) && ( (event_id == SYSVIEW_EVTID_ISR_ENTER) || (event_id == SYSVIEW_EVTID_ISR_EXIT) || (event_id == SYSVIEW_EVTID_TASK_START_EXEC) || (event_id == SYSVIEW_EVTID_TASK_STOP_EXEC) || (event_id == SYSVIEW_EVTID_TASK_START_READY) || (event_id == SYSVIEW_EVTID_TASK_STOP_READY) || (event_id == SYSVIEW_EVTID_MARK_START) || (event_id == SYSVIEW_EVTID_MARK_STOP) || (event_id == SYSVIEW_EVTID_TIMER_ENTER) || (event_id == SYSVIEW_EVTID_TIMER_EXIT) || (event_id == SYSVIEW_EVTID_STACK_INFO) || (event_id == SYSVIEW_EVTID_MODULEDESC) ) ) { return NumBytes; } // This is workaround for SystemView! // Without this line SystemView will hangs on when heap tracing enabled. if (event_id == SYSVIEW_EVTID_MODULEDESC) { return NumBytes; } } if (NumBytes > SYSVIEW_EVENTS_BUF_SZ) { ESP_LOGE(TAG, "Too large event %u bytes!", NumBytes); return 0; } if (link_type == ESP_TRACE_LINK_DEBUG_PROBE) { if (esp_cpu_get_core_id()) { // dual core specific code // use the highest - 1 bit of event ID to indicate core ID // the highest bit can not be used due to event ID encoding method // this reduces supported ID range to [0..63] (for 1 byte IDs) plus [128..16383] (for 2 bytes IDs) if (*pbuf & 0x80) { // 2 bytes ID *(pbuf + 1) |= (1 << 6); } else if (NumBytes != 10 || *pbuf != 0) { // ignore sync sequence *pbuf |= (1 << 6); } } if (s_events_buf_filled + NumBytes > SYSVIEW_EVENTS_BUF_SZ) { esp_err_t res = tp->vt->write(tp, s_events_buf, s_events_buf_filled, SEGGER_HOST_WAIT_TMO); if (res != ESP_OK) { return 0; // skip current data buffer only, accumulated events are kept } s_events_buf_filled = 0; } } memcpy(&s_events_buf[s_events_buf_filled], pBuffer, NumBytes); s_events_buf_filled += NumBytes; if (link_type != ESP_TRACE_LINK_DEBUG_PROBE) { esp_err_t res = tp->vt->write(tp, pBuffer, NumBytes, SEGGER_HOST_WAIT_TMO); if (res != ESP_OK) { return 0; // skip current data buffer only, accumulated events are kept } s_events_buf_filled = 0; } if (event_id == SYSVIEW_EVTID_TRACE_STOP) { SEGGER_RTT_ESP_FlushNoLock(); } return NumBytes; } /********************************************************************* * * SEGGER_RTT_ConfigUpBuffer * * Function description * Run-time configuration of a specific up-buffer (T->H). * Buffer to be configured is specified by index. * This includes: Buffer address, size, name, flags, ... * * Parameters * BufferIndex Index of the buffer to configure. * sName Pointer to a constant name string. * pBuffer Pointer to a buffer to be used. * BufferSize Size of the buffer. * Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). * * Return value * >= 0 - O.K. * < 0 - Error * * Additional information * Buffer 0 is configured on compile-time. * May only be called once per buffer. * Buffer name and flags can be reconfigured using the appropriate functions. */ int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags) { s_events_buf_filled = 0; return 0; } /********************************************************************* * * SEGGER_RTT_ConfigDownBuffer * * Function description * Run-time configuration of a specific down-buffer (H->T). * Buffer to be configured is specified by index. * This includes: Buffer address, size, name, flags, ... * * Parameters * BufferIndex Index of the buffer to configure. * sName Pointer to a constant name string. * pBuffer Pointer to a buffer to be used. * BufferSize Size of the buffer. * Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). * * Return value * >= 0 O.K. * < 0 Error * * Additional information * Buffer 0 is configured on compile-time. * May only be called once per buffer. * Buffer name and flags can be reconfigured using the appropriate functions. */ int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char *sName, void *pBuffer, unsigned BufferSize, unsigned Flags) { esp_trace_encoder_t *encoder = SEGGER_SYSVIEW_ESP_GetEncoder(); if (!encoder) { return -1; } return encoder->tp->vt->down_buffer_config(encoder->tp, s_down_buf, sizeof(s_down_buf)); } ================================================ FILE: esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c ================================================ /* * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_log.h" #include "esp_trace_port_encoder.h" #include "esp_trace_port_transport.h" #include "adapter_encoder_sysview.h" static const char *TAG = "sysview-esp"; /** * @brief Encoder reference used by RTT layer * Set by SEGGER_SYSVIEW_ESP_SetEncoder() during encoder init. */ static esp_trace_encoder_t *s_encoder = NULL; /********************************************************************* * * Public code * ********************************************************************** */ /********************************************************************* * * SEGGER_SYSVIEW_ESP_SetEncoder() * * Function description * Inject encoder handle from esp_trace adapter. * This allows SEGGER RTT to access transport through the encoder's * transport reference. * * Parameters * encoder Pointer to encoder instance from esp_trace * * Return value * 0 if successful, -1 if encoder is not initialized or missing required functions in transport. */ int SEGGER_SYSVIEW_ESP_SetEncoder(void *encoder) { esp_trace_encoder_t *enc = encoder; /* Check if adapter has all required functions */ if (!enc || !enc->ctx || !enc->vt->give_lock || !enc->vt->take_lock || !enc->tp->vt->down_buffer_config || !enc->tp->vt->write || !enc->tp->vt->flush_nolock || !enc->tp->vt->read || !enc->tp->vt->get_link_type) { ESP_LOGE(TAG, "Encoder not initialized or missing required functions in transport"); return -1; } s_encoder = enc; return 0; } /********************************************************************* * * SEGGER_SYSVIEW_ESP_GetEncoder() * * Function description * Returns the encoder handle for accessing transport functions. * This is used by SEGGER_SYSVIEW_Config_FreeRTOS.c to access * transport lock functions. * * Parameters * None * * Return value * Pointer to encoder instance, or NULL if not initialized. */ void *SEGGER_SYSVIEW_ESP_GetEncoder(void) { return s_encoder; } /********************************************************************* * * SEGGER_SYSVIEW_ESP_GetDestCpu() * * Function description * Gets the destination CPU from the encoder context. * * Parameters * None * * Return value * CPU ID (0 or 1) to trace */ int SEGGER_SYSVIEW_ESP_GetDestCpu(void) { sysview_encoder_ctx_t *ctx = s_encoder->ctx; return ctx->dest_cpu; } ================================================ FILE: esp_sysview/src/esp/adapter_encoder_sysview.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_err.h" #include "esp_heap_caps.h" #include "esp_trace_types.h" #include "esp_trace_registry.h" #include "esp_trace_port_encoder.h" #include "esp_trace_port_transport.h" #include "adapter_encoder_sysview.h" #include "esp_trace_util.h" #include "SEGGER_SYSVIEW.h" #include "SEGGER_RTT.h" /* * This adapter is used to create a public system-wide APIs for SystemView. * All encoding and transport operations are done by SystemView component. (SEGGER_RTT_esp.c) */ #define SYSVIEW_FLUSH_TMO_US (1000 * 1000) /* 1second */ #define SYSVIEW_FLUSH_THRESH 0 /** * @brief Initializes sysview encoder. * This function is called for each core. * Adapter implementations do NOT need their own multi-core protection. Core does it for them. * * @param enc Pointer to the encoder structure. Must not be NULL. * @param enc_cfg Pointer to the encoder configuration. Can be NULL for defaults. * * @return ESP_OK on success, otherwise \see esp_err_t */ static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) { static bool initialized = false; if (!enc) { return ESP_ERR_INVALID_ARG; } if (initialized) { return ESP_OK; } const esp_trace_sysview_config_t *cfg = enc_cfg; int dest_cpu = 0; // Default to CPU0 if (cfg) { dest_cpu = cfg->dest_cpu; } // Allocate and initialize encoder context sysview_encoder_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (!ctx) { return ESP_ERR_NO_MEM; } /* Segger Sysview needs locking mechanism. */ esp_trace_lock_init(&ctx->lock); ctx->dest_cpu = dest_cpu; enc->ctx = ctx; if (SEGGER_SYSVIEW_ESP_SetEncoder(enc) != 0) { heap_caps_free(ctx); enc->ctx = NULL; return ESP_ERR_NOT_SUPPORTED; } /* Configure transport for SystemView requirements */ if (enc->tp->vt->set_config) { uint32_t flush_tmo = SYSVIEW_FLUSH_TMO_US; uint32_t flush_thresh = SYSVIEW_FLUSH_THRESH; uint32_t header_size = 2; /* SystemView uses 2-byte (16-bit) trace headers */ enc->tp->vt->set_config(enc->tp, ESP_TRACE_TRANSPORT_CFG_FLUSH_TMO, &flush_tmo); enc->tp->vt->set_config(enc->tp, ESP_TRACE_TRANSPORT_CFG_FLUSH_THRESH, &flush_thresh); enc->tp->vt->set_config(enc->tp, ESP_TRACE_TRANSPORT_CFG_HEADER_SIZE, &header_size); } SEGGER_SYSVIEW_Conf(); initialized = true; return ESP_OK; } /** * @brief Panic handler * * Called during system panic to finalize encoder state. * * @param enc Pointer to the encoder structure. Must not be NULL. * @param info Panic information */ static void panic_handler(esp_trace_encoder_t *enc, const void *info) { (void)enc; (void)info; SEGGER_RTT_ESP_Flush(); } /** * @brief Takes the lock of sysview encoder. * * @param enc Pointer to the encoder structure. Must not be NULL. * @param tmo Timeout for the operation (in us). */ static unsigned int take_lock(esp_trace_encoder_t *enc, uint32_t tmo_us) { sysview_encoder_ctx_t *ctx = enc->ctx; esp_trace_lock_take(&ctx->lock, tmo_us); return ctx->lock.int_state; } /** * @brief Gives the lock of sysview encoder. * * @param enc Pointer to the encoder structure. Must not be NULL. * @param int_state The interrupt state. */ static void give_lock(esp_trace_encoder_t *enc, unsigned int_state) { sysview_encoder_ctx_t *ctx = enc->ctx; // Restore interrupt state before releasing lock ctx->lock.int_state = int_state; esp_trace_lock_give(&ctx->lock); } /** * @brief Sysview encoder vtable. */ static const esp_trace_encoder_vtable_t s_sysview_vt = { .init = init, .panic_handler = panic_handler, .take_lock = take_lock, .give_lock = give_lock, }; ESP_TRACE_REGISTER_ENCODER("sysview", &s_sysview_vt); ================================================ FILE: esp_sysview/src/esp/adapter_encoder_sysview.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_trace_util.h" #ifdef __cplusplus extern "C" { #endif /** * @brief SystemView encoder configuration */ typedef struct { /** * @brief CPU to trace (0 or 1) * * Determines which CPU's events are captured by SystemView. * Only relevant when using UART apptrace transport. * * - 0: Capture events from CPU0 (Pro CPU) * - 1: Capture events from CPU1 (App CPU) * * @note This parameter is ignored for single-core systems * @note This parameter is ignored when using JTAG transport */ int dest_cpu; } esp_trace_sysview_config_t; /** * @brief SystemView encoder context structure * * This structure is shared between the sysview adapter and the SEGGER RTT layer * to allow proper access to encoder-specific configuration. */ typedef struct { int dest_cpu; ///< CPU to trace (0 or 1) esp_trace_lock_t lock; ///< Encoder lock } sysview_encoder_ctx_t; #ifdef __cplusplus } #endif ================================================ FILE: esp_sysview/src/ext/heap_trace_module.c ================================================ /* * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "SEGGER_SYSVIEW.h" #include "SEGGER_RTT.h" #include "esp_app_trace.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" const static char *TAG = "sysview_heap_trace"; #ifdef CONFIG_HEAP_TRACING_STACK_DEPTH #define CALLSTACK_SIZE CONFIG_HEAP_TRACING_STACK_DEPTH #else #define CALLSTACK_SIZE 0 #endif static SEGGER_SYSVIEW_MODULE s_esp_sysview_heap_module = { .sModule = "M=ESP32 SystemView Heap Tracing Module", .NumEvents = 2, }; static bool s_mod_registered; esp_err_t esp_sysview_heap_trace_start(uint32_t tmo) { uint32_t tmo_ticks = tmo / (1000 * portTICK_PERIOD_MS); ESP_EARLY_LOGV(TAG, "%s", __func__); do { if (tmo != (uint32_t) -1) { // Currently timeout implementation is simple and has granularity of 1 OS tick, // so just count down the number of times to call vTaskDelay if (tmo_ticks-- == 0) { return ESP_ERR_TIMEOUT; } } vTaskDelay(1); } while (!SEGGER_SYSVIEW_Started()); SEGGER_SYSVIEW_RegisterModule(&s_esp_sysview_heap_module); s_mod_registered = true; return ESP_OK; } esp_err_t esp_sysview_heap_trace_stop(void) { ESP_EARLY_LOGV(TAG, "%s", __func__); SEGGER_RTT_ESP_Flush(); return ESP_OK; } void esp_sysview_heap_trace_alloc(const void *addr, uint32_t size, const void *callers) { U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + (2 + CALLSTACK_SIZE)*SEGGER_SYSVIEW_QUANTA_U32]; U8 *pPayload = SEGGER_SYSVIEW_PREPARE_PACKET(aPacket); U32 *calls = (U32 *)callers; if (!s_mod_registered) { return; } ESP_EARLY_LOGV(TAG, "%s %p %lu", __func__, addr, size); pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)addr); pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, size); for (int i = 0; i < CALLSTACK_SIZE; i++) { pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, calls[i]); } SEGGER_SYSVIEW_SendPacket(&aPacket[0], pPayload, s_esp_sysview_heap_module.EventOffset + 0); } void esp_sysview_heap_trace_free(const void *addr, const void *callers) { U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + (1 + CALLSTACK_SIZE)*SEGGER_SYSVIEW_QUANTA_U32]; U8 *pPayload = SEGGER_SYSVIEW_PREPARE_PACKET(aPacket); U32 *calls = (U32 *)callers; if (!s_mod_registered) { return; } ESP_EARLY_LOGV(TAG, "%s %p", __func__, addr); pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)addr); for (int i = 0; i < CALLSTACK_SIZE; i++) { pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, calls[i]); } SEGGER_SYSVIEW_SendPacket(&aPacket[0], pPayload, s_esp_sysview_heap_module.EventOffset + 1); } esp_err_t esp_sysview_flush(void) { SEGGER_RTT_ESP_Flush(); return ESP_OK; } ================================================ FILE: esp_sysview/src/ext/heap_trace_tohost.c ================================================ /* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "esp_heap_trace.h" #include "esp_heap_caps.h" #include "esp_sysview_heap_trace_module.h" #define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH static bool s_tracing; esp_err_t heap_trace_init_tohost(void) { if (s_tracing) { return ESP_ERR_INVALID_STATE; } return ESP_OK; } esp_err_t heap_trace_start(heap_trace_mode_t mode_param) { esp_err_t ret = esp_sysview_heap_trace_start((uint32_t) -1); if (ret != ESP_OK) { return ret; } s_tracing = true; return ESP_OK; } esp_err_t heap_trace_stop(void) { esp_err_t ret = esp_sysview_heap_trace_stop(); s_tracing = false; return ret; } esp_err_t heap_trace_resume(void) { return heap_trace_start(HEAP_TRACE_ALL); } size_t heap_trace_get_count(void) { return 0; } esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t heap_trace_summary(heap_trace_summary_t *summary) { return ESP_ERR_NOT_SUPPORTED; } void heap_trace_dump(void) { return; } void heap_trace_dump_caps(__attribute__((unused)) const uint32_t caps) { return; } /* Add a new allocation to the heap trace records */ static HEAP_IRAM_ATTR void record_allocation(const heap_trace_record_t *record) { if (!s_tracing) { return; } esp_sysview_heap_trace_alloc(record->address, record->size, record->alloced_by); } /* record a free event in the heap trace log For HEAP_TRACE_ALL, this means filling in the freed_by pointer. For HEAP_TRACE_LEAKS, this means removing the record from the log. */ static HEAP_IRAM_ATTR void record_free(void *p, void **callers) { if (!s_tracing) { return; } esp_sysview_heap_trace_free(p, callers); } #include "heap_trace.inc" ================================================ FILE: esp_sysview/src/ext/logging.c ================================================ /* * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "SEGGER_SYSVIEW_Int.h" #include "freertos/FreeRTOS.h" static portMUX_TYPE s_log_mutex = portMUX_INITIALIZER_UNLOCKED; int esp_sysview_vprintf(const char *format, va_list args) { static char log_buffer[SEGGER_SYSVIEW_MAX_STRING_LEN]; portENTER_CRITICAL(&s_log_mutex); size_t len = vsnprintf(log_buffer, sizeof(log_buffer), format, args); if (len > sizeof(log_buffer) - 1) { log_buffer[sizeof(log_buffer) - 1] = 0; } SEGGER_SYSVIEW_Print(log_buffer); portEXIT_CRITICAL(&s_log_mutex); return len; } ================================================ FILE: esp_sysview/src/include/esp_sysview_heap_trace_module.h ================================================ /* * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include "esp_err.h" /** * @brief Flushes remaining data in SystemView trace buffer to host. * * @return ESP_OK. */ esp_err_t esp_sysview_flush(void); /** * @brief vprintf-like function to sent log messages to the host. * * @param format Address of format string. * @param args List of arguments. * * @return Number of bytes written. */ int esp_sysview_vprintf(const char *format, va_list args); /** * @brief Starts SystemView heap tracing. * * @param tmo Timeout (in us) to wait for the host to be connected. Use -1 to wait forever. * * @return ESP_OK on success, ESP_ERR_TIMEOUT if operation has been timed out. */ esp_err_t esp_sysview_heap_trace_start(uint32_t tmo); /** * @brief Stops SystemView heap tracing. * * @return ESP_OK. */ esp_err_t esp_sysview_heap_trace_stop(void); /** * @brief Sends heap allocation event to the host. * * @param addr Address of allocated block. * @param size Size of allocated block. * @param callers Pointer to array with callstack addresses. * Array size must be CONFIG_HEAP_TRACING_STACK_DEPTH. */ void esp_sysview_heap_trace_alloc(void *addr, uint32_t size, const void *callers); /** * @brief Sends heap de-allocation event to the host. * * @param addr Address of de-allocated block. * @param callers Pointer to array with callstack addresses. * Array size must be CONFIG_HEAP_TRACING_STACK_DEPTH. */ void esp_sysview_heap_trace_free(void *addr, const void *callers); #ifdef __cplusplus } #endif ================================================ FILE: esp_sysview/src/include/esp_trace_freertos_impl.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "SEGGER_SYSVIEW_FreeRTOS.h" #undef INLINE /* to avoid redefinition */ ================================================ FILE: expat/.build-test-rules.yml ================================================ expat/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: expat/CMakeLists.txt ================================================ idf_component_register(SRCS "expat/expat/lib/xmlparse.c" "expat/expat/lib/xmlrole.c" "expat/expat/lib/xmltok.c" "expat/expat/lib/xmltok_impl.c" "expat/expat/lib/xmltok_ns.c" "expat/expat/lib/random_getrandom.c" INCLUDE_DIRS expat/expat/lib port/include) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_EXPAT_CONFIG_H) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_GETRANDOM) # Temporary suppress "fallthrough" warnings until they are fixed in expat repo target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough) ================================================ FILE: expat/idf_component.yml ================================================ version: "2.8.0" description: "Expat - XML Parsing C Library" url: https://github.com/espressif/idf-extra-components/tree/master/expat dependencies: idf: ">=4.3" sbom: manifests: - path: sbom_libexpat.yml dest: expat ================================================ FILE: expat/port/include/expat_config.h ================================================ /* expat_config.h. Generated from expat_config.h.in by configure. */ /* expat_config.h.in. Generated from configure.ac by autoheader. */ #ifndef EXPAT_CONFIG_H #define EXPAT_CONFIG_H 1 /* 1234 = LIL_ENDIAN, 4321 = BIGENDIAN */ #define BYTEORDER 1234 /* Define to 1 if you have the `bcopy' function. */ #define HAVE_BCOPY 1 /* Define to 1 if you have the header file. */ #define HAVE_DLFCN_H 1 /* Define to 1 if you have the header file. */ #define HAVE_FCNTL_H 1 /* Define to 1 if you have the `getpagesize' function. */ #define HAVE_GETPAGESIZE 1 /* Define to 1 if you have the `getrandom' function. */ #define HAVE_GETRANDOM 1 /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if you have the `memmove' function. */ #define HAVE_MEMMOVE 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have a working `mmap' system call. */ #define HAVE_MMAP 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_PARAM_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UNISTD_H 1 /* Define to the sub-directory where libtool stores uninstalled libraries. */ #define LT_OBJDIR ".libs/" /* Name of package */ #define PACKAGE "expat" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "https://github.com/libexpat/libexpat/issues" /* Define to the full name of this package. */ #define PACKAGE_NAME "expat" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "expat 2.8.0" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "expat" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* Define to the version of this package. */ #define PACKAGE_VERSION "2.8.0" /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Version number of package */ #define VERSION "2.8.0" /* whether byteorder is bigendian */ /* #undef WORDS_BIGENDIAN */ /* Define to specify how much context to retain around the current parse point. */ #define XML_CONTEXT_BYTES 1024 /* Define to make parameter entity parsing functionality available. */ #define XML_DTD 1 /* Define as 1/0 to enable/disable support for general entities. */ #define XML_GE 1 /* Define to make XML Namespaces functionality available. */ #define XML_NS 1 /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ /* Define to `long int' if does not define. */ /* #undef off_t */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ #endif // ndef EXPAT_CONFIG_H ================================================ FILE: expat/sbom_libexpat.yml ================================================ name: libexpat version: 2.8.0 cpe: cpe:2.3:a:libexpat_project:libexpat:{}:*:*:*:*:*:*:* supplier: 'Organization: libexpat_project' description: Fast streaming XML parser written in C99 url: https://github.com/libexpat/libexpat/ hash: 6345e05baa634fbd60ace0134228cb6af4cfdaef cve-exclude-list: - cve: CVE-2024-8176 reason: Resolved in version 2.7.0, please see https://github.com/libexpat/libexpat/pull/973 ================================================ FILE: expat/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(expat_test) ================================================ FILE: expat/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "test_expat.c" "test_main.c" PRIV_INCLUDE_DIRS "." PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: expat/test_apps/main/idf_component.yml ================================================ dependencies: espressif/expat: version: "*" override_path: "../.." ================================================ FILE: expat/test_apps/main/test_expat.c ================================================ /* * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "unity.h" typedef struct { int depth; char output[512]; int output_off; } user_data_t; static void insert_space(user_data_t *user_data) { const char align_str[] = " "; TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off); user_data->output[user_data->output_off++] = '\n'; for (int i = 0; i < user_data->depth; i++) { for (int j = 0; j < strlen(align_str); ++j) { TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off); user_data->output[user_data->output_off++] = align_str[j]; } } } static void XMLCALL start_element(void *userData, const XML_Char *name, const XML_Char **atts) { user_data_t *user_data = (user_data_t *) userData; insert_space(user_data); const int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off, "<%s>", name); TEST_ASSERT_EQUAL(strlen(name) + 2, ret); // 2 are the tag characters: "<>" user_data->output_off += ret; ++user_data->depth; } static void XMLCALL end_element(void *userData, const XML_Char *name) { user_data_t *user_data = (user_data_t *) userData; --user_data->depth; insert_space(user_data); int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off, "", name); TEST_ASSERT_EQUAL(strlen(name) + 3, ret); // 3 are the tag characters: "" user_data->output_off += ret; } static void data_handler(void *userData, const XML_Char *s, int len) { user_data_t *user_data = (user_data_t *) userData; insert_space(user_data); // s is not zero-terminated char tmp_str[len + 1]; strlcpy(tmp_str, s, len + 1); int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off, "%s", tmp_str); TEST_ASSERT_EQUAL(strlen(tmp_str), ret); user_data->output_off += ret; } TEST_CASE("Expat parses XML", "[expat]") { const char test_in[] = "Page titleheader
  1. A
  2. "\ "
  3. B
  4. C
"; const char test_expected[] = "\n"\ "\n"\ " \n"\ " Page title\n"\ " \n"\ " \n"\ " \n"\ " header\n"\ " \n"\ "
    \n"\ "
  1. \n"\ " A\n"\ "
  2. \n"\ "
  3. \n"\ " B\n"\ "
  4. \n"\ "
  5. \n"\ " C\n"\ "
  6. \n"\ "
\n"\ " \n"\ ""; user_data_t user_data = { .depth = 0, .output = { '\0' }, .output_off = 0 }; XML_Parser parser = XML_ParserCreate(NULL); XML_SetUserData(parser, &user_data); XML_SetElementHandler(parser, start_element, end_element); XML_SetCharacterDataHandler(parser, data_handler); TEST_ASSERT_NOT_EQUAL(XML_STATUS_ERROR, XML_Parse(parser, test_in, strlen(test_in), 1)); XML_ParserFree(parser); TEST_ASSERT_EQUAL(0, user_data.depth); // all closing tags have been found TEST_ASSERT_EQUAL(strlen(test_expected), strlen(user_data.output)); TEST_ASSERT_EQUAL_STRING(test_expected, user_data.output); } ================================================ FILE: expat/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running expat component tests\n"); unity_run_menu(); } ================================================ FILE: expat/test_apps/pytest_expat.py ================================================ import pytest @pytest.mark.generic def test_expat(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: expat/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: fmt/CMakeLists.txt ================================================ idf_component_register( ) set(FMT_INSTALL OFF) add_subdirectory(fmt) target_link_libraries(${COMPONENT_LIB} INTERFACE fmt::fmt) ================================================ FILE: fmt/README.md ================================================ # FMT [![Component Registry](https://components.espressif.com/components/espressif/fmt/badge.svg)](https://components.espressif.com/components/espressif/fmt) **fmt** is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams. See the project [README](https://github.com/fmtlib/fmt/blob/master/README.md) for details. ================================================ FILE: fmt/examples/hello_fmt/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(hello_fmt) ================================================ FILE: fmt/examples/hello_fmt/README.md ================================================ # `fmt` basic example This is a basic example of using `fmt` library in an ESP-IDF project. It includes `fmt` and prints `Hello, fmt!` on the console. The example runs on any ESP development board. To build and run the example, follow the same steps as for any other ESP-IDF project. For example, for ESP32-C3: ```bash idf.py set-target esp32c3 idf.py flash monitor ``` The example should print the following: ``` I (6074) main_task: Calling app_main() Hello, fmt! I (6084) main_task: Returned from app_main() ``` ================================================ FILE: fmt/examples/hello_fmt/main/CMakeLists.txt ================================================ idf_component_register(SRCS "hello_fmt.cpp" INCLUDE_DIRS ".") ================================================ FILE: fmt/examples/hello_fmt/main/hello_fmt.cpp ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include extern "C" void app_main() { fmt::print("Hello, fmt!\n"); } ================================================ FILE: fmt/examples/hello_fmt/main/idf_component.yml ================================================ dependencies: fmt: version: "*" override_path: "../../../" ================================================ FILE: fmt/examples/hello_fmt/pytest_fmt.py ================================================ # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut @pytest.mark.generic def test_fmt_example(dut: Dut) -> None: dut.expect_exact('Hello, fmt!') ================================================ FILE: fmt/idf_component.yml ================================================ version: "11.0.1" description: Formatting library providing a fast and safe alternative to C stdio and C++ iostreams. url: https://github.com/espressif/idf-extra-components/tree/master/fmt dependencies: idf: ">=4.1" sbom: manifests: - path: sbom_fmt.yml dest: fmt ================================================ FILE: fmt/sbom_fmt.yml ================================================ name: fmt version: 11.0.1 cpe: cpe:2.3:a:fmt:fmt:{}:*:*:*:*:*:*:* supplier: 'Organization: fmt ' description: A modern formatting library url: https://github.com/fmtlib/fmt/ hash: b50e685db996c167e6c831dcef582aba6e14276a ================================================ FILE: freetype/CMakeLists.txt ================================================ idf_component_register() # Override options defined in freetype/CMakeLists.txt. # We could have used normal set(...) here if freetype enabled CMake policy CMP0077. option(FT_DISABLE_HARFBUZZ "" ON) option(FT_DISABLE_BZIP2 "" ON) option(FT_DISABLE_BROTLI "" ON) option(FT_DISABLE_PNG "" ON) option(FT_DISABLE_ZLIB "" ON) # These are regular CMake variables, so we can set them directly. set(SKIP_INSTALL_ALL TRUE) set(BUILD_SHARED_LIBS OFF) add_subdirectory(freetype output) # https://gitlab.freedesktop.org/freetype/freetype/-/issues/1299 target_compile_options(freetype PRIVATE "-Wno-dangling-pointer") target_link_libraries(${COMPONENT_LIB} INTERFACE freetype) ================================================ FILE: freetype/LICENSE ================================================ FREETYPE LICENSES ----------------- The FreeType 2 font engine is copyrighted work and cannot be used legally without a software license. In order to make this project usable to a vast majority of developers, we distribute it under two mutually exclusive open-source licenses. This means that *you* must choose *one* of the two licenses described below, then obey all its terms and conditions when using FreeType 2 in any of your projects or products. - The FreeType License, found in the file `docs/FTL.TXT`, which is similar to the original BSD license *with* an advertising clause that forces you to explicitly cite the FreeType project in your product's documentation. All details are in the license file. This license is suited to products which don't use the GNU General Public License. Note that this license is compatible to the GNU General Public License version 3, but not version 2. - The GNU General Public License version 2, found in `docs/GPLv2.TXT` (any later version can be used also), for programs which already use the GPL. Note that the FTL is incompatible with GPLv2 due to its advertisement clause. The contributed BDF and PCF drivers come with a license similar to that of the X Window System. It is compatible to the above two licenses (see files `src/bdf/README` and `src/pcf/README`). The same holds for the source code files `src/base/fthash.c` and `include/freetype/internal/fthash.h`; they were part of the BDF driver in earlier FreeType versions. The gzip module uses the zlib license (see `src/gzip/zlib.h`) which too is compatible to the above two licenses. The files `src/autofit/ft-hb.c` and `src/autofit/ft-hb.h` contain code taken almost verbatim from the HarfBuzz file `hb-ft.cc`, which uses the 'Old MIT' license, compatible to the above two licenses. The MD5 checksum support (only used for debugging in development builds) is in the public domain. --- end of LICENSE.TXT --- ================================================ FILE: freetype/README.md ================================================ # freetype compression and decompression Library [![Component Registry](https://components.espressif.com/components/espressif/freetype/badge.svg)](https://components.espressif.com/components/espressif/freetype) This is an IDF component for freetype library. For usage instructions, please refer to the official documentation: https://freetype.org/freetype2/docs/documentation.html ================================================ FILE: freetype/examples/freetype-example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.17) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(freetype-example) ================================================ FILE: freetype/examples/freetype-example/README.md ================================================ # FreeType Example This is a simple example of initializing FreeType library, loading a font from a filesystem, and rendering a line of text. The font file (DejaVu Sans) is downloaded at compile time and is added into a SPIFFS filesystem image. The filesystem is flashed to the board together with the application. The example loads the font file and renders "FreeType" text into the console as ASCII art. This example doesn't require any special hardware and can run on any development board. ## Building and running Run the application as usual for an ESP-IDF project. For example, for ESP32: ``` idf.py set-target esp32 idf.py -p PORT flash monitor ``` ## Example output The example should output the following: ``` I (468) main_task: Calling app_main() I (538) example: FreeType library initialized I (1258) example: Font loaded I (1268) example: Rendering char: 'F' I (1388) example: Rendering char: 'r' I (1528) example: Rendering char: 'e' I (1658) example: Rendering char: 'e' I (1798) example: Rendering char: 'T' I (1938) example: Rendering char: 'y' I (2078) example: Rendering char: 'p' I (2208) example: Rendering char: 'e' ######. ######### ## +# ## ##### +###+ +###+ +# +# ######## +###+ ## ##+ +#. .#+ +#. .#+ +# #+ #+##+ .#+ +#. .#+ ###### ## #+ +# #+ +# +# ## +# ## +# #+ +# ## ## .####### .####### +# .# ## ## .# .####### ## ## .#. .#. +# #+.#. ## .# .#. ## ## #+ #+ +# +### ## +# #+ ## ## ##+ ++ ##+ ++ +# ##+ ##+ .#+ ##+ ++ ## ## +#### +#### +# ## ###### +#### ## ## +#. ## ##+ ## ``` ================================================ FILE: freetype/examples/freetype-example/main/CMakeLists.txt ================================================ idf_component_register(SRCS "freetype-example.c" INCLUDE_DIRS "." PRIV_REQUIRES spiffs) # Download the example font into a directory "spiffs" in the build directory set(URL "https://github.com/espressif/esp-docs/raw/f036a337d8bee5d1a93b2264ecd29255baec4260/src/esp_docs/fonts/DejaVuSans.ttf") set(FILE "DejaVuSans.ttf") set(SPIFFS_DIR "${CMAKE_BINARY_DIR}/spiffs") file(MAKE_DIRECTORY ${SPIFFS_DIR}) file(DOWNLOAD ${URL} ${SPIFFS_DIR}/${FILE} SHOW_PROGRESS) # Create a partition named "fonts" with the example font spiffs_create_partition_image(fonts ${SPIFFS_DIR} FLASH_IN_PROJECT) ================================================ FILE: freetype/examples/freetype-example/main/freetype-example.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "esp_log.h" #include "esp_err.h" #include "esp_spiffs.h" #include "ft2build.h" #include FT_FREETYPE_H static const char *TAG = "example"; static void init_filesystem(void); static void init_freetype(void); static void load_font(void); static void render_text(void); #define BITMAP_WIDTH 80 #define BITMAP_HEIGHT 18 static FT_Library s_library; static FT_Face s_face; static uint8_t s_bitmap[BITMAP_HEIGHT][BITMAP_WIDTH]; void app_main(void) { init_filesystem(); init_freetype(); load_font(); render_text(); } static void init_filesystem(void) { esp_vfs_spiffs_conf_t conf = { .base_path = "/fonts", .partition_label = "fonts", .max_files = 1, }; ESP_ERROR_CHECK(esp_vfs_spiffs_register(&conf)); } static void init_freetype(void) { FT_Error error = FT_Init_FreeType( &s_library ); if (error) { ESP_LOGE(TAG, "Error initializing FreeType library: %d", error); abort(); } ESP_LOGI(TAG, "FreeType library initialized"); } static void load_font(void) { FT_Error error = FT_New_Face( s_library, "/fonts/DejaVuSans.ttf", 0, &s_face ); if (error) { ESP_LOGE(TAG, "Error loading font: %d", error); abort(); } ESP_LOGI(TAG, "Font loaded"); } static void render_text(void) { /* Configure character size. */ const int font_size = 14; const int freetype_scale = 64; FT_Error error = FT_Set_Char_Size(s_face, 0, font_size * freetype_scale, 0, 0 ); if (error) { ESP_LOGE(TAG, "Error setting font size: %d", error); abort(); } const char *text = "FreeType"; int num_chars = strlen(text); /* current drawing position */ int x = 0; int y = 12; for (int n = 0; n < num_chars; n++) { ESP_LOGI(TAG, "Rendering char: '%c'", text[n]); /* retrieve glyph index from character code */ FT_UInt glyph_index = FT_Get_Char_Index( s_face, text[n] ); /* load glyph image into the slot (erase previous one) */ error = FT_Load_Glyph( s_face, glyph_index, FT_LOAD_DEFAULT ); if (error) { ESP_LOGE(TAG, "Error loading glyph: %d", error); abort(); } /* convert to a bitmap */ error = FT_Render_Glyph( s_face->glyph, FT_RENDER_MODE_NORMAL ); if (error) { ESP_LOGE(TAG, "Error rendering glyph: %d", error); abort(); } /* copy the glyph bitmap into the overall bitmap */ FT_GlyphSlot slot = s_face->glyph; for (int iy = 0; iy < slot->bitmap.rows; iy++) { for (int ix = 0; ix < slot->bitmap.width; ix++) { /* bounds check */ int res_x = ix + x; int res_y = y + iy - slot->bitmap_top; if (res_x >= BITMAP_WIDTH || res_y >= BITMAP_HEIGHT) { continue; } s_bitmap[res_y][res_x] = slot->bitmap.buffer[ix + iy * slot->bitmap.width]; } } /* increment horizontal position */ x += slot->advance.x / 64; if (x >= BITMAP_WIDTH) { break; } } /* output the resulting bitmap to console */ for (int iy = 0; iy < BITMAP_HEIGHT; iy++) { for (int ix = 0; ix < x; ix++) { int val = s_bitmap[iy][ix]; if (val > 127) { putchar('#'); } else if (val > 64) { putchar('+'); } else if (val > 32) { putchar('.'); } else { putchar(' '); } } putchar('\n'); } } ================================================ FILE: freetype/examples/freetype-example/main/idf_component.yml ================================================ dependencies: espressif/freetype: version: "*" override_path: "../../.." ================================================ FILE: freetype/examples/freetype-example/partitions.csv ================================================ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, fonts, data, spiffs, , 0xF0000, ================================================ FILE: freetype/examples/freetype-example/pytest_freetype.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 from __future__ import unicode_literals import textwrap import pytest from pytest_embedded import Dut @pytest.mark.generic def test_freetype_example(dut: Dut) -> None: dut.expect_exact('FreeType library initialized') dut.expect_exact('Font loaded') for c in 'FreeType': dut.expect_exact(f'Rendering char: \'{c}\'') ================================================ FILE: freetype/examples/freetype-example/sdkconfig.defaults ================================================ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000 CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y ================================================ FILE: freetype/idf_component.yml ================================================ version: "2.14.2" description: freetype C library url: https://github.com/espressif/idf-extra-components/tree/master/freetype repository: "https://github.com/espressif/idf-extra-components.git" documentation: "https://freetype.org/freetype2/docs/documentation.html" issues: "https://github.com/espressif/idf-extra-components/issues" # URL of the issue tracker dependencies: idf: ">=4.4" sbom: manifests: - path: sbom_freetype.yml dest: freetype ================================================ FILE: freetype/sbom_freetype.yml ================================================ name: freetype version: 2.14.2 cpe: cpe:2.3:a:freetype:freetype:{}:*:*:*:*:*:*:* supplier: 'Organization: freetype ' description: FreeType is a software font engine url: https://github.com/freetype/freetype hash: f4205da14867c5387cd6a329b90ee10a6df6eeff cve-exclude-list: - cve: CVE-2026-23865 reason: Fixed in version 2.14.2 with commit https://github.com/freetype/freetype/commit/fc85a255849229c024c8e65f536fe1875d84841c ================================================ FILE: iqmath/.build-test-rules.yml ================================================ ================================================ FILE: iqmath/CHANGELOG.md ================================================ ## 1.11.0 - Initial port of the IQMath Library, obtained from TI MSPM0 SDK ================================================ FILE: iqmath/CMakeLists.txt ================================================ set(srcs "_IQNfunctions/_atoIQN.c" "_IQNfunctions/_IQNasin_acos.c" "_IQNfunctions/_IQNatan2.c" "_IQNfunctions/_IQNdiv.c" "_IQNfunctions/_IQNexp.c" "_IQNfunctions/_IQNfrac.c" "_IQNfunctions/_IQNlog.c" "_IQNfunctions/_IQNmpy.c" "_IQNfunctions/_IQNmpyIQX.c" "_IQNfunctions/_IQNrepeat.c" "_IQNfunctions/_IQNrmpy.c" "_IQNfunctions/_IQNrsmpy.c" "_IQNfunctions/_IQNsin_cos.c" "_IQNfunctions/_IQNsqrt.c" "_IQNfunctions/_IQNtables.c" "_IQNfunctions/_IQNtoa.c" "_IQNfunctions/_IQNtoF.c" "_IQNfunctions/_IQNversion.c") idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include") ================================================ FILE: iqmath/LICENSE ================================================ Copyright (c) 2023, Texas Instruments Incorporated All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Texas Instruments Incorporated nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: iqmath/README.md ================================================ # IQMath Library [![Component Registry](https://components.espressif.com/components/espressif/iqmath/badge.svg)](https://components.espressif.com/components/espressif/iqmath) The IQmath Library is a collection of highly optimized and high-precision mathematical functions for C programmers to seamlessly port a floating-point algorithm into fixed-point code on ESP chips. These routines are typically used in computationally intensive real-time applications where optimal execution speed, high accuracy and ultra-low energy are critical. By using the IQmath library, it is possible to achieve execution speeds considerably faster and energy consumption considerably lower than equivalent code written using floating-point math. The IQmath library provides functions for use with 32-bit data types and high accuracy. ## IQmath Data Types The IQmath library uses a 32-bit fixed-point signed number (i.e. `int32_t`) as its basic data type. The IQ format of this fixed-point number can range from `IQ1` to `IQ30`, where the IQ format number indicates the number of fractional bits. The IQ format value is stored as an integer with an implied scale based on the IQ format and the number of fractional bits. The equation below shows how a IQ format decimal number $x_{iq}$ is stored using an integer value $x_i$ with an implied scale, where $n$ represents the number of fractional bits. $$ IQ_n(x_{iq}) = x_i * 2^{-n} $$ For example the IQ24 value of 3.625 is stored as an integer value of 60817408, shown in the equation below. $$ IQ_{24}(3.625) = 60817408 * 2^{-24} $$ C typedefs are provided for the various IQ formats, and these IQmath data types should be used in preference to the underlying “int32_t” data type to make it clear which variables are in IQ format. The following table provides the characteristics of the various IQ formats: | Type | Integer Bits | Fractional Bits | Min Range | Max Range | Resolution | |-------|--------------|-----------------|----------------|---------------------------|---------------| | _iq30 | 2 | 30 | -2 | 1.999 999 999 | 0.000 000 001 | | _iq29 | 3 | 29 | -4 | 3.999 999 998 | 0.000 000 002 | | _iq28 | 4 | 28 | -8 | 7.999 999 996 | 0.000 000 004 | | _iq27 | 5 | 27 | -16 | 15.999 999 993 | 0.000 000 007 | | _iq26 | 6 | 26 | -32 | 31.999 999 985 | 0.000 000 015 | | _iq25 | 7 | 25 | -64 | 63.999 999 970 | 0.000 000 030 | | _iq24 | 8 | 24 | -128 | 127.999 999 940 | 0.000 000 060 | | _iq23 | 9 | 23 | -256 | 255.999 999 881 | 0.000 000 119 | | _iq22 | 10 | 22 | -512 | 511.999 999 762 | 0.000 000 238 | | _iq21 | 11 | 21 | -1,024 | 1,023.999 999 523 | 0.000 000 477 | | _iq20 | 12 | 20 | -2,048 | 2,047.999 999 046 | 0.000 000 954 | | _iq19 | 13 | 19 | -4,096 | 4,095.999 998 093 | 0.000 001 907 | | _iq18 | 14 | 18 | -8,192 | 8,191.999 996 185 | 0.000 003 815 | | _iq17 | 15 | 17 | -16,384 | 16,383.999 992 371 | 0.000 007 629 | | _iq16 | 16 | 16 | -32,768 | 32,767.999 984 741 | 0.000 015 259 | | _iq15 | 17 | 15 | -65,536 | 65,535.999 969 483 | 0.000 030 518 | | _iq14 | 18 | 14 | -131,072 | 131,071.999 938 965 | 0.000 061 035 | | _iq13 | 19 | 13 | -262,144 | 262,143.999 877 930 | 0.000 122 070 | | _iq12 | 20 | 12 | -524,288 | 524,287.999 755 859 | 0.000 244 141 | | _iq11 | 21 | 11 | -1,048,576 | 1,048,575.999 511 720 | 0.000 488 281 | | _iq10 | 22 | 10 | -2,097,152 | 2,097,151.999 023 440 | 0.000 976 563 | | _iq9 | 23 | 9 | -4,194,304 | 4,194,303.998 046 880 | 0.001 953 125 | | _iq8 | 24 | 8 | -8,388,608 | 8,388,607.996 093 750 | 0.003 906 250 | | _iq7 | 25 | 7 | -16,777,216 | 16,777,215.992 187 500 | 0.007 812 500 | | _iq6 | 26 | 6 | -33,554,432 | 33,554,431.984 375 000 | 0.015 625 000 | | _iq5 | 27 | 5 | -67,108,864 | 67,108,863.968 750 000 | 0.031 250 000 | | _iq4 | 28 | 4 | -134,217,728 | 134,217,727.937 500 000 | 0.062 500 000 | | _iq3 | 29 | 3 | -268,435,456 | 268,435,455.875 000 000 | 0.125 000 000 | | _iq2 | 30 | 2 | -536,870,912 | 536,870,911.750 000 000 | 0.250 000 000 | | _iq1 | 31 | 1 | -1,073,741,824 | 1,073,741,823.500 000 000 | 0.500 000 000 | ## API Guide IQmath includes five types of routines: * **Format conversion functions**: methods to convert numbers to and from the various formats. * **Arithmetic functions**: methods to perform basic arithmetic (addition, subtraction, multiplication, division). * **Trigonometric functions**: methods to perform trigonometric functions (sin, cos, atan, and so on). * **Mathematical functions**: methods to perform advanced arithmetic (square root, ex , and so on). * **Miscellaneous**: miscellaneous methods (saturation and absolute value). ================================================ FILE: iqmath/_IQNfunctions/_IQNasin_acos.c ================================================ /*!**************************************************************************** * @file _IQNasin_acos.c * @brief Functions to compute the inverse sine of the input * and return the result, in radians. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" /* Hidden Q31 sqrt function. */ #ifndef DOXYGEN_SHOULD_SKIP_THIS /** * @brief Computes the square root of the IQ31 input. * * @param iq31Input IQ31 type input. * * @return IQ31 type result of square root. */ extern int_fast32_t _IQ31sqrt(int_fast32_t iq31Input); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the inverse sine of the IQN input. * * @param iqNInput IQN type input. * @param q_value IQ format. * * @return IQN type result of inverse sine. */ /* * Calculate asin using a 4th order Taylor series for inputs in the range of * zero to 0.5. The coefficients are stored in a lookup table with 17 ranges * to give an accuracy of 26 bits. * * For inputs greater than 0.5 we apply the following transformation: * * asin(x) = PI/2 - 2*asin(sqrt((1 - x)/2)) * * This transformation is derived from the following trig identities: * * (1) asin(x) = PI/2 - acos(x) * (2) sin(t/2)^2 = (1 - cos(t))/2 * (3) cos(t) = x * (4) t = acos(x) * * Identity (2) can be simplified to give equation (5): * * (5) t = 2*asin(sqrt((1 - cos(t))/2)) * * Substituting identities (3) and (4) into equation (5) gives equation (6): * * (6) acos(x) = 2*asin(sqrt((1 - x)/2)) * * The final step is substituting equation (6) into identity (1): * * asin(x) = PI/2 - 2*asin(sqrt((1 - x)/2)) * * Acos is implemented using asin and identity (1). */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNasin) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNasin(int_fast32_t iqNInput, const int8_t q_value) { uint8_t ui8Status = 0; uint_fast16_t index; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; int_fast32_t iq29Result; const int_fast32_t *piq29Coeffs; uint_fast32_t uiq31Input; uint_fast32_t uiq31InputTemp; /* * Extract the sign from the input and set the following status bits: * * ui8Status = xxxxxxTS * x = unused * T = transform was applied * S = sign bit needs to be set (-y) */ if (iqNInput < 0) { ui8Status |= 1; iqNInput = -iqNInput; } /* * Check if input is within the valid input range: * * 0 <= iqNInput <= 1 */ if (iqNInput > ((uint_fast32_t)1 << q_value)) { return 0; } /* Convert input to unsigned iq31. */ uiq31Input = (uint_fast32_t)iqNInput << (31 - q_value); /* * Apply the transformation from asin to acos if input is greater than 0.5. * The first step is to calculate the following: * * (sqrt((1 - uiq31Input)/2)) */ uiq31InputTemp = 0x80000000 - uiq31Input; if (uiq31InputTemp < 0x40000000) { /* Halve the result. */ uiq31Input = uiq31InputTemp >> 1; /* Calculate sqrt((1 - uiq31Input)/2) */ uiq31Input = _IQ31sqrt(uiq31Input); /* Flag that the transformation was used. */ ui8Status |= 2; } /* Calculate the index using the left 6 most bits of the input. */ index = (int_fast16_t)(uiq31Input >> 26) & 0x003f; /* Set the coefficient pointer. */ piq29Coeffs = _IQ29Asin_coeffs[index]; /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* * Calculate asin(x) using the following Taylor series: * * asin(x) = (((c4*x + c3)*x + c2)*x + c1)*x + c0 */ /* c4*x */ iq29Result = __mpyf_l(uiq31Input, *piq29Coeffs++); /* c4*x + c3 */ iq29Result = iq29Result + *piq29Coeffs++; /* (c4*x + c3)*x */ iq29Result = __mpyf_l(uiq31Input, iq29Result); /* (c4*x + c3)*x + c2 */ iq29Result = iq29Result + *piq29Coeffs++; /* ((c4*x + c3)*x + c2)*x */ iq29Result = __mpyf_l(uiq31Input, iq29Result); /* ((c4*x + c3)*x + c2)*x + c1 */ iq29Result = iq29Result + *piq29Coeffs++; /* (((c4*x + c3)*x + c2)*x + c1)*x */ iq29Result = __mpyf_l(uiq31Input, iq29Result); /* (((c4*x + c3)*x + c2)*x + c1)*x + c0 */ iq29Result = iq29Result + *piq29Coeffs++; /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); /* check if we switched to acos */ if (ui8Status & 2) { /* asin(x) = pi/2 - 2*iq29Result */ iq29Result = iq29Result << 1; iq29Result -= iq29_halfPi; // this is equivalent to the above iq29Result = -iq29Result; // but avoids using temporary registers } /* Shift iq29 result to specified q_value. */ iq29Result >>= 29 - q_value; /* Add sign to the result. */ if (ui8Status & 1) { iq29Result = -iq29Result; } return iq29Result; } /* ASIN */ /** * @brief Computes the inverse sine of the IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of inverse sine. */ int32_t _IQ29asin(int32_t a) { return __IQNasin(a, 29); } /** * @brief Computes the inverse sine of the IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of inverse sine. */ int32_t _IQ28asin(int32_t a) { return __IQNasin(a, 28); } /** * @brief Computes the inverse sine of the IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of inverse sine. */ int32_t _IQ27asin(int32_t a) { return __IQNasin(a, 27); } /** * @brief Computes the inverse sine of the IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of inverse sine. */ int32_t _IQ26asin(int32_t a) { return __IQNasin(a, 26); } /** * @brief Computes the inverse sine of the IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of inverse sine. */ int32_t _IQ25asin(int32_t a) { return __IQNasin(a, 25); } /** * @brief Computes the inverse sine of the IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of inverse sine. */ int32_t _IQ24asin(int32_t a) { return __IQNasin(a, 24); } /** * @brief Computes the inverse sine of the IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of inverse sine. */ int32_t _IQ23asin(int32_t a) { return __IQNasin(a, 23); } /** * @brief Computes the inverse sine of the IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of inverse sine. */ int32_t _IQ22asin(int32_t a) { return __IQNasin(a, 22); } /** * @brief Computes the inverse sine of the IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of inverse sine. */ int32_t _IQ21asin(int32_t a) { return __IQNasin(a, 21); } /** * @brief Computes the inverse sine of the IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of inverse sine. */ int32_t _IQ20asin(int32_t a) { return __IQNasin(a, 20); } /** * @brief Computes the inverse sine of the IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of inverse sine. */ int32_t _IQ19asin(int32_t a) { return __IQNasin(a, 19); } /** * @brief Computes the inverse sine of the IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of inverse sine. */ int32_t _IQ18asin(int32_t a) { return __IQNasin(a, 18); } /** * @brief Computes the inverse sine of the IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of inverse sine. */ int32_t _IQ17asin(int32_t a) { return __IQNasin(a, 17); } /** * @brief Computes the inverse sine of the IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of inverse sine. */ int32_t _IQ16asin(int32_t a) { return __IQNasin(a, 16); } /** * @brief Computes the inverse sine of the IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of inverse sine. */ int32_t _IQ15asin(int32_t a) { return __IQNasin(a, 15); } /** * @brief Computes the inverse sine of the IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of inverse sine. */ int32_t _IQ14asin(int32_t a) { return __IQNasin(a, 14); } /** * @brief Computes the inverse sine of the IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of inverse sine. */ int32_t _IQ13asin(int32_t a) { return __IQNasin(a, 13); } /** * @brief Computes the inverse sine of the IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of inverse sine. */ int32_t _IQ12asin(int32_t a) { return __IQNasin(a, 12); } /** * @brief Computes the inverse sine of the IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of inverse sine. */ int32_t _IQ11asin(int32_t a) { return __IQNasin(a, 11); } /** * @brief Computes the inverse sine of the IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of inverse sine. */ int32_t _IQ10asin(int32_t a) { return __IQNasin(a, 10); } /** * @brief Computes the inverse sine of the IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of inverse sine. */ int32_t _IQ9asin(int32_t a) { return __IQNasin(a, 9); } /** * @brief Computes the inverse sine of the IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of inverse sine. */ int32_t _IQ8asin(int32_t a) { return __IQNasin(a, 8); } /** * @brief Computes the inverse sine of the IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of inverse sine. */ int32_t _IQ7asin(int32_t a) { return __IQNasin(a, 7); } /** * @brief Computes the inverse sine of the IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of inverse sine. */ int32_t _IQ6asin(int32_t a) { return __IQNasin(a, 6); } /** * @brief Computes the inverse sine of the IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of inverse sine. */ int32_t _IQ5asin(int32_t a) { return __IQNasin(a, 5); } /** * @brief Computes the inverse sine of the IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of inverse sine. */ int32_t _IQ4asin(int32_t a) { return __IQNasin(a, 4); } /** * @brief Computes the inverse sine of the IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of inverse sine. */ int32_t _IQ3asin(int32_t a) { return __IQNasin(a, 3); } /** * @brief Computes the inverse sine of the IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of inverse sine. */ int32_t _IQ2asin(int32_t a) { return __IQNasin(a, 2); } /** * @brief Computes the inverse sine of the IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of inverse sine. */ int32_t _IQ1asin(int32_t a) { return __IQNasin(a, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNatan2.c ================================================ /*!**************************************************************************** * @file _IQNatan2.c * @brief Functions to compute the 4-quadrant arctangent of the input * and return the result. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" #include "../include/IQmathLib.h" #include "_IQNmpy.h" #include "_IQNdiv.h" /*! * @brief The value of PI */ #define PI (3.1415926536) /*! * @brief Used to specify per-unit result */ #define TYPE_PU (0) /*! * @brief Used to specify result in radians */ #define TYPE_RAD (1) #if ((!defined (__IQMATH_USE_MATHACL__)) || (!defined (__MSPM0_HAS_MATHACL__))) #ifndef DOXYGEN_SHOULD_SKIP_THIS /* Hidden _UIQ31div function. */ /** * @brief Computes the division of the IQ31 inputs. * * @param uiq31Input1 IQ31 type value numerator to be divided. * @param uiq31Input2 IQ31 type value denominator to divide by. * * @return IQ31 type result of division. */ extern uint_fast32_t _UIQ31div(uint_fast32_t uiq31Input1, uint_fast32_t uiq31Input2); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Compute the 4-quadrant arctangent of the IQN input * and return the result. * * @param iqNInputY IQN type input y. * @param iqNInputX IQN type input x. * @param type Specifies radians or per-unit operation. * @param q_value IQ format. * * @return IQN type result of 4-quadrant arctangent. */ /* * Calculate atan2 using a 3rd order Taylor series. The coefficients are stored * in a lookup table with 17 ranges to give an accuracy of XX bits. * * The input to the Taylor series is the ratio of the two inputs and must be * in the range of 0 <= input <= 1. If the y argument is larger than the x * argument we must apply the following transformation: * * atan(y/x) = pi/2 - atan(x/y) */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNatan2) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNatan2(int_fast32_t iqNInputY, int_fast32_t iqNInputX, const uint8_t type, const int8_t q_value) { uint8_t ui8Status = 0; uint8_t ui8Index; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; uint_fast32_t uiqNInputX; uint_fast32_t uiqNInputY; uint_fast32_t uiq32ResultPU; int_fast32_t iqNResult; int_fast32_t iq29Result; const int_fast32_t *piq32Coeffs; uint_fast32_t uiq31Input; /* * Extract the sign from the inputs and set the following status bits: * * ui8Status = xxxxxTQS * x = unused * T = transform was applied * Q = 2nd or 3rd quadrant (-x) * S = sign bit needs to be set (-y) */ if (iqNInputY < 0) { ui8Status |= 1; iqNInputY = -iqNInputY; } if (iqNInputX < 0) { ui8Status |= 2; iqNInputX = -iqNInputX; } /* Save inputs to unsigned iqN formats. */ uiqNInputX = (uint_fast32_t)iqNInputX; uiqNInputY = (uint_fast32_t)iqNInputY; /* * Calculate the ratio of the inputs in iq31. When using the iq31 div * functions with inputs of matching type the result will be iq31: * * iq31 = _IQ31div(iqN, iqN); */ if (uiqNInputX < uiqNInputY) { ui8Status |= 4; uiq31Input = _UIQ31div(uiqNInputX, uiqNInputY); } else { uiq31Input = _UIQ31div(uiqNInputY, uiqNInputX); } /* Calculate the index using the left 8 most bits of the input. */ ui8Index = (uint_fast16_t)(uiq31Input >> 24); ui8Index = ui8Index & 0x00fc; /* Set the coefficient pointer. */ piq32Coeffs = &_IQ32atan_coeffs[ui8Index]; /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* * Calculate atan(x) using the following Taylor series: * * atan(x) = ((c3*x + c2)*x + c1)*x + c0 */ /* c3*x */ uiq32ResultPU = __mpyf_l(uiq31Input, *piq32Coeffs++); /* c3*x + c2 */ uiq32ResultPU = uiq32ResultPU + *piq32Coeffs++; /* (c3*x + c2)*x */ uiq32ResultPU = __mpyf_l(uiq31Input, uiq32ResultPU); /* (c3*x + c2)*x + c1 */ uiq32ResultPU = uiq32ResultPU + *piq32Coeffs++; /* ((c3*x + c2)*x + c1)*x */ uiq32ResultPU = __mpyf_l(uiq31Input, uiq32ResultPU); /* ((c3*x + c2)*x + c1)*x + c0 */ uiq32ResultPU = uiq32ResultPU + *piq32Coeffs++; /* Check if we applied the transformation. */ if (ui8Status & 4) { /* atan(y/x) = pi/2 - uiq32ResultPU */ uiq32ResultPU = (uint32_t)(0x40000000 - uiq32ResultPU); } /* Check if the result needs to be mirrored to the 2nd/3rd quadrants. */ if (ui8Status & 2) { /* atan(y/x) = pi - uiq32ResultPU */ uiq32ResultPU = (uint32_t)(0x80000000 - uiq32ResultPU); } /* Round and convert result to correct format (radians/PU and iqN type). */ if (type == TYPE_PU) { uiq32ResultPU += (uint_fast32_t)1 << (31 - q_value); iqNResult = uiq32ResultPU >> (32 - q_value); } else { /* * Multiply the per-unit result by 2*pi: * * iq31mpy(iq32, iq28) = iq29 */ iq29Result = __mpyf_l(uiq32ResultPU, iq28_twoPi); /* Only round IQ formats < 29 */ if (q_value < 29) { iq29Result += (uint_fast32_t)1 << (28 - q_value); } iqNResult = iq29Result >> (29 - q_value); } /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); /* Set the sign bit and result to correct quadrant. */ if (ui8Status & 1) { return -iqNResult; } else { return iqNResult; } } #else /** * @brief Compute the 4-quadrant arctangent of the IQN input * and return the result, using MathACL. * * @param iqNInputY IQN type input y. * @param iqNInputX IQN type input x. * @param type Specifies radians or per-unit operation. * @param q_value IQ format. * * @return IQN type result of 4-quadrant arctangent. */ /* Calculate atan2 using MATHACL */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNatan2) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNatan2(int_fast32_t iqNInputY, int_fast32_t iqNInputX, const uint8_t type, const int8_t q_value) { int_fast32_t res, res1, abs_max, temp; int_fast32_t iqNnormX, iqNnormY, iq31normX, iq31normY; /* ATAN2 Operation with MatchACL requires X,Y input values to be IQ31. * Therefore, we need to normalize the input values. */ /* Normalize input values by using the largest abs max input value */ /* Code for _IQXabs is the same for all Q values */ if (_IQabs(iqNInputY) > _IQabs(iqNInputX)) { abs_max = _IQabs(iqNInputY); } else { abs_max = _IQabs(iqNInputX); } /* Both inputs are 0 */ if (abs_max == 0) { return 0; } /* IQ31 doesn't support 1.0. Therefore, we need to ensure the normalized * values are not equal to 1 but rather slightly below 1.0. */ /* Check to see if abs_max is equal to the max value * represented by the specified Q value (0x7FFFFFFFF) being represented. * This will determine which approach is taken to ensure abs_max is > than * the abs value of either inputs when normalization is performed. */ if (abs_max == 0x7FFFFFFF) { /* We want to represent as close as .999999 that we can for a given * q value. Therefore we want to go to the number slightly lower than * 1 for the given q value. */ temp = (1 << q_value) - 1; /* Scale down the inputs slightly so we ensure that abs_max is slightly * larger than the abs value of the inputs. */ iqNInputX = __IQNmpy(iqNInputX, temp, q_value); iqNInputY = __IQNmpy(iqNInputY, temp, q_value); } else { /* The decimal value 1 is the smallest positive value for a given IQ. * So by adding this to our variable we are using to normalize we * ensure the resulting normalized values are < 1.0. */ abs_max += 1; } /* Normalize Inputs */ iqNnormX = __IQNdiv_MathACL(iqNInputX, abs_max, q_value); iqNnormY = __IQNdiv_MathACL(iqNInputY, abs_max, q_value); /* Shift from IQX to IQ31 which is required for MathACL ATAN2 operation */ iq31normX = (uint_fast32_t)iqNnormX << (31 - q_value); iq31normY = (uint_fast32_t)iqNnormY << (31 - q_value); /* MathACL ATAN2 Operation */ MATHACL->CTL = 2 | (31 << 24); /* write operands to HWA */ MATHACL->OP2 = iq31normY; /* write to OP1 is the trigger */ MATHACL->OP1 = iq31normX; /* read atan2 */ res1 = MATHACL->RES1; /* Shift from IQ31 to IQ28 for result scaling */ res = res1 >> (3); /* Round and convert result to correct format (radians/PU and iqN type). */ if (type == TYPE_PU) { /* IQ28(2.0) = 0x20000000 in int32 */ res = _IQ28div(res, 0x20000000); } else { /* IQ28(PI) = 0x3243F6A8 in int32 */ res = _IQ28mpy(0x3243F6A8, res); } /* Shift to q_value type */ if (q_value < 28) { res = res >> (28 - q_value); } else { res = res << (q_value - 28); } return res; } #endif /* ATAN2 */ /** * @brief Compute the 4-quadrant arctangent of the IQ29 input * and return the result, in radians. * * @param y IQ29 type input y. * @param x IQ29 type input x. * * @return IQ29 type result of 4-quadrant arctangent. */ int32_t _IQ29atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 29); } /** * @brief Compute the 4-quadrant arctangent of the IQ28 input * and return the result, in radians. * * @param y IQ28 type input y. * @param x IQ28 type input x. * * @return IQ28 type result of 4-quadrant arctangent. */ int32_t _IQ28atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 28); } /** * @brief Compute the 4-quadrant arctangent of the IQ27 input * and return the result, in radians. * * @param y IQ27 type input y. * @param x IQ27 type input x. * * @return IQ27 type result of 4-quadrant arctangent. */ int32_t _IQ27atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 27); } /** * @brief Compute the 4-quadrant arctangent of the IQ26 input * and return the result, in radians. * * @param y IQ26 type input y. * @param x IQ26 type input x. * * @return IQ26 type result of 4-quadrant arctangent. */ int32_t _IQ26atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 26); } /** * @brief Compute the 4-quadrant arctangent of the IQ25 input * and return the result, in radians. * * @param y IQ25 type input y. * @param x IQ25 type input x. * * @return IQ25 type result of 4-quadrant arctangent. */ int32_t _IQ25atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 25); } /** * @brief Compute the 4-quadrant arctangent of the IQ24 input * and return the result, in radians. * * @param y IQ24 type input y. * @param x IQ24 type input x. * * @return IQ24 type result of 4-quadrant arctangent. */ int32_t _IQ24atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 24); } /** * @brief Compute the 4-quadrant arctangent of the IQ23 input * and return the result, in radians. * * @param y IQ23 type input y. * @param x IQ23 type input x. * * @return IQ23 type result of 4-quadrant arctangent. */ int32_t _IQ23atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 23); } /** * @brief Compute the 4-quadrant arctangent of the IQ22 input * and return the result, in radians. * * @param y IQ22 type input y. * @param x IQ22 type input x. * * @return IQ22 type result of 4-quadrant arctangent. */ int32_t _IQ22atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 22); } /** * @brief Compute the 4-quadrant arctangent of the IQ21 input * and return the result, in radians. * * @param y IQ21 type input y. * @param x IQ21 type input x. * * @return IQ21 type result of 4-quadrant arctangent. */ int32_t _IQ21atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 21); } /** * @brief Compute the 4-quadrant arctangent of the IQ20 input * and return the result, in radians. * * @param y IQ20 type input y. * @param x IQ20 type input x. * * @return IQ20 type result of 4-quadrant arctangent. */ int32_t _IQ20atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 20); } /** * @brief Compute the 4-quadrant arctangent of the IQ19 input * and return the result, in radians. * * @param y IQ19 type input y. * @param x IQ19 type input x. * * @return IQ19 type result of 4-quadrant arctangent. */ int32_t _IQ19atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 19); } /** * @brief Compute the 4-quadrant arctangent of the IQ18 input * and return the result, in radians. * * @param y IQ18 type input y. * @param x IQ18 type input x. * * @return IQ18 type result of 4-quadrant arctangent. */ int32_t _IQ18atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 18); } /** * @brief Compute the 4-quadrant arctangent of the IQ17 input * and return the result, in radians. * * @param y IQ17 type input y. * @param x IQ17 type input x. * * @return IQ17 type result of 4-quadrant arctangent. */ int32_t _IQ17atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 17); } /** * @brief Compute the 4-quadrant arctangent of the IQ16 input * and return the result, in radians. * * @param y IQ16 type input y. * @param x IQ16 type input x. * * @return IQ16 type result of 4-quadrant arctangent. */ int32_t _IQ16atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 16); } /** * @brief Compute the 4-quadrant arctangent of the IQ15 input * and return the result, in radians. * * @param y IQ15 type input y. * @param x IQ15 type input x. * * @return IQ15 type result of 4-quadrant arctangent. */ int32_t _IQ15atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 15); } /** * @brief Compute the 4-quadrant arctangent of the IQ14 input * and return the result, in radians. * * @param y IQ14 type input y. * @param x IQ14 type input x. * * @return IQ14 type result of 4-quadrant arctangent. */ int32_t _IQ14atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 14); } /** * @brief Compute the 4-quadrant arctangent of the IQ13 input * and return the result, in radians. * * @param y IQ13 type input y. * @param x IQ13 type input x. * * @return IQ13 type result of 4-quadrant arctangent. */ int32_t _IQ13atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 13); } /** * @brief Compute the 4-quadrant arctangent of the IQ12 input * and return the result, in radians. * * @param y IQ12 type input y. * @param x IQ12 type input x. * * @return IQ12 type result of 4-quadrant arctangent. */ int32_t _IQ12atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 12); } /** * @brief Compute the 4-quadrant arctangent of the IQ11 input * and return the result, in radians. * * @param y IQ11 type input y. * @param x IQ11 type input x. * * @return IQ11 type result of 4-quadrant arctangent. */ int32_t _IQ11atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 11); } /** * @brief Compute the 4-quadrant arctangent of the IQ10 input * and return the result, in radians. * * @param y IQ10 type input y. * @param x IQ10 type input x. * * @return IQ10 type result of 4-quadrant arctangent. */ int32_t _IQ10atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 10); } /** * @brief Compute the 4-quadrant arctangent of the IQ9 input * and return the result, in radians. * * @param y IQ9 type input y. * @param x IQ9 type input x. * * @return IQ9 type result of 4-quadrant arctangent. */ int32_t _IQ9atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 9); } /** * @brief Compute the 4-quadrant arctangent of the IQ8 input * and return the result, in radians. * * @param y IQ8 type input y. * @param x IQ8 type input x. * * @return IQ8 type result of 4-quadrant arctangent. */ int32_t _IQ8atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 8); } /** * @brief Compute the 4-quadrant arctangent of the IQ7 input * and return the result, in radians. * * @param y IQ7 type input y. * @param x IQ7 type input x. * * @return IQ7 type result of 4-quadrant arctangent. */ int32_t _IQ7atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 7); } /** * @brief Compute the 4-quadrant arctangent of the IQ6 input * and return the result, in radians. * * @param y IQ6 type input y. * @param x IQ6 type input x. * * @return IQ6 type result of 4-quadrant arctangent. */ int32_t _IQ6atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 6); } /** * @brief Compute the 4-quadrant arctangent of the IQ5 input * and return the result, in radians. * * @param y IQ5 type input y. * @param x IQ5 type input x. * * @return IQ5 type result of 4-quadrant arctangent. */ int32_t _IQ5atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 5); } /** * @brief Compute the 4-quadrant arctangent of the IQ4 input * and return the result, in radians. * * @param y IQ4 type input y. * @param x IQ4 type input x. * * @return IQ4 type result of 4-quadrant arctangent. */ int32_t _IQ4atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 4); } /** * @brief Compute the 4-quadrant arctangent of the IQ3 input * and return the result, in radians. * * @param y IQ3 type input y. * @param x IQ3 type input x. * * @return IQ3 type result of 4-quadrant arctangent. */ int32_t _IQ3atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 3); } /** * @brief Compute the 4-quadrant arctangent of the IQ2 input * and return the result, in radians. * * @param y IQ2 type input y. * @param x IQ2 type input x. * * @return IQ2 type result of 4-quadrant arctangent. */ int32_t _IQ2atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 2); } /** * @brief Compute the 4-quadrant arctangent of the IQ1 input * and return the result, in radians. * * @param y IQ1 type input y. * @param x IQ1 type input x. * * @return IQ1 type result of 4-quadrant arctangent. */ int32_t _IQ1atan2(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_RAD, 1); } /* ATAN2PU */ /** * @brief Compute the 4-quadrant arctangent of the IQ31 input * and return the result. * * @param y IQ31 type input y. * @param x IQ31 type input x. * * @return IQ31 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ31atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 31); } /** * @brief Compute the 4-quadrant arctangent of the IQ30 input * and return the result. * * @param y IQ30 type input y. * @param x IQ30 type input x. * * @return IQ30 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ30atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 30); } /** * @brief Compute the 4-quadrant arctangent of the IQ29 input * and return the result. * * @param y IQ29 type input y. * @param x IQ29 type input x. * * @return IQ29 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ29atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 29); } /** * @brief Compute the 4-quadrant arctangent of the IQ28 input * and return the result. * * @param y IQ28 type input y. * @param x IQ28 type input x. * * @return IQ28 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ28atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 28); } /** * @brief Compute the 4-quadrant arctangent of the IQ27 input * and return the result. * * @param y IQ27 type input y. * @param x IQ27 type input x. * * @return IQ27 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ27atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 27); } /** * @brief Compute the 4-quadrant arctangent of the IQ26 input * and return the result. * * @param y IQ26 type input y. * @param x IQ26 type input x. * * @return IQ26 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ26atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 26); } /** * @brief Compute the 4-quadrant arctangent of the IQ25 input * and return the result. * * @param y IQ25 type input y. * @param x IQ25 type input x. * * @return IQ25 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ25atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 25); } /** * @brief Compute the 4-quadrant arctangent of the IQ24 input * and return the result. * * @param y IQ24 type input y. * @param x IQ24 type input x. * * @return IQ24 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ24atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 24); } /** * @brief Compute the 4-quadrant arctangent of the IQ23 input * and return the result. * * @param y IQ23 type input y. * @param x IQ23 type input x. * * @return IQ23 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ23atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 23); } /** * @brief Compute the 4-quadrant arctangent of the IQ22 input * and return the result. * * @param y IQ22 type input y. * @param x IQ22 type input x. * * @return IQ22 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ22atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 22); } /** * @brief Compute the 4-quadrant arctangent of the IQ21 input * and return the result. * * @param y IQ21 type input y. * @param x IQ21 type input x. * * @return IQ21 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ21atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 21); } /** * @brief Compute the 4-quadrant arctangent of the IQ20 input * and return the result. * * @param y IQ20 type input y. * @param x IQ20 type input x. * * @return IQ20 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ20atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 20); } /** * @brief Compute the 4-quadrant arctangent of the IQ19 input * and return the result. * * @param y IQ19 type input y. * @param x IQ19 type input x. * * @return IQ19 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ19atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 19); } /** * @brief Compute the 4-quadrant arctangent of the IQ18 input * and return the result. * * @param y IQ18 type input y. * @param x IQ18 type input x. * * @return IQ18 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ18atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 18); } /** * @brief Compute the 4-quadrant arctangent of the IQ17 input * and return the result. * * @param y IQ17 type input y. * @param x IQ17 type input x. * * @return IQ17 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ17atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 17); } /** * @brief Compute the 4-quadrant arctangent of the IQ16 input * and return the result. * * @param y IQ16 type input y. * @param x IQ16 type input x. * * @return IQ16 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ16atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 16); } /** * @brief Compute the 4-quadrant arctangent of the IQ15 input * and return the result. * * @param y IQ15 type input y. * @param x IQ15 type input x. * * @return IQ15 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ15atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 15); } /** * @brief Compute the 4-quadrant arctangent of the IQ14 input * and return the result. * * @param y IQ14 type input y. * @param x IQ14 type input x. * * @return IQ14 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ14atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 14); } /** * @brief Compute the 4-quadrant arctangent of the IQ13 input * and return the result. * * @param y IQ13 type input y. * @param x IQ13 type input x. * * @return IQ13 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ13atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 13); } /** * @brief Compute the 4-quadrant arctangent of the IQ12 input * and return the result. * * @param y IQ12 type input y. * @param x IQ12 type input x. * * @return IQ12 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ12atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 12); } /** * @brief Compute the 4-quadrant arctangent of the IQ11 input * and return the result. * * @param y IQ11 type input y. * @param x IQ11 type input x. * * @return IQ11 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ11atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 11); } /** * @brief Compute the 4-quadrant arctangent of the IQ10 input * and return the result. * * @param y IQ10 type input y. * @param x IQ10 type input x. * * @return IQ10 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ10atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 10); } /** * @brief Compute the 4-quadrant arctangent of the IQ9 input * and return the result. * * @param y IQ9 type input y. * @param x IQ9 type input x. * * @return IQ9 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ9atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 9); } /** * @brief Compute the 4-quadrant arctangent of the IQ8 input * and return the result. * * @param y IQ8 type input y. * @param x IQ8 type input x. * * @return IQ8 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ8atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 8); } /** * @brief Compute the 4-quadrant arctangent of the IQ7 input * and return the result. * * @param y IQ7 type input y. * @param x IQ7 type input x. * * @return IQ7 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ7atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 7); } /** * @brief Compute the 4-quadrant arctangent of the IQ6 input * and return the result. * * @param y IQ6 type input y. * @param x IQ6 type input x. * * @return IQ6 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ6atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 6); } /** * @brief Compute the 4-quadrant arctangent of the IQ5 input * and return the result. * * @param y IQ5 type input y. * @param x IQ5 type input x. * * @return IQ5 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ5atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 5); } /** * @brief Compute the 4-quadrant arctangent of the IQ4 input * and return the result. * * @param y IQ4 type input y. * @param x IQ4 type input x. * * @return IQ4 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ4atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 4); } /** * @brief Compute the 4-quadrant arctangent of the IQ3 input * and return the result. * * @param y IQ3 type input y. * @param x IQ3 type input x. * * @return IQ3 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ3atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 3); } /** * @brief Compute the 4-quadrant arctangent of the IQ2 input * and return the result. * * @param y IQ2 type input y. * @param x IQ2 type input x. * * @return IQ2 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ2atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 2); } /** * @brief Compute the 4-quadrant arctangent of the IQ1 input * and return the result. * * @param y IQ1 type input y. * @param x IQ1 type input x. * * @return IQ1 type per-unit result of 4-quadrant arctangent. */ int32_t _IQ1atan2PU(int32_t y, int32_t x) { return __IQNatan2(y, x, TYPE_PU, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNdiv.c ================================================ #include "_IQNdiv.h" /* RTS Functions */ #if ((!defined (__IQMATH_USE_MATHACL__)) || (!defined (__MSPM0_HAS_MATHACL__))) /** * @brief Divides two values of IQ31 format. * * @param a IQ31 type value numerator to be divided. * @param b IQ31 type value denominator to divide by. * * @return IQ31 type result of the multiplication. */ int32_t _IQ31div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 31); } /** * @brief Divides two values of IQ30 format. * * @param a IQ30 type value numerator to be divided. * @param b IQ30 type value denominator to divide by. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 30); } /** * @brief Divides two values of IQ29 format. * * @param a IQ29 type value numerator to be divided. * @param b IQ29 type value denominator to divide by. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 29); } /** * @brief Divides two values of IQ28 format. * * @param a IQ28 type value numerator to be divided. * @param b IQ28 type value denominator to divide by. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 28); } /** * @brief Divides two values of IQ27 format. * * @param a IQ27 type value numerator to be divided. * @param b IQ27 type value denominator to divide by. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 27); } /** * @brief Divides two values of IQ26 format. * * @param a IQ26 type value numerator to be divided. * @param b IQ26 type value denominator to divide by. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 26); } /** * @brief Divides two values of IQ25 format. * * @param a IQ25 type value numerator to be divided. * @param b IQ25 type value denominator to divide by. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 25); } /** * @brief Divides two values of IQ24 format. * * @param a IQ24 type value numerator to be divided. * @param b IQ24 type value denominator to divide by. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 24); } /** * @brief Divides two values of IQ23 format. * * @param a IQ23 type value numerator to be divided. * @param b IQ23 type value denominator to divide by. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 23); } /** * @brief Divides two values of IQ22 format. * * @param a IQ22 type value numerator to be divided. * @param b IQ22 type value denominator to divide by. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 22); } /** * @brief Divides two values of IQ21 format. * * @param a IQ21 type value numerator to be divided. * @param b IQ21 type value denominator to divide by. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 21); } /** * @brief Divides two values of IQ20 format. * * @param a IQ20 type value numerator to be divided. * @param b IQ20 type value denominator to divide by. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 20); } /** * @brief Divides two values of IQ19 format. * * @param a IQ19 type value numerator to be divided. * @param b IQ19 type value denominator to divide by. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 19); } /** * @brief Divides two values of IQ18 format. * * @param a IQ18 type value numerator to be divided. * @param b IQ18 type value denominator to divide by. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 18); } /** * @brief Divides two values of IQ17 format. * * @param a IQ17 type value numerator to be divided. * @param b IQ17 type value denominator to divide by. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 17); } /** * @brief Divides two values of IQ16 format. * * @param a IQ16 type value numerator to be divided. * @param b IQ16 type value denominator to divide by. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 16); } /** * @brief Divides two values of IQ15 format. * * @param a IQ15 type value numerator to be divided. * @param b IQ15 type value denominator to divide by. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 15); } /** * @brief Divides two values of IQ14 format. * * @param a IQ14 type value numerator to be divided. * @param b IQ14 type value denominator to divide by. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 14); } /** * @brief Divides two values of IQ13 format. * * @param a IQ13 type value numerator to be divided. * @param b IQ13 type value denominator to divide by. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 13); } /** * @brief Divides two values of IQ12 format. * * @param a IQ12 type value numerator to be divided. * @param b IQ12 type value denominator to divide by. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 12); } /** * @brief Divides two values of IQ11 format. * * @param a IQ11 type value numerator to be divided. * @param b IQ11 type value denominator to divide by. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 11); } /** * @brief Divides two values of IQ10 format. * * @param a IQ10 type value numerator to be divided. * @param b IQ10 type value denominator to divide by. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 10); } /** * @brief Divides two values of IQ9 format. * * @param a IQ9 type value numerator to be divided. * @param b IQ9 type value denominator to divide by. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 9); } /** * @brief Divides two values of IQ8 format. * * @param a IQ8 type value numerator to be divided. * @param b IQ8 type value denominator to divide by. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 8); } /** * @brief Divides two values of IQ7 format. * * @param a IQ7 type value numerator to be divided. * @param b IQ7 type value denominator to divide by. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 7); } /** * @brief Divides two values of IQ6 format. * * @param a IQ6 type value numerator to be divided. * @param b IQ6 type value denominator to divide by. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 6); } /** * @brief Divides two values of IQ5 format. * * @param a IQ5 type value numerator to be divided. * @param b IQ5 type value denominator to divide by. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 5); } /** * @brief Divides two values of IQ4 format. * * @param a IQ4 type value numerator to be divided. * @param b IQ4 type value denominator to divide by. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 4); } /** * @brief Divides two values of IQ3 format. * * @param a IQ3 type value numerator to be divided. * @param b IQ3 type value denominator to divide by. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 3); } /** * @brief Divides two values of IQ2 format. * * @param a IQ2 type value numerator to be divided. * @param b IQ2 type value denominator to divide by. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 2); } /** * @brief Divides two values of IQ1 format. * * @param a IQ1 type value numerator to be divided. * @param b IQ1 type value denominator to divide by. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1div(int32_t a, int32_t b) { return __IQNdiv(a, b, TYPE_DEFAULT, 1); } /* MathACL Functions */ #else /** * @brief Divides two values of IQ31 format, using MathACL * * @param a IQ31 type value numerator to be divided. * @param b IQ31 type value denominator to divide by. * * @return IQ31 type result of the multiplication. */ int32_t _IQ31div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 31); } /** * @brief Divides two values of IQ30 format, using MathACL * * @param a IQ30 type value numerator to be divided. * @param b IQ30 type value denominator to divide by. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 30); } /** * @brief Divides two values of IQ29 format, using MathACL * * @param a IQ29 type value numerator to be divided. * @param b IQ29 type value denominator to divide by. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 29); } /** * @brief Divides two values of IQ28 format, using MathACL * * @param a IQ28 type value numerator to be divided. * @param b IQ28 type value denominator to divide by. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 28); } /** * @brief Divides two values of IQ27 format, using MathACL * * @param a IQ27 type value numerator to be divided. * @param b IQ27 type value denominator to divide by. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 27); } /** * @brief Divides two values of IQ26 format, using MathACL * * @param a IQ26 type value numerator to be divided. * @param b IQ26 type value denominator to divide by. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 26); } /** * @brief Divides two values of IQ25 format, using MathACL * * @param a IQ25 type value numerator to be divided. * @param b IQ25 type value denominator to divide by. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 25); } /** * @brief Divides two values of IQ24 format, using MathACL * * @param a IQ24 type value numerator to be divided. * @param b IQ24 type value denominator to divide by. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 24); } /** * @brief Divides two values of IQ23 format, using MathACL * * @param a IQ23 type value numerator to be divided. * @param b IQ23 type value denominator to divide by. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 23); } /** * @brief Divides two values of IQ22 format, using MathACL * * @param a IQ22 type value numerator to be divided. * @param b IQ22 type value denominator to divide by. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 22); } /** * @brief Divides two values of IQ21 format, using MathACL * * @param a IQ21 type value numerator to be divided. * @param b IQ21 type value denominator to divide by. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 21); } /** * @brief Divides two values of IQ20 format, using MathACL * * @param a IQ20 type value numerator to be divided. * @param b IQ20 type value denominator to divide by. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 20); } /** * @brief Divides two values of IQ19 format, using MathACL * * @param a IQ19 type value numerator to be divided. * @param b IQ19 type value denominator to divide by. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 19); } /** * @brief Divides two values of IQ18 format, using MathACL * * @param a IQ18 type value numerator to be divided. * @param b IQ18 type value denominator to divide by. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 18); } /** * @brief Divides two values of IQ17 format, using MathACL * * @param a IQ17 type value numerator to be divided. * @param b IQ17 type value denominator to divide by. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 17); } /** * @brief Divides two values of IQ16 format, using MathACL * * @param a IQ16 type value numerator to be divided. * @param b IQ16 type value denominator to divide by. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 16); } /** * @brief Divides two values of IQ15 format, using MathACL * * @param a IQ15 type value numerator to be divided. * @param b IQ15 type value denominator to divide by. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 15); } /** * @brief Divides two values of IQ14 format, using MathACL * * @param a IQ14 type value numerator to be divided. * @param b IQ14 type value denominator to divide by. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 14); } /** * @brief Divides two values of IQ13 format, using MathACL * * @param a IQ13 type value numerator to be divided. * @param b IQ13 type value denominator to divide by. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 13); } /** * @brief Divides two values of IQ12 format, using MathACL * * @param a IQ12 type value numerator to be divided. * @param b IQ12 type value denominator to divide by. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 12); } /** * @brief Divides two values of IQ11 format, using MathACL * * @param a IQ11 type value numerator to be divided. * @param b IQ11 type value denominator to divide by. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 11); } /** * @brief Divides two values of IQ10 format, using MathACL * * @param a IQ10 type value numerator to be divided. * @param b IQ10 type value denominator to divide by. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 10); } /** * @brief Divides two values of IQ9 format, using MathACL * * @param a IQ9 type value numerator to be divided. * @param b IQ9 type value denominator to divide by. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 9); } /** * @brief Divides two values of IQ8 format, using MathACL * * @param a IQ8 type value numerator to be divided. * @param b IQ8 type value denominator to divide by. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 8); } /** * @brief Divides two values of IQ7 format, using MathACL * * @param a IQ7 type value numerator to be divided. * @param b IQ7 type value denominator to divide by. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 7); } /** * @brief Divides two values of IQ6 format, using MathACL * * @param a IQ6 type value numerator to be divided. * @param b IQ6 type value denominator to divide by. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 6); } /** * @brief Divides two values of IQ5 format, using MathACL * * @param a IQ5 type value numerator to be divided. * @param b IQ5 type value denominator to divide by. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 5); } /** * @brief Divides two values of IQ4 format, using MathACL * * @param a IQ4 type value numerator to be divided. * @param b IQ4 type value denominator to divide by. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 4); } /** * @brief Divides two values of IQ3 format, using MathACL * * @param a IQ3 type value numerator to be divided. * @param b IQ3 type value denominator to divide by. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 3); } /** * @brief Divides two values of IQ2 format, using MathACL * * @param a IQ2 type value numerator to be divided. * @param b IQ2 type value denominator to divide by. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 2); } /** * @brief Divides two values of IQ1 format, using MathACL * * @param a IQ1 type value numerator to be divided. * @param b IQ1 type value denominator to divide by. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1div(int32_t a, int32_t b) { return __IQNdiv_MathACL(a, b, 1); } #endif /* Hidden unsigned uiq31 divide without sign checks, used for atan2. */ /** * @brief Divides two values of IQ31 format, without sign checks. * * @param a IQ31 type value numerator to be divided. * @param b IQ31 type value denominator to divide by. * * @return IQ31 type result of the multiplication. */ uint32_t _UIQ31div(uint32_t a, uint32_t b) { return __IQNdiv(a, b, TYPE_UNSIGNED, 31); } ================================================ FILE: iqmath/_IQNfunctions/_IQNdiv.h ================================================ /*!**************************************************************************** * @file _IQNdiv.c * @brief Functions to divide two values of IQN type. * *
******************************************************************************/ #ifndef ti_iq_iqndiv__include #define ti_iq_iqndiv__include #include #include "../support/support.h" #include "_IQNtables.h" /*! * @brief Used to specify signed division on IQNdiv */ #define TYPE_DEFAULT (0) /*! * @brief Used to specify unsigned division on IQNdiv */ #define TYPE_UNSIGNED (1) /** * @brief Divide two values of IQN type * * @param iqNInput1 IQN type value numerator to be divided. * @param iqNInput2 IQN type value denominator to divide by. * @param type Specify operation is signed or unsigned. * @param q_value IQ format. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNdiv) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNdiv(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const uint8_t type, const int8_t q_value) { uint8_t ui8Index, ui8Sign = 0; uint_fast32_t ui32Temp; uint_fast32_t uiq30Guess; uint_fast32_t uiqNInput1; uint_fast32_t uiqNInput2; uint_fast32_t uiqNResult; uint_fast64_t uiiqNInput1; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; if (type == TYPE_DEFAULT) { /* save sign of denominator */ if (iqNInput2 <= 0) { /* check for divide by zero */ if (iqNInput2 == 0) { return INT32_MAX; } else { ui8Sign = 1; iqNInput2 = -iqNInput2; } } /* save sign of numerator */ if (iqNInput1 < 0) { ui8Sign ^= 1; iqNInput1 = -iqNInput1; } } else { /* Check for divide by zero */ if (iqNInput2 == 0) { return INT32_MAX; } } /* Save input1 and input2 to unsigned IQN and IIQN (64-bit). */ uiiqNInput1 = (uint_fast64_t)iqNInput1; uiqNInput2 = (uint_fast32_t)iqNInput2; /* Scale inputs so that 0.5 <= uiqNInput2 < 1.0. */ while (uiqNInput2 < 0x40000000) { uiqNInput2 <<= 1; uiiqNInput1 <<= 1; } /* * Shift input1 back from iq31 to iqN but scale by 2 since we multiply * by result in iq30 format. */ if (q_value < 31) { uiiqNInput1 >>= (31 - q_value - 1); } else { uiiqNInput1 <<= 1; } /* Check for saturation. */ if (uiiqNInput1 >> 32) { if (ui8Sign) { return INT32_MIN; } else { return INT32_MAX; } } else { uiqNInput1 = (uint_fast32_t)uiiqNInput1; } /* use left most 7 bits as ui8Index into lookup table (range: 32-64) */ ui8Index = uiqNInput2 >> 24; ui8Index -= 64; uiq30Guess = (uint_fast32_t)_IQ6div_lookup[ui8Index] << 24; /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* 1st iteration */ ui32Temp = __mpyf_ul(uiq30Guess, uiqNInput2); ui32Temp = -((uint_fast32_t)ui32Temp - 0x80000000); ui32Temp = ui32Temp << 1; uiq30Guess = __mpyf_ul_reuse_arg1(uiq30Guess, ui32Temp); /* 2nd iteration */ ui32Temp = __mpyf_ul(uiq30Guess, uiqNInput2); ui32Temp = -((uint_fast32_t)ui32Temp - 0x80000000); ui32Temp = ui32Temp << 1; uiq30Guess = __mpyf_ul_reuse_arg1(uiq30Guess, ui32Temp); /* 3rd iteration */ ui32Temp = __mpyf_ul(uiq30Guess, uiqNInput2); ui32Temp = -((uint_fast32_t)ui32Temp - 0x80000000); ui32Temp = ui32Temp << 1; uiq30Guess = __mpyf_ul_reuse_arg1(uiq30Guess, ui32Temp); /* Multiply 1/uiqNInput2 and uiqNInput1. */ uiqNResult = __mpyf_ul(uiq30Guess, uiqNInput1); /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); /* Saturate, add the sign and return. */ if (type == TYPE_DEFAULT) { if (uiqNResult > INT32_MAX) { if (ui8Sign) { return INT32_MIN; } else { return INT32_MAX; } } else { if (ui8Sign) { return -(int_fast32_t)uiqNResult; } else { return (int_fast32_t)uiqNResult; } } } else { return uiqNResult; } } // TODO: unless we find a different use for it, or we are intending to keep same params as RTS function, I see no use for TYPE here. #if ((defined (__IQMATH_USE_MATHACL__)) && (defined (__MSPM0_HAS_MATHACL__))) /** * @brief Divide two values of IQN type, using MathACL * * @param iqNInput1 IQN type value numerator to be divided. * @param iqNInput2 IQN type value denominator to divide by. * @param q_value IQ format. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNdiv_MathACL) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNdiv_MathACL(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const int8_t q_value) { /* write control */ MATHACL->CTL = 4 | (q_value << 8) | (1 << 5); /* write operands to HWA. OP2 = divisor, OP1 = dividend */ MATHACL->OP2 = iqNInput2; /* trigger is write to OP1 */ MATHACL->OP1 = iqNInput1; /* read quotient and remainder */ return MATHACL->RES1; } #endif #endif ================================================ FILE: iqmath/_IQNfunctions/_IQNexp.c ================================================ /*!**************************************************************************** * @file _IQNexp.c * @brief Functions to compute the exponential of the input * and return the result. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" /** * @brief Computes the exponential of an IQN input. * * @param iqNInput IQN type input. * @param iqNLookupTable Integer result lookup table. * @param ui8IntegerOffset Integer portion offset * @param iqN_MIN Minimum parameter value. * @param iqN_MAX Maximum parameter value. * @param q_value IQ format. * * * @return IQN type result of exponential. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNexp) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNexp(int_fast32_t iqNInput, const uint_fast32_t *iqNLookupTable, uint8_t ui8IntegerOffset, const int_fast32_t iqN_MIN, const int_fast32_t iqN_MAX, const int8_t q_value) { uint8_t ui8Count; int_fast16_t i16Integer; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; int_fast32_t iq31Fractional; uint_fast32_t uiqNResult; uint_fast32_t uiqNIntegerResult; uint_fast32_t uiq30FractionalResult; uint_fast32_t uiq31FractionalResult; const uint_fast32_t *piq30Coeffs; /* Input is negative. */ if (iqNInput < 0) { /* Check for the minimum value. */ if (iqNInput < iqN_MIN) { return 0; } /* Extract the fractional portion in iq31 and set sign bit. */ iq31Fractional = iqNInput; iq31Fractional <<= (31 - q_value); iq31Fractional |= 0x80000000; /* Extract the integer portion. */ i16Integer = (int_fast16_t)(iqNInput >> q_value) + 1; /* Offset the integer portion and lookup the integer result. */ i16Integer += ui8IntegerOffset; uiqNIntegerResult = iqNLookupTable[i16Integer]; /* Reduce the fractional portion to -ln(2) < iq31Fractional < 0 */ if (iq31Fractional <= -iq31_ln2) { iq31Fractional += iq31_ln2; uiqNIntegerResult >>= 1; } } /* Input is positive. */ else { /* Check for the maximum value. */ if (iqNInput > iqN_MAX) { return INT32_MAX; } /* Extract the fractional portion in iq31 and clear sign bit. */ iq31Fractional = iqNInput; iq31Fractional <<= (31 - q_value); iq31Fractional &= 0x7fffffff; /* Extract the integer portion. */ i16Integer = (int_fast16_t)(iqNInput >> q_value); /* Offset the integer portion and lookup the integer result. */ i16Integer += ui8IntegerOffset; uiqNIntegerResult = iqNLookupTable[i16Integer]; /* Reduce the fractional portion to 0 < iq31Fractional < ln(2) */ if (iq31Fractional >= iq31_ln2) { iq31Fractional -= iq31_ln2; uiqNIntegerResult <<= 1; } } /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* * Initialize the coefficient pointer to the Taylor Series iq30 coefficients * for the exponential functions. Set the iq30 result to the first * coefficient in the table. */ piq30Coeffs = _IQ30exp_coeffs; uiq30FractionalResult = *piq30Coeffs++; /* Compute exp^(iq31Fractional). */ for (ui8Count = _IQ30exp_order; ui8Count > 0; ui8Count--) { uiq30FractionalResult = __mpyf_l(iq31Fractional, uiq30FractionalResult); uiq30FractionalResult += *piq30Coeffs++; } /* Scale the iq30 fractional result by to iq31. */ uiq31FractionalResult = uiq30FractionalResult << 1; /* * Multiply the integer result in iqN format and the fractional result in * iq31 format to obtain the result in iqN format. */ uiqNResult = __mpyf_ul(uiqNIntegerResult, uiq31FractionalResult); /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); /* The result is scaled by 2, round the result and scale to iqN format. */ uiqNResult++; uiqNResult >>= 1; return uiqNResult; } /** * @brief Computes the exponential of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type result of exponential. */ int32_t _IQ30exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup30, _IQNexp_offset[30 - 1], _IQNexp_min[30 - 1], _IQNexp_max[30 - 1], 30); } /** * @brief Computes the exponential of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of exponential. */ int32_t _IQ29exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup29, _IQNexp_offset[29 - 1], _IQNexp_min[29 - 1], _IQNexp_max[29 - 1], 29); } /** * @brief Computes the exponential of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of exponential. */ int32_t _IQ28exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup28, _IQNexp_offset[28 - 1], _IQNexp_min[28 - 1], _IQNexp_max[28 - 1], 28); } /** * @brief Computes the exponential of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of exponential. */ int32_t _IQ27exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup27, _IQNexp_offset[27 - 1], _IQNexp_min[27 - 1], _IQNexp_max[27 - 1], 27); } /** * @brief Computes the exponential of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of exponential. */ int32_t _IQ26exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup26, _IQNexp_offset[26 - 1], _IQNexp_min[26 - 1], _IQNexp_max[26 - 1], 26); } /** * @brief Computes the exponential of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of exponential. */ int32_t _IQ25exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup25, _IQNexp_offset[25 - 1], _IQNexp_min[25 - 1], _IQNexp_max[25 - 1], 25); } /** * @brief Computes the exponential of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of exponential. */ int32_t _IQ24exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup24, _IQNexp_offset[24 - 1], _IQNexp_min[24 - 1], _IQNexp_max[24 - 1], 24); } /** * @brief Computes the exponential of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of exponential. */ int32_t _IQ23exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup23, _IQNexp_offset[23 - 1], _IQNexp_min[23 - 1], _IQNexp_max[23 - 1], 23); } /** * @brief Computes the exponential of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of exponential. */ int32_t _IQ22exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup22, _IQNexp_offset[22 - 1], _IQNexp_min[22 - 1], _IQNexp_max[22 - 1], 22); } /** * @brief Computes the exponential of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of exponential. */ int32_t _IQ21exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup21, _IQNexp_offset[21 - 1], _IQNexp_min[21 - 1], _IQNexp_max[21 - 1], 21); } /** * @brief Computes the exponential of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of exponential. */ int32_t _IQ20exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup20, _IQNexp_offset[20 - 1], _IQNexp_min[20 - 1], _IQNexp_max[20 - 1], 20); } /** * @brief Computes the exponential of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of exponential. */ int32_t _IQ19exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup19, _IQNexp_offset[19 - 1], _IQNexp_min[19 - 1], _IQNexp_max[19 - 1], 19); } /** * @brief Computes the exponential of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of exponential. */ int32_t _IQ18exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup18, _IQNexp_offset[18 - 1], _IQNexp_min[18 - 1], _IQNexp_max[18 - 1], 18); } /** * @brief Computes the exponential of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of exponential. */ int32_t _IQ17exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup17, _IQNexp_offset[17 - 1], _IQNexp_min[17 - 1], _IQNexp_max[17 - 1], 17); } /** * @brief Computes the exponential of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of exponential. */ int32_t _IQ16exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup16, _IQNexp_offset[16 - 1], _IQNexp_min[16 - 1], _IQNexp_max[16 - 1], 16); } /** * @brief Computes the exponential of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of exponential. */ int32_t _IQ15exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup15, _IQNexp_offset[15 - 1], _IQNexp_min[15 - 1], _IQNexp_max[15 - 1], 15); } /** * @brief Computes the exponential of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of exponential. */ int32_t _IQ14exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup14, _IQNexp_offset[14 - 1], _IQNexp_min[14 - 1], _IQNexp_max[14 - 1], 14); } /** * @brief Computes the exponential of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of exponential. */ int32_t _IQ13exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup13, _IQNexp_offset[13 - 1], _IQNexp_min[13 - 1], _IQNexp_max[13 - 1], 13); } /** * @brief Computes the exponential of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of exponential. */ int32_t _IQ12exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup12, _IQNexp_offset[12 - 1], _IQNexp_min[12 - 1], _IQNexp_max[12 - 1], 12); } /** * @brief Computes the exponential of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of exponential. */ int32_t _IQ11exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup11, _IQNexp_offset[11 - 1], _IQNexp_min[11 - 1], _IQNexp_max[11 - 1], 11); } /** * @brief Computes the exponential of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of exponential. */ int32_t _IQ10exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup10, _IQNexp_offset[10 - 1], _IQNexp_min[10 - 1], _IQNexp_max[10 - 1], 10); } /** * @brief Computes the exponential of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of exponential. */ int32_t _IQ9exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup9, _IQNexp_offset[9 - 1], _IQNexp_min[9 - 1], _IQNexp_max[9 - 1], 9); } /** * @brief Computes the exponential of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of exponential. */ int32_t _IQ8exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup8, _IQNexp_offset[8 - 1], _IQNexp_min[8 - 1], _IQNexp_max[8 - 1], 8); } /** * @brief Computes the exponential of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of exponential. */ int32_t _IQ7exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup7, _IQNexp_offset[7 - 1], _IQNexp_min[7 - 1], _IQNexp_max[7 - 1], 7); } /** * @brief Computes the exponential of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of exponential. */ int32_t _IQ6exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup6, _IQNexp_offset[6 - 1], _IQNexp_min[6 - 1], _IQNexp_max[6 - 1], 6); } /** * @brief Computes the exponential of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of exponential. */ int32_t _IQ5exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup5, _IQNexp_offset[5 - 1], _IQNexp_min[5 - 1], _IQNexp_max[5 - 1], 5); } /** * @brief Computes the exponential of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of exponential. */ int32_t _IQ4exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup4, _IQNexp_offset[4 - 1], _IQNexp_min[4 - 1], _IQNexp_max[4 - 1], 4); } /** * @brief Computes the exponential of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of exponential. */ int32_t _IQ3exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup3, _IQNexp_offset[3 - 1], _IQNexp_min[3 - 1], _IQNexp_max[3 - 1], 3); } /** * @brief Computes the exponential of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of exponential. */ int32_t _IQ2exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup2, _IQNexp_offset[2 - 1], _IQNexp_min[2 - 1], _IQNexp_max[2 - 1], 2); } /** * @brief Computes the exponential of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of exponential. */ int32_t _IQ1exp(int32_t a) { return __IQNexp(a, _IQNexp_lookup1, _IQNexp_offset[1 - 1], _IQNexp_min[1 - 1], _IQNexp_max[1 - 1], 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNfrac.c ================================================ /*!**************************************************************************** * @file _IQNfrac.c * @brief Functions to return the fractional portion of the input. * *
******************************************************************************/ #include #include "../support/support.h" /** * @brief Return the fractional portion of an IQN input. * * @param iqNInput IQN type input. * @param q_value IQ format. * * @return IQN type fractional portion of input. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNfrac) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNfrac(int_fast32_t iqNInput, int8_t q_value) { int_fast32_t iqNInteger; iqNInteger = (uint_fast32_t)iqNInput & ((uint_fast32_t)0xffffffff << q_value); return (iqNInput - iqNInteger); } /** * @brief Return the fractional portion of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type fractional portion of input. */ int32_t _IQ30frac(int32_t a) { return __IQNfrac(a, 30); } /** * @brief Return the fractional portion of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type fractional portion of input. */ int32_t _IQ29frac(int32_t a) { return __IQNfrac(a, 29); } /** * @brief Return the fractional portion of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type fractional portion of input. */ int32_t _IQ28frac(int32_t a) { return __IQNfrac(a, 28); } /** * @brief Return the fractional portion of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type fractional portion of input. */ int32_t _IQ27frac(int32_t a) { return __IQNfrac(a, 27); } /** * @brief Return the fractional portion of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type fractional portion of input. */ int32_t _IQ26frac(int32_t a) { return __IQNfrac(a, 26); } /** * @brief Return the fractional portion of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type fractional portion of input. */ int32_t _IQ25frac(int32_t a) { return __IQNfrac(a, 25); } /** * @brief Return the fractional portion of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type fractional portion of input. */ int32_t _IQ24frac(int32_t a) { return __IQNfrac(a, 24); } /** * @brief Return the fractional portion of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type fractional portion of input. */ int32_t _IQ23frac(int32_t a) { return __IQNfrac(a, 23); } /** * @brief Return the fractional portion of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type fractional portion of input. */ int32_t _IQ22frac(int32_t a) { return __IQNfrac(a, 22); } /** * @brief Return the fractional portion of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type fractional portion of input. */ int32_t _IQ21frac(int32_t a) { return __IQNfrac(a, 21); } /** * @brief Return the fractional portion of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type fractional portion of input. */ int32_t _IQ20frac(int32_t a) { return __IQNfrac(a, 20); } /** * @brief Return the fractional portion of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type fractional portion of input. */ int32_t _IQ19frac(int32_t a) { return __IQNfrac(a, 19); } /** * @brief Return the fractional portion of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type fractional portion of input. */ int32_t _IQ18frac(int32_t a) { return __IQNfrac(a, 18); } /** * @brief Return the fractional portion of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type fractional portion of input. */ int32_t _IQ17frac(int32_t a) { return __IQNfrac(a, 17); } /** * @brief Return the fractional portion of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type fractional portion of input. */ int32_t _IQ16frac(int32_t a) { return __IQNfrac(a, 16); } /** * @brief Return the fractional portion of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type fractional portion of input. */ int32_t _IQ15frac(int32_t a) { return __IQNfrac(a, 15); } /** * @brief Return the fractional portion of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type fractional portion of input. */ int32_t _IQ14frac(int32_t a) { return __IQNfrac(a, 14); } /** * @brief Return the fractional portion of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type fractional portion of input. */ int32_t _IQ13frac(int32_t a) { return __IQNfrac(a, 13); } /** * @brief Return the fractional portion of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type fractional portion of input. */ int32_t _IQ12frac(int32_t a) { return __IQNfrac(a, 12); } /** * @brief Return the fractional portion of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type fractional portion of input. */ int32_t _IQ11frac(int32_t a) { return __IQNfrac(a, 11); } /** * @brief Return the fractional portion of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type fractional portion of input. */ int32_t _IQ10frac(int32_t a) { return __IQNfrac(a, 10); } /** * @brief Return the fractional portion of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type fractional portion of input. */ int32_t _IQ9frac(int32_t a) { return __IQNfrac(a, 9); } /** * @brief Return the fractional portion of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type fractional portion of input. */ int32_t _IQ8frac(int32_t a) { return __IQNfrac(a, 8); } /** * @brief Return the fractional portion of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type fractional portion of input. */ int32_t _IQ7frac(int32_t a) { return __IQNfrac(a, 7); } /** * @brief Return the fractional portion of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type fractional portion of input. */ int32_t _IQ6frac(int32_t a) { return __IQNfrac(a, 6); } /** * @brief Return the fractional portion of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type fractional portion of input. */ int32_t _IQ5frac(int32_t a) { return __IQNfrac(a, 5); } /** * @brief Return the fractional portion of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type fractional portion of input. */ int32_t _IQ4frac(int32_t a) { return __IQNfrac(a, 4); } /** * @brief Return the fractional portion of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type fractional portion of input. */ int32_t _IQ3frac(int32_t a) { return __IQNfrac(a, 3); } /** * @brief Return the fractional portion of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type fractional portion of input. */ int32_t _IQ2frac(int32_t a) { return __IQNfrac(a, 2); } /** * @brief Return the fractional portion of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type fractional portion of input. */ int32_t _IQ1frac(int32_t a) { return __IQNfrac(a, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNlog.c ================================================ /*!**************************************************************************** * @file _IQNlog.c * @brief Functions to compute the base-e logarithm of an IQN number. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" /** * @brief Computes the base-e logarithm of an IQN input. * * @param iqNInput IQN type input. * @param iqNMin Minimum parameter value. * @param q_value IQ format. * * @return IQN type result of exponential. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNlog) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNlog(int_fast32_t iqNInput, const int_fast32_t iqNMin, const int8_t q_value) { uint8_t ui8Counter; int_fast16_t i16Exp; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; int_fast32_t iqNResult; int_fast32_t iq30Result; uint_fast32_t uiq31Input; const uint_fast32_t *piq30Coeffs; /* * Check the sign of the input and for negative saturation for q_values * larger than iq26. */ if (q_value > 26) { if (iqNInput <= 0) { return 0; } else if (iqNInput <= iqNMin) { return INT32_MIN; } } /* * Only check the sign of the input and that it is not equal to zero for * q_values less than or equal to iq26. */ else { if (iqNInput <= 0) { return 0; } } /* Initialize the exponent value. */ i16Exp = (31 - q_value); /* * Scale the input so it is within the following range in iq31: * * 0.666666 < uiq31Input < 1.333333. */ uiq31Input = (uint_fast32_t)iqNInput; while (uiq31Input < iq31_twoThird) { uiq31Input <<= 1; i16Exp--; } /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* * Initialize the coefficient pointer to the Taylor Series iq30 coefficients * for the logarithm functions. Set the iq30 result to the first * coefficient in the table. Subtract one from the iq31 input. */ piq30Coeffs = _IQ30log_coeffs; iq30Result = *piq30Coeffs++; uiq31Input -= iq31_one; /* Calculate log(uiq31Input) using the iq30 Taylor Series coefficients. */ for (ui8Counter = _IQ30log_order; ui8Counter > 0; ui8Counter--) { iq30Result = __mpyf_l(uiq31Input, iq30Result); iq30Result += *piq30Coeffs++; } /* Scale the iq30 result to match the function iq type. */ iqNResult = iq30Result >> (30 - q_value); /* * Add i16Exp * ln(2) to the iqN result. This will never saturate since we * check for the minimum value at the start of the function. Negative * exponents require separate handling to allow for an extra bit with the * unsigned data type. */ if (i16Exp > 0) { iqNResult += __mpyf_ul(iq31_ln2, ((int_fast32_t)i16Exp << q_value)); } else { iqNResult -= __mpyf_ul(iq31_ln2, (((uint_fast32_t) - i16Exp) << q_value)); } /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); return iqNResult; } /** * @brief Computes the base-e logarithm of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type result of exponential. */ int32_t _IQ30log(int32_t a) { return __IQNlog(a, _IQNlog_min[30 - 27], 30); } /** * @brief Computes the base-e logarithm of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of exponential. */ int32_t _IQ29log(int32_t a) { return __IQNlog(a, _IQNlog_min[29 - 27], 29); } /** * @brief Computes the base-e logarithm of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of exponential. */ int32_t _IQ28log(int32_t a) { return __IQNlog(a, _IQNlog_min[28 - 27], 28); } /** * @brief Computes the base-e logarithm of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of exponential. */ int32_t _IQ27log(int32_t a) { return __IQNlog(a, _IQNlog_min[27 - 27], 27); } /** * @brief Computes the base-e logarithm of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of exponential. */ int32_t _IQ26log(int32_t a) { return __IQNlog(a, 1, 26); } /** * @brief Computes the base-e logarithm of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of exponential. */ int32_t _IQ25log(int32_t a) { return __IQNlog(a, 1, 25); } /** * @brief Computes the base-e logarithm of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of exponential. */ int32_t _IQ24log(int32_t a) { return __IQNlog(a, 1, 24); } /** * @brief Computes the base-e logarithm of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of exponential. */ int32_t _IQ23log(int32_t a) { return __IQNlog(a, 1, 23); } /** * @brief Computes the base-e logarithm of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of exponential. */ int32_t _IQ22log(int32_t a) { return __IQNlog(a, 1, 22); } /** * @brief Computes the base-e logarithm of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of exponential. */ int32_t _IQ21log(int32_t a) { return __IQNlog(a, 1, 21); } /** * @brief Computes the base-e logarithm of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of exponential. */ int32_t _IQ20log(int32_t a) { return __IQNlog(a, 1, 20); } /** * @brief Computes the base-e logarithm of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of exponential. */ int32_t _IQ19log(int32_t a) { return __IQNlog(a, 1, 19); } /** * @brief Computes the base-e logarithm of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of exponential. */ int32_t _IQ18log(int32_t a) { return __IQNlog(a, 1, 18); } /** * @brief Computes the base-e logarithm of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of exponential. */ int32_t _IQ17log(int32_t a) { return __IQNlog(a, 1, 17); } /** * @brief Computes the base-e logarithm of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of exponential. */ int32_t _IQ16log(int32_t a) { return __IQNlog(a, 1, 16); } /** * @brief Computes the base-e logarithm of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of exponential. */ int32_t _IQ15log(int32_t a) { return __IQNlog(a, 1, 15); } /** * @brief Computes the base-e logarithm of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of exponential. */ int32_t _IQ14log(int32_t a) { return __IQNlog(a, 1, 14); } /** * @brief Computes the base-e logarithm of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of exponential. */ int32_t _IQ13log(int32_t a) { return __IQNlog(a, 1, 13); } /** * @brief Computes the base-e logarithm of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of exponential. */ int32_t _IQ12log(int32_t a) { return __IQNlog(a, 1, 12); } /** * @brief Computes the base-e logarithm of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of exponential. */ int32_t _IQ11log(int32_t a) { return __IQNlog(a, 1, 11); } /** * @brief Computes the base-e logarithm of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of exponential. */ int32_t _IQ10log(int32_t a) { return __IQNlog(a, 1, 10); } /** * @brief Computes the base-e logarithm of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of exponential. */ int32_t _IQ9log(int32_t a) { return __IQNlog(a, 1, 9); } /** * @brief Computes the base-e logarithm of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of exponential. */ int32_t _IQ8log(int32_t a) { return __IQNlog(a, 1, 8); } /** * @brief Computes the base-e logarithm of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of exponential. */ int32_t _IQ7log(int32_t a) { return __IQNlog(a, 1, 7); } /** * @brief Computes the base-e logarithm of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of exponential. */ int32_t _IQ6log(int32_t a) { return __IQNlog(a, 1, 6); } /** * @brief Computes the base-e logarithm of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of exponential. */ int32_t _IQ5log(int32_t a) { return __IQNlog(a, 1, 5); } /** * @brief Computes the base-e logarithm of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of exponential. */ int32_t _IQ4log(int32_t a) { return __IQNlog(a, 1, 4); } /** * @brief Computes the base-e logarithm of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of exponential. */ int32_t _IQ3log(int32_t a) { return __IQNlog(a, 1, 3); } /** * @brief Computes the base-e logarithm of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of exponential. */ int32_t _IQ2log(int32_t a) { return __IQNlog(a, 1, 2); } /** * @brief Computes the base-e logarithm of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of exponential. */ int32_t _IQ1log(int32_t a) { return __IQNlog(a, 1, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNmpy.c ================================================ /*!**************************************************************************** * @file _IQNmpy.c * @brief Functions to multiply two values of IQN type. * *
******************************************************************************/ #include "_IQNmpy.h" /** * @brief Multiplies two values of IQ31 format. * * @param a IQ31 type value to be multiplied. * @param b IQ31 type value to be multiplied. * * @return IQ31 type result of the multiplication. */ int32_t _IQ31mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 31); } /** * @brief Multiplies two values of IQ30 format. * * @param a IQ30 type value to be multiplied. * @param b IQ30 type value to be multiplied. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 30); } /** * @brief Multiplies two values of IQ29 format. * * @param a IQ29 type value to be multiplied. * @param b IQ29 type value to be multiplied. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 29); } /** * @brief Multiplies two values of IQ28 format. * * @param a IQ28 type value to be multiplied. * @param b IQ28 type value to be multiplied. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 28); } /** * @brief Multiplies two values of IQ27 format. * * @param a IQ27 type value to be multiplied. * @param b IQ27 type value to be multiplied. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 27); } /** * @brief Multiplies two values of IQ26 format. * * @param a IQ26 type value to be multiplied. * @param b IQ26 type value to be multiplied. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 26); } /** * @brief Multiplies two values of IQ25 format. * * @param a IQ25 type value to be multiplied. * @param b IQ25 type value to be multiplied. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 25); } /** * @brief Multiplies two values of IQ24 format. * * @param a IQ24 type value to be multiplied. * @param b IQ24 type value to be multiplied. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 24); } /** * @brief Multiplies two values of IQ23 format. * * @param a IQ23 type value to be multiplied. * @param b IQ23 type value to be multiplied. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 23); } /** * @brief Multiplies two values of IQ22 format. * * @param a IQ22 type value to be multiplied. * @param b IQ22 type value to be multiplied. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 22); } /** * @brief Multiplies two values of IQ21 format. * * @param a IQ21 type value to be multiplied. * @param b IQ21 type value to be multiplied. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 21); } /** * @brief Multiplies two values of IQ20 format. * * @param a IQ20 type value to be multiplied. * @param b IQ20 type value to be multiplied. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 20); } /** * @brief Multiplies two values of IQ19 format. * * @param a IQ19 type value to be multiplied. * @param b IQ19 type value to be multiplied. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 19); } /** * @brief Multiplies two values of IQ18 format. * * @param a IQ18 type value to be multiplied. * @param b IQ18 type value to be multiplied. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 18); } /** * @brief Multiplies two values of IQ17 format. * * @param a IQ17 type value to be multiplied. * @param b IQ17 type value to be multiplied. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 17); } /** * @brief Multiplies two values of IQ16 format. * * @param a IQ16 type value to be multiplied. * @param b IQ16 type value to be multiplied. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 16); } /** * @brief Multiplies two values of IQ15 format. * * @param a IQ15 type value to be multiplied. * @param b IQ15 type value to be multiplied. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 15); } /** * @brief Multiplies two values of IQ14 format. * * @param a IQ14 type value to be multiplied. * @param b IQ14 type value to be multiplied. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 14); } /** * @brief Multiplies two values of IQ13 format. * * @param a IQ13 type value to be multiplied. * @param b IQ13 type value to be multiplied. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 13); } /** * @brief Multiplies two values of IQ12 format. * * @param a IQ12 type value to be multiplied. * @param b IQ12 type value to be multiplied. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 12); } /** * @brief Multiplies two values of IQ11 format. * * @param a IQ11 type value to be multiplied. * @param b IQ11 type value to be multiplied. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 11); } /** * @brief Multiplies two values of IQ10 format. * * @param a IQ10 type value to be multiplied. * @param b IQ10 type value to be multiplied. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 10); } /** * @brief Multiplies two values of IQ9 format. * * @param a IQ9 type value to be multiplied. * @param b IQ9 type value to be multiplied. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 9); } /** * @brief Multiplies two values of IQ8 format. * * @param a IQ8 type value to be multiplied. * @param b IQ8 type value to be multiplied. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 8); } /** * @brief Multiplies two values of IQ7 format. * * @param a IQ7 type value to be multiplied. * @param b IQ7 type value to be multiplied. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 7); } /** * @brief Multiplies two values of IQ6 format. * * @param a IQ6 type value to be multiplied. * @param b IQ6 type value to be multiplied. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 6); } /** * @brief Multiplies two values of IQ5 format. * * @param a IQ5 type value to be multiplied. * @param b IQ5 type value to be multiplied. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 5); } /** * @brief Multiplies two values of IQ4 format. * * @param a IQ4 type value to be multiplied. * @param b IQ4 type value to be multiplied. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 4); } /** * @brief Multiplies two values of IQ3 format. * * @param a IQ3 type value to be multiplied. * @param b IQ3 type value to be multiplied. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 3); } /** * @brief Multiplies two values of IQ2 format. * * @param a IQ2 type value to be multiplied. * @param b IQ2 type value to be multiplied. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 2); } /** * @brief Multiplies two values of IQ1 format. * * @param a IQ1 type value to be multiplied. * @param b IQ1 type value to be multiplied. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1mpy(int32_t a, int32_t b) { return __IQNmpy(a, b, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNmpy.h ================================================ /*!**************************************************************************** * @file _IQNmpy.c * @brief Functions to multiply two values of IQN type. * *
******************************************************************************/ #ifndef ti_iq_iqnmpy__include #define ti_iq_iqnmpy__include #include #include "../support/support.h" #if ((!defined (__IQMATH_USE_MATHACL__)) || (!defined (__MSPM0_HAS_MATHACL__))) /** * @brief Multiply two values of IQN type. * * @param iqNInput1 IQN type value input to be multiplied. * @param iqNInput2 IQN type value input to be multiplied. * @param q_value IQ format. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNmpy) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNmpy(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const int8_t q_value) { int_fast64_t iqNResult; iqNResult = (int_fast64_t)iqNInput1 * (int_fast64_t)iqNInput2; iqNResult = iqNResult >> q_value; return (int_fast32_t)iqNResult; } #else /** * @brief Multiply two values of IQN type, using MathACL. * * @param iqNInput1 IQN type value input to be multiplied. * @param iqNInput2 IQN type value input to be multiplied. * @param q_value IQ format. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNmpy) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNmpy(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const int8_t q_value) { /* write control */ MATHACL->CTL = 6 | (q_value << 8) | (1 << 5); /* write operands to HWA */ MATHACL->OP2 = iqNInput2; /* write trigger word last */ MATHACL->OP1 = iqNInput1; /* read iqmpy product */ return MATHACL->RES1; } #endif #endif ================================================ FILE: iqmath/_IQNfunctions/_IQNmpyIQX.c ================================================ /*!**************************************************************************** * @file _IQNmpyIQX.c * @brief Functions to multiply two IQ numbers in different IQ formats, * returning the product in a third IQ format. The result is neither rounded * nor saturated, so if the product is greater than the minimum or maximum * values for the given output IQ format, the return value will wrap around * and produce inaccurate results. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in a third IQ format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * @param q_value IQ format for result. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNmpyIQX) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNmpyIQX(int_fast32_t a, int n1, int_fast32_t b, int n2, int8_t q_value) { uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; int_fast32_t i32Shift; int_fast64_t i64Result; /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpy_start(&ui16IntState, &ui16MPYState); i64Result = __mpyx(a, b); /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); i32Shift = (n1 + n2) - q_value; if (i32Shift > 0) { i64Result >>= i32Shift; } else { i64Result <<= -i32Shift; } return (int_fast32_t)i64Result; } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ30 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 30); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ29 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 29); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ28 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 28); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ27 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 27); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ26 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 26); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ25 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 25); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ24 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 24); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ23 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 23); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ22 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 22); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ21 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 21); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ20 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 20); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ19 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 19); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ18 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 18); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ17 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 17); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ16 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 16); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ15 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 15); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ14 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 14); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ13 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 13); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ12 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 12); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ11 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 11); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ10 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 10); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ9 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 9); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ8 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 8); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ7 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 7); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ6 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 6); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ5 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 5); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ4 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 4); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ3 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 3); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ2 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 2); } /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in IQ1 format. * * @param a IQN1 type value input to be multiplied. * @param n1 IQ format for first value. * @param b IQN2 type value input to be multiplied. * @param n2 IQ format for second value. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1mpyIQX(int32_t a, int n1, int32_t b, int n2) { return __IQNmpyIQX(a, n1, b, n2, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNrepeat.c ================================================ #include #include "../support/support.h" #if ((defined (__IQMATH_USE_MATHACL__)) && (defined (__MSPM0_HAS_MATHACL__))) #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNmpy) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif /** * @brief Repeats the last IQMath multiplication or division operation on two given parameters. * Function assumes MathACL Control register has been initialized by previous function call * with operation and IQ format. Using without initializing can lead to unexpected results. * * @param iqNInput1 IQN format number to be multiplied or divided. * @param iqNInput2 IQN format number to be multiplied or divided by. * * @return IQN type result of operation. */ __STATIC_INLINE int_fast32_t __IQopRepeat(int_fast32_t iqNInput1, int_fast32_t iqNInput2) { /* write operands to HWA */ MATHACL->OP2 = iqNInput2; /* write trigger word last */ MATHACL->OP1 = iqNInput1; /* read operation result */ return MATHACL->RES1; } /** * @brief Repeats the last IQMath multiplication or division operation on two given parameters. * Function assumes MathACL Control register has been initialized by previous function call * with operation and IQ format. Using without initializing can lead to unexpected results. * * @param A IQN format number to be multiplied or divided. * @param B IQN format number to be multiplied or divided by. * * @return IQN type result of operation. */ int32_t _IQrepeat(int32_t A, int32_t B) { return __IQopRepeat(A, B); } #endif ================================================ FILE: iqmath/_IQNfunctions/_IQNrmpy.c ================================================ /*!**************************************************************************** * @file _IQNrmpy.c * @brief Functions to multiply two IQ numbers, returning the product * in IQ format. The result is rounded but not saturated, so if the product * is greater than the minimum or maximum values for the given IQ format, * the return value wraps around and produces inaccurate results. * *
******************************************************************************/ #include #include "../support/support.h" /** * @brief Multiply two values of IQN type, with rounding. * * @param iqNInput1 IQN type value input to be multiplied. * @param iqNInput2 IQN type value input to be multiplied. * @param q_value IQ format for result. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNrmpy) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNrmpy(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const int8_t q_value) { int_fast64_t iqNResult; iqNResult = (int_fast64_t)iqNInput1 * (int_fast64_t)iqNInput2; iqNResult = iqNResult + ((uint_fast32_t)1 << (q_value - 1)); iqNResult = iqNResult >> q_value; return (int_fast32_t)iqNResult; } /** * @brief Multiply two values of IQ31 type, with rounding. * * @param a IQ31 type value input to be multiplied. * @param b IQ31 type value input to be multiplied. * * @return IQ31 type result of the multiplication. */ int32_t _IQ31rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 31); } /** * @brief Multiply two values of IQ30 type, with rounding. * * @param a IQ30 type value input to be multiplied. * @param b IQ30 type value input to be multiplied. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 30); } /** * @brief Multiply two values of IQ29 type, with rounding. * * @param a IQ29 type value input to be multiplied. * @param b IQ29 type value input to be multiplied. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 29); } /** * @brief Multiply two values of IQ28 type, with rounding. * * @param a IQ28 type value input to be multiplied. * @param b IQ28 type value input to be multiplied. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 28); } /** * @brief Multiply two values of IQ27 type, with rounding. * * @param a IQ27 type value input to be multiplied. * @param b IQ27 type value input to be multiplied. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 27); } /** * @brief Multiply two values of IQ26 type, with rounding. * * @param a IQ26 type value input to be multiplied. * @param b IQ26 type value input to be multiplied. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 26); } /** * @brief Multiply two values of IQ25 type, with rounding. * * @param a IQ25 type value input to be multiplied. * @param b IQ25 type value input to be multiplied. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 25); } /** * @brief Multiply two values of IQ24 type, with rounding. * * @param a IQ24 type value input to be multiplied. * @param b IQ24 type value input to be multiplied. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 24); } /** * @brief Multiply two values of IQ23 type, with rounding. * * @param a IQ23 type value input to be multiplied. * @param b IQ23 type value input to be multiplied. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 23); } /** * @brief Multiply two values of IQ22 type, with rounding. * * @param a IQ22 type value input to be multiplied. * @param b IQ22 type value input to be multiplied. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 22); } /** * @brief Multiply two values of IQ21 type, with rounding. * * @param a IQ21 type value input to be multiplied. * @param b IQ21 type value input to be multiplied. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 21); } /** * @brief Multiply two values of IQ20 type, with rounding. * * @param a IQ20 type value input to be multiplied. * @param b IQ20 type value input to be multiplied. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 20); } /** * @brief Multiply two values of IQ19 type, with rounding. * * @param a IQ19 type value input to be multiplied. * @param b IQ19 type value input to be multiplied. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 19); } /** * @brief Multiply two values of IQ18 type, with rounding. * * @param a IQ18 type value input to be multiplied. * @param b IQ18 type value input to be multiplied. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 18); } /** * @brief Multiply two values of IQ17 type, with rounding. * * @param a IQ17 type value input to be multiplied. * @param b IQ17 type value input to be multiplied. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 17); } /** * @brief Multiply two values of IQ16 type, with rounding. * * @param a IQ16 type value input to be multiplied. * @param b IQ16 type value input to be multiplied. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 16); } /** * @brief Multiply two values of IQ15 type, with rounding. * * @param a IQ15 type value input to be multiplied. * @param b IQ15 type value input to be multiplied. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 15); } /** * @brief Multiply two values of IQ14 type, with rounding. * * @param a IQ14 type value input to be multiplied. * @param b IQ14 type value input to be multiplied. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 14); } /** * @brief Multiply two values of IQ13 type, with rounding. * * @param a IQ13 type value input to be multiplied. * @param b IQ13 type value input to be multiplied. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 13); } /** * @brief Multiply two values of IQ12 type, with rounding. * * @param a IQ12 type value input to be multiplied. * @param b IQ12 type value input to be multiplied. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 12); } /** * @brief Multiply two values of IQ11 type, with rounding. * * @param a IQ11 type value input to be multiplied. * @param b IQ11 type value input to be multiplied. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 11); } /** * @brief Multiply two values of IQ10 type, with rounding. * * @param a IQ10 type value input to be multiplied. * @param b IQ10 type value input to be multiplied. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 10); } /** * @brief Multiply two values of IQ9 type, with rounding. * * @param a IQ9 type value input to be multiplied. * @param b IQ9 type value input to be multiplied. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 9); } /** * @brief Multiply two values of IQ8 type, with rounding. * * @param a IQ8 type value input to be multiplied. * @param b IQ8 type value input to be multiplied. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 8); } /** * @brief Multiply two values of IQ7 type, with rounding. * * @param a IQ7 type value input to be multiplied. * @param b IQ7 type value input to be multiplied. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 7); } /** * @brief Multiply two values of IQ6 type, with rounding. * * @param a IQ6 type value input to be multiplied. * @param b IQ6 type value input to be multiplied. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 6); } /** * @brief Multiply two values of IQ5 type, with rounding. * * @param a IQ5 type value input to be multiplied. * @param b IQ5 type value input to be multiplied. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 5); } /** * @brief Multiply two values of IQ4 type, with rounding. * * @param a IQ4 type value input to be multiplied. * @param b IQ4 type value input to be multiplied. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 4); } /** * @brief Multiply two values of IQ3 type, with rounding. * * @param a IQ3 type value input to be multiplied. * @param b IQ3 type value input to be multiplied. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 3); } /** * @brief Multiply two values of IQ2 type, with rounding. * * @param a IQ2 type value input to be multiplied. * @param b IQ2 type value input to be multiplied. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 2); } /** * @brief Multiply two values of IQ1 type, with rounding. * * @param a IQ1 type value input to be multiplied. * @param b IQ1 type value input to be multiplied. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1rmpy(int32_t a, int32_t b) { return __IQNrmpy(a, b, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNrsmpy.c ================================================ /*!**************************************************************************** * @file _IQNrsmpy.c * @brief Functions to multiply two IQ numbers, returning the product in * IQ format. The result is rounded and saturated, so if the product is * greater than the minimum or maximum values for the given IQ format, the * return value is saturated to the minimum or maximum value for the given IQ * format (as appropriate). * *
******************************************************************************/ #include #include "../support/support.h" /** * @brief Multiplies two IQN numbers, with rounding and saturation. * * @param iqNInput1 IQN type value input to be multiplied. * @param iqNInput2 IQN type value input to be multiplied. * @param q_value IQ format for result. * * @return IQN type result of the multiplication. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNrsmpy) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNrsmpy(int_fast32_t iqNInput1, int_fast32_t iqNInput2, const int8_t q_value) { int_fast64_t iqNResult; iqNResult = (int_fast64_t)iqNInput1 * (int_fast64_t)iqNInput2; iqNResult = iqNResult + ((uint_fast32_t)1 << (q_value - 1)); iqNResult = iqNResult >> q_value; if (iqNResult > INT32_MAX) { return INT32_MAX; } else if (iqNResult < INT32_MIN) { return INT32_MIN; } else { return (int_fast32_t)iqNResult; } } /** * @brief Multiplies two IQ31 numbers, with rounding and saturation. * * @param a IQ31 type value input to be multiplied. * @param b IQ31 type value input to be multiplied. * * @return IQ31 type result of the multiplication. */ int32_t _IQ31rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 31); } /** * @brief Multiplies two IQ30 numbers, with rounding and saturation. * * @param a IQ30 type value input to be multiplied. * @param b IQ30 type value input to be multiplied. * * @return IQ30 type result of the multiplication. */ int32_t _IQ30rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 30); } /** * @brief Multiplies two IQ29 numbers, with rounding and saturation. * * @param a IQ29 type value input to be multiplied. * @param b IQ29 type value input to be multiplied. * * @return IQ29 type result of the multiplication. */ int32_t _IQ29rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 29); } /** * @brief Multiplies two IQ28 numbers, with rounding and saturation. * * @param a IQ28 type value input to be multiplied. * @param b IQ28 type value input to be multiplied. * * @return IQ28 type result of the multiplication. */ int32_t _IQ28rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 28); } /** * @brief Multiplies two IQ27 numbers, with rounding and saturation. * * @param a IQ27 type value input to be multiplied. * @param b IQ27 type value input to be multiplied. * * @return IQ27 type result of the multiplication. */ int32_t _IQ27rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 27); } /** * @brief Multiplies two IQ26 numbers, with rounding and saturation. * * @param a IQ26 type value input to be multiplied. * @param b IQ26 type value input to be multiplied. * * @return IQ26 type result of the multiplication. */ int32_t _IQ26rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 26); } /** * @brief Multiplies two IQ25 numbers, with rounding and saturation. * * @param a IQ25 type value input to be multiplied. * @param b IQ25 type value input to be multiplied. * * @return IQ25 type result of the multiplication. */ int32_t _IQ25rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 25); } /** * @brief Multiplies two IQ24 numbers, with rounding and saturation. * * @param a IQ24 type value input to be multiplied. * @param b IQ24 type value input to be multiplied. * * @return IQ24 type result of the multiplication. */ int32_t _IQ24rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 24); } /** * @brief Multiplies two IQ23 numbers, with rounding and saturation. * * @param a IQ23 type value input to be multiplied. * @param b IQ23 type value input to be multiplied. * * @return IQ23 type result of the multiplication. */ int32_t _IQ23rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 23); } /** * @brief Multiplies two IQ22 numbers, with rounding and saturation. * * @param a IQ22 type value input to be multiplied. * @param b IQ22 type value input to be multiplied. * * @return IQ22 type result of the multiplication. */ int32_t _IQ22rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 22); } /** * @brief Multiplies two IQ21 numbers, with rounding and saturation. * * @param a IQ21 type value input to be multiplied. * @param b IQ21 type value input to be multiplied. * * @return IQ21 type result of the multiplication. */ int32_t _IQ21rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 21); } /** * @brief Multiplies two IQ20 numbers, with rounding and saturation. * * @param a IQ20 type value input to be multiplied. * @param b IQ20 type value input to be multiplied. * * @return IQ20 type result of the multiplication. */ int32_t _IQ20rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 20); } /** * @brief Multiplies two IQ19 numbers, with rounding and saturation. * * @param a IQ19 type value input to be multiplied. * @param b IQ19 type value input to be multiplied. * * @return IQ19 type result of the multiplication. */ int32_t _IQ19rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 19); } /** * @brief Multiplies two IQ18 numbers, with rounding and saturation. * * @param a IQ18 type value input to be multiplied. * @param b IQ18 type value input to be multiplied. * * @return IQ18 type result of the multiplication. */ int32_t _IQ18rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 18); } /** * @brief Multiplies two IQ17 numbers, with rounding and saturation. * * @param a IQ17 type value input to be multiplied. * @param b IQ17 type value input to be multiplied. * * @return IQ17 type result of the multiplication. */ int32_t _IQ17rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 17); } /** * @brief Multiplies two IQ16 numbers, with rounding and saturation. * * @param a IQ16 type value input to be multiplied. * @param b IQ16 type value input to be multiplied. * * @return IQ16 type result of the multiplication. */ int32_t _IQ16rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 16); } /** * @brief Multiplies two IQ15 numbers, with rounding and saturation. * * @param a IQ15 type value input to be multiplied. * @param b IQ15 type value input to be multiplied. * * @return IQ15 type result of the multiplication. */ int32_t _IQ15rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 15); } /** * @brief Multiplies two IQ14 numbers, with rounding and saturation. * * @param a IQ14 type value input to be multiplied. * @param b IQ14 type value input to be multiplied. * * @return IQ14 type result of the multiplication. */ int32_t _IQ14rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 14); } /** * @brief Multiplies two IQ13 numbers, with rounding and saturation. * * @param a IQ13 type value input to be multiplied. * @param b IQ13 type value input to be multiplied. * * @return IQ13 type result of the multiplication. */ int32_t _IQ13rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 13); } /** * @brief Multiplies two IQ12 numbers, with rounding and saturation. * * @param a IQ12 type value input to be multiplied. * @param b IQ12 type value input to be multiplied. * * @return IQ12 type result of the multiplication. */ int32_t _IQ12rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 12); } /** * @brief Multiplies two IQ11 numbers, with rounding and saturation. * * @param a IQ11 type value input to be multiplied. * @param b IQ11 type value input to be multiplied. * * @return IQ11 type result of the multiplication. */ int32_t _IQ11rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 11); } /** * @brief Multiplies two IQ10 numbers, with rounding and saturation. * * @param a IQ10 type value input to be multiplied. * @param b IQ10 type value input to be multiplied. * * @return IQ10 type result of the multiplication. */ int32_t _IQ10rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 10); } /** * @brief Multiplies two IQ9 numbers, with rounding and saturation. * * @param a IQ9 type value input to be multiplied. * @param b IQ9 type value input to be multiplied. * * @return IQ9 type result of the multiplication. */ int32_t _IQ9rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 9); } /** * @brief Multiplies two IQ8 numbers, with rounding and saturation. * * @param a IQ8 type value input to be multiplied. * @param b IQ8 type value input to be multiplied. * * @return IQ8 type result of the multiplication. */ int32_t _IQ8rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 8); } /** * @brief Multiplies two IQ7 numbers, with rounding and saturation. * * @param a IQ7 type value input to be multiplied. * @param b IQ7 type value input to be multiplied. * * @return IQ7 type result of the multiplication. */ int32_t _IQ7rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 7); } /** * @brief Multiplies two IQ6 numbers, with rounding and saturation. * * @param a IQ6 type value input to be multiplied. * @param b IQ6 type value input to be multiplied. * * @return IQ6 type result of the multiplication. */ int32_t _IQ6rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 6); } /** * @brief Multiplies two IQ5 numbers, with rounding and saturation. * * @param a IQ5 type value input to be multiplied. * @param b IQ5 type value input to be multiplied. * * @return IQ5 type result of the multiplication. */ int32_t _IQ5rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 5); } /** * @brief Multiplies two IQ4 numbers, with rounding and saturation. * * @param a IQ4 type value input to be multiplied. * @param b IQ4 type value input to be multiplied. * * @return IQ4 type result of the multiplication. */ int32_t _IQ4rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 4); } /** * @brief Multiplies two IQ3 numbers, with rounding and saturation. * * @param a IQ3 type value input to be multiplied. * @param b IQ3 type value input to be multiplied. * * @return IQ3 type result of the multiplication. */ int32_t _IQ3rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 3); } /** * @brief Multiplies two IQ2 numbers, with rounding and saturation. * * @param a IQ2 type value input to be multiplied. * @param b IQ2 type value input to be multiplied. * * @return IQ2 type result of the multiplication. */ int32_t _IQ2rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 2); } /** * @brief Multiplies two IQ1 numbers, with rounding and saturation. * * @param a IQ1 type value input to be multiplied. * @param b IQ1 type value input to be multiplied. * * @return IQ1 type result of the multiplication. */ int32_t _IQ1rsmpy(int32_t a, int32_t b) { return __IQNrsmpy(a, b, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNsin_cos.c ================================================ /*!**************************************************************************** * @file _IQNsin_cos.c * @brief Functions to compute the sine and cosine of the input * and return the result. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" #include "../include/IQmathLib.h" /*! * @brief The value of PI */ #define PI (3.1415926536) /*! * @brief Used to specify sine operation */ #define TYPE_SIN (0) /*! * @brief Used to specify cosine operation */ #define TYPE_COS (1) /*! * @brief Used to specify result in radians */ #define TYPE_RAD (0) /*! * @brief Used to specify per-unit result */ #define TYPE_PU (1) #if ((!defined (__IQMATH_USE_MATHACL__)) || (!defined (__MSPM0_HAS_MATHACL__))) /** * @brief Computes the sine of an UIQ31 input. * * @param uiq31Input UIQ31 type input. * * @return UIQ31 type result of sine. */ /* * Perform the calculation where the input is only in the first quadrant * using one of the following two functions. * * This algorithm is derived from the following trig identities: * sin(k + x) = sin(k)*cos(x) + cos(k)*sin(x) * cos(k + x) = cos(k)*cos(x) - sin(k)*sin(x) * * First we calculate an index k and the remainder x according to the following * formulas: * * k = 0x3F & int(Radian*64) * x = fract(Radian*64)/64 * * Two lookup tables store the values of sin(k) and cos(k) for all possible * indexes. The remainder, x, is calculated using second order Taylor series. * * sin(x) = x - (x^3)/6 (~36.9 bits of accuracy) * cos(x) = 1 - (x^2)/2 (~28.5 bits of accuracy) * * Combining the trig identities with the Taylor series approximiations gives * the following two functions: * * cos(Radian) = C(k) + x*(-S(k) + 0.5*x*(-C(k) + 0.333*x*S(k))) * sin(Radian) = S(k) + x*(C(k) + 0.5*x*(-S(k) - 0.333*x*C(k))) * * where S(k) = Sin table value at offset "k" * C(k) = Cos table value at offset "k" * * Using a lookup table with a 64 bit index (52 indexes since the input range is * only 0 - 0.785398) and second order Taylor series gives 28 bits of accuracy. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNcalcSin) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNcalcSin(uint_fast32_t uiq31Input) { uint_fast16_t index; int_fast32_t iq31X; int_fast32_t iq31Sin; int_fast32_t iq31Cos; int_fast32_t iq31Res; /* Calculate index for sin and cos lookup using bits 31:26 */ index = (uint_fast16_t)(uiq31Input >> 25) & 0x003f; /* Lookup S(k) and C(k) values. */ iq31Sin = _IQ31SinLookup[index]; iq31Cos = _IQ31CosLookup[index]; /* * Calculated x (the remainder) by subtracting the index from the unsigned * iq31 input. This can be accomplished by masking out the bits used for * the index. */ iq31X = uiq31Input & 0x01ffffff; /* 0.333*x*C(k) */ iq31Res = __mpyf_l(0x2aaaaaab, iq31X); iq31Res = __mpyf_l(iq31Cos, iq31Res); /* -S(k) - 0.333*x*C(k) */ iq31Res = -(iq31Sin + iq31Res); /* 0.5*x*(-S(k) - 0.333*x*C(k)) */ iq31Res = iq31Res >> 1; iq31Res = __mpyf_l(iq31X, iq31Res); /* C(k) + 0.5*x*(-S(k) - 0.333*x*C(k)) */ iq31Res = iq31Cos + iq31Res; /* x*(C(k) + 0.5*x*(-S(k) - 0.333*x*C(k))) */ iq31Res = __mpyf_l(iq31X, iq31Res); /* sin(Radian) = S(k) + x*(C(k) + 0.5*x*(-S(k) - 0.333*x*C(k))) */ iq31Res = iq31Sin + iq31Res; return iq31Res; } /** * @brief Computes the cosine of an UIQ31 input. * * @param uiq31Input UIQ31 type input. * * @return UIQ31 type result of cosine. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNcalcCos) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNcalcCos(uint_fast32_t uiq31Input) { uint_fast16_t index; int_fast32_t iq31X; int_fast32_t iq31Sin; int_fast32_t iq31Cos; int_fast32_t iq31Res; /* Calculate index for sin and cos lookup using bits 31:26 */ index = (uint_fast16_t)(uiq31Input >> 25) & 0x003f; /* Lookup S(k) and C(k) values. */ iq31Sin = _IQ31SinLookup[index]; iq31Cos = _IQ31CosLookup[index]; /* * Calculated x (the remainder) by subtracting the index from the unsigned * iq31 input. This can be accomplished by masking out the bits used for * the index. */ iq31X = uiq31Input & 0x01ffffff; /* 0.333*x*S(k) */ iq31Res = __mpyf_l(0x2aaaaaab, iq31X); iq31Res = __mpyf_l(iq31Sin, iq31Res); /* -C(k) + 0.333*x*S(k) */ iq31Res = iq31Res - iq31Cos; /* 0.5*x*(-C(k) + 0.333*x*S(k)) */ iq31Res = iq31Res >> 1; iq31Res = __mpyf_l(iq31X, iq31Res); /* -S(k) + 0.5*x*(-C(k) + 0.333*x*S(k)) */ iq31Res = iq31Res - iq31Sin; /* x*(-S(k) + 0.5*x*(-C(k) + 0.333*x*S(k))) */ iq31Res = __mpyf_l(iq31X, iq31Res); /* cos(Radian) = C(k) + x*(-S(k) + 0.5*x*(-C(k) + 0.333*x*S(k))) */ iq31Res = iq31Cos + iq31Res; return iq31Res; } /** * @brief Computes the sine or cosine of an IQN input. * * @param iqNInput IQN type input. * @param q_value IQ format. * @param type Specifies sine or cosine operation. * @param format Specifies radians or per-unit operation. * * @return IQN type result of sin or cosine operation. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNsin_cos) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNsin_cos(int_fast32_t iqNInput, const int8_t q_value, const int8_t type, const int8_t format) { uint8_t ui8Sign = 0; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; uint_fast32_t uiq29Input; uint_fast32_t uiq30Input; uint_fast32_t uiq31Input; uint_fast32_t uiq32Input; uint_fast32_t uiq31Result = 0; /* Remove sign from input */ if (iqNInput < 0) { iqNInput = -iqNInput; /* Flip sign only for sin */ if (type == TYPE_SIN) { ui8Sign = 1; } } /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* Per unit API */ if (format == TYPE_PU) { /* * Scale input to unsigned iq32 to allow for maximum range. This removes * the integer component of the per unit input. */ uiq32Input = (uint_fast32_t)iqNInput << (32 - q_value); /* Reduce the input to the first two quadrants. */ if (uiq32Input >= 0x80000000) { uiq32Input -= 0x80000000; ui8Sign ^= 1; } /* * Multiply unsigned iq32 input by 2*pi and scale to unsigned iq30: * iq32 * iq30 = iq30 * 2 */ uiq30Input = __mpyf_ul(uiq32Input, iq30_pi); } /* Radians API */ else { /* Calculate the exponent difference from input format to iq29. */ int_fast16_t exp = 29 - q_value; /* Save input as unsigned iq29 format. */ uiq29Input = (uint_fast32_t)iqNInput; /* Reduce the input exponent to zero by scaling by 2*pi. */ while (exp) { if (uiq29Input >= iq29_pi) { uiq29Input -= iq29_pi; } uiq29Input <<= 1; exp--; } /* Reduce the range to the first two quadrants. */ if (uiq29Input >= iq29_pi) { uiq29Input -= iq29_pi; ui8Sign ^= 1; } /* Scale the unsigned iq29 input to unsigned iq30. */ uiq30Input = uiq29Input << 1; } /* Reduce the iq30 input range to the first quadrant. */ if (uiq30Input >= iq30_halfPi) { uiq30Input = iq30_pi - uiq30Input; /* flip sign for cos calculations */ if (type == TYPE_COS) { ui8Sign ^= 1; } } /* Convert the unsigned iq30 input to unsigned iq31 */ uiq31Input = uiq30Input << 1; /* Only one of these cases will be compiled per function. */ if (type == TYPE_COS) { /* If input is greater than pi/4 use sin for calculations */ if (uiq31Input > iq31_quarterPi) { uiq31Input = iq31_halfPi - uiq31Input; uiq31Result = __IQNcalcSin(uiq31Input); } else { uiq31Result = __IQNcalcCos(uiq31Input); } } else if (type == TYPE_SIN) { /* If input is greater than pi/4 use cos for calculations */ if (uiq31Input > iq31_quarterPi) { uiq31Input = iq31_halfPi - uiq31Input; uiq31Result = __IQNcalcCos(uiq31Input); } else { uiq31Result = __IQNcalcSin(uiq31Input); } } /* * Mark the end of all multiplies. This restores MPY and interrupt states * (MSP430 only). */ __mpy_stop(&ui16IntState, &ui16MPYState); /* Shift to Q type */ uiq31Result >>= (31 - q_value); /* set sign */ if (ui8Sign) { uiq31Result = -uiq31Result; } return uiq31Result; } #else /** * @brief Computes the sine or cosine of an IQN input, using MathACL. * * @param iqNInput IQN type input. * @param q_value IQ format. * @param type Specifies sine or cosine operation. * @param format Specifies radians or per-unit operation. * * @return IQN type result of sin or cosine operation. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNsin_cos) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNsin_cos(int_fast32_t iqNInput, const int8_t q_value, const int8_t type, const int8_t format) { int_fast32_t res, res1, resMult, resDiv; int_fast32_t iq31input; /* Per unit API */ if (format == TYPE_PU) { /* multiply by 2 for MathACL scaling. */ resMult = (uint_fast32_t)iqNInput << (1); /* shift to IQ31 for sin/cos calculation */ iq31input = (uint_fast32_t)resMult << (31 - q_value); } /* Radians API */ else { /* divide by PI for MathACL scaling * write control */ MATHACL->CTL = 4 | (q_value << 8) | (1 << 5); /* write operands to HWA. OP2 = divisor, OP1 = dividend */ MATHACL->OP2 = ((uint_fast32_t)((PI) * ((uint_fast32_t)1 << q_value))); /* trigger is write to OP1 */ MATHACL->OP1 = iqNInput; /* read quotient and remainder */ resDiv = MATHACL->RES1; /* shift from q_value to IQ31 for sin/cos calculation */ iq31input = (uint_fast32_t)resDiv << (31 - q_value); } /* * write control * operation = sincos, iterations = 31 */ MATHACL->CTL = 1 | (31 << 24); /* write operand to HWA */ MATHACL->OP1 = iq31input; if (type == TYPE_COS) { /* read cosine */ res1 = MATHACL->RES1; } else if (type == TYPE_SIN) { /* read sine */ res1 = MATHACL->RES2; } /* Shift to q_value type */ res = res1 >> (31 - q_value); return res; } #endif /* IQ sin functions */ /** * @brief Computes the cosine of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of cosine operation, in radians. */ int32_t _IQ29sin(int32_t a) { return __IQNsin_cos(a, 29, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of sine operation, in radians. */ int32_t _IQ28sin(int32_t a) { return __IQNsin_cos(a, 28, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of sine operation, in radians. */ int32_t _IQ27sin(int32_t a) { return __IQNsin_cos(a, 27, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of sine operation, in radians. */ int32_t _IQ26sin(int32_t a) { return __IQNsin_cos(a, 26, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of sine operation, in radians. */ int32_t _IQ25sin(int32_t a) { return __IQNsin_cos(a, 25, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of sine operation, in radians. */ int32_t _IQ24sin(int32_t a) { return __IQNsin_cos(a, 24, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of sine operation, in radians. */ int32_t _IQ23sin(int32_t a) { return __IQNsin_cos(a, 23, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of sine operation, in radians. */ int32_t _IQ22sin(int32_t a) { return __IQNsin_cos(a, 22, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of sine operation, in radians. */ int32_t _IQ21sin(int32_t a) { return __IQNsin_cos(a, 21, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of sine operation, in radians. */ int32_t _IQ20sin(int32_t a) { return __IQNsin_cos(a, 20, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of sine operation, in radians. */ int32_t _IQ19sin(int32_t a) { return __IQNsin_cos(a, 19, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of sine operation, in radians. */ int32_t _IQ18sin(int32_t a) { return __IQNsin_cos(a, 18, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of sine operation, in radians. */ int32_t _IQ17sin(int32_t a) { return __IQNsin_cos(a, 17, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of sine operation, in radians. */ int32_t _IQ16sin(int32_t a) { return __IQNsin_cos(a, 16, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of sine operation, in radians. */ int32_t _IQ15sin(int32_t a) { return __IQNsin_cos(a, 15, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of sine operation, in radians. */ int32_t _IQ14sin(int32_t a) { return __IQNsin_cos(a, 14, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of sine operation, in radians. */ int32_t _IQ13sin(int32_t a) { return __IQNsin_cos(a, 13, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of sine operation, in radians. */ int32_t _IQ12sin(int32_t a) { return __IQNsin_cos(a, 12, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of sine operation, in radians. */ int32_t _IQ11sin(int32_t a) { return __IQNsin_cos(a, 11, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of sine operation, in radians. */ int32_t _IQ10sin(int32_t a) { return __IQNsin_cos(a, 10, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of sine operation, in radians. */ int32_t _IQ9sin(int32_t a) { return __IQNsin_cos(a, 9, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of sine operation, in radians. */ int32_t _IQ8sin(int32_t a) { return __IQNsin_cos(a, 8, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of sine operation, in radians. */ int32_t _IQ7sin(int32_t a) { return __IQNsin_cos(a, 7, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of sine operation, in radians. */ int32_t _IQ6sin(int32_t a) { return __IQNsin_cos(a, 6, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of sine operation, in radians. */ int32_t _IQ5sin(int32_t a) { return __IQNsin_cos(a, 5, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of sine operation, in radians. */ int32_t _IQ4sin(int32_t a) { return __IQNsin_cos(a, 4, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of sine operation, in radians. */ int32_t _IQ3sin(int32_t a) { return __IQNsin_cos(a, 3, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of sine operation, in radians. */ int32_t _IQ2sin(int32_t a) { return __IQNsin_cos(a, 2, TYPE_SIN, TYPE_RAD); } /** * @brief Computes the sine of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of sine operation, in radians. */ int32_t _IQ1sin(int32_t a) { return __IQNsin_cos(a, 1, TYPE_SIN, TYPE_RAD); } /* IQ cos functions */ /** * @brief Computes the cosine of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of cosine operation, in radians. */ int32_t _IQ29cos(int32_t a) { return __IQNsin_cos(a, 29, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of cosine operation, in radians. */ int32_t _IQ28cos(int32_t a) { return __IQNsin_cos(a, 28, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of cosine operation, in radians. */ int32_t _IQ27cos(int32_t a) { return __IQNsin_cos(a, 27, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of cosine operation, in radians. */ int32_t _IQ26cos(int32_t a) { return __IQNsin_cos(a, 26, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of cosine operation, in radians. */ int32_t _IQ25cos(int32_t a) { return __IQNsin_cos(a, 25, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of cosine operation, in radians. */ int32_t _IQ24cos(int32_t a) { return __IQNsin_cos(a, 24, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of cosine operation, in radians. */ int32_t _IQ23cos(int32_t a) { return __IQNsin_cos(a, 23, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of cosine operation, in radians. */ int32_t _IQ22cos(int32_t a) { return __IQNsin_cos(a, 22, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of cosine operation, in radians. */ int32_t _IQ21cos(int32_t a) { return __IQNsin_cos(a, 21, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of cosine operation, in radians. */ int32_t _IQ20cos(int32_t a) { return __IQNsin_cos(a, 20, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of cosine operation, in radians. */ int32_t _IQ19cos(int32_t a) { return __IQNsin_cos(a, 19, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of cosine operation, in radians. */ int32_t _IQ18cos(int32_t a) { return __IQNsin_cos(a, 18, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of cosine operation, in radians. */ int32_t _IQ17cos(int32_t a) { return __IQNsin_cos(a, 17, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of cosine operation, in radians. */ int32_t _IQ16cos(int32_t a) { return __IQNsin_cos(a, 16, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of cosine operation, in radians. */ int32_t _IQ15cos(int32_t a) { return __IQNsin_cos(a, 15, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of cosine operation, in radians. */ int32_t _IQ14cos(int32_t a) { return __IQNsin_cos(a, 14, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of cosine operation, in radians. */ int32_t _IQ13cos(int32_t a) { return __IQNsin_cos(a, 13, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of cosine operation, in radians. */ int32_t _IQ12cos(int32_t a) { return __IQNsin_cos(a, 12, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of cosine operation, in radians. */ int32_t _IQ11cos(int32_t a) { return __IQNsin_cos(a, 11, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of cosine operation, in radians. */ int32_t _IQ10cos(int32_t a) { return __IQNsin_cos(a, 10, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of cosine operation, in radians. */ int32_t _IQ9cos(int32_t a) { return __IQNsin_cos(a, 9, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of cosine operation, in radians. */ int32_t _IQ8cos(int32_t a) { return __IQNsin_cos(a, 8, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of cosine operation, in radians. */ int32_t _IQ7cos(int32_t a) { return __IQNsin_cos(a, 7, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of cosine operation, in radians. */ int32_t _IQ6cos(int32_t a) { return __IQNsin_cos(a, 6, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of cosine operation, in radians. */ int32_t _IQ5cos(int32_t a) { return __IQNsin_cos(a, 5, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of cosine operation, in radians. */ int32_t _IQ4cos(int32_t a) { return __IQNsin_cos(a, 4, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of cosine operation, in radians. */ int32_t _IQ3cos(int32_t a) { return __IQNsin_cos(a, 3, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of cosine operation, in radians. */ int32_t _IQ2cos(int32_t a) { return __IQNsin_cos(a, 2, TYPE_COS, TYPE_RAD); } /** * @brief Computes the cosine of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of cosine operation, in radians. */ int32_t _IQ1cos(int32_t a) { return __IQNsin_cos(a, 1, TYPE_COS, TYPE_RAD); } /* IQ sinPU functions */ /** * @brief Computes the sine of an IQ31 input. * * @param a IQ31 type input. * * @return IQ31 type per-unit result of sine operation. */ int32_t _IQ31sinPU(int32_t a) { return __IQNsin_cos(a, 31, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type per-unit result of sine operation. */ int32_t _IQ30sinPU(int32_t a) { return __IQNsin_cos(a, 30, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type per-unit result of sine operation. */ int32_t _IQ29sinPU(int32_t a) { return __IQNsin_cos(a, 29, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type per-unit result of sine operation. */ int32_t _IQ28sinPU(int32_t a) { return __IQNsin_cos(a, 28, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type per-unit result of sine operation. */ int32_t _IQ27sinPU(int32_t a) { return __IQNsin_cos(a, 27, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type per-unit result of sine operation. */ int32_t _IQ26sinPU(int32_t a) { return __IQNsin_cos(a, 26, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type per-unit result of sine operation. */ int32_t _IQ25sinPU(int32_t a) { return __IQNsin_cos(a, 25, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type per-unit result of sine operation. */ int32_t _IQ24sinPU(int32_t a) { return __IQNsin_cos(a, 24, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type per-unit result of sine operation. */ int32_t _IQ23sinPU(int32_t a) { return __IQNsin_cos(a, 23, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type per-unit result of sine operation. */ int32_t _IQ22sinPU(int32_t a) { return __IQNsin_cos(a, 22, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type per-unit result of sine operation. */ int32_t _IQ21sinPU(int32_t a) { return __IQNsin_cos(a, 21, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type per-unit result of sine operation. */ int32_t _IQ20sinPU(int32_t a) { return __IQNsin_cos(a, 20, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type per-unit result of sine operation. */ int32_t _IQ19sinPU(int32_t a) { return __IQNsin_cos(a, 19, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type per-unit result of sine operation. */ int32_t _IQ18sinPU(int32_t a) { return __IQNsin_cos(a, 18, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type per-unit result of sine operation. */ int32_t _IQ17sinPU(int32_t a) { return __IQNsin_cos(a, 17, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type per-unit result of sine operation. */ int32_t _IQ16sinPU(int32_t a) { return __IQNsin_cos(a, 16, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type per-unit result of sine operation. */ int32_t _IQ15sinPU(int32_t a) { return __IQNsin_cos(a, 15, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type per-unit result of sine operation. */ int32_t _IQ14sinPU(int32_t a) { return __IQNsin_cos(a, 14, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type per-unit result of sine operation. */ int32_t _IQ13sinPU(int32_t a) { return __IQNsin_cos(a, 13, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type per-unit result of sine operation. */ int32_t _IQ12sinPU(int32_t a) { return __IQNsin_cos(a, 12, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type per-unit result of sine operation. */ int32_t _IQ11sinPU(int32_t a) { return __IQNsin_cos(a, 11, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type per-unit result of sine operation. */ int32_t _IQ10sinPU(int32_t a) { return __IQNsin_cos(a, 10, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type per-unit result of sine operation. */ int32_t _IQ9sinPU(int32_t a) { return __IQNsin_cos(a, 9, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type per-unit result of sine operation. */ int32_t _IQ8sinPU(int32_t a) { return __IQNsin_cos(a, 8, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type per-unit result of sine operation. */ int32_t _IQ7sinPU(int32_t a) { return __IQNsin_cos(a, 7, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type per-unit result of sine operation. */ int32_t _IQ6sinPU(int32_t a) { return __IQNsin_cos(a, 6, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type per-unit result of sine operation. */ int32_t _IQ5sinPU(int32_t a) { return __IQNsin_cos(a, 5, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type per-unit result of sine operation. */ int32_t _IQ4sinPU(int32_t a) { return __IQNsin_cos(a, 4, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type per-unit result of sine operation. */ int32_t _IQ3sinPU(int32_t a) { return __IQNsin_cos(a, 3, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type per-unit result of sine operation. */ int32_t _IQ2sinPU(int32_t a) { return __IQNsin_cos(a, 2, TYPE_SIN, TYPE_PU); } /** * @brief Computes the sine of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type per-unit result of sine operation. */ int32_t _IQ1sinPU(int32_t a) { return __IQNsin_cos(a, 1, TYPE_SIN, TYPE_PU); } /* IQ cosPU functions */ /** * @brief Computes the cosine of an IQ31 input. * * @param a IQ31 type input. * * @return IQ31 type per-unit result of cosine operation. */ int32_t _IQ31cosPU(int32_t a) { return __IQNsin_cos(a, 31, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type per-unit result of cosine operation. */ int32_t _IQ30cosPU(int32_t a) { return __IQNsin_cos(a, 30, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type per-unit result of cosine operation. */ int32_t _IQ29cosPU(int32_t a) { return __IQNsin_cos(a, 29, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type per-unit result of cosine operation. */ int32_t _IQ28cosPU(int32_t a) { return __IQNsin_cos(a, 28, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type per-unit result of cosine operation. */ int32_t _IQ27cosPU(int32_t a) { return __IQNsin_cos(a, 27, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type per-unit result of cosine operation. */ int32_t _IQ26cosPU(int32_t a) { return __IQNsin_cos(a, 26, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type per-unit result of cosine operation. */ int32_t _IQ25cosPU(int32_t a) { return __IQNsin_cos(a, 25, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type per-unit result of cosine operation. */ int32_t _IQ24cosPU(int32_t a) { return __IQNsin_cos(a, 24, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type per-unit result of cosine operation. */ int32_t _IQ23cosPU(int32_t a) { return __IQNsin_cos(a, 23, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type per-unit result of cosine operation. */ int32_t _IQ22cosPU(int32_t a) { return __IQNsin_cos(a, 22, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type per-unit result of cosine operation. */ int32_t _IQ21cosPU(int32_t a) { return __IQNsin_cos(a, 21, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type per-unit result of cosine operation. */ int32_t _IQ20cosPU(int32_t a) { return __IQNsin_cos(a, 20, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type per-unit result of cosine operation. */ int32_t _IQ19cosPU(int32_t a) { return __IQNsin_cos(a, 19, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type per-unit result of cosine operation. */ int32_t _IQ18cosPU(int32_t a) { return __IQNsin_cos(a, 18, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type per-unit result of cosine operation. */ int32_t _IQ17cosPU(int32_t a) { return __IQNsin_cos(a, 17, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type per-unit result of cosine operation. */ int32_t _IQ16cosPU(int32_t a) { return __IQNsin_cos(a, 16, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type per-unit result of cosine operation. */ int32_t _IQ15cosPU(int32_t a) { return __IQNsin_cos(a, 15, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type per-unit result of cosine operation. */ int32_t _IQ14cosPU(int32_t a) { return __IQNsin_cos(a, 14, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type per-unit result of cosine operation. */ int32_t _IQ13cosPU(int32_t a) { return __IQNsin_cos(a, 13, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type per-unit result of cosine operation. */ int32_t _IQ12cosPU(int32_t a) { return __IQNsin_cos(a, 12, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type per-unit result of cosine operation. */ int32_t _IQ11cosPU(int32_t a) { return __IQNsin_cos(a, 11, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type per-unit result of cosine operation. */ int32_t _IQ10cosPU(int32_t a) { return __IQNsin_cos(a, 10, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type per-unit result of cosine operation. */ int32_t _IQ9cosPU(int32_t a) { return __IQNsin_cos(a, 9, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type per-unit result of cosine operation. */ int32_t _IQ8cosPU(int32_t a) { return __IQNsin_cos(a, 8, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type per-unit result of cosine operation. */ int32_t _IQ7cosPU(int32_t a) { return __IQNsin_cos(a, 7, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type per-unit result of cosine operation. */ int32_t _IQ6cosPU(int32_t a) { return __IQNsin_cos(a, 6, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type per-unit result of cosine operation. */ int32_t _IQ5cosPU(int32_t a) { return __IQNsin_cos(a, 5, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type per-unit result of cosine operation. */ int32_t _IQ4cosPU(int32_t a) { return __IQNsin_cos(a, 4, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type per-unit result of cosine operation. */ int32_t _IQ3cosPU(int32_t a) { return __IQNsin_cos(a, 3, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type per-unit result of cosine operation. */ int32_t _IQ2cosPU(int32_t a) { return __IQNsin_cos(a, 2, TYPE_COS, TYPE_PU); } /** * @brief Computes the cosine of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type per-unit result of cosine operation. */ int32_t _IQ1cosPU(int32_t a) { return __IQNsin_cos(a, 1, TYPE_COS, TYPE_PU); } ================================================ FILE: iqmath/_IQNfunctions/_IQNsqrt.c ================================================ /*!**************************************************************************** * @file _IQNsqrt.c * @brief Functions to compute square root, inverse square root and the * magnitude of two IQN inputs. * *
******************************************************************************/ #include #include "../support/support.h" #include "_IQNtables.h" #include "../include/IQmathLib.h" /*! * @brief Specifies inverse square root operation type. */ #define TYPE_ISQRT (0) /*! * @brief Specifies square root operation type. */ #define TYPE_SQRT (1) /*! * @brief Specifies magnitude operation type. */ #define TYPE_MAG (2) /*! * @brief Specifies inverse magnitude operation type. */ #define TYPE_IMAG (3) /** * @brief Calculate square root, inverse square root and the magnitude of two inputs. * * @param iqNInputX IQN type input x. * @param iqNInputY IQN type input y. * @param type Operation type. * @param q_value IQ format. * * @return IQN type result of the square root or magnitude operation. */ /* * Calculate square root, inverse square root and the magnitude of two inputs * using a Newton-Raphson iterative method. This method takes an initial guess * and performs an error correction with each iteration. The equation is: * * x1 = x0 - f(x0)/f'(x0) * * Where f' is the derivative of f. The approximation for inverse square root * is: * * g' = g * (1.5 - (x/2) * g * g) * * g' = new guess approximation * g = best guess approximation * x = input * * The inverse square root is multiplied by the initial input x to get the * square root result for square root and magnitude functions. * * root(x) = x * 1/root(x) */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNsqrt) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNsqrt(int_fast32_t iqNInputX, int_fast32_t iqNInputY, const int8_t q_value, const int8_t type) { uint8_t ui8Index; uint8_t ui8Loops; int_fast16_t i16Exponent; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; uint_fast32_t uiq30Guess; uint_fast32_t uiq30Result; uint_fast32_t uiq31Result; uint_fast32_t uiq32Input; /* If the type is (inverse) magnitude we need to calculate x^2 + y^2 first. */ if (type == TYPE_MAG || type == TYPE_IMAG) { uint_fast64_t ui64Sum; __mpy_start(&ui16IntState, &ui16MPYState); /* Calculate x^2 */ ui64Sum = __mpyx(iqNInputX, iqNInputX); /* Calculate y^2 and add to x^2 */ ui64Sum += __mpyx(iqNInputY, iqNInputY); __mpy_stop(&ui16IntState, &ui16MPYState); /* Return if the magnitude is simply zero. */ if (ui64Sum == 0) { return 0; } /* * Initialize the exponent to positive for magnitude, negative for * inverse magnitude. */ if (type == TYPE_MAG) { i16Exponent = (32 - q_value); } else { i16Exponent = -(32 - q_value); } /* Shift to iq64 by keeping track of exponent. */ while ((uint_fast16_t)(ui64Sum >> 48) < 0x4000) { ui64Sum <<= 2; /* Decrement exponent for mag */ if (type == TYPE_MAG) { i16Exponent--; } /* Increment exponent for imag */ else { i16Exponent++; } } /* Shift ui64Sum to unsigned iq32 and set as uiq32Input */ uiq32Input = (uint_fast32_t)(ui64Sum >> 32); } else { /* check sign of input */ if (iqNInputX <= 0) { return 0; } /* If the q_value gives an odd starting exponent make it even. */ if ((32 - q_value) % 2 == 1) { iqNInputX <<= 1; /* Start with positive exponent for sqrt */ if (type == TYPE_SQRT) { i16Exponent = ((32 - q_value) - 1) >> 1; } /* start with negative exponent for isqrt */ else { i16Exponent = -(((32 - q_value) - 1) >> 1); } } else { /* start with positive exponent for sqrt */ if (type == TYPE_SQRT) { i16Exponent = (32 - q_value) >> 1; } /* start with negative exponent for isqrt */ else { i16Exponent = -((32 - q_value) >> 1); } } /* Save input as unsigned iq32. */ uiq32Input = (uint_fast32_t)iqNInputX; /* Shift to iq32 by keeping track of exponent */ while ((uint_fast16_t)(uiq32Input >> 16) < 0x4000) { uiq32Input <<= 2; /* Decrement exponent for sqrt and mag */ if (type) { i16Exponent--; } /* Increment exponent for isqrt */ else { i16Exponent++; } } } /* Use left most byte as index into lookup table (range: 32-128) */ ui8Index = uiq32Input >> 25; ui8Index -= 32; uiq30Guess = (uint_fast32_t)_IQ14sqrt_lookup[ui8Index] << 16; /* * Mark the start of any multiplies. This will disable interrupts and set * the multiplier to fractional mode. This is designed to reduce overhead * of constantly switching states when using repeated multiplies (MSP430 * only). */ __mpyf_start(&ui16IntState, &ui16MPYState); /* * Set the loop counter: * * iq1 <= q_value < 24 - 2 loops * iq22 <= q_value <= 31 - 3 loops */ if (q_value < 24) { ui8Loops = 2; } else { ui8Loops = 3; } /* Iterate through Newton-Raphson algorithm. */ while (ui8Loops--) { /* x*g */ uiq31Result = __mpyf_ul(uiq32Input, uiq30Guess); /* x*g*g */ uiq30Result = __mpyf_ul(uiq31Result, uiq30Guess); /* 3 - x*g*g */ uiq30Result = -(uiq30Result - 0xC0000000); /* * g/2*(3 - x*g*g) * uiq30Guess = uiq31Guess/2 */ uiq30Guess = __mpyf_ul(uiq30Guess, uiq30Result); } /* Calculate sqrt(x) for both sqrt and mag */ if (type == TYPE_SQRT || type == TYPE_MAG) { /* * uiq30Guess contains the inverse square root approximation, multiply * by uiq32Input to get square root result. */ uiq31Result = __mpyf_ul(uiq30Guess, uiq32Input); __mpy_stop(&ui16IntState, &ui16MPYState); /* * Shift the result right by 31 - q_value. */ i16Exponent -= (31 - q_value); /* Saturate value for any shift larger than 1 (only need this for mag) */ if (type == TYPE_MAG) { if (i16Exponent > 0) { return 0x7fffffff; } } /* Shift left by 1 check only needed for iq30 and iq31 mag/sqrt */ if (q_value >= 30) { if (i16Exponent > 0) { uiq31Result <<= 1; return uiq31Result; } } } /* Separate handling for isqrt and imag. */ else { __mpy_stop(&ui16IntState, &ui16MPYState); /* * Shift the result right by 31 - q_value, add one since we use the uiq30 * result without shifting. */ i16Exponent = i16Exponent - (31 - q_value) + 1; uiq31Result = uiq30Guess; /* Saturate any positive non-zero exponent for isqrt. */ if (i16Exponent > 0) { return 0x7fffffff; } } /* Shift uiq31Result right by -exponent */ if (i16Exponent <= -32) { return 0; } if (i16Exponent <= -16) { uiq31Result >>= 16; i16Exponent += 16; } if (i16Exponent <= -8) { uiq31Result >>= 8; i16Exponent += 8; } while (i16Exponent < -1) { uiq31Result >>= 1; i16Exponent++; } if (i16Exponent) { uiq31Result++; uiq31Result >>= 1; } return uiq31Result; } #if ((defined (__IQMATH_USE_MATHACL__)) && (defined (__MSPM0_HAS_MATHACL__))) /** * @brief Calculate square root of an IQN input, using MathACL. * * @param iqNInputX IQN type input. * @param q_value IQ format. * * @return IQN type result of the square root operation. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNsqrt_MathACL) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __IQNsqrt_MathACL(int_fast32_t iqNInputX, const int8_t q_value) { /* check sign of input */ if (iqNInputX <= 0) { return 0; } /* Scale factor computation: * output: IQ30 format value whose square root is to be computed by MATHACL * scale_factor: (n) where the input has been divided by 2^(2n) to render IQ30 * value in the range (1,2). */ uint32_t input, output; uint8_t scale_factor; uint8_t n = 0; input = iqNInputX; /* check input is within 32-bit boundaries */ if (input & 0x80000000) { scale_factor = 0; output = input; } else { n = 0; /* check while input != IQ30(1.0) */ while ((input & 0x40000000) == 0) { n++; /* multiply by 2 until reaching IQ30 [1.0,2.0 range] */ input <<= 1; } /* * Scale factor: take into account the shift from q_value to IQ30, the remaining value * is the scale factor such that scaled number = (nonscaled number)^(2^scale_factor) */ scale_factor = (30 - q_value) - n; output = input; } /* SQRT MATHACL Operation * write control * CTL parameters are: sqrt operation | number of iterations | scale factor */ MATHACL->CTL = 5 | (31 << 24) | (scale_factor << 16); /* write operands to HWA * write to OP1 is the trigger */ MATHACL->OP1 = output; /* read sqrt * shift output from IQ16 to q_value */ if (q_value > 16) { return (uint_fast32_t)MATHACL->RES1 << (q_value - 16); } else { return (uint_fast32_t)MATHACL->RES1 >> (16 - q_value); } } #endif #if ((!defined (__IQMATH_USE_MATHACL__)) || (!defined (__MSPM0_HAS_MATHACL__))) /* RTS SQRT */ /** * @brief Calculate square root of an IQ31 input. * * @param a IQ31 type input. * * @return IQ31 type result of the square root operation. */ int32_t _IQ31sqrt(int32_t a) { return __IQNsqrt(a, 0, 31, TYPE_SQRT); } /** * @brief Calculate square root of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type result of the square root operation. */ int32_t _IQ30sqrt(int32_t a) { return __IQNsqrt(a, 0, 30, TYPE_SQRT); } /** * @brief Calculate square root of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of the square root operation. */ int32_t _IQ29sqrt(int32_t a) { return __IQNsqrt(a, 0, 29, TYPE_SQRT); } /** * @brief Calculate square root of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of the square root operation. */ int32_t _IQ28sqrt(int32_t a) { return __IQNsqrt(a, 0, 28, TYPE_SQRT); } /** * @brief Calculate square root of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of the square root operation. */ int32_t _IQ27sqrt(int32_t a) { return __IQNsqrt(a, 0, 27, TYPE_SQRT); } /** * @brief Calculate square root of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of the square root operation. */ int32_t _IQ26sqrt(int32_t a) { return __IQNsqrt(a, 0, 26, TYPE_SQRT); } /** * @brief Calculate square root of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of the square root operation. */ int32_t _IQ25sqrt(int32_t a) { return __IQNsqrt(a, 0, 25, TYPE_SQRT); } /** * @brief Calculate square root of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of the square root operation. */ int32_t _IQ24sqrt(int32_t a) { return __IQNsqrt(a, 0, 24, TYPE_SQRT); } /** * @brief Calculate square root of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of the square root operation. */ int32_t _IQ23sqrt(int32_t a) { return __IQNsqrt(a, 0, 23, TYPE_SQRT); } /** * @brief Calculate square root of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of the square root operation. */ int32_t _IQ22sqrt(int32_t a) { return __IQNsqrt(a, 0, 22, TYPE_SQRT); } /** * @brief Calculate square root of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of the square root operation. */ int32_t _IQ21sqrt(int32_t a) { return __IQNsqrt(a, 0, 21, TYPE_SQRT); } /** * @brief Calculate square root of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of the square root operation. */ int32_t _IQ20sqrt(int32_t a) { return __IQNsqrt(a, 0, 20, TYPE_SQRT); } /** * @brief Calculate square root of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of the square root operation. */ int32_t _IQ19sqrt(int32_t a) { return __IQNsqrt(a, 0, 19, TYPE_SQRT); } /** * @brief Calculate square root of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of the square root operation. */ int32_t _IQ18sqrt(int32_t a) { return __IQNsqrt(a, 0, 18, TYPE_SQRT); } /** * @brief Calculate square root of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of the square root operation. */ int32_t _IQ17sqrt(int32_t a) { return __IQNsqrt(a, 0, 17, TYPE_SQRT); } /** * @brief Calculate square root of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of the square root operation. */ int32_t _IQ16sqrt(int32_t a) { return __IQNsqrt(a, 0, 16, TYPE_SQRT); } /** * @brief Calculate square root of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of the square root operation. */ int32_t _IQ15sqrt(int32_t a) { return __IQNsqrt(a, 0, 15, TYPE_SQRT); } /** * @brief Calculate square root of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of the square root operation. */ int32_t _IQ14sqrt(int32_t a) { return __IQNsqrt(a, 0, 14, TYPE_SQRT); } /** * @brief Calculate square root of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of the square root operation. */ int32_t _IQ13sqrt(int32_t a) { return __IQNsqrt(a, 0, 13, TYPE_SQRT); } /** * @brief Calculate square root of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of the square root operation. */ int32_t _IQ12sqrt(int32_t a) { return __IQNsqrt(a, 0, 12, TYPE_SQRT); } /** * @brief Calculate square root of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of the square root operation. */ int32_t _IQ11sqrt(int32_t a) { return __IQNsqrt(a, 0, 11, TYPE_SQRT); } /** * @brief Calculate square root of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of the square root operation. */ int32_t _IQ10sqrt(int32_t a) { return __IQNsqrt(a, 0, 10, TYPE_SQRT); } /** * @brief Calculate square root of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of the square root operation. */ int32_t _IQ9sqrt(int32_t a) { return __IQNsqrt(a, 0, 9, TYPE_SQRT); } /** * @brief Calculate square root of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of the square root operation. */ int32_t _IQ8sqrt(int32_t a) { return __IQNsqrt(a, 0, 8, TYPE_SQRT); } /** * @brief Calculate square root of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of the square root operation. */ int32_t _IQ7sqrt(int32_t a) { return __IQNsqrt(a, 0, 7, TYPE_SQRT); } /** * @brief Calculate square root of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of the square root operation. */ int32_t _IQ6sqrt(int32_t a) { return __IQNsqrt(a, 0, 6, TYPE_SQRT); } /** * @brief Calculate square root of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of the square root operation. */ int32_t _IQ5sqrt(int32_t a) { return __IQNsqrt(a, 0, 5, TYPE_SQRT); } /** * @brief Calculate square root of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of the square root operation. */ int32_t _IQ4sqrt(int32_t a) { return __IQNsqrt(a, 0, 4, TYPE_SQRT); } /** * @brief Calculate square root of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of the square root operation. */ int32_t _IQ3sqrt(int32_t a) { return __IQNsqrt(a, 0, 3, TYPE_SQRT); } /** * @brief Calculate square root of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of the square root operation. */ int32_t _IQ2sqrt(int32_t a) { return __IQNsqrt(a, 0, 2, TYPE_SQRT); } /** * @brief Calculate square root of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of the square root operation. */ int32_t _IQ1sqrt(int32_t a) { return __IQNsqrt(a, 0, 1, TYPE_SQRT); } #else /* MATHACL SQRT */ /** * @brief Calculate square root of an IQ31 input, using MathACL. * * @param a IQ31 type input. * * @return IQ31 type result of the square root operation. */ int32_t _IQ31sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 31); } /** * @brief Calculate square root of an IQ30 input, using MathACL. * * @param a IQ30 type input. * * @return IQ30 type result of the square root operation. */ int32_t _IQ30sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 30); } /** * @brief Calculate square root of an IQ29 input, using MathACL. * * @param a IQ29 type input. * * @return IQ29 type result of the square root operation. */ int32_t _IQ29sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 29); } /** * @brief Calculate square root of an IQ28 input, using MathACL. * * @param a IQ28 type input. * * @return IQ28 type result of the square root operation. */ int32_t _IQ28sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 28); } /** * @brief Calculate square root of an IQ27 input, using MathACL. * * @param a IQ27 type input. * * @return IQ27 type result of the square root operation. */ int32_t _IQ27sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 27); } /** * @brief Calculate square root of an IQ26 input, using MathACL. * * @param a IQ26 type input. * * @return IQ26 type result of the square root operation. */ int32_t _IQ26sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 26); } /** * @brief Calculate square root of an IQ25 input, using MathACL. * * @param a IQ25 type input. * * @return IQ25 type result of the square root operation. */ int32_t _IQ25sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 25); } /** * @brief Calculate square root of an IQ24 input, using MathACL. * * @param a IQ24 type input. * * @return IQ24 type result of the square root operation. */ int32_t _IQ24sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 24); } /** * @brief Calculate square root of an IQ23 input, using MathACL. * * @param a IQ23 type input. * * @return IQ23 type result of the square root operation. */ int32_t _IQ23sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 23); } /** * @brief Calculate square root of an IQ22 input, using MathACL. * * @param a IQ22 type input. * * @return IQ22 type result of the square root operation. */ int32_t _IQ22sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 22); } /** * @brief Calculate square root of an IQ21 input, using MathACL. * * @param a IQ21 type input. * * @return IQ21 type result of the square root operation. */ int32_t _IQ21sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 21); } /** * @brief Calculate square root of an IQ20 input, using MathACL. * * @param a IQ20 type input. * * @return IQ20 type result of the square root operation. */ int32_t _IQ20sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 20); } /** * @brief Calculate square root of an IQ19 input, using MathACL. * * @param a IQ19 type input. * * @return IQ19 type result of the square root operation. */ int32_t _IQ19sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 19); } /** * @brief Calculate square root of an IQ18 input, using MathACL. * * @param a IQ18 type input. * * @return IQ18 type result of the square root operation. */ int32_t _IQ18sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 18); } /** * @brief Calculate square root of an IQ17 input, using MathACL. * * @param a IQ17 type input. * * @return IQ17 type result of the square root operation. */ int32_t _IQ17sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 17); } /** * @brief Calculate square root of an IQ16 input, using MathACL. * * @param a IQ16 type input. * * @return IQ16 type result of the square root operation. */ int32_t _IQ16sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 16); } /** * @brief Calculate square root of an IQ15 input, using MathACL. * * @param a IQ15 type input. * * @return IQ15 type result of the square root operation. */ int32_t _IQ15sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 15); } /** * @brief Calculate square root of an IQ14 input, using MathACL. * * @param a IQ14 type input. * * @return IQ14 type result of the square root operation. */ int32_t _IQ14sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 14); } /** * @brief Calculate square root of an IQ13 input, using MathACL. * * @param a IQ13 type input. * * @return IQ13 type result of the square root operation. */ int32_t _IQ13sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 13); } /** * @brief Calculate square root of an IQ12 input, using MathACL. * * @param a IQ12 type input. * * @return IQ12 type result of the square root operation. */ int32_t _IQ12sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 12); } /** * @brief Calculate square root of an IQ11 input, using MathACL. * * @param a IQ11 type input. * * @return IQ11 type result of the square root operation. */ int32_t _IQ11sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 11); } /** * @brief Calculate square root of an IQ10 input, using MathACL. * * @param a IQ10 type input. * * @return IQ10 type result of the square root operation. */ int32_t _IQ10sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 10); } /** * @brief Calculate square root of an IQ9 input, using MathACL. * * @param a IQ9 type input. * * @return IQ9 type result of the square root operation. */ int32_t _IQ9sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 9); } /** * @brief Calculate square root of an IQ8 input, using MathACL. * * @param a IQ8 type input. * * @return IQ8 type result of the square root operation. */ int32_t _IQ8sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 8); } /** * @brief Calculate square root of an IQ7 input, using MathACL. * * @param a IQ7 type input. * * @return IQ7 type result of the square root operation. */ int32_t _IQ7sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 7); } /** * @brief Calculate square root of an IQ6 input, using MathACL. * * @param a IQ6 type input. * * @return IQ6 type result of the square root operation. */ int32_t _IQ6sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 6); } /** * @brief Calculate square root of an IQ5 input, using MathACL. * * @param a IQ5 type input. * * @return IQ5 type result of the square root operation. */ int32_t _IQ5sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 5); } /** * @brief Calculate square root of an IQ4 input, using MathACL. * * @param a IQ4 type input. * * @return IQ4 type result of the square root operation. */ int32_t _IQ4sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 4); } /** * @brief Calculate square root of an IQ3 input, using MathACL. * * @param a IQ3 type input. * * @return IQ3 type result of the square root operation. */ int32_t _IQ3sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 3); } /** * @brief Calculate square root of an IQ2 input, using MathACL. * * @param a IQ2 type input. * * @return IQ2 type result of the square root operation. */ int32_t _IQ2sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 2); } /** * @brief Calculate square root of an IQ1 input, using MathACL. * * @param a IQ1 type input. * * @return IQ1 type result of the square root operation. */ int32_t _IQ1sqrt(int32_t a) { return __IQNsqrt_MathACL(a, 1); } #endif /* INVERSE SQRT */ /** * @brief Calculate inverse square root of an IQ30 input. * * @param a IQ30 type input. * * @return IQ30 type result of the inverse square root operation. */ int32_t _IQ30isqrt(int32_t a) { return __IQNsqrt(a, 0, 30, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ29 input. * * @param a IQ29 type input. * * @return IQ29 type result of the inverse square root operation. */ int32_t _IQ29isqrt(int32_t a) { return __IQNsqrt(a, 0, 29, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ28 input. * * @param a IQ28 type input. * * @return IQ28 type result of the inverse square root operation. */ int32_t _IQ28isqrt(int32_t a) { return __IQNsqrt(a, 0, 28, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ27 input. * * @param a IQ27 type input. * * @return IQ27 type result of the inverse square root operation. */ int32_t _IQ27isqrt(int32_t a) { return __IQNsqrt(a, 0, 27, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ26 input. * * @param a IQ26 type input. * * @return IQ26 type result of the inverse square root operation. */ int32_t _IQ26isqrt(int32_t a) { return __IQNsqrt(a, 0, 26, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ25 input. * * @param a IQ25 type input. * * @return IQ25 type result of the inverse square root operation. */ int32_t _IQ25isqrt(int32_t a) { return __IQNsqrt(a, 0, 25, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ24 input. * * @param a IQ24 type input. * * @return IQ24 type result of the inverse square root operation. */ int32_t _IQ24isqrt(int32_t a) { return __IQNsqrt(a, 0, 24, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ23 input. * * @param a IQ23 type input. * * @return IQ23 type result of the inverse square root operation. */ int32_t _IQ23isqrt(int32_t a) { return __IQNsqrt(a, 0, 23, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ22 input. * * @param a IQ22 type input. * * @return IQ22 type result of the inverse square root operation. */ int32_t _IQ22isqrt(int32_t a) { return __IQNsqrt(a, 0, 22, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ21 input. * * @param a IQ21 type input. * * @return IQ21 type result of the inverse square root operation. */ int32_t _IQ21isqrt(int32_t a) { return __IQNsqrt(a, 0, 21, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ20 input. * * @param a IQ20 type input. * * @return IQ20 type result of the inverse square root operation. */ int32_t _IQ20isqrt(int32_t a) { return __IQNsqrt(a, 0, 20, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ19 input. * * @param a IQ19 type input. * * @return IQ19 type result of the inverse square root operation. */ int32_t _IQ19isqrt(int32_t a) { return __IQNsqrt(a, 0, 19, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ18 input. * * @param a IQ18 type input. * * @return IQ18 type result of the inverse square root operation. */ int32_t _IQ18isqrt(int32_t a) { return __IQNsqrt(a, 0, 18, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ17 input. * * @param a IQ17 type input. * * @return IQ17 type result of the inverse square root operation. */ int32_t _IQ17isqrt(int32_t a) { return __IQNsqrt(a, 0, 17, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ16 input. * * @param a IQ16 type input. * * @return IQ16 type result of the inverse square root operation. */ int32_t _IQ16isqrt(int32_t a) { return __IQNsqrt(a, 0, 16, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ15 input. * * @param a IQ15 type input. * * @return IQ15 type result of the inverse square root operation. */ int32_t _IQ15isqrt(int32_t a) { return __IQNsqrt(a, 0, 15, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ14 input. * * @param a IQ14 type input. * * @return IQ14 type result of the inverse square root operation. */ int32_t _IQ14isqrt(int32_t a) { return __IQNsqrt(a, 0, 14, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ13 input. * * @param a IQ13 type input. * * @return IQ13 type result of the inverse square root operation. */ int32_t _IQ13isqrt(int32_t a) { return __IQNsqrt(a, 0, 13, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ12 input. * * @param a IQ12 type input. * * @return IQ12 type result of the inverse square root operation. */ int32_t _IQ12isqrt(int32_t a) { return __IQNsqrt(a, 0, 12, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ11 input. * * @param a IQ11 type input. * * @return IQ11 type result of the inverse square root operation. */ int32_t _IQ11isqrt(int32_t a) { return __IQNsqrt(a, 0, 11, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ10 input. * * @param a IQ10 type input. * * @return IQ10 type result of the inverse square root operation. */ int32_t _IQ10isqrt(int32_t a) { return __IQNsqrt(a, 0, 10, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ9 input. * * @param a IQ9 type input. * * @return IQ9 type result of the inverse square root operation. */ int32_t _IQ9isqrt(int32_t a) { return __IQNsqrt(a, 0, 9, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ8 input. * * @param a IQ8 type input. * * @return IQ8 type result of the inverse square root operation. */ int32_t _IQ8isqrt(int32_t a) { return __IQNsqrt(a, 0, 8, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ7 input. * * @param a IQ7 type input. * * @return IQ7 type result of the inverse square root operation. */ int32_t _IQ7isqrt(int32_t a) { return __IQNsqrt(a, 0, 7, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ6 input. * * @param a IQ6 type input. * * @return IQ6 type result of the inverse square root operation. */ int32_t _IQ6isqrt(int32_t a) { return __IQNsqrt(a, 0, 6, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ5 input. * * @param a IQ5 type input. * * @return IQ5 type result of the inverse square root operation. */ int32_t _IQ5isqrt(int32_t a) { return __IQNsqrt(a, 0, 5, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ4 input. * * @param a IQ4 type input. * * @return IQ4 type result of the inverse square root operation. */ int32_t _IQ4isqrt(int32_t a) { return __IQNsqrt(a, 0, 4, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ3 input. * * @param a IQ3 type input. * * @return IQ3 type result of the inverse square root operation. */ int32_t _IQ3isqrt(int32_t a) { return __IQNsqrt(a, 0, 3, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ2 input. * * @param a IQ2 type input. * * @return IQ2 type result of the inverse square root operation. */ int32_t _IQ2isqrt(int32_t a) { return __IQNsqrt(a, 0, 2, TYPE_ISQRT); } /** * @brief Calculate inverse square root of an IQ1 input. * * @param a IQ1 type input. * * @return IQ1 type result of the inverse square root operation. */ int32_t _IQ1isqrt(int32_t a) { return __IQNsqrt(a, 0, 1, TYPE_ISQRT); } /* MAGNITUDE */ /** * @brief Calculate the magnitude of two IQ31 inputs. * * @param a IQ31 type input. * @param b IQ31 type input. * * @return IQ31 type result of the magnitude operation. */ int32_t _IQmag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 31, TYPE_MAG); } /* INVERSE MAGNITUDE */ /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ30 type input. * @param b IQ30 type input. * * @return IQ30 type result of the inverse magnitude operation. */ int32_t _IQ30imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 30, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ29 type input. * @param b IQ29 type input. * * @return IQ29 type result of the inverse magnitude operation. */ int32_t _IQ29imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 29, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ28 type input. * @param b IQ28 type input. * * @return IQ28 type result of the inverse magnitude operation. */ int32_t _IQ28imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 28, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ27 type input. * @param b IQ27 type input. * * @return IQ27 type result of the inverse magnitude operation. */ int32_t _IQ27imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 27, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ26 type input. * @param b IQ26 type input. * * @return IQ26 type result of the inverse magnitude operation. */ int32_t _IQ26imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 26, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ25 type input. * @param b IQ25 type input. * * @return IQ25 type result of the inverse magnitude operation. */ int32_t _IQ25imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 25, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ24 type input. * @param b IQ24 type input. * * @return IQ24 type result of the inverse magnitude operation. */ int32_t _IQ24imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 24, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ23 type input. * @param b IQ23 type input. * * @return IQ23 type result of the inverse magnitude operation. */ int32_t _IQ23imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 23, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ22 type input. * @param b IQ22 type input. * * @return IQ22 type result of the inverse magnitude operation. */ int32_t _IQ22imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 22, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ21 type input. * @param b IQ21 type input. * * @return IQ21 type result of the inverse magnitude operation. */ int32_t _IQ21imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 21, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ20 type input. * @param b IQ20 type input. * * @return IQ20 type result of the inverse magnitude operation. */ int32_t _IQ20imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 20, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ19 type input. * @param b IQ19 type input. * * @return IQ19 type result of the inverse magnitude operation. */ int32_t _IQ19imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 19, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ18 type input. * @param b IQ18 type input. * * @return IQ18 type result of the inverse magnitude operation. */ int32_t _IQ18imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 18, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ17 type input. * @param b IQ17 type input. * * @return IQ17 type result of the inverse magnitude operation. */ int32_t _IQ17imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 17, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ16 type input. * @param b IQ16 type input. * * @return IQ16 type result of the inverse magnitude operation. */ int32_t _IQ16imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 16, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ15 type input. * @param b IQ15 type input. * * @return IQ15 type result of the inverse magnitude operation. */ int32_t _IQ15imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 15, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ14 type input. * @param b IQ14 type input. * * @return IQ14 type result of the inverse magnitude operation. */ int32_t _IQ14imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 14, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ13 type input. * @param b IQ13 type input. * * @return IQ13 type result of the inverse magnitude operation. */ int32_t _IQ13imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 13, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ12 type input. * @param b IQ12 type input. * * @return IQ12 type result of the inverse magnitude operation. */ int32_t _IQ12imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 12, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ11 type input. * @param b IQ11 type input. * * @return IQ11 type result of the inverse magnitude operation. */ int32_t _IQ11imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 11, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ10 type input. * @param b IQ10 type input. * * @return IQ10 type result of the inverse magnitude operation. */ int32_t _IQ10imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 10, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ9 type input. * @param b IQ9 type input. * * @return IQ9 type result of the inverse magnitude operation. */ int32_t _IQ9imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 9, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ8 type input. * @param b IQ8 type input. * * @return IQ8 type result of the inverse magnitude operation. */ int32_t _IQ8imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 8, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ7 type input. * @param b IQ7 type input. * * @return IQ7 type result of the inverse magnitude operation. */ int32_t _IQ7imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 7, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ6 type input. * @param b IQ6 type input. * * @return IQ6 type result of the inverse magnitude operation. */ int32_t _IQ6imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 6, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ5 type input. * @param b IQ5 type input. * * @return IQ5 type result of the inverse magnitude operation. */ int32_t _IQ5imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 5, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ4 type input. * @param b IQ4 type input. * * @return IQ4 type result of the inverse magnitude operation. */ int32_t _IQ4imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 4, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ3 type input. * @param b IQ3 type input. * * @return IQ3 type result of the inverse magnitude operation. */ int32_t _IQ3imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 3, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ2 type input. * @param b IQ2 type input. * * @return IQ2 type result of the inverse magnitude operation. */ int32_t _IQ2imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 2, TYPE_IMAG); } /** * @brief Calculate inverse magnitude of two inputs. * * @param a IQ1 type input. * @param b IQ1 type input. * * @return IQ1 type result of the inverse magnitude operation. */ int32_t _IQ1imag(int32_t a, int32_t b) { return __IQNsqrt(a, b, 1, TYPE_IMAG); } ================================================ FILE: iqmath/_IQNfunctions/_IQNtables.c ================================================ #include #include "_IQNtables.h" /* * Coefficients, parameters and lookup tables used for CPU approximations */ /* cos */ const int_fast32_t _IQ31CosLookup[52] = { 2147483647, 2147221509, 2146435157, 2145124784, 2143290709, 2140933381, 2138053374, 2134651392, 2130728266, 2126284953, 2121322538, 2115842232, 2109845374, 2103333428, 2096307983, 2088770754, 2080723582, 2072168431, 2063107390, 2053542671, 2043476608, 2032911661, 2021850407, 2010295547, 1998249902, 1985716414, 1972698141, 1959198262, 1945220073, 1930766987, 1915842531, 1900450350, 1884594201, 1868277956, 1851505597, 1834281220, 1816609030, 1798493340, 1779938574, 1760949261, 1741530038, 1721685646, 1701420928, 1680740833, 1659650409, 1638154806, 1616259270, 1593969148, 1571289881, 1548227007, 1524786154, 1500973048 }; /* sin */ const int_fast32_t _IQ31SinLookup[52] = { 0, 33553067, 67097942, 100626436, 134130364, 167601545, 201031810, 234412995, 267736951, 300995544, 334180652, 367284176, 400298032, 433214161, 466024527, 498721120, 531295957, 563741086, 596048586, 628210568, 660219183, 692066614, 723745087, 755246868, 786564267, 817689637, 848615380, 879333946, 909837834, 940119599, 970171848, 999987242, 1029558505, 1058878415, 1087939815, 1116735610, 1145258771, 1173502333, 1201459401, 1229123150, 1256486826, 1283543749, 1310287313, 1336710990, 1362808327, 1388572955, 1413998582, 1439079002, 1463808091, 1488179813, 1512188216, 1535827441 }; /* Asin */ const int_fast32_t _IQ29Asin_coeffs[17][5] = { { 3149732, 89392309, 962, 536870908, 0}, { 9526495, 88593699, 40416, 536870004, 8}, { 16138900, 86937495, 197996, 536863257, 118}, { 23156787, 84300941, 571586, 536839597, 683}, { 30770290, 80487362, 1290238, 536779215, 2591}, { 39200158, 75209531, 2531955, 536649108, 7714}, { 48710843, 68064494, 4547783, 536395980, 19650}, { 59627725, 58496679, 7695660, 535935197, 44970}, { 72359850, 45744506, 12489135, 535133733, 95261}, { 87431720, 28762778, 19668817, 533783769, 190507}, {105527689, 6109335, 30308851, 531561517, 364646}, {127555573, -24222672, 45978054, 527962405, 674787}, {154739551, -65056242, 68987651, 522197749, 1216568}, {188758701, -120414117, 102778528, 513027743, 2150044}, {231956922, -196114786, 152538402, 498486871, 3743891}, {287669673, -300718733, 226205346, 475423960, 6452123}, {287669673, -300718733, 226205346, 475423960, 6452123} // repeat last set }; /* atan */ const int_fast32_t _IQ32atan_coeffs[132] = { -227484580, -9261, 683565333, 0, -224831707, -276221, 683574534, -108, -219602897, -1274081, 683638558, -1488, -211947885, -3443234, 683844263, -8015, -202081292, -7157693, 684311451, -27646, -190271590, -12705955, 685181594, -73201, -176827842, -20278271, 686604775, -162452, -162085247, -29960921, 688726164, -317494, -146390502, -41737384, 691673332, -563488, -130087951, -55495711, 695545487, -926914, -113507288, -71040919, 700405423, -1433559, -96953369, -88110970, 706274644, -2106439, -80698479, -106394779, 713131732, -2963878, -64977126, -125550747, 720913749, -4017911, -49983270, -145224548, 729520198, -5273132, -35869738, -165065147, 738818936, -6726059, -22749467, -184738347, 748653357, -8365003, -10698168, -203937495, 758850169, -10170435, 241989, -222391214, 769227187, -12115749, 10058068, -239868310, 779600651, -14168336, 18761768, -256180110, 789791697, -16290870, 26384816, -271180628, 799631786, -18442695, 32974556, -284764983, 808966944, -20581230, 38589896, -296866517, 817660829, -22663305, 43297701, -307453021, 825596674, -24646386, 47169678, -316522417, 832678225, -26489630, 50279770, -324098234, 838829825, -28154764, 52702039, -330225080, 843995800, -29606769, 54508999, -334964326, 848139312, -30814380, 55770360, -338390095, 851240831, -31750408, 56552116, -340585675, 853296356, -32391901, 56915945, -341640355, 854315504, -32720182, 56915945, -341640355, 854315504, -32720182, }; /* exp */ const uint_fast32_t _IQ30exp_coeffs[11] = { 0x00000127, 0x00000B93, 0x00006806, 0x00034034, 0x0016C16C, 0x00888888, 0x02AAAAAA, 0x0AAAAAAA, 0x20000000, 0x40000000, 0x40000000 }; const uint_fast32_t _IQNexp_min[30] = { 0xffffffff, 0xfffffffb, 0xfffffff0, 0xffffffd4, 0xffffff92, 0xfffffef6, 0xfffffd93, 0xfffffa75, 0xfffff386, 0xffffe447, 0xffffc301, 0xffff7aeb, 0xfffedfa7, 0xfffd92f1, 0xfffacd29, 0xfff4e8df, 0xffe86ed9, 0xffce17ea, 0xff96a442, 0xff223163, 0xfe2e3482, 0xfc300c7d, 0xf8075fed, 0xef5d4dc1, 0xdd57b752, 0xb7e9a644, 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; const uint_fast32_t _IQNexp_max[30] = { 0x00000029, 0x00000050, 0x0000009b, 0x0000012b, 0x00000240, 0x00000455, 0x00000851, 0x00000ff1, 0x00001e7f, 0x00003a39, 0x00006ee7, 0x0000d2b7, 0x00018f40, 0x0002f224, 0x00058b90, 0x000a65af, 0x0013687a, 0x00240b2c, 0x00428ac8, 0x0079fe70, 0x00ddce9d, 0x018f40b5, 0x02c5c85f, 0x04da1ea7, 0x0851591f, 0x0ddce9df, 0x162e42fe, 0x2145647e, 0x2c5c85fd, 0x2c5c85fd }; const uint_fast16_t _IQNexp_offset[30] = { 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 20 }; const uint_fast32_t _IQNexp_lookup1[22] = { 0x00000004, 0x0000000A, 0x0000001D, 0x00000050, 0x000000DA, 0x00000251, 0x0000064D, 0x00001122, 0x00002E93, 0x00007E9C, 0x00015829, 0x0003A788, 0x0009EF0B, 0x001B00B5, 0x004966B1, 0x00C78665, 0x021E5D7A, 0x05C24D23, 0x0FA79104, 0x2A8DB1F3, 0x73AC222D, 0x00000000 }; const uint_fast32_t _IQNexp_lookup2[22] = { 0x00000002, 0x00000008, 0x00000015, 0x0000003B, 0x000000A0, 0x000001B4, 0x000004A3, 0x00000C9B, 0x00002245, 0x00005D27, 0x0000FD38, 0x0002B053, 0x00074F11, 0x0013DE16, 0x0036016B, 0x0092CD62, 0x018F0CCA, 0x043CBAF4, 0x0B849A46, 0x1F4F2209, 0x551B63E7, 0xE758445B }; const uint_fast32_t _IQNexp_lookup3[22] = { 0x00000002, 0x00000005, 0x00000010, 0x0000002B, 0x00000076, 0x00000141, 0x00000369, 0x00000946, 0x00001936, 0x0000448A, 0x0000BA4F, 0x0001FA71, 0x000560A7, 0x000E9E22, 0x0027BC2C, 0x006C02D6, 0x01259AC4, 0x031E1995, 0x087975E8, 0x1709348C, 0x3E9E4412, 0xAA36C7CF }; const uint_fast32_t _IQNexp_lookup4[22] = { 0x00000004, 0x0000000B, 0x00000020, 0x00000056, 0x000000EC, 0x00000282, 0x000006D3, 0x0000128D, 0x0000326D, 0x00008914, 0x0001749E, 0x0003F4E2, 0x000AC14E, 0x001D3C44, 0x004F7859, 0x00D805AC, 0x024B3589, 0x063C332B, 0x10F2EBD0, 0x2E126918, 0x7D3C8824, 0x00000000 }; const uint_fast32_t _IQNexp_lookup5[22] = { 0x00000003, 0x00000008, 0x00000017, 0x00000040, 0x000000AD, 0x000001D8, 0x00000505, 0x00000DA6, 0x0000251A, 0x000064DB, 0x00011228, 0x0002E93D, 0x0007E9C5, 0x0015829D, 0x003A7889, 0x009EF0B2, 0x01B00B59, 0x04966B12, 0x0C786657, 0x21E5D7A1, 0x5C24D230, 0xFA791048 }; const uint_fast32_t _IQNexp_lookup6[22] = { 0x00000002, 0x00000006, 0x00000011, 0x0000002F, 0x00000080, 0x0000015B, 0x000003B1, 0x00000A0A, 0x00001B4C, 0x00004A34, 0x0000C9B6, 0x00022451, 0x0005D27A, 0x000FD38A, 0x002B053B, 0x0074F112, 0x013DE165, 0x036016B2, 0x092CD624, 0x18F0CCAF, 0x43CBAF42, 0xB849A460 }; const uint_fast32_t _IQNexp_lookup7[22] = { 0x00000004, 0x0000000C, 0x00000022, 0x0000005E, 0x00000100, 0x000002B7, 0x00000763, 0x00001415, 0x00003699, 0x00009469, 0x0001936D, 0x000448A2, 0x000BA4F5, 0x001FA715, 0x00560A77, 0x00E9E224, 0x027BC2CA, 0x06C02D64, 0x1259AC48, 0x31E1995F, 0x87975E85, 0x00000000 }; const uint_fast32_t _IQNexp_lookup8[22] = { 0x00000003, 0x00000009, 0x00000019, 0x00000045, 0x000000BC, 0x00000200, 0x0000056F, 0x00000EC7, 0x0000282B, 0x00006D32, 0x000128D3, 0x000326DB, 0x00089144, 0x001749EA, 0x003F4E2A, 0x00AC14EE, 0x01D3C448, 0x04F78595, 0x0D805AC8, 0x24B35891, 0x63C332BE, 0x00000000 }; const uint_fast32_t _IQNexp_lookup9[22] = { 0x00000002, 0x00000006, 0x00000012, 0x00000032, 0x0000008A, 0x00000178, 0x00000400, 0x00000ADF, 0x00001D8E, 0x00005057, 0x0000DA64, 0x000251A7, 0x00064DB7, 0x00112288, 0x002E93D4, 0x007E9C55, 0x015829DC, 0x03A78891, 0x09EF0B2A, 0x1B00B591, 0x4966B122, 0xC786657D }; const uint_fast32_t _IQNexp_lookup10[22] = { 0x00000005, 0x0000000D, 0x00000025, 0x00000065, 0x00000115, 0x000002F1, 0x00000800, 0x000015BF, 0x00003B1C, 0x0000A0AF, 0x0001B4C9, 0x0004A34E, 0x000C9B6E, 0x00224510, 0x005D27A9, 0x00FD38AB, 0x02B053B9, 0x074F1122, 0x13DE1654, 0x36016B22, 0x92CD6245, 0x00000000 }; const uint_fast32_t _IQNexp_lookup11[22] = { 0x00000003, 0x0000000A, 0x0000001B, 0x0000004B, 0x000000CB, 0x0000022A, 0x000005E2, 0x00001000, 0x00002B7E, 0x00007639, 0x0001415E, 0x00036992, 0x0009469C, 0x001936DC, 0x00448A21, 0x00BA4F53, 0x01FA7157, 0x0560A773, 0x0E9E2244, 0x27BC2CA9, 0x6C02D645, 0x00000000 }; const uint_fast32_t _IQNexp_lookup12[22] = { 0x00000002, 0x00000007, 0x00000014, 0x00000037, 0x00000096, 0x00000197, 0x00000454, 0x00000BC5, 0x00002000, 0x000056FC, 0x0000EC73, 0x000282BC, 0x0006D324, 0x00128D38, 0x00326DB8, 0x00891442, 0x01749EA7, 0x03F4E2AF, 0x0AC14EE7, 0x1D3C4488, 0x4F785953, 0xD805AC8B }; const uint_fast32_t _IQNexp_lookup13[22] = { 0x00000002, 0x00000005, 0x0000000E, 0x00000028, 0x0000006E, 0x0000012C, 0x0000032F, 0x000008A9, 0x0000178B, 0x00004000, 0x0000ADF8, 0x0001D8E6, 0x00050579, 0x000DA648, 0x00251A71, 0x0064DB71, 0x01122885, 0x02E93D4F, 0x07E9C55F, 0x15829DCF, 0x3A788911, 0x9EF0B2A6 }; const uint_fast32_t _IQNexp_lookup14[22] = { 0x00000004, 0x0000000A, 0x0000001D, 0x00000051, 0x000000DC, 0x00000258, 0x0000065F, 0x00001152, 0x00002F16, 0x00008000, 0x00015BF0, 0x0003B1CC, 0x000A0AF2, 0x001B4C90, 0x004A34E2, 0x00C9B6E2, 0x0224510B, 0x05D27A9F, 0x0FD38ABE, 0x2B053B9F, 0x74F11223, 0x00000000 }; const uint_fast32_t _IQNexp_lookup15[22] = { 0x00000002, 0x00000008, 0x00000015, 0x0000003B, 0x000000A2, 0x000001B9, 0x000004B0, 0x00000CBE, 0x000022A5, 0x00005E2D, 0x00010000, 0x0002B7E1, 0x00076399, 0x001415E5, 0x00369920, 0x009469C4, 0x01936DC5, 0x0448A216, 0x0BA4F53E, 0x1FA7157C, 0x560A773E, 0xE9E22447 }; const uint_fast32_t _IQNexp_lookup16[22] = { 0x00000002, 0x00000005, 0x00000010, 0x0000002B, 0x00000077, 0x00000144, 0x00000373, 0x00000960, 0x0000197D, 0x0000454A, 0x0000BC5A, 0x00020000, 0x00056FC2, 0x000EC732, 0x00282BCB, 0x006D3240, 0x0128D389, 0x0326DB8A, 0x0891442D, 0x1749EA7D, 0x3F4E2AF8, 0xAC14EE7C }; const uint_fast32_t _IQNexp_lookup17[22] = { 0x00000004, 0x0000000B, 0x00000020, 0x00000057, 0x000000EF, 0x00000289, 0x000006E6, 0x000012C1, 0x000032FB, 0x00008A95, 0x000178B5, 0x00040000, 0x000ADF85, 0x001D8E64, 0x00505796, 0x00DA6481, 0x0251A713, 0x064DB715, 0x1122885A, 0x2E93D4FA, 0x7E9C55F1, 0x00000000 }; const uint_fast32_t _IQNexp_lookup18[22] = { 0x00000003, 0x00000008, 0x00000017, 0x00000040, 0x000000AF, 0x000001DE, 0x00000513, 0x00000DCC, 0x00002582, 0x000065F6, 0x0001152A, 0x0002F16A, 0x00080000, 0x0015BF0A, 0x003B1CC9, 0x00A0AF2D, 0x01B4C902, 0x04A34E26, 0x0C9B6E2B, 0x224510B5, 0x5D27A9F5, 0xFD38ABE2 }; const uint_fast32_t _IQNexp_lookup19[22] = { 0x00000002, 0x00000006, 0x00000011, 0x0000002F, 0x00000081, 0x0000015F, 0x000003BC, 0x00000A27, 0x00001B99, 0x00004B05, 0x0000CBED, 0x00022A55, 0x0005E2D5, 0x00100000, 0x002B7E15, 0x00763992, 0x01415E5B, 0x03699205, 0x09469C4C, 0x1936DC56, 0x448A216A, 0xBA4F53EA }; const uint_fast32_t _IQNexp_lookup20[22] = { 0x00000004, 0x0000000C, 0x00000023, 0x0000005F, 0x00000102, 0x000002BF, 0x00000778, 0x0000144E, 0x00003732, 0x0000960A, 0x000197DB, 0x000454AA, 0x000BC5AB, 0x00200000, 0x0056FC2A, 0x00EC7325, 0x0282BCB7, 0x06D3240B, 0x128D3899, 0x326DB8AD, 0x891442D5, 0x00000000 }; const uint_fast32_t _IQNexp_lookup21[22] = { 0x00000003, 0x00000009, 0x00000019, 0x00000046, 0x000000BE, 0x00000205, 0x0000057F, 0x00000EF0, 0x0000289C, 0x00006E64, 0x00012C15, 0x00032FB6, 0x0008A955, 0x00178B56, 0x00400000, 0x00ADF854, 0x01D8E64B, 0x0505796F, 0x0DA64817, 0x251A7132, 0x64DB715A, 0x00000000 }; const uint_fast32_t _IQNexp_lookup22[22] = { 0x00000002, 0x00000006, 0x00000012, 0x00000033, 0x0000008C, 0x0000017C, 0x0000040B, 0x00000AFE, 0x00001DE1, 0x00005139, 0x0000DCC9, 0x0002582A, 0x00065F6C, 0x001152AA, 0x002F16AC, 0x00800000, 0x015BF0A8, 0x03B1CC97, 0x0A0AF2DF, 0x1B4C902E, 0x4A34E265, 0xC9B6E2B4 }; const uint_fast32_t _IQNexp_lookup23[22] = { 0x00000005, 0x0000000D, 0x00000025, 0x00000067, 0x00000118, 0x000002F9, 0x00000816, 0x000015FC, 0x00003BC2, 0x0000A272, 0x0001B993, 0x0004B055, 0x000CBED8, 0x0022A555, 0x005E2D58, 0x01000000, 0x02B7E151, 0x0763992E, 0x1415E5BF, 0x3699205C, 0x9469C4CB, 0x00000000 }; const uint_fast32_t _IQNexp_lookup24[22] = { 0x00000003, 0x0000000A, 0x0000001B, 0x0000004B, 0x000000CE, 0x00000230, 0x000005F3, 0x0000102C, 0x00002BF8, 0x00007785, 0x000144E5, 0x00037327, 0x000960AA, 0x00197DB0, 0x00454AAA, 0x00BC5AB1, 0x02000000, 0x056FC2A2, 0x0EC7325C, 0x282BCB7E, 0x6D3240B8, 0x00000000 }; const uint_fast32_t _IQNexp_lookup25[22] = { 0x00000002, 0x00000007, 0x00000014, 0x00000037, 0x00000097, 0x0000019C, 0x00000460, 0x00000BE6, 0x00002059, 0x000057F0, 0x0000EF0B, 0x000289CA, 0x0006E64F, 0x0012C155, 0x0032FB61, 0x008A9555, 0x0178B563, 0x04000000, 0x0ADF8545, 0x1D8E64B8, 0x505796FD, 0xDA648171 }; const uint_fast32_t _IQNexp_lookup26[22] = { 0x00000002, 0x00000005, 0x0000000F, 0x00000029, 0x0000006F, 0x0000012F, 0x00000338, 0x000008C1, 0x000017CD, 0x000040B3, 0x0000AFE1, 0x0001DE16, 0x00051394, 0x000DCC9F, 0x002582AB, 0x0065F6C3, 0x01152AAA, 0x02F16AC6, 0x08000000, 0x15BF0A8B, 0x3B1CC971, 0xA0AF2DFB }; const uint_fast32_t _IQNexp_lookup27[22] = { 0x00000004, 0x0000000B, 0x0000001E, 0x00000052, 0x000000DF, 0x0000025E, 0x00000671, 0x00001183, 0x00002F9A, 0x00008167, 0x00015FC2, 0x0003BC2D, 0x000A2728, 0x001B993F, 0x004B0556, 0x00CBED86, 0x022A5554, 0x05E2D58D, 0x10000000, 0x2B7E1516, 0x763992E3, 0x00000000 }; const uint_fast32_t _IQNexp_lookup28[22] = { 0x00000003, 0x00000008, 0x00000016, 0x0000003C, 0x000000A4, 0x000001BE, 0x000004BD, 0x00000CE2, 0x00002306, 0x00005F35, 0x000102CF, 0x0002BF84, 0x0007785A, 0x00144E51, 0x0037327F, 0x00960AAD, 0x0197DB0C, 0x0454AAA8, 0x0BC5AB1B, 0x20000000, 0x56FC2A2C, 0xEC7325C6 }; const uint_fast32_t _IQNexp_lookup29[22] = { 0x00000002, 0x00000006, 0x00000010, 0x0000002C, 0x00000078, 0x00000148, 0x0000037C, 0x0000097B, 0x000019C5, 0x0000460D, 0x0000BE6B, 0x0002059E, 0x00057F08, 0x000EF0B5, 0x00289CA3, 0x006E64FF, 0x012C155B, 0x032FB619, 0x08A95551, 0x178B5636, 0x40000000, 0xADF85458 }; const uint_fast32_t _IQNexp_lookup30[22] = { 0x00000004, 0x0000000C, 0x00000020, 0x00000058, 0x000000F1, 0x00000290, 0x000006F9, 0x000012F6, 0x0000338A, 0x00008C1A, 0x00017CD7, 0x00040B3C, 0x000AFE10, 0x001DE16B, 0x00513947, 0x00DCC9FF, 0x02582AB7, 0x065F6C33, 0x1152AAA3, 0x2F16AC6C, 0x80000000, 0x00000000 }; /* log */ const uint_fast32_t _IQNlog_min[5] = { 0x00000010, 0x00015FC3, 0x00960AAE, 0x08A95552, 0x2F16AC6D }; const uint_fast32_t _IQ30log_coeffs[15] = { 0xfb6db6db, 0x04ec4ec4, 0xfaaaaaab, 0x05d1745d, 0xf999999a, 0x071c71c7, 0xf8000000, 0x09249249, 0xf5555556, 0x0ccccccc, 0xf0000000, 0x15555555, 0xe0000000, 0x40000000, 0x00000000 }; /* div */ const uint8_t _IQ6div_lookup[65] = { 0x7F, 0x7D, 0x7B, 0x79, 0x78, 0x76, 0x74, 0x73, 0x71, 0x6F, 0x6E, 0x6D, 0x6B, 0x6A, 0x68, 0x67, 0x66, 0x65, 0x63, 0x62, 0x61, 0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4E, 0x4D, 0x4C, 0x4C, 0x4B, 0x4A, 0x49, 0x49, 0x48, 0x48, 0x47, 0x46, 0x46, 0x45, 0x45, 0x44, 0x43, 0x43, 0x42, 0x42, 0x41, 0x41, 0x40, 0x40 }; /* sqrt */ const uint_fast16_t _IQ14sqrt_lookup[96] = { 0x7f02, 0x7d19, 0x7b46, 0x7986, 0x77d9, 0x763d, 0x74b2, 0x7335, 0x71c7, 0x7066, 0x6f11, 0x6dc8, 0x6c8b, 0x6b58, 0x6a2f, 0x690f, 0x67f8, 0x66ea, 0x65e4, 0x64e5, 0x63ee, 0x62fe, 0x6214, 0x6131, 0x6054, 0x5f7d, 0x5eab, 0x5dde, 0x5d17, 0x5c54, 0x5b96, 0x5add, 0x5a28, 0x5977, 0x58ca, 0x5821, 0x577c, 0x56da, 0x563c, 0x55a1, 0x5509, 0x5475, 0x53e3, 0x5354, 0x52c9, 0x523f, 0x51b9, 0x5135, 0x50b3, 0x5034, 0x4fb7, 0x4f3d, 0x4ec4, 0x4e4e, 0x4dda, 0x4d68, 0x4cf7, 0x4c89, 0x4c1d, 0x4bb2, 0x4b49, 0x4ae1, 0x4a7c, 0x4a18, 0x49b5, 0x4954, 0x48f4, 0x4896, 0x483a, 0x47de, 0x4784, 0x472c, 0x46d4, 0x467e, 0x4629, 0x45d6, 0x4583, 0x4532, 0x44e1, 0x4492, 0x4444, 0x43f7, 0x43aa, 0x435f, 0x4315, 0x42cc, 0x4284, 0x423c, 0x41f6, 0x41b0, 0x416b, 0x4127, 0x40e4, 0x40a2, 0x4060, 0x4020 }; /* shift with mpy */ //const uint_fast32_t _IQNshift32[32] = { // 0xffffffff, 0x80000000, 0x40000000, 0x20000000, // 0x10000000, 0x08000000, 0x04000000, 0x02000000, // 0x01000000, 0x00800000, 0x00400000, 0x00200000, // 0x00100000, 0x00080000, 0x00040000, 0x00020000, // 0x00010000, 0x00008000, 0x00004000, 0x00002000, // 0x00001000, 0x00000800, 0x00000400, 0x00000200, // 0x00000100, 0x00000080, 0x00000040, 0x00000020, // 0x00000010, 0x00000008, 0x00000004, 0x00000002 //}; ================================================ FILE: iqmath/_IQNfunctions/_IQNtables.h ================================================ #ifndef _IQNTABLES_H_ #define _IQNTABLES_H_ #include /* LOG lookup and coefficient tables. */ #define _IQ30log_order 14 extern const uint_fast32_t _IQNlog_min[5]; extern const uint_fast32_t _IQ30log_coeffs[15]; /* asin and acos coefficient table. */ extern const int_fast32_t _IQ29Asin_coeffs[17][5]; /* sin and cos lookup tables. */ extern const int_fast32_t _IQ31CosLookup[52]; extern const int_fast32_t _IQ31SinLookup[52]; /* atan coefficient table. */ extern const int_fast32_t _IQ32atan_coeffs[132]; /* Tables for exp function. Min/Max and integer lookup for each q type */ #define _IQ30exp_order 10 extern const uint_fast32_t _IQNexp_min[30]; extern const uint_fast32_t _IQNexp_max[30]; extern const uint_fast16_t _IQNexp_offset[30]; extern const uint_fast32_t _IQNexp_lookup1[22]; extern const uint_fast32_t _IQNexp_lookup2[22]; extern const uint_fast32_t _IQNexp_lookup3[22]; extern const uint_fast32_t _IQNexp_lookup4[22]; extern const uint_fast32_t _IQNexp_lookup5[22]; extern const uint_fast32_t _IQNexp_lookup6[22]; extern const uint_fast32_t _IQNexp_lookup7[22]; extern const uint_fast32_t _IQNexp_lookup8[22]; extern const uint_fast32_t _IQNexp_lookup9[22]; extern const uint_fast32_t _IQNexp_lookup10[22]; extern const uint_fast32_t _IQNexp_lookup11[22]; extern const uint_fast32_t _IQNexp_lookup12[22]; extern const uint_fast32_t _IQNexp_lookup13[22]; extern const uint_fast32_t _IQNexp_lookup14[22]; extern const uint_fast32_t _IQNexp_lookup15[22]; extern const uint_fast32_t _IQNexp_lookup16[22]; extern const uint_fast32_t _IQNexp_lookup17[22]; extern const uint_fast32_t _IQNexp_lookup18[22]; extern const uint_fast32_t _IQNexp_lookup19[22]; extern const uint_fast32_t _IQNexp_lookup20[22]; extern const uint_fast32_t _IQNexp_lookup21[22]; extern const uint_fast32_t _IQNexp_lookup22[22]; extern const uint_fast32_t _IQNexp_lookup23[22]; extern const uint_fast32_t _IQNexp_lookup24[22]; extern const uint_fast32_t _IQNexp_lookup25[22]; extern const uint_fast32_t _IQNexp_lookup26[22]; extern const uint_fast32_t _IQNexp_lookup27[22]; extern const uint_fast32_t _IQNexp_lookup28[22]; extern const uint_fast32_t _IQNexp_lookup29[22]; extern const uint_fast32_t _IQNexp_lookup30[22]; extern const uint_fast32_t _IQ30exp_coeffs[11]; /* * Q0.15 lookup table for 1/2x best guess. */ extern const uint8_t _IQ6div_lookup[65]; /* * Q0.15 lookup table for 1/(2*sqrt(x)) best guess. * 96 entries gives us enough accuracy to only need 2 iterations. */ extern const uint_fast16_t _IQ14sqrt_lookup[96]; /* * Lookup table for shifting using the multiplier. * Right: Index is the shift count, result is high 32 bits. * Left: Index is 32 - count, result is low (and high) 32 bits. */ extern const uint_fast32_t _IQNshift32[32]; #endif ================================================ FILE: iqmath/_IQNfunctions/_IQNtoF.c ================================================ /*!**************************************************************************** * @file _IQNtoF.c * @brief Functions to convert IQN type to floating point. * *
******************************************************************************/ #include #include #include "../support/support.h" /** * @brief Converts IQN type to floating point. * * @param iqNInput IQN type value input to be converted. * @param q_value IQ format. * * @return Conversion of iqNInput to floating point. */ #if defined(__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNtoF) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline = forced #endif __STATIC_INLINE float __IQNtoF(int_fast32_t iqNInput, int8_t q_value) { uint_fast16_t ui16Exp; uint_fast32_t uiq23Result; uint_fast32_t uiq31Input; /* Initialize exponent to the offset iq value. */ ui16Exp = 0x3f80 + ((31 - q_value) * ((uint_fast32_t) 1 << (23 - 16))); /* Save the sign of the iqN input to the exponent construction. */ if (iqNInput < 0) { ui16Exp |= 0x8000; uiq31Input = -iqNInput; } else if (iqNInput == 0) { return (0); } else { uiq31Input = iqNInput; } /* Scale the iqN input to uiq31 by keeping track of the exponent. */ while ((uint_fast16_t)(uiq31Input >> 16) < 0x8000) { uiq31Input <<= 1; ui16Exp -= 0x0080; } /* Round the uiq31 result and and shift to uiq23 */ uiq23Result = (uiq31Input + 0x0080) >> 8; /* Remove the implied MSB bit of the mantissa. */ uiq23Result &= ~0x00800000; /* * Add the constructed exponent and sign bit to the mantissa. We must use * an add in the case where rounding would cause the mantissa to overflow. * When this happens the mantissa result is two where the MSB is zero and * the LSB of the exp is set to 1 instead. Adding one to the exponent is the * correct handling for a mantissa of two. It is not required to scale the * mantissa since it will always be equal to zero in this scenario. */ uiq23Result += (uint_fast32_t) ui16Exp << 16; /* Return the mantissa + exp + sign result as a floating point type. */ /* Use memcpy to avoid strict-aliasing violation */ float result; memcpy(&result, &uiq23Result, sizeof(float)); return result; } /** * @brief Converts input to floating point using IQ30 format. * * @param a IQ30 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ30toF(int32_t a) { return __IQNtoF(a, 30); } /** * @brief Converts input to floating point using IQ29 format. * * @param a IQ29 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ29toF(int32_t a) { return __IQNtoF(a, 29); } /** * @brief Converts input to floating point using IQ28 format. * * @param a IQ28 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ28toF(int32_t a) { return __IQNtoF(a, 28); } /** * @brief Converts input to floating point using IQ27 format. * * @param a IQ27 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ27toF(int32_t a) { return __IQNtoF(a, 27); } /** * @brief Converts input to floating point using IQ26 format. * * @param a IQ26 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ26toF(int32_t a) { return __IQNtoF(a, 26); } /** * @brief Converts input to floating point using IQ25 format. * * @param a IQ25 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ25toF(int32_t a) { return __IQNtoF(a, 25); } /** * @brief Converts input to floating point using IQ24 format. * * @param a IQ24 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ24toF(int32_t a) { return __IQNtoF(a, 24); } /** * @brief Converts input to floating point using IQ23 format. * * @param a IQ23 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ23toF(int32_t a) { return __IQNtoF(a, 23); } /** * @brief Converts input to floating point using IQ22 format. * * @param a IQ22 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ22toF(int32_t a) { return __IQNtoF(a, 22); } /** * @brief Converts input to floating point using IQ21 format. * * @param a IQ21 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ21toF(int32_t a) { return __IQNtoF(a, 21); } /** * @brief Converts input to floating point using IQ20 format. * * @param a IQ20 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ20toF(int32_t a) { return __IQNtoF(a, 20); } /** * @brief Converts input to floating point using IQ19 format. * * @param a IQ19 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ19toF(int32_t a) { return __IQNtoF(a, 19); } /** * @brief Converts input to floating point using IQ18 format. * * @param a IQ18 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ18toF(int32_t a) { return __IQNtoF(a, 18); } /** * @brief Converts input to floating point using IQ17 format. * * @param a IQ17 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ17toF(int32_t a) { return __IQNtoF(a, 17); } /** * @brief Converts input to floating point using IQ16 format. * * @param a IQ16 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ16toF(int32_t a) { return __IQNtoF(a, 16); } /** * @brief Converts input to floating point using IQ15 format. * * @param a IQ15 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ15toF(int32_t a) { return __IQNtoF(a, 15); } /** * @brief Converts input to floating point using IQ14 format. * * @param a IQ14 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ14toF(int32_t a) { return __IQNtoF(a, 14); } /** * @brief Converts input to floating point using IQ13 format. * * @param a IQ13 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ13toF(int32_t a) { return __IQNtoF(a, 13); } /** * @brief Converts input to floating point using IQ12 format. * * @param a IQ12 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ12toF(int32_t a) { return __IQNtoF(a, 12); } /** * @brief Converts input to floating point using IQ11 format. * * @param a IQ11 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ11toF(int32_t a) { return __IQNtoF(a, 11); } /** * @brief Converts input to floating point using IQ10 format. * * @param a IQ10 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ10toF(int32_t a) { return __IQNtoF(a, 10); } /** * @brief Converts input to floating point using IQ9 format. * * @param a IQ9 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ9toF(int32_t a) { return __IQNtoF(a, 9); } /** * @brief Converts input to floating point using IQ8 format. * * @param a IQ8 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ8toF(int32_t a) { return __IQNtoF(a, 8); } /** * @brief Converts input to floating point using IQ7 format. * * @param a IQ7 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ7toF(int32_t a) { return __IQNtoF(a, 7); } /** * @brief Converts input to floating point using IQ6 format. * * @param a IQ6 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ6toF(int32_t a) { return __IQNtoF(a, 6); } /** * @brief Converts input to floating point using IQ5 format. * * @param a IQ5 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ5toF(int32_t a) { return __IQNtoF(a, 5); } /** * @brief Converts input to floating point using IQ4 format. * * @param a IQ4 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ4toF(int32_t a) { return __IQNtoF(a, 4); } /** * @brief Converts input to floating point using IQ3 format. * * @param a IQ3 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ3toF(int32_t a) { return __IQNtoF(a, 3); } /** * @brief Converts input to floating point using IQ2 format. * * @param a IQ2 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ2toF(int32_t a) { return __IQNtoF(a, 2); } /** * @brief Converts input to floating point using IQ1 format. * * @param a IQ1 type value to be converted. * * @return Conversion of input to floating point. */ float _IQ1toF(int32_t a) { return __IQNtoF(a, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNtoa.c ================================================ /*!**************************************************************************** * @file _IQNtoa.c * @brief Functions to convert an IQ number to a string. * *
******************************************************************************/ #include #include "../support/support.h" /* * Convert IQN values to string. */ /** * @brief Convert an IQ number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQN type input. * @param q_value IQ format. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__IQNtoa) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif int_fast16_t __IQNtoa(char *string, const char *format, int_fast32_t iqNInput, int_fast16_t q_value) { char *pcBuf; // buffer pointer int_fast16_t count; // conversion character counter uint_fast16_t ui16IntWidth; // integer format width uint_fast16_t ui16FracWidth; // fractional format width uint_fast16_t ui16IntState; // save interrupt state uint_fast16_t ui16MPYState; // save multiplier state uint_fast32_t uiqNInput; // unsigned input uint_fast32_t uiq32Fractional; // working variable uint_fast32_t ui32Integer; // working variable uint_fast32_t ui32IntTemp; // temp variable uint_fast32_t ui32IntegerTenth; // uval scaled by 10 uint_fast64_t uiiq32FractionalTen; // fractional input scaled by 10 /* Check that 1st character is a '%' character. */ if (*format++ != '%') { /* Error: format not preceded with '%' character. */ return (2); } /* Check that an integer is provided and extract the integer width. */ if (*format < '0' || *format > '9') { /* Error: integer width is non integer. */ return (2); } /* Initialize local variables and extract the integer width. */ count = 0; ui16IntWidth = 0; while (*format >= '0' && *format <= '9') { __mpy_start(&ui16IntState, &ui16MPYState); ui16IntWidth = __mpy_uw(ui16IntWidth, 10); __mpy_stop(&ui16IntState, &ui16MPYState); ui16IntWidth = ui16IntWidth + (*format++ - '0'); /* If we don't find the '.' after 2 counts, something is wrong. */ if (++count > 2) { /* Error: integer width field too many characters */ return (2); } } /* Check integer width for errors. */ if (ui16IntWidth > 11) { /* Error: integer width too large */ return (2); } /* Check the next character for '.' and increment over. */ if (*format++ != '.') { /* Error: format missing the '.' character. */ return (2); } /* Re-initialize the local variables and extract the fractional width. */ count = 0; ui16FracWidth = 0; while (*format >= '0' && *format <= '9') { __mpy_start(&ui16IntState, &ui16MPYState); ui16FracWidth = __mpy_uw(ui16FracWidth, 10); __mpy_stop(&ui16IntState, &ui16MPYState); ui16FracWidth = ui16FracWidth + (*format++ - '0'); /* If we don't exit after 2 counts, something is wrong. */ if (++count > 2) { /* Error: fractional width field too many characters */ return (2); } } /* Check the next character for 'f' or 'F'. */ if (*format != 'f' && *format != 'F') { /* Error: format missing the format specifying character. */ return (2); } /* Check that the next character is the NULL string terminator. */ if (*++format != 0) { /* Error: missing null terminator. */ return (2); } /* * Begin constructing the string. */ pcBuf = string; /* Check for negative value. */ if (iqNInput < 0) { iqNInput = -iqNInput; *pcBuf++ = '-'; } uiqNInput = (uint_fast32_t)iqNInput; /* Construct the integer string in reverse. */ pcBuf += ui16IntWidth; ui32Integer = uiqNInput >> q_value; for (count = ui16IntWidth; count > 0; count--) { /* Integer position n = ui32Integer - floor(ui32Integer/(10^n))*(10^n) */ __mpyf_start(&ui16IntState, &ui16MPYState); ui32IntegerTenth = __mpyf_l(ui32Integer, iq31_oneTenth); __mpy_clear_ctl0(); ui32IntTemp = __mpy_ul(ui32IntegerTenth, 10); /* Handle any possible rounding. */ if (ui32IntTemp > ui32Integer) { ui32IntTemp -= 10; ui32IntegerTenth -= 1; } ui32Integer -= ui32IntTemp; __mpy_stop(&ui16IntState, &ui16MPYState); *--pcBuf = ui32Integer + '0'; ui32Integer = ui32IntegerTenth; } /* Check if there is any remaining input. */ if (ui32Integer) { /* Error: integer format too small. */ return (1); } /* Construct the fractional string if specified using unsigned iq32. */ pcBuf += ui16IntWidth; uiq32Fractional = uiqNInput << (32 - q_value); if (ui16FracWidth > 0) { *pcBuf++ = '.'; while (ui16FracWidth--) { __mpy_start(&ui16IntState, &ui16MPYState); uiiq32FractionalTen = __mpyx_u(uiq32Fractional, 10); __mpy_stop(&ui16IntState, &ui16MPYState); uiq32Fractional = (uint_fast32_t)uiiq32FractionalTen; *pcBuf++ = (uint8_t)(uiiq32FractionalTen >> 32) + '0'; } } /* Add null terminating character and return. */ *pcBuf = 0; return (0); } /** * @brief Convert an IQ31 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ31 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ31toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 31); } /** * @brief Convert an IQ30 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ30 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ30toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 30); } /** * @brief Convert an IQ29 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ29 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ29toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 29); } /** * @brief Convert an IQ28 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ28 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ28toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 28); } /** * @brief Convert an IQ27 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ27 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ27toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 27); } /** * @brief Convert an IQ26 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ26 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ26toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 26); } /** * @brief Convert an IQ25 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ25 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ25toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 25); } /** * @brief Convert an IQ24 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ24 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ24toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 24); } /** * @brief Convert an IQ23 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ23 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ23toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 23); } /** * @brief Convert an IQ22 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ22 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ22toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 22); } /** * @brief Convert an IQ21 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ21 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ21toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 21); } /** * @brief Convert an IQ20 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ20 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ20toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 20); } /** * @brief Convert an IQ19 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ19 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ19toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 19); } /** * @brief Convert an IQ18 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ18 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ18toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 18); } /** * @brief Convert an IQ17 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ17 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ17toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 17); } /** * @brief Convert an IQ16 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ16 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ16toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 16); } /** * @brief Convert an IQ15 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ15 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ15toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 15); } /** * @brief Convert an IQ14 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ14 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ14toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 14); } /** * @brief Convert an IQ13 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ13 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ13toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 13); } /** * @brief Convert an IQ12 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ12 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ12toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 12); } /** * @brief Convert an IQ11 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ11 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ11toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 11); } /** * @brief Convert an IQ10 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ10 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ10toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 10); } /** * @brief Convert an IQ9 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ9 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ9toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 9); } /** * @brief Convert an IQ8 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ8 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ8toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 8); } /** * @brief Convert an IQ7 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ7 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ7toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 7); } /** * @brief Convert an IQ6 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ6 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ6toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 6); } /** * @brief Convert an IQ5 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ5 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ5toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 5); } /** * @brief Convert an IQ4 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ4 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ4toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 4); } /** * @brief Convert an IQ3 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ3 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ3toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 3); } /** * @brief Convert an IQ2 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ2 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ2toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 2); } /** * @brief Convert an IQ1 number to a string. * * @param string Pointer to the buffer to store the converted IQ number. * @param format The format string specifying how to convert the IQ number. * @param iqNInput IQ1 type input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ int16_t _IQ1toa(char *string, const char *format, int32_t iqNInput) { return __IQNtoa(string, format, iqNInput, 1); } ================================================ FILE: iqmath/_IQNfunctions/_IQNversion.c ================================================ const char IQmathLibVersionString[] = "IQmathLib version 01_11_00_00"; const char *_IQmathLibVersionString(void) { return IQmathLibVersionString; } ================================================ FILE: iqmath/_IQNfunctions/_atoIQN.c ================================================ /*!**************************************************************************** * @file _atoIQN.c * @brief Functions to convert a string to an IQN number * *
******************************************************************************/ #include #include "../support/support.h" /** * @brief Converts string to an IQN number. * * @param string Pointer to the string to be converted. * @param q_value IQ format. * * @return Conversion of string to IQN type. */ #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__atoIQN) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif __STATIC_INLINE int_fast32_t __atoIQN(const char *string, int_fast32_t q_value) { uint8_t sgn; uint_fast16_t ui16IntState; uint_fast16_t ui16MPYState; uint_fast32_t iqNResult; uint_fast32_t uiq0Integer = 0; uint_fast32_t uiq31Fractional = 0; uint_fast32_t max_int = 0x7fffffff >> q_value; /* Check for sign */ if (*string == '-') { string++; sgn = 1; } else { sgn = 0; } /* Setup the device multiplier. */ __mpy_start(&ui16IntState, &ui16MPYState); /* Process integer portion of string starting from first character. */ while ((*string != '.') && (*string != 0)) { /* Check for invalid character */ if (*string < '0' || *string > '9') { return 0; } /* Check that multiplying by 10 won't cause overflow */ if (uiq0Integer > iq31_pointOne) { if (sgn) { return 0x80000000; } else { return 0x7fffffff; } } /* Multiply by 10 */ uiq0Integer = __mpy_l(uiq0Integer, 10); /* Add next integer to result */ uiq0Integer += *string++ - '0'; /* Check to see if integer portion has overflowed */ if (uiq0Integer > max_int) { if (sgn) { return 0x80000000; } else { return 0x7fffffff; } } } /* Restore multiplier context. */ __mpy_stop(&ui16IntState, &ui16MPYState); /* Check if previous loop ended with null character and return. */ if (*string == 0) { /* Shift integer portion up */ iqNResult = uiq0Integer << q_value; /* Return the result. */ return iqNResult; } /* Increment to the null terminating character and back up one character. */ while (*++string); string--; /* Setup the device multiplier. */ __mpy_start(&ui16IntState, &ui16MPYState); /* Process fractional portion of string starting at the last character. */ while (*string != '.') { /* Check for invalid character */ if (*string < '0' || *string > '9') { return 0; } /* Multiply fractional piece by 0.1 to setup the next decimal place. */ __mpy_set_frac(); uiq31Fractional = __mpyf_ul(uiq31Fractional, iq31_pointOne); __mpy_clear_ctl0(); /* * Add the current decimal place converted to iq31 to the sum and * decrement pointer. */ uiq31Fractional += __mpy_ul((*string - '0'), iq31_pointOne); string--; } /* Restore multiplier context. */ __mpy_stop(&ui16IntState, &ui16MPYState); /* Shift integer portion up */ uiq0Integer <<= q_value; /* Shift fractional portion to match Q type with rounding. */ if (q_value != 31) { uiq31Fractional += ((uint_fast32_t)1 << (30 - q_value)); } uiq31Fractional >>= (31 - q_value); /* Construct the iqN result. */ iqNResult = uiq0Integer + uiq31Fractional; if (sgn) { iqNResult = -iqNResult; } /* Finished, return the result */ return iqNResult; } /** * @brief Converts string to IQ31 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ31 type. */ int32_t _atoIQ31(const char *string) { return __atoIQN(string, 31); } /** * @brief Converts string to IQ30 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ30 type. */ int32_t _atoIQ30(const char *string) { return __atoIQN(string, 30); } /** * @brief Converts string to IQ29 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ29 type. */ int32_t _atoIQ29(const char *string) { return __atoIQN(string, 29); } /** * @brief Converts string to IQ28 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ28 type. */ int32_t _atoIQ28(const char *string) { return __atoIQN(string, 28); } /** * @brief Converts string to IQ27 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ27 type. */ int32_t _atoIQ27(const char *string) { return __atoIQN(string, 27); } /** * @brief Converts string to IQ26 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ26 type. */ int32_t _atoIQ26(const char *string) { return __atoIQN(string, 26); } /** * @brief Converts string to IQ25 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ25 type. */ int32_t _atoIQ25(const char *string) { return __atoIQN(string, 25); } /** * @brief Converts string to IQ24 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ24 type. */ int32_t _atoIQ24(const char *string) { return __atoIQN(string, 24); } /** * @brief Converts string to IQ23 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ23 type. */ int32_t _atoIQ23(const char *string) { return __atoIQN(string, 23); } /** * @brief Converts string to IQ22 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ22 type. */ int32_t _atoIQ22(const char *string) { return __atoIQN(string, 22); } /** * @brief Converts string to IQ21 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ21 type. */ int32_t _atoIQ21(const char *string) { return __atoIQN(string, 21); } /** * @brief Converts string to IQ20 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ20 type. */ int32_t _atoIQ20(const char *string) { return __atoIQN(string, 20); } /** * @brief Converts string to IQ19 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ19 type. */ int32_t _atoIQ19(const char *string) { return __atoIQN(string, 19); } /** * @brief Converts string to IQ18 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ18 type. */ int32_t _atoIQ18(const char *string) { return __atoIQN(string, 18); } /** * @brief Converts string to IQ17 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ17 type. */ int32_t _atoIQ17(const char *string) { return __atoIQN(string, 17); } /** * @brief Converts string to IQ16 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ16 type. */ int32_t _atoIQ16(const char *string) { return __atoIQN(string, 16); } /** * @brief Converts string to IQ15 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ15 type. */ int32_t _atoIQ15(const char *string) { return __atoIQN(string, 15); } /** * @brief Converts string to IQ14 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ14 type. */ int32_t _atoIQ14(const char *string) { return __atoIQN(string, 14); } /** * @brief Converts string to IQ13 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ13 type. */ int32_t _atoIQ13(const char *string) { return __atoIQN(string, 13); } /** * @brief Converts string to IQ12 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ12 type. */ int32_t _atoIQ12(const char *string) { return __atoIQN(string, 12); } /** * @brief Converts string to IQ11 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ11 type. */ int32_t _atoIQ11(const char *string) { return __atoIQN(string, 11); } /** * @brief Converts string to IQ10 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ10 type. */ int32_t _atoIQ10(const char *string) { return __atoIQN(string, 10); } /** * @brief Converts string to IQ9 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ9 type. */ int32_t _atoIQ9(const char *string) { return __atoIQN(string, 9); } /** * @brief Converts string to IQ8 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ8 type. */ int32_t _atoIQ8(const char *string) { return __atoIQN(string, 8); } /** * @brief Converts string to IQ7 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ7 type. */ int32_t _atoIQ7(const char *string) { return __atoIQN(string, 7); } /** * @brief Converts string to IQ6 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ6 type. */ int32_t _atoIQ6(const char *string) { return __atoIQN(string, 6); } /** * @brief Converts string to IQ5 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ5 type. */ int32_t _atoIQ5(const char *string) { return __atoIQN(string, 5); } /** * @brief Converts string to IQ4 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ4 type. */ int32_t _atoIQ4(const char *string) { return __atoIQN(string, 4); } /** * @brief Converts string to IQ3 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ3 type. */ int32_t _atoIQ3(const char *string) { return __atoIQN(string, 3); } /** * @brief Converts string to IQ2 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ2 type. */ int32_t _atoIQ2(const char *string) { return __atoIQN(string, 2); } /** * @brief Converts string to IQ1 number. * * @param string Pointer to the string to be converted. * * @return Conversion of string to IQ1 type. */ int32_t _atoIQ1(const char *string) { return __atoIQN(string, 1); } ================================================ FILE: iqmath/examples/get_started/CMakeLists.txt ================================================ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # The following five lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(iqmath_get_started) ================================================ FILE: iqmath/examples/get_started/README.md ================================================ # IQMath Get Started This example demonstrates how to use the [IQMath](https://components.espressif.com/component/espressif/iqmath) library. ## How to Use Example ### Hardware Required * A development board with Espressif SoC * A USB cable for Power supply and programming ### Configure the Example Before project configuration and build, be sure to set the correct chip target using `idf.py set-target `. ### Build and Flash Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. (To exit the serial monitor, type ``Ctrl-]``.) See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. ## Example Output ```text I (308) main_task: Started on CPU0 I (308) main_task: Calling app_main() I (308) example: IQMath test passed I (318) main_task: Returned from app_main() ``` ================================================ FILE: iqmath/examples/get_started/main/CMakeLists.txt ================================================ idf_component_register(SRCS "iqmath_example_main.c" INCLUDE_DIRS ".") ================================================ FILE: iqmath/examples/get_started/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/iqmath: version: '^1' override_path: '../../../' ================================================ FILE: iqmath/examples/get_started/main/iqmath_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "esp_log.h" #include "IQmathLib.h" static const char *TAG = "example"; #define ERROR_WITHIN_TOLERANCE(result, expected, tolerance) \ (((result) >= ((expected) - ((expected) * (tolerance)))) && \ ((result) <= ((expected) + ((expected) * (tolerance))))) void app_main(void) { const float error_tolerance = 0.01; bool test_failure = false; /* floating point variable to verify results */ float res; /* IQ variables using global type */ _iq qA, qB, qC; /* IQ variables using IQ8 type */ _iq8 q8A, q8B, q8C; /* IQ variables using IQ15 type */ _iq15 q15A, q15C; /* Basic global IQ operations. */ qA = _IQ(1.0); qB = _IQ(2.5); qC = qA + qB; /* 3.5 = 1.0 + 2.5 */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 3.5, error_tolerance)) { test_failure = true; }; qC = qC - _IQmpy2(qA); /* 1.5 = 3.5 - 2*(1.0) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 1.5, error_tolerance)) { test_failure = true; }; qC = _IQmpy(qB, qC); /* 3.75 = 2.5 * 1.5 */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 3.75, error_tolerance)) { test_failure = true; }; qC = _IQdiv(qC, qB); /* 1.5 = 3.75 / 2.5 */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 1.5, error_tolerance)) { test_failure = true; }; qC = _IQsqrt(qB); /* 1.58113885 = sqrt(2.5) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 1.58113885, error_tolerance)) { test_failure = true; }; /* Trigonometric global IQ operations. */ qA = _IQ(M_PI / 4.0); qB = _IQ(0.5); qC = _IQsin(qA); /* 0.707106709 = sin(PI/4) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 0.707106709, error_tolerance)) { test_failure = true; }; qC = _IQcos(qA); /* 0.707106769 = cos(PI/4) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 0.707106769, error_tolerance)) { test_failure = true; }; qC = _IQatan(qB); /* 0.463647604 = atan(0.5) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 0.463647604, error_tolerance)) { test_failure = true; }; /* Exponential global IQ operations. */ qA = _IQ(2.71828); qB = _IQ(0.5); qC = _IQlog(qA); /* 0.9999999225 = ln(e) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 0.9999999225, error_tolerance)) { test_failure = true; }; qC = _IQexp(qB); /* 1.64872134 = e^0.5 */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 1.64872134, error_tolerance)) { test_failure = true; }; /* Basic explicit type IQ8 operations. */ q8A = _IQ8(1.0); q8B = _IQ8(2.5); q8C = q8A + q8B; /* 3.5 = 1.0 + 2.5 */ res = _IQ8toF(q8C); if (!ERROR_WITHIN_TOLERANCE(res, 3.5, error_tolerance)) { test_failure = true; }; q8C = q8C - _IQmpy2(q8A); /* 1.5 = 3.5 - 2*(1.0) */ res = _IQ8toF(q8C); if (!ERROR_WITHIN_TOLERANCE(res, 1.5, error_tolerance)) { test_failure = true; }; q8C = _IQ8mpy(q8B, q8C); /* 3.75 = 2.5 * 1.5 */ res = _IQ8toF(q8C); if (!ERROR_WITHIN_TOLERANCE(res, 3.75, error_tolerance)) { test_failure = true; }; q8C = _IQ8div(q8C, q8B); /* 1.5 = 3.75 / 2.5 */ res = _IQ8toF(q8C); if (!ERROR_WITHIN_TOLERANCE(res, 1.5, error_tolerance)) { test_failure = true; }; q8C = _IQ8sqrt(q8B); /* 1.58203125 = sqrt(2.5) */ res = _IQ8toF(q8C); if (!ERROR_WITHIN_TOLERANCE(res, 1.58203125, error_tolerance)) { test_failure = true; }; /* Trigonometric explicit type IQ15 operations. */ q15A = _IQ15(M_PI / 4.0); q15C = _IQ15sin(q15A); /* 0.707061768 = sin(PI/4) */ res = _IQ15toF(q15C); if (!ERROR_WITHIN_TOLERANCE(res, 0.707061768, error_tolerance)) { test_failure = true; }; q15C = _IQ15cos(q15A); /* 0.707061768 = cos(PI/4) */ res = _IQ15toF(q15C); if (!ERROR_WITHIN_TOLERANCE(res, 0.707061768, error_tolerance)) { test_failure = true; }; /* Explicit type IQ8 to Global IQ conversion with saturation check. */ q8A = _IQ8(1.0); q8B = _IQ8(16.0); qC = _IQ8toIQ(_IQsat(q8A, _IQtoQ8(MAX_IQ_POS), _IQtoQ8(MAX_IQ_NEG))); /* _IQ8(1.0) -> _IQ(1.0) (q8A does not saturate) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 1.0, error_tolerance)) { test_failure = true; }; qC = _IQ8toIQ(_IQsat(q8B, _IQtoQ8(MAX_IQ_POS), _IQtoQ8(MAX_IQ_NEG))); /* _IQ8(16.0) -> ~MAX_IQ_POS (q8A saturates to maximum positive _IQ value) */ res = _IQtoF(qC); if (!ERROR_WITHIN_TOLERANCE(res, 16.0, error_tolerance)) { test_failure = true; }; if (test_failure == true) { ESP_LOGE(TAG, "IQMath test failed"); } else { ESP_LOGI(TAG, "IQMath test passed"); } } ================================================ FILE: iqmath/examples/get_started/pytest_iqmath_example.py ================================================ import pytest @pytest.mark.generic def test_iqmath_example(dut) -> None: dut.expect_exact("IQMath test passed") ================================================ FILE: iqmath/idf_component.yml ================================================ version: "1.11.0~1" description: IQMath fixed-point mathematical library url: https://github.com/espressif/idf-extra-components/tree/master/iqmath dependencies: idf: ">=4.4" ================================================ FILE: iqmath/include/IQmathLib.h ================================================ /*!**************************************************************************** * @file IQmathLib.h * @brief Library of IQMath operations. * *
******************************************************************************/ #ifndef __IQMATHLIB_H__ #define __IQMATHLIB_H__ //***************************************************************************** // // If building with a C++ compiler, make all of the definitions in this header // have a C binding. // //***************************************************************************** #ifdef __cplusplus extern "C" { #endif //***************************************************************************** /*! * @brief The IQ format to be used when the IQ format is not explicitly specified * (such as _IQcos instead of _IQ16cos). This value must be between 1 and 30, * inclusive. */ //***************************************************************************** #ifndef GLOBAL_IQ #define GLOBAL_IQ 24 #endif //***************************************************************************** // // Include some standard headers. // //***************************************************************************** #include #include #include #ifndef DOXYGEN_SHOULD_SKIP_THIS //***************************************************************************** // // Various Useful Constant Definitions: // //***************************************************************************** #define Q30 30 #define Q29 29 #define Q28 28 #define Q27 27 #define Q26 26 #define Q25 25 #define Q24 24 #define Q23 23 #define Q22 22 #define Q21 21 #define Q20 20 #define Q19 19 #define Q18 18 #define Q17 17 #define Q16 16 #define Q15 15 #define Q14 14 #define Q13 13 #define Q12 12 #define Q11 11 #define Q10 10 #define Q9 9 #define Q8 8 #define Q7 7 #define Q6 6 #define Q5 5 #define Q4 4 #define Q3 3 #define Q2 2 #define Q1 1 #ifndef QG #define QG GLOBAL_IQ #endif #define MAX_IQ_POS LONG_MAX #define MAX_IQ_NEG LONG_MIN #define MIN_IQ_POS 1 #define MIN_IQ_NEG -1 //***************************************************************************** // // The types for the various IQ formats. // //***************************************************************************** typedef int32_t _iq30; typedef int32_t _iq29; typedef int32_t _iq28; typedef int32_t _iq27; typedef int32_t _iq26; typedef int32_t _iq25; typedef int32_t _iq24; typedef int32_t _iq23; typedef int32_t _iq22; typedef int32_t _iq21; typedef int32_t _iq20; typedef int32_t _iq19; typedef int32_t _iq18; typedef int32_t _iq17; typedef int32_t _iq16; typedef int32_t _iq15; typedef int32_t _iq14; typedef int32_t _iq13; typedef int32_t _iq12; typedef int32_t _iq11; typedef int32_t _iq10; typedef int32_t _iq9; typedef int32_t _iq8; typedef int32_t _iq7; typedef int32_t _iq6; typedef int32_t _iq5; typedef int32_t _iq4; typedef int32_t _iq3; typedef int32_t _iq2; typedef int32_t _iq1; typedef int32_t _iq; #endif /* DOXYGEN_SHOULD_SKIP_THIS */ //***************************************************************************** // // Simple multiplies or divides, which are accomplished with simple shifts. // //***************************************************************************** /** * @brief Multiplies an IQ value by 2. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy2(A) ((A) << 1) /** * @brief Multiplies an IQ value by 4. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy4(A) ((A) << 2) /** * @brief Multiplies an IQ value by 8. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy8(A) ((A) << 3) /** * @brief Multiplies an IQ value by 16. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy16(A) ((A) << 4) /** * @brief Multiplies an IQ value by 32. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy32(A) ((A) << 5) /** * @brief Multiplies an IQ value by 64. * * @param A IQ type input. * * @return IQ type result of multiplication. */ #define _IQmpy64(A) ((A) << 6) /** * @brief Divides an IQ value by 2. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv2(A) ((A) >> 1) /** * @brief Divides an IQ value by 4. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv4(A) ((A) >> 2) /** * @brief Divides an IQ value by 8. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv8(A) ((A) >> 3) /** * @brief Divides an IQ value by 16. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv16(A) ((A) >> 4) /** * @brief Divides an IQ value by 32. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv32(A) ((A) >> 5) /** * @brief Divides an IQ value by 64. * * @param A IQ type input. * * @return IQ type result of division. */ #define _IQdiv64(A) ((A) >> 6) //***************************************************************************** // // Convert a value into an IQ number. // //***************************************************************************** /** * @brief Converts a value into an IQ30 number. * * @param A Number input. * * @return IQ30 type result. */ #define _IQ30(A) ((_iq30)((A) * ((_iq30)1 << 30))) /** * @brief Converts a value into an IQ29 number. * * @param A Number input. * * @return IQ29 type result. */ #define _IQ29(A) ((_iq29)((A) * ((_iq29)1 << 29))) /** * @brief Converts a value into an IQ28 number. * * @param A Number input. * * @return IQ28 type result. */ #define _IQ28(A) ((_iq28)((A) * ((_iq28)1 << 28))) /** * @brief Converts a value into an IQ27 number. * * @param A Number input. * * @return IQ27 type result. */ #define _IQ27(A) ((_iq27)((A) * ((_iq27)1 << 27))) /** * @brief Converts a value into an IQ26 number. * * @param A Number input. * * @return IQ26 type result. */ #define _IQ26(A) ((_iq26)((A) * ((_iq26)1 << 26))) /** * @brief Converts a value into an IQ25 number. * * @param A Number input. * * @return IQ25 type result. */ #define _IQ25(A) ((_iq25)((A) * ((_iq25)1 << 25))) /** * @brief Converts a value into an IQ24 number. * * @param A Number input. * * @return IQ24 type result. */ #define _IQ24(A) ((_iq24)((A) * ((_iq24)1 << 24))) /** * @brief Converts a value into an IQ23 number. * * @param A Number input. * * @return IQ23 type result. */ #define _IQ23(A) ((_iq23)((A) * ((_iq23)1 << 23))) /** * @brief Converts a value into an IQ22 number. * * @param A Number input. * * @return IQ22 type result. */ #define _IQ22(A) ((_iq22)((A) * ((_iq22)1 << 22))) /** * @brief Converts a value into an IQ21 number. * * @param A Number input. * * @return IQ21 type result. */ #define _IQ21(A) ((_iq21)((A) * ((_iq21)1 << 21))) /** * @brief Converts a value into an IQ20 number. * * @param A Number input. * * @return IQ20 type result. */ #define _IQ20(A) ((_iq20)((A) * ((_iq20)1 << 20))) /** * @brief Converts a value into an IQ19 number. * * @param A Number input. * * @return IQ19 type result. */ #define _IQ19(A) ((_iq19)((A) * ((_iq19)1 << 19))) /** * @brief Converts a value into an IQ18 number. * * @param A Number input. * * @return IQ18 type result. */ #define _IQ18(A) ((_iq18)((A) * ((_iq18)1 << 18))) /** * @brief Converts a value into an IQ17 number. * * @param A Number input. * * @return IQ17 type result. */ #define _IQ17(A) ((_iq17)((A) * ((_iq17)1 << 17))) /** * @brief Converts a value into an IQ16 number. * * @param A Number input. * * @return IQ16 type result. */ #define _IQ16(A) ((_iq16)((A) * ((_iq16)1 << 16))) /** * @brief Converts a value into an IQ15 number. * * @param A Number input. * * @return IQ15 type result. */ #define _IQ15(A) ((_iq15)((A) * ((_iq15)1 << 15))) /** * @brief Converts a value into an IQ14 number. * * @param A Number input. * * @return IQ14 type result. */ #define _IQ14(A) ((_iq14)((A) * ((_iq14)1 << 14))) /** * @brief Converts a value into an IQ13 number. * * @param A Number input. * * @return IQ13 type result. */ #define _IQ13(A) ((_iq13)((A) * ((_iq13)1 << 13))) /** * @brief Converts a value into an IQ12 number. * * @param A Number input. * * @return IQ12 type result. */ #define _IQ12(A) ((_iq12)((A) * ((_iq12)1 << 12))) /** * @brief Converts a value into an IQ11 number. * * @param A Number input. * * @return IQ11 type result. */ #define _IQ11(A) ((_iq11)((A) * ((_iq11)1 << 11))) /** * @brief Converts a value into an IQ10 number. * * @param A Number input. * * @return IQ10 type result. */ #define _IQ10(A) ((_iq10)((A) * ((_iq10)1 << 10))) /** * @brief Converts a value into an IQ9 number. * * @param A Number input. * * @return IQ9 type result. */ #define _IQ9(A) ((_iq9)((A) * ((_iq9)1 << 9))) /** * @brief Converts a value into an IQ8 number. * * @param A Number input. * * @return IQ8 type result. */ #define _IQ8(A) ((_iq8)((A) * ((_iq8)1 << 8))) /** * @brief Converts a value into an IQ7 number. * * @param A Number input. * * @return IQ7 type result. */ #define _IQ7(A) ((_iq7)((A) * ((_iq7)1 << 7))) /** * @brief Converts a value into an IQ6 number. * * @param A Number input. * * @return IQ6 type result. */ #define _IQ6(A) ((_iq6)((A) * ((_iq6)1 << 6))) /** * @brief Converts a value into an IQ5 number. * * @param A Number input. * * @return IQ5 type result. */ #define _IQ5(A) ((_iq5)((A) * ((_iq5)1 << 5))) /** * @brief Converts a value into an IQ4 number. * * @param A Number input. * * @return IQ4 type result. */ #define _IQ4(A) ((_iq4)((A) * ((_iq4)1 << 4))) /** * @brief Converts a value into an IQ3 number. * * @param A Number input. * * @return IQ3 type result. */ #define _IQ3(A) ((_iq3)((A) * ((_iq3)1 << 3))) /** * @brief Converts a value into an IQ2 number. * * @param A Number input. * * @return IQ2 type result. */ #define _IQ2(A) ((_iq2)((A) * ((_iq2)1 << 2))) /** * @brief Converts a value into an IQ1 number. * * @param A Number input. * * @return IQ1 type result. */ #define _IQ1(A) ((_iq1)((A) * ((_iq1)1 << 1))) /** * @brief Converts a value into an the global IQ format. * * @param A Number input. * * @return Global IQ type result. */ #if GLOBAL_IQ == 30 #define _IQ(A) _IQ30(A) #endif #if GLOBAL_IQ == 29 #define _IQ(A) _IQ29(A) #endif #if GLOBAL_IQ == 28 #define _IQ(A) _IQ28(A) #endif #if GLOBAL_IQ == 27 #define _IQ(A) _IQ27(A) #endif #if GLOBAL_IQ == 26 #define _IQ(A) _IQ26(A) #endif #if GLOBAL_IQ == 25 #define _IQ(A) _IQ25(A) #endif #if GLOBAL_IQ == 24 #define _IQ(A) _IQ24(A) #endif #if GLOBAL_IQ == 23 #define _IQ(A) _IQ23(A) #endif #if GLOBAL_IQ == 22 #define _IQ(A) _IQ22(A) #endif #if GLOBAL_IQ == 21 #define _IQ(A) _IQ21(A) #endif #if GLOBAL_IQ == 20 #define _IQ(A) _IQ20(A) #endif #if GLOBAL_IQ == 19 #define _IQ(A) _IQ19(A) #endif #if GLOBAL_IQ == 18 #define _IQ(A) _IQ18(A) #endif #if GLOBAL_IQ == 17 #define _IQ(A) _IQ17(A) #endif #if GLOBAL_IQ == 16 #define _IQ(A) _IQ16(A) #endif #if GLOBAL_IQ == 15 #define _IQ(A) _IQ15(A) #endif #if GLOBAL_IQ == 14 #define _IQ(A) _IQ14(A) #endif #if GLOBAL_IQ == 13 #define _IQ(A) _IQ13(A) #endif #if GLOBAL_IQ == 12 #define _IQ(A) _IQ12(A) #endif #if GLOBAL_IQ == 11 #define _IQ(A) _IQ11(A) #endif #if GLOBAL_IQ == 10 #define _IQ(A) _IQ10(A) #endif #if GLOBAL_IQ == 9 #define _IQ(A) _IQ9(A) #endif #if GLOBAL_IQ == 8 #define _IQ(A) _IQ8(A) #endif #if GLOBAL_IQ == 7 #define _IQ(A) _IQ7(A) #endif #if GLOBAL_IQ == 6 #define _IQ(A) _IQ6(A) #endif #if GLOBAL_IQ == 5 #define _IQ(A) _IQ5(A) #endif #if GLOBAL_IQ == 4 #define _IQ(A) _IQ4(A) #endif #if GLOBAL_IQ == 3 #define _IQ(A) _IQ3(A) #endif #if GLOBAL_IQ == 2 #define _IQ(A) _IQ2(A) #endif #if GLOBAL_IQ == 1 #define _IQ(A) _IQ1(A) #endif //***************************************************************************** // // Convert an IQ number to a floating point value. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern float _IQ30toF(_iq30 A); extern float _IQ29toF(_iq29 A); extern float _IQ28toF(_iq28 A); extern float _IQ27toF(_iq27 A); extern float _IQ26toF(_iq26 A); extern float _IQ25toF(_iq25 A); extern float _IQ24toF(_iq24 A); extern float _IQ23toF(_iq23 A); extern float _IQ22toF(_iq22 A); extern float _IQ21toF(_iq21 A); extern float _IQ20toF(_iq20 A); extern float _IQ19toF(_iq19 A); extern float _IQ18toF(_iq18 A); extern float _IQ17toF(_iq17 A); extern float _IQ16toF(_iq16 A); extern float _IQ15toF(_iq15 A); extern float _IQ14toF(_iq14 A); extern float _IQ13toF(_iq13 A); extern float _IQ12toF(_iq12 A); extern float _IQ11toF(_iq11 A); extern float _IQ10toF(_iq10 A); extern float _IQ9toF(_iq9 A); extern float _IQ8toF(_iq8 A); extern float _IQ7toF(_iq7 A); extern float _IQ6toF(_iq6 A); extern float _IQ5toF(_iq5 A); extern float _IQ4toF(_iq4 A); extern float _IQ3toF(_iq3 A); extern float _IQ2toF(_iq2 A); extern float _IQ1toF(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Convert a global IQ format number to a floating point value. * * @param A Number input. * * @return Global IQ type result. */ #if GLOBAL_IQ == 30 #define _IQtoF(A) _IQ30toF(A) #endif #if GLOBAL_IQ == 29 #define _IQtoF(A) _IQ29toF(A) #endif #if GLOBAL_IQ == 28 #define _IQtoF(A) _IQ28toF(A) #endif #if GLOBAL_IQ == 27 #define _IQtoF(A) _IQ27toF(A) #endif #if GLOBAL_IQ == 26 #define _IQtoF(A) _IQ26toF(A) #endif #if GLOBAL_IQ == 25 #define _IQtoF(A) _IQ25toF(A) #endif #if GLOBAL_IQ == 24 #define _IQtoF(A) _IQ24toF(A) #endif #if GLOBAL_IQ == 23 #define _IQtoF(A) _IQ23toF(A) #endif #if GLOBAL_IQ == 22 #define _IQtoF(A) _IQ22toF(A) #endif #if GLOBAL_IQ == 21 #define _IQtoF(A) _IQ21toF(A) #endif #if GLOBAL_IQ == 20 #define _IQtoF(A) _IQ20toF(A) #endif #if GLOBAL_IQ == 19 #define _IQtoF(A) _IQ19toF(A) #endif #if GLOBAL_IQ == 18 #define _IQtoF(A) _IQ18toF(A) #endif #if GLOBAL_IQ == 17 #define _IQtoF(A) _IQ17toF(A) #endif #if GLOBAL_IQ == 16 #define _IQtoF(A) _IQ16toF(A) #endif #if GLOBAL_IQ == 15 #define _IQtoF(A) _IQ15toF(A) #endif #if GLOBAL_IQ == 14 #define _IQtoF(A) _IQ14toF(A) #endif #if GLOBAL_IQ == 13 #define _IQtoF(A) _IQ13toF(A) #endif #if GLOBAL_IQ == 12 #define _IQtoF(A) _IQ12toF(A) #endif #if GLOBAL_IQ == 11 #define _IQtoF(A) _IQ11toF(A) #endif #if GLOBAL_IQ == 10 #define _IQtoF(A) _IQ10toF(A) #endif #if GLOBAL_IQ == 9 #define _IQtoF(A) _IQ9toF(A) #endif #if GLOBAL_IQ == 8 #define _IQtoF(A) _IQ8toF(A) #endif #if GLOBAL_IQ == 7 #define _IQtoF(A) _IQ7toF(A) #endif #if GLOBAL_IQ == 6 #define _IQtoF(A) _IQ6toF(A) #endif #if GLOBAL_IQ == 5 #define _IQtoF(A) _IQ5toF(A) #endif #if GLOBAL_IQ == 4 #define _IQtoF(A) _IQ4toF(A) #endif #if GLOBAL_IQ == 3 #define _IQtoF(A) _IQ3toF(A) #endif #if GLOBAL_IQ == 2 #define _IQtoF(A) _IQ2toF(A) #endif #if GLOBAL_IQ == 1 #define _IQtoF(A) _IQ1toF(A) #endif //***************************************************************************** // // Saturates an IQ number in a given range. // //***************************************************************************** /** * @brief Saturates an IQ number in a given range. * * @param A IQ number to be saturated. * @param Pos Maximum positive value. * @param Neg Minimum negative Value. * * @return Saturated IQ type result. */ #define _IQsat(A, Pos, Neg) (((A) > (Pos)) ? \ (Pos) : \ (((A) < (Neg)) ? (Neg) : (A))) #ifndef DOXYGEN_SHOULD_SKIP_THIS //***************************************************************************** // // Converts an IQ number between the global IQ format and a specified IQ // format. // //***************************************************************************** #define _IQtoIQ30(A) ((_iq30)(A) << (30 - GLOBAL_IQ)) #define _IQ30toIQ(A) ((_iq30)(A) >> (30 - GLOBAL_IQ)) #if (GLOBAL_IQ >= 29) #define _IQtoIQ29(A) ((_iq29)(A) >> (GLOBAL_IQ - 29)) #define _IQ29toIQ(A) ((_iq29)(A) << (GLOBAL_IQ - 29)) #else #define _IQtoIQ29(A) ((_iq29)(A) << (29 - GLOBAL_IQ)) #define _IQ29toIQ(A) ((_iq29)(A) >> (29 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 28) #define _IQtoIQ28(A) ((_iq28)(A) >> (GLOBAL_IQ - 28)) #define _IQ28toIQ(A) ((_iq28)(A) << (GLOBAL_IQ - 28)) #else #define _IQtoIQ28(A) ((_iq28)(A) << (28 - GLOBAL_IQ)) #define _IQ28toIQ(A) ((_iq28)(A) >> (28 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 27) #define _IQtoIQ27(A) ((_iq27)(A) >> (GLOBAL_IQ - 27)) #define _IQ27toIQ(A) ((_iq27)(A) << (GLOBAL_IQ - 27)) #else #define _IQtoIQ27(A) ((_iq27)(A) << (27 - GLOBAL_IQ)) #define _IQ27toIQ(A) ((_iq27)(A) >> (27 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 26) #define _IQtoIQ26(A) ((_iq26)(A) >> (GLOBAL_IQ - 26)) #define _IQ26toIQ(A) ((_iq26)(A) << (GLOBAL_IQ - 26)) #else #define _IQtoIQ26(A) ((_iq26)(A) << (26 - GLOBAL_IQ)) #define _IQ26toIQ(A) ((_iq26)(A) >> (26 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 25) #define _IQtoIQ25(A) ((_iq25)(A) >> (GLOBAL_IQ - 25)) #define _IQ25toIQ(A) ((_iq25)(A) << (GLOBAL_IQ - 25)) #else #define _IQtoIQ25(A) ((_iq25)(A) << (25 - GLOBAL_IQ)) #define _IQ25toIQ(A) ((_iq25)(A) >> (25 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 24) #define _IQtoIQ24(A) ((_iq24)(A) >> (GLOBAL_IQ - 24)) #define _IQ24toIQ(A) ((_iq24)(A) << (GLOBAL_IQ - 24)) #else #define _IQtoIQ24(A) ((_iq24)(A) << (24 - GLOBAL_IQ)) #define _IQ24toIQ(A) ((_iq24)(A) >> (24 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 23) #define _IQtoIQ23(A) ((_iq23)(A) >> (GLOBAL_IQ - 23)) #define _IQ23toIQ(A) ((_iq23)(A) << (GLOBAL_IQ - 23)) #else #define _IQtoIQ23(A) ((_iq23)(A) << (23 - GLOBAL_IQ)) #define _IQ23toIQ(A) ((_iq23)(A) >> (23 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 22) #define _IQtoIQ22(A) ((_iq22)(A) >> (GLOBAL_IQ - 22)) #define _IQ22toIQ(A) ((_iq22)(A) << (GLOBAL_IQ - 22)) #else #define _IQtoIQ22(A) ((_iq22)(A) << (22 - GLOBAL_IQ)) #define _IQ22toIQ(A) ((_iq22)(A) >> (22 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 21) #define _IQtoIQ21(A) ((_iq21)(A) >> (GLOBAL_IQ - 21)) #define _IQ21toIQ(A) ((_iq21)(A) << (GLOBAL_IQ - 21)) #else #define _IQtoIQ21(A) ((_iq21)(A) << (21 - GLOBAL_IQ)) #define _IQ21toIQ(A) ((_iq21)(A) >> (21 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 20) #define _IQtoIQ20(A) ((_iq20)(A) >> (GLOBAL_IQ - 20)) #define _IQ20toIQ(A) ((_iq20)(A) << (GLOBAL_IQ - 20)) #else #define _IQtoIQ20(A) ((_iq20)(A) << (20 - GLOBAL_IQ)) #define _IQ20toIQ(A) ((_iq20)(A) >> (20 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 19) #define _IQtoIQ19(A) ((_iq19)(A) >> (GLOBAL_IQ - 19)) #define _IQ19toIQ(A) ((_iq19)(A) << (GLOBAL_IQ - 19)) #else #define _IQtoIQ19(A) ((_iq19)(A) << (19 - GLOBAL_IQ)) #define _IQ19toIQ(A) ((_iq19)(A) >> (19 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 18) #define _IQtoIQ18(A) ((_iq18)(A) >> (GLOBAL_IQ - 18)) #define _IQ18toIQ(A) ((_iq18)(A) << (GLOBAL_IQ - 18)) #else #define _IQtoIQ18(A) ((_iq18)(A) << (18 - GLOBAL_IQ)) #define _IQ18toIQ(A) ((_iq18)(A) >> (18 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 17) #define _IQtoIQ17(A) ((_iq17)(A) >> (GLOBAL_IQ - 17)) #define _IQ17toIQ(A) ((_iq17)(A) << (GLOBAL_IQ - 17)) #else #define _IQtoIQ17(A) ((_iq17)(A) << (17 - GLOBAL_IQ)) #define _IQ17toIQ(A) ((_iq17)(A) >> (17 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 16) #define _IQtoIQ16(A) ((_iq16)(A) >> (GLOBAL_IQ - 16)) #define _IQ16toIQ(A) ((_iq16)(A) << (GLOBAL_IQ - 16)) #else #define _IQtoIQ16(A) ((_iq16)(A) << (16 - GLOBAL_IQ)) #define _IQ16toIQ(A) ((_iq16)(A) >> (16 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 15) #define _IQtoIQ15(A) ((_iq15)(A) >> (GLOBAL_IQ - 15)) #define _IQ15toIQ(A) ((_iq15)(A) << (GLOBAL_IQ - 15)) #else #define _IQtoIQ15(A) ((_iq15)(A) << (15 - GLOBAL_IQ)) #define _IQ15toIQ(A) ((_iq15)(A) >> (15 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 14) #define _IQtoIQ14(A) ((_iq14)(A) >> (GLOBAL_IQ - 14)) #define _IQ14toIQ(A) ((_iq14)(A) << (GLOBAL_IQ - 14)) #else #define _IQtoIQ14(A) ((_iq14)(A) << (14 - GLOBAL_IQ)) #define _IQ14toIQ(A) ((_iq14)(A) >> (14 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 13) #define _IQtoIQ13(A) ((_iq13)(A) >> (GLOBAL_IQ - 13)) #define _IQ13toIQ(A) ((_iq13)(A) << (GLOBAL_IQ - 13)) #else #define _IQtoIQ13(A) ((_iq13)(A) << (13 - GLOBAL_IQ)) #define _IQ13toIQ(A) ((_iq13)(A) >> (13 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 12) #define _IQtoIQ12(A) ((_iq12)(A) >> (GLOBAL_IQ - 12)) #define _IQ12toIQ(A) ((_iq12)(A) << (GLOBAL_IQ - 12)) #else #define _IQtoIQ12(A) ((_iq12)(A) << (12 - GLOBAL_IQ)) #define _IQ12toIQ(A) ((_iq12)(A) >> (12 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 11) #define _IQtoIQ11(A) ((_iq11)(A) >> (GLOBAL_IQ - 11)) #define _IQ11toIQ(A) ((_iq11)(A) << (GLOBAL_IQ - 11)) #else #define _IQtoIQ11(A) ((_iq11)(A) << (11 - GLOBAL_IQ)) #define _IQ11toIQ(A) ((_iq11)(A) >> (11 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 10) #define _IQtoIQ10(A) ((_iq10)(A) >> (GLOBAL_IQ - 10)) #define _IQ10toIQ(A) ((_iq10)(A) << (GLOBAL_IQ - 10)) #else #define _IQtoIQ10(A) ((_iq10)(A) << (10 - GLOBAL_IQ)) #define _IQ10toIQ(A) ((_iq10)(A) >> (10 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 9) #define _IQtoIQ9(A) ((_iq9)(A) >> (GLOBAL_IQ - 9)) #define _IQ9toIQ(A) ((_iq9)(A) << (GLOBAL_IQ - 9)) #else #define _IQtoIQ9(A) ((_iq9)(A) << (9 - GLOBAL_IQ)) #define _IQ9toIQ(A) ((_iq9)(A) >> (9 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 8) #define _IQtoIQ8(A) ((_iq8)(A) >> (GLOBAL_IQ - 8)) #define _IQ8toIQ(A) ((_iq8)(A) << (GLOBAL_IQ - 8)) #else #define _IQtoIQ8(A) ((_iq8)(A) << (8 - GLOBAL_IQ)) #define _IQ8toIQ(A) ((_iq8)(A) >> (8 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 7) #define _IQtoIQ7(A) ((_iq7)(A) >> (GLOBAL_IQ - 7)) #define _IQ7toIQ(A) ((_iq7)(A) << (GLOBAL_IQ - 7)) #else #define _IQtoIQ7(A) ((_iq7)(A) << (7 - GLOBAL_IQ)) #define _IQ7toIQ(A) ((_iq7)(A) >> (7 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 6) #define _IQtoIQ6(A) ((_iq6)(A) >> (GLOBAL_IQ - 6)) #define _IQ6toIQ(A) ((_iq6)(A) << (GLOBAL_IQ - 6)) #else #define _IQtoIQ6(A) ((_iq6)(A) << (6 - GLOBAL_IQ)) #define _IQ6toIQ(A) ((_iq6)(A) >> (6 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 5) #define _IQtoIQ5(A) ((_iq5)(A) >> (GLOBAL_IQ - 5)) #define _IQ5toIQ(A) ((_iq5)(A) << (GLOBAL_IQ - 5)) #else #define _IQtoIQ5(A) ((_iq5)(A) << (5 - GLOBAL_IQ)) #define _IQ5toIQ(A) ((_iq5)(A) >> (5 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 4) #define _IQtoIQ4(A) ((_iq4)(A) >> (GLOBAL_IQ - 4)) #define _IQ4toIQ(A) ((_iq4)(A) << (GLOBAL_IQ - 4)) #else #define _IQtoIQ4(A) ((_iq4)(A) << (4 - GLOBAL_IQ)) #define _IQ4toIQ(A) ((_iq4)(A) >> (4 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 3) #define _IQtoIQ3(A) ((_iq3)(A) >> (GLOBAL_IQ - 3)) #define _IQ3toIQ(A) ((_iq3)(A) << (GLOBAL_IQ - 3)) #else #define _IQtoIQ3(A) ((_iq3)(A) << (3 - GLOBAL_IQ)) #define _IQ3toIQ(A) ((_iq3)(A) >> (3 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 2) #define _IQtoIQ2(A) ((_iq2)(A) >> (GLOBAL_IQ - 2)) #define _IQ2toIQ(A) ((_iq2)(A) << (GLOBAL_IQ - 2)) #else #define _IQtoIQ2(A) ((_iq2)(A) << (2 - GLOBAL_IQ)) #define _IQ2toIQ(A) ((_iq2)(A) >> (2 - GLOBAL_IQ)) #endif #define _IQtoIQ1(A) ((_iq1)(A) >> (GLOBAL_IQ - 1)) #define _IQ1toIQ(A) ((_iq1)(A) << (GLOBAL_IQ - 1)) #endif /* DOXYGEN_SHOULD_SKIP_THIS */ #ifndef DOXYGEN_SHOULD_SKIP_THIS //***************************************************************************** // // Converts an IQ number between a specified IQ format and another // specified IQ format. // // For the functions of type _IQXtoIQY, the values are shifted by the // difference of X and Y, depending on which one is larger. The definition is // as follows: // // #define _IQXtoIQY(A) // If X < Y // ((_iqY) (A) << (Y-X) // else // ((_iqY) (A) >> (X-Y) // //***************************************************************************** /* IQ1 to IQN */ #define _IQ1toIQ30(A) ((_iq30)(A) << (29)) #define _IQ1toIQ29(A) ((_iq29)(A) << (28)) #define _IQ1toIQ28(A) ((_iq28)(A) << (27)) #define _IQ1toIQ27(A) ((_iq27)(A) << (26)) #define _IQ1toIQ26(A) ((_iq26)(A) << (25)) #define _IQ1toIQ25(A) ((_iq25)(A) << (24)) #define _IQ1toIQ24(A) ((_iq24)(A) << (23)) #define _IQ1toIQ23(A) ((_iq23)(A) << (22)) #define _IQ1toIQ22(A) ((_iq22)(A) << (21)) #define _IQ1toIQ21(A) ((_iq21)(A) << (20)) #define _IQ1toIQ20(A) ((_iq20)(A) << (19)) #define _IQ1toIQ19(A) ((_iq19)(A) << (18)) #define _IQ1toIQ18(A) ((_iq18)(A) << (17)) #define _IQ1toIQ17(A) ((_iq17)(A) << (16)) #define _IQ1toIQ16(A) ((_iq16)(A) << (15)) #define _IQ1toIQ15(A) ((_iq15)(A) << (14)) #define _IQ1toIQ14(A) ((_iq14)(A) << (13)) #define _IQ1toIQ13(A) ((_iq13)(A) << (12)) #define _IQ1toIQ12(A) ((_iq12)(A) << (11)) #define _IQ1toIQ11(A) ((_iq11)(A) << (10)) #define _IQ1toIQ10(A) ((_iq10)(A) << (9)) #define _IQ1toIQ9(A) ((_iq9 )(A) << (8)) #define _IQ1toIQ8(A) ((_iq8 )(A) << (7)) #define _IQ1toIQ7(A) ((_iq7 )(A) << (6)) #define _IQ1toIQ6(A) ((_iq6 )(A) << (5)) #define _IQ1toIQ5(A) ((_iq5 )(A) << (4)) #define _IQ1toIQ4(A) ((_iq4 )(A) << (3)) #define _IQ1toIQ3(A) ((_iq3 )(A) << (2)) #define _IQ1toIQ2(A) ((_iq2 )(A) << (1)) /* IQ2 to IQN */ #define _IQ2toIQ30(A) ((_iq30)(A) << (28)) #define _IQ2toIQ29(A) ((_iq29)(A) << (27)) #define _IQ2toIQ28(A) ((_iq28)(A) << (26)) #define _IQ2toIQ27(A) ((_iq27)(A) << (25)) #define _IQ2toIQ26(A) ((_iq26)(A) << (24)) #define _IQ2toIQ25(A) ((_iq25)(A) << (23)) #define _IQ2toIQ24(A) ((_iq24)(A) << (22)) #define _IQ2toIQ23(A) ((_iq23)(A) << (21)) #define _IQ2toIQ22(A) ((_iq22)(A) << (20)) #define _IQ2toIQ21(A) ((_iq21)(A) << (19)) #define _IQ2toIQ20(A) ((_iq20)(A) << (18)) #define _IQ2toIQ19(A) ((_iq19)(A) << (17)) #define _IQ2toIQ18(A) ((_iq18)(A) << (16)) #define _IQ2toIQ17(A) ((_iq17)(A) << (15)) #define _IQ2toIQ16(A) ((_iq16)(A) << (14)) #define _IQ2toIQ15(A) ((_iq15)(A) << (13)) #define _IQ2toIQ14(A) ((_iq14)(A) << (12)) #define _IQ2toIQ13(A) ((_iq13)(A) << (11)) #define _IQ2toIQ12(A) ((_iq12)(A) << (10)) #define _IQ2toIQ11(A) ((_iq11)(A) << (9)) #define _IQ2toIQ10(A) ((_iq10)(A) << (8)) #define _IQ2toIQ9(A) ((_iq9 )(A) << (7)) #define _IQ2toIQ8(A) ((_iq8 )(A) << (6)) #define _IQ2toIQ7(A) ((_iq7 )(A) << (5)) #define _IQ2toIQ6(A) ((_iq6 )(A) << (4)) #define _IQ2toIQ5(A) ((_iq5 )(A) << (3)) #define _IQ2toIQ4(A) ((_iq4 )(A) << (2)) #define _IQ2toIQ3(A) ((_iq3 )(A) << (1)) #define _IQ2toIQ1(A) ((_iq1 )(A) >> (1)) /* IQ3 to IQN */ #define _IQ3toIQ30(A) ((_iq30)(A) << (27)) #define _IQ3toIQ29(A) ((_iq29)(A) << (26)) #define _IQ3toIQ28(A) ((_iq28)(A) << (25)) #define _IQ3toIQ27(A) ((_iq27)(A) << (24)) #define _IQ3toIQ26(A) ((_iq26)(A) << (23)) #define _IQ3toIQ25(A) ((_iq25)(A) << (22)) #define _IQ3toIQ24(A) ((_iq24)(A) << (21)) #define _IQ3toIQ23(A) ((_iq23)(A) << (20)) #define _IQ3toIQ22(A) ((_iq22)(A) << (19)) #define _IQ3toIQ21(A) ((_iq21)(A) << (18)) #define _IQ3toIQ20(A) ((_iq20)(A) << (17)) #define _IQ3toIQ19(A) ((_iq19)(A) << (16)) #define _IQ3toIQ18(A) ((_iq18)(A) << (15)) #define _IQ3toIQ17(A) ((_iq17)(A) << (14)) #define _IQ3toIQ16(A) ((_iq16)(A) << (13)) #define _IQ3toIQ15(A) ((_iq15)(A) << (12)) #define _IQ3toIQ14(A) ((_iq14)(A) << (11)) #define _IQ3toIQ13(A) ((_iq13)(A) << (10)) #define _IQ3toIQ12(A) ((_iq12)(A) << (9)) #define _IQ3toIQ11(A) ((_iq11)(A) << (8)) #define _IQ3toIQ10(A) ((_iq10)(A) << (7)) #define _IQ3toIQ9(A) ((_iq9 )(A) << (6)) #define _IQ3toIQ8(A) ((_iq8 )(A) << (5)) #define _IQ3toIQ7(A) ((_iq7 )(A) << (4)) #define _IQ3toIQ6(A) ((_iq6 )(A) << (3)) #define _IQ3toIQ5(A) ((_iq5 )(A) << (2)) #define _IQ3toIQ4(A) ((_iq4 )(A) << (1)) #define _IQ3toIQ2(A) ((_iq2 )(A) >> (1)) #define _IQ3toIQ1(A) ((_iq1 )(A) >> (2)) /* IQ4 to IQN */ #define _IQ4toIQ30(A) ((_iq30)(A) << (26)) #define _IQ4toIQ29(A) ((_iq29)(A) << (25)) #define _IQ4toIQ28(A) ((_iq28)(A) << (24)) #define _IQ4toIQ27(A) ((_iq27)(A) << (23)) #define _IQ4toIQ26(A) ((_iq26)(A) << (22)) #define _IQ4toIQ25(A) ((_iq25)(A) << (21)) #define _IQ4toIQ24(A) ((_iq24)(A) << (20)) #define _IQ4toIQ23(A) ((_iq23)(A) << (19)) #define _IQ4toIQ22(A) ((_iq22)(A) << (18)) #define _IQ4toIQ21(A) ((_iq21)(A) << (17)) #define _IQ4toIQ20(A) ((_iq20)(A) << (16)) #define _IQ4toIQ19(A) ((_iq19)(A) << (15)) #define _IQ4toIQ18(A) ((_iq18)(A) << (14)) #define _IQ4toIQ17(A) ((_iq17)(A) << (13)) #define _IQ4toIQ16(A) ((_iq16)(A) << (12)) #define _IQ4toIQ15(A) ((_iq15)(A) << (11)) #define _IQ4toIQ14(A) ((_iq14)(A) << (10)) #define _IQ4toIQ13(A) ((_iq13)(A) << (9)) #define _IQ4toIQ12(A) ((_iq12)(A) << (8)) #define _IQ4toIQ11(A) ((_iq11)(A) << (7)) #define _IQ4toIQ10(A) ((_iq10)(A) << (6)) #define _IQ4toIQ9(A) ((_iq9 )(A) << (5)) #define _IQ4toIQ8(A) ((_iq8 )(A) << (4)) #define _IQ4toIQ7(A) ((_iq7 )(A) << (3)) #define _IQ4toIQ6(A) ((_iq6 )(A) << (2)) #define _IQ4toIQ5(A) ((_iq5 )(A) << (1)) #define _IQ4toIQ3(A) ((_iq3 )(A) >> (1)) #define _IQ4toIQ2(A) ((_iq2 )(A) >> (2)) #define _IQ4toIQ1(A) ((_iq1 )(A) >> (3)) /* IQ5 to IQN */ #define _IQ5toIQ30(A) ((_iq30)(A) << (25)) #define _IQ5toIQ29(A) ((_iq29)(A) << (24)) #define _IQ5toIQ28(A) ((_iq28)(A) << (23)) #define _IQ5toIQ27(A) ((_iq27)(A) << (22)) #define _IQ5toIQ26(A) ((_iq26)(A) << (21)) #define _IQ5toIQ25(A) ((_iq25)(A) << (20)) #define _IQ5toIQ24(A) ((_iq24)(A) << (19)) #define _IQ5toIQ23(A) ((_iq23)(A) << (18)) #define _IQ5toIQ22(A) ((_iq22)(A) << (17)) #define _IQ5toIQ21(A) ((_iq21)(A) << (16)) #define _IQ5toIQ20(A) ((_iq20)(A) << (15)) #define _IQ5toIQ19(A) ((_iq19)(A) << (14)) #define _IQ5toIQ18(A) ((_iq18)(A) << (13)) #define _IQ5toIQ17(A) ((_iq17)(A) << (12)) #define _IQ5toIQ16(A) ((_iq16)(A) << (11)) #define _IQ5toIQ15(A) ((_iq15)(A) << (10)) #define _IQ5toIQ14(A) ((_iq14)(A) << (9)) #define _IQ5toIQ13(A) ((_iq13)(A) << (8)) #define _IQ5toIQ12(A) ((_iq12)(A) << (7)) #define _IQ5toIQ11(A) ((_iq11)(A) << (6)) #define _IQ5toIQ10(A) ((_iq10)(A) << (5)) #define _IQ5toIQ9(A) ((_iq9 )(A) << (4)) #define _IQ5toIQ8(A) ((_iq8 )(A) << (3)) #define _IQ5toIQ7(A) ((_iq7 )(A) << (2)) #define _IQ5toIQ6(A) ((_iq6 )(A) << (1)) #define _IQ5toIQ4(A) ((_iq4 )(A) >> (1)) #define _IQ5toIQ3(A) ((_iq3 )(A) >> (2)) #define _IQ5toIQ2(A) ((_iq2 )(A) >> (3)) #define _IQ5toIQ1(A) ((_iq1 )(A) >> (4)) /* IQ6 to IQN */ #define _IQ6toIQ30(A) ((_iq30)(A) << (24)) #define _IQ6toIQ29(A) ((_iq29)(A) << (23)) #define _IQ6toIQ28(A) ((_iq28)(A) << (22)) #define _IQ6toIQ27(A) ((_iq27)(A) << (21)) #define _IQ6toIQ26(A) ((_iq26)(A) << (20)) #define _IQ6toIQ25(A) ((_iq25)(A) << (19)) #define _IQ6toIQ24(A) ((_iq24)(A) << (18)) #define _IQ6toIQ23(A) ((_iq23)(A) << (17)) #define _IQ6toIQ22(A) ((_iq22)(A) << (16)) #define _IQ6toIQ21(A) ((_iq21)(A) << (15)) #define _IQ6toIQ20(A) ((_iq20)(A) << (14)) #define _IQ6toIQ19(A) ((_iq19)(A) << (13)) #define _IQ6toIQ18(A) ((_iq18)(A) << (12)) #define _IQ6toIQ17(A) ((_iq17)(A) << (11)) #define _IQ6toIQ16(A) ((_iq16)(A) << (10)) #define _IQ6toIQ15(A) ((_iq15)(A) << (9)) #define _IQ6toIQ14(A) ((_iq14)(A) << (8)) #define _IQ6toIQ13(A) ((_iq13)(A) << (7)) #define _IQ6toIQ12(A) ((_iq12)(A) << (6)) #define _IQ6toIQ11(A) ((_iq11)(A) << (5)) #define _IQ6toIQ10(A) ((_iq10)(A) << (4)) #define _IQ6toIQ9(A) ((_iq9 )(A) << (3)) #define _IQ6toIQ8(A) ((_iq8 )(A) << (2)) #define _IQ6toIQ7(A) ((_iq7 )(A) << (1)) #define _IQ6toIQ5(A) ((_iq5 )(A) >> (1)) #define _IQ6toIQ4(A) ((_iq4 )(A) >> (2)) #define _IQ6toIQ3(A) ((_iq3 )(A) >> (3)) #define _IQ6toIQ2(A) ((_iq2 )(A) >> (4)) #define _IQ6toIQ1(A) ((_iq1 )(A) >> (5)) /* IQ7 to IQN */ #define _IQ7toIQ30(A) ((_iq30)(A) << (23)) #define _IQ7toIQ29(A) ((_iq29)(A) << (22)) #define _IQ7toIQ28(A) ((_iq28)(A) << (21)) #define _IQ7toIQ27(A) ((_iq27)(A) << (20)) #define _IQ7toIQ26(A) ((_iq26)(A) << (19)) #define _IQ7toIQ25(A) ((_iq25)(A) << (18)) #define _IQ7toIQ24(A) ((_iq24)(A) << (17)) #define _IQ7toIQ23(A) ((_iq23)(A) << (16)) #define _IQ7toIQ22(A) ((_iq22)(A) << (15)) #define _IQ7toIQ21(A) ((_iq21)(A) << (14)) #define _IQ7toIQ20(A) ((_iq20)(A) << (13)) #define _IQ7toIQ19(A) ((_iq19)(A) << (12)) #define _IQ7toIQ18(A) ((_iq18)(A) << (11)) #define _IQ7toIQ17(A) ((_iq17)(A) << (10)) #define _IQ7toIQ16(A) ((_iq16)(A) << (9)) #define _IQ7toIQ15(A) ((_iq15)(A) << (8)) #define _IQ7toIQ14(A) ((_iq14)(A) << (7)) #define _IQ7toIQ13(A) ((_iq13)(A) << (6)) #define _IQ7toIQ12(A) ((_iq12)(A) << (5)) #define _IQ7toIQ11(A) ((_iq11)(A) << (4)) #define _IQ7toIQ10(A) ((_iq10)(A) << (3)) #define _IQ7toIQ9(A) ((_iq9 )(A) << (2)) #define _IQ7toIQ8(A) ((_iq8 )(A) << (1)) #define _IQ7toIQ6(A) ((_iq6 )(A) >> (1)) #define _IQ7toIQ5(A) ((_iq5 )(A) >> (2)) #define _IQ7toIQ4(A) ((_iq4 )(A) >> (3)) #define _IQ7toIQ3(A) ((_iq3 )(A) >> (4)) #define _IQ7toIQ2(A) ((_iq2 )(A) >> (5)) #define _IQ7toIQ1(A) ((_iq1 )(A) >> (6)) /* IQ8 to IQN */ #define _IQ8toIQ30(A) ((_iq30)(A) << (22)) #define _IQ8toIQ29(A) ((_iq29)(A) << (21)) #define _IQ8toIQ28(A) ((_iq28)(A) << (20)) #define _IQ8toIQ27(A) ((_iq27)(A) << (19)) #define _IQ8toIQ26(A) ((_iq26)(A) << (18)) #define _IQ8toIQ25(A) ((_iq25)(A) << (17)) #define _IQ8toIQ24(A) ((_iq24)(A) << (16)) #define _IQ8toIQ23(A) ((_iq23)(A) << (15)) #define _IQ8toIQ22(A) ((_iq22)(A) << (14)) #define _IQ8toIQ21(A) ((_iq21)(A) << (13)) #define _IQ8toIQ20(A) ((_iq20)(A) << (12)) #define _IQ8toIQ19(A) ((_iq19)(A) << (11)) #define _IQ8toIQ18(A) ((_iq18)(A) << (10)) #define _IQ8toIQ17(A) ((_iq17)(A) << (9)) #define _IQ8toIQ16(A) ((_iq16)(A) << (8)) #define _IQ8toIQ15(A) ((_iq15)(A) << (7)) #define _IQ8toIQ14(A) ((_iq14)(A) << (6)) #define _IQ8toIQ13(A) ((_iq13)(A) << (5)) #define _IQ8toIQ12(A) ((_iq12)(A) << (4)) #define _IQ8toIQ11(A) ((_iq11)(A) << (3)) #define _IQ8toIQ10(A) ((_iq10)(A) << (2)) #define _IQ8toIQ9(A) ((_iq9 )(A) << (1)) #define _IQ8toIQ7(A) ((_iq7 )(A) >> (1)) #define _IQ8toIQ6(A) ((_iq6 )(A) >> (2)) #define _IQ8toIQ5(A) ((_iq5 )(A) >> (3)) #define _IQ8toIQ4(A) ((_iq4 )(A) >> (4)) #define _IQ8toIQ3(A) ((_iq3 )(A) >> (5)) #define _IQ8toIQ2(A) ((_iq2 )(A) >> (6)) #define _IQ8toIQ1(A) ((_iq1 )(A) >> (7)) /* IQ9 to IQN */ #define _IQ9toIQ30(A) ((_iq30)(A) << (21)) #define _IQ9toIQ29(A) ((_iq29)(A) << (20)) #define _IQ9toIQ28(A) ((_iq28)(A) << (19)) #define _IQ9toIQ27(A) ((_iq27)(A) << (18)) #define _IQ9toIQ26(A) ((_iq26)(A) << (17)) #define _IQ9toIQ25(A) ((_iq25)(A) << (16)) #define _IQ9toIQ24(A) ((_iq24)(A) << (15)) #define _IQ9toIQ23(A) ((_iq23)(A) << (14)) #define _IQ9toIQ22(A) ((_iq22)(A) << (13)) #define _IQ9toIQ21(A) ((_iq21)(A) << (12)) #define _IQ9toIQ20(A) ((_iq20)(A) << (11)) #define _IQ9toIQ19(A) ((_iq19)(A) << (10)) #define _IQ9toIQ18(A) ((_iq18)(A) << (9)) #define _IQ9toIQ17(A) ((_iq17)(A) << (8)) #define _IQ9toIQ16(A) ((_iq16)(A) << (7)) #define _IQ9toIQ15(A) ((_iq15)(A) << (6)) #define _IQ9toIQ14(A) ((_iq14)(A) << (5)) #define _IQ9toIQ13(A) ((_iq13)(A) << (4)) #define _IQ9toIQ12(A) ((_iq12)(A) << (3)) #define _IQ9toIQ11(A) ((_iq11)(A) << (2)) #define _IQ9toIQ10(A) ((_iq10)(A) << (1)) #define _IQ9toIQ8(A) ((_iq8 )(A) >> (1)) #define _IQ9toIQ7(A) ((_iq7 )(A) >> (2)) #define _IQ9toIQ6(A) ((_iq6 )(A) >> (3)) #define _IQ9toIQ5(A) ((_iq5 )(A) >> (4)) #define _IQ9toIQ4(A) ((_iq4 )(A) >> (5)) #define _IQ9toIQ3(A) ((_iq3 )(A) >> (6)) #define _IQ9toIQ2(A) ((_iq2 )(A) >> (7)) #define _IQ9toIQ1(A) ((_iq1 )(A) >> (8)) /* IQ10 to IQN */ #define _IQ10toIQ30(A) ((_iq30)(A) << (20)) #define _IQ10toIQ29(A) ((_iq29)(A) << (19)) #define _IQ10toIQ28(A) ((_iq28)(A) << (18)) #define _IQ10toIQ27(A) ((_iq27)(A) << (17)) #define _IQ10toIQ26(A) ((_iq26)(A) << (16)) #define _IQ10toIQ25(A) ((_iq25)(A) << (15)) #define _IQ10toIQ24(A) ((_iq24)(A) << (14)) #define _IQ10toIQ23(A) ((_iq23)(A) << (13)) #define _IQ10toIQ22(A) ((_iq22)(A) << (12)) #define _IQ10toIQ21(A) ((_iq21)(A) << (11)) #define _IQ10toIQ20(A) ((_iq20)(A) << (10)) #define _IQ10toIQ19(A) ((_iq19)(A) << (9)) #define _IQ10toIQ18(A) ((_iq18)(A) << (8)) #define _IQ10toIQ17(A) ((_iq17)(A) << (7)) #define _IQ10toIQ16(A) ((_iq16)(A) << (6)) #define _IQ10toIQ15(A) ((_iq15)(A) << (5)) #define _IQ10toIQ14(A) ((_iq14)(A) << (4)) #define _IQ10toIQ13(A) ((_iq13)(A) << (3)) #define _IQ10toIQ12(A) ((_iq12)(A) << (2)) #define _IQ10toIQ11(A) ((_iq11)(A) << (1)) #define _IQ10toIQ9(A) ((_iq9 )(A) >> (1)) #define _IQ10toIQ8(A) ((_iq8 )(A) >> (2)) #define _IQ10toIQ7(A) ((_iq7 )(A) >> (3)) #define _IQ10toIQ6(A) ((_iq6 )(A) >> (4)) #define _IQ10toIQ5(A) ((_iq5 )(A) >> (5)) #define _IQ10toIQ4(A) ((_iq4 )(A) >> (6)) #define _IQ10toIQ3(A) ((_iq3 )(A) >> (7)) #define _IQ10toIQ2(A) ((_iq2 )(A) >> (8)) #define _IQ10toIQ1(A) ((_iq1 )(A) >> (9)) /* IQ11 to IQN */ #define _IQ11toIQ30(A) ((_iq30)(A) << (19)) #define _IQ11toIQ29(A) ((_iq29)(A) << (18)) #define _IQ11toIQ28(A) ((_iq28)(A) << (17)) #define _IQ11toIQ27(A) ((_iq27)(A) << (16)) #define _IQ11toIQ26(A) ((_iq26)(A) << (15)) #define _IQ11toIQ25(A) ((_iq25)(A) << (14)) #define _IQ11toIQ24(A) ((_iq24)(A) << (13)) #define _IQ11toIQ23(A) ((_iq23)(A) << (12)) #define _IQ11toIQ22(A) ((_iq22)(A) << (11)) #define _IQ11toIQ21(A) ((_iq21)(A) << (10)) #define _IQ11toIQ20(A) ((_iq20)(A) << (9)) #define _IQ11toIQ19(A) ((_iq19)(A) << (8)) #define _IQ11toIQ18(A) ((_iq18)(A) << (7)) #define _IQ11toIQ17(A) ((_iq17)(A) << (6)) #define _IQ11toIQ16(A) ((_iq16)(A) << (5)) #define _IQ11toIQ15(A) ((_iq15)(A) << (4)) #define _IQ11toIQ14(A) ((_iq14)(A) << (3)) #define _IQ11toIQ13(A) ((_iq13)(A) << (2)) #define _IQ11toIQ12(A) ((_iq12)(A) << (1)) #define _IQ11toIQ10(A) ((_iq10)(A) >> (1)) #define _IQ11toIQ9(A) ((_iq9 )(A) >> (2)) #define _IQ11toIQ8(A) ((_iq8 )(A) >> (3)) #define _IQ11toIQ7(A) ((_iq7 )(A) >> (4)) #define _IQ11toIQ6(A) ((_iq6 )(A) >> (5)) #define _IQ11toIQ5(A) ((_iq5 )(A) >> (6)) #define _IQ11toIQ4(A) ((_iq4 )(A) >> (7)) #define _IQ11toIQ3(A) ((_iq3 )(A) >> (8)) #define _IQ11toIQ2(A) ((_iq2 )(A) >> (9)) #define _IQ11toIQ1(A) ((_iq1 )(A) >> (10)) /* IQ12 to IQN */ #define _IQ12toIQ30(A) ((_iq30)(A) << (18)) #define _IQ12toIQ29(A) ((_iq29)(A) << (17)) #define _IQ12toIQ28(A) ((_iq28)(A) << (16)) #define _IQ12toIQ27(A) ((_iq27)(A) << (15)) #define _IQ12toIQ26(A) ((_iq26)(A) << (14)) #define _IQ12toIQ25(A) ((_iq25)(A) << (13)) #define _IQ12toIQ24(A) ((_iq24)(A) << (12)) #define _IQ12toIQ23(A) ((_iq23)(A) << (11)) #define _IQ12toIQ22(A) ((_iq22)(A) << (10)) #define _IQ12toIQ21(A) ((_iq21)(A) << (9)) #define _IQ12toIQ20(A) ((_iq20)(A) << (8)) #define _IQ12toIQ19(A) ((_iq19)(A) << (7)) #define _IQ12toIQ18(A) ((_iq18)(A) << (6)) #define _IQ12toIQ17(A) ((_iq17)(A) << (5)) #define _IQ12toIQ16(A) ((_iq16)(A) << (4)) #define _IQ12toIQ15(A) ((_iq15)(A) << (3)) #define _IQ12toIQ14(A) ((_iq14)(A) << (2)) #define _IQ12toIQ13(A) ((_iq13)(A) << (1)) #define _IQ12toIQ11(A) ((_iq11)(A) >> (1)) #define _IQ12toIQ10(A) ((_iq10)(A) >> (2)) #define _IQ12toIQ9(A) ((_iq9 )(A) >> (3)) #define _IQ12toIQ8(A) ((_iq8 )(A) >> (4)) #define _IQ12toIQ7(A) ((_iq7 )(A) >> (5)) #define _IQ12toIQ6(A) ((_iq6 )(A) >> (6)) #define _IQ12toIQ5(A) ((_iq5 )(A) >> (7)) #define _IQ12toIQ4(A) ((_iq4 )(A) >> (8)) #define _IQ12toIQ3(A) ((_iq3 )(A) >> (9)) #define _IQ12toIQ2(A) ((_iq2 )(A) >> (10)) #define _IQ12toIQ1(A) ((_iq1 )(A) >> (11)) /* IQ13 to IQN */ #define _IQ13toIQ30(A) ((_iq30)(A) << (17)) #define _IQ13toIQ29(A) ((_iq29)(A) << (16)) #define _IQ13toIQ28(A) ((_iq28)(A) << (15)) #define _IQ13toIQ27(A) ((_iq27)(A) << (14)) #define _IQ13toIQ26(A) ((_iq26)(A) << (13)) #define _IQ13toIQ25(A) ((_iq25)(A) << (12)) #define _IQ13toIQ24(A) ((_iq24)(A) << (11)) #define _IQ13toIQ23(A) ((_iq23)(A) << (10)) #define _IQ13toIQ22(A) ((_iq22)(A) << (9)) #define _IQ13toIQ21(A) ((_iq21)(A) << (8)) #define _IQ13toIQ20(A) ((_iq20)(A) << (7)) #define _IQ13toIQ19(A) ((_iq19)(A) << (6)) #define _IQ13toIQ18(A) ((_iq18)(A) << (5)) #define _IQ13toIQ17(A) ((_iq17)(A) << (4)) #define _IQ13toIQ16(A) ((_iq16)(A) << (3)) #define _IQ13toIQ15(A) ((_iq15)(A) << (2)) #define _IQ13toIQ14(A) ((_iq14)(A) << (1)) #define _IQ13toIQ12(A) ((_iq12)(A) >> (1)) #define _IQ13toIQ11(A) ((_iq11)(A) >> (2)) #define _IQ13toIQ10(A) ((_iq10)(A) >> (3)) #define _IQ13toIQ9(A) ((_iq9 )(A) >> (4)) #define _IQ13toIQ8(A) ((_iq8 )(A) >> (5)) #define _IQ13toIQ7(A) ((_iq7 )(A) >> (6)) #define _IQ13toIQ6(A) ((_iq6 )(A) >> (7)) #define _IQ13toIQ5(A) ((_iq5 )(A) >> (8)) #define _IQ13toIQ4(A) ((_iq4 )(A) >> (9)) #define _IQ13toIQ3(A) ((_iq3 )(A) >> (10)) #define _IQ13toIQ2(A) ((_iq2 )(A) >> (11)) #define _IQ13toIQ1(A) ((_iq1 )(A) >> (12)) /* IQ14 to IQN */ #define _IQ14toIQ30(A) ((_iq30)(A) << (16)) #define _IQ14toIQ29(A) ((_iq29)(A) << (15)) #define _IQ14toIQ28(A) ((_iq28)(A) << (14)) #define _IQ14toIQ27(A) ((_iq27)(A) << (13)) #define _IQ14toIQ26(A) ((_iq26)(A) << (12)) #define _IQ14toIQ25(A) ((_iq25)(A) << (11)) #define _IQ14toIQ24(A) ((_iq24)(A) << (10)) #define _IQ14toIQ23(A) ((_iq23)(A) << (9)) #define _IQ14toIQ22(A) ((_iq22)(A) << (8)) #define _IQ14toIQ21(A) ((_iq21)(A) << (7)) #define _IQ14toIQ20(A) ((_iq20)(A) << (6)) #define _IQ14toIQ19(A) ((_iq19)(A) << (5)) #define _IQ14toIQ18(A) ((_iq18)(A) << (4)) #define _IQ14toIQ17(A) ((_iq17)(A) << (3)) #define _IQ14toIQ16(A) ((_iq16)(A) << (2)) #define _IQ14toIQ15(A) ((_iq15)(A) << (1)) #define _IQ14toIQ13(A) ((_iq13)(A) >> (1)) #define _IQ14toIQ12(A) ((_iq12)(A) >> (2)) #define _IQ14toIQ11(A) ((_iq11)(A) >> (3)) #define _IQ14toIQ10(A) ((_iq10)(A) >> (4)) #define _IQ14toIQ9(A) ((_iq9 )(A) >> (5)) #define _IQ14toIQ8(A) ((_iq8 )(A) >> (6)) #define _IQ14toIQ7(A) ((_iq7 )(A) >> (7)) #define _IQ14toIQ6(A) ((_iq6 )(A) >> (8)) #define _IQ14toIQ5(A) ((_iq5 )(A) >> (9)) #define _IQ14toIQ4(A) ((_iq4 )(A) >> (10)) #define _IQ14toIQ3(A) ((_iq3 )(A) >> (11)) #define _IQ14toIQ2(A) ((_iq2 )(A) >> (12)) #define _IQ14toIQ1(A) ((_iq1 )(A) >> (13)) /* IQ15 to IQN */ #define _IQ15toIQ30(A) ((_iq30)(A) << (15)) #define _IQ15toIQ29(A) ((_iq29)(A) << (14)) #define _IQ15toIQ28(A) ((_iq28)(A) << (13)) #define _IQ15toIQ27(A) ((_iq27)(A) << (12)) #define _IQ15toIQ26(A) ((_iq26)(A) << (11)) #define _IQ15toIQ25(A) ((_iq25)(A) << (10)) #define _IQ15toIQ24(A) ((_iq24)(A) << (9)) #define _IQ15toIQ23(A) ((_iq23)(A) << (8)) #define _IQ15toIQ22(A) ((_iq22)(A) << (7)) #define _IQ15toIQ21(A) ((_iq21)(A) << (6)) #define _IQ15toIQ20(A) ((_iq20)(A) << (5)) #define _IQ15toIQ19(A) ((_iq19)(A) << (4)) #define _IQ15toIQ18(A) ((_iq18)(A) << (3)) #define _IQ15toIQ17(A) ((_iq17)(A) << (2)) #define _IQ15toIQ16(A) ((_iq16)(A) << (1)) #define _IQ15toIQ14(A) ((_iq14)(A) >> (1)) #define _IQ15toIQ13(A) ((_iq13)(A) >> (2)) #define _IQ15toIQ12(A) ((_iq12)(A) >> (3)) #define _IQ15toIQ11(A) ((_iq11)(A) >> (4)) #define _IQ15toIQ10(A) ((_iq10)(A) >> (5)) #define _IQ15toIQ9(A) ((_iq9 )(A) >> (6)) #define _IQ15toIQ8(A) ((_iq8 )(A) >> (7)) #define _IQ15toIQ7(A) ((_iq7 )(A) >> (8)) #define _IQ15toIQ6(A) ((_iq6 )(A) >> (9)) #define _IQ15toIQ5(A) ((_iq5 )(A) >> (10)) #define _IQ15toIQ4(A) ((_iq4 )(A) >> (11)) #define _IQ15toIQ3(A) ((_iq3 )(A) >> (12)) #define _IQ15toIQ2(A) ((_iq2 )(A) >> (13)) #define _IQ15toIQ1(A) ((_iq1 )(A) >> (14)) /* IQ16 to IQN */ #define _IQ16toIQ30(A) ((_iq30)(A) << (14)) #define _IQ16toIQ29(A) ((_iq29)(A) << (13)) #define _IQ16toIQ28(A) ((_iq28)(A) << (12)) #define _IQ16toIQ27(A) ((_iq27)(A) << (11)) #define _IQ16toIQ26(A) ((_iq26)(A) << (10)) #define _IQ16toIQ25(A) ((_iq25)(A) << (9)) #define _IQ16toIQ24(A) ((_iq24)(A) << (8)) #define _IQ16toIQ23(A) ((_iq23)(A) << (7)) #define _IQ16toIQ22(A) ((_iq22)(A) << (6)) #define _IQ16toIQ21(A) ((_iq21)(A) << (5)) #define _IQ16toIQ20(A) ((_iq20)(A) << (4)) #define _IQ16toIQ19(A) ((_iq19)(A) << (3)) #define _IQ16toIQ18(A) ((_iq18)(A) << (2)) #define _IQ16toIQ17(A) ((_iq17)(A) << (1)) #define _IQ16toIQ15(A) ((_iq15)(A) >> (1)) #define _IQ16toIQ14(A) ((_iq14)(A) >> (2)) #define _IQ16toIQ13(A) ((_iq13)(A) >> (3)) #define _IQ16toIQ12(A) ((_iq12)(A) >> (4)) #define _IQ16toIQ11(A) ((_iq11)(A) >> (5)) #define _IQ16toIQ10(A) ((_iq10)(A) >> (6)) #define _IQ16toIQ9(A) ((_iq9 )(A) >> (7)) #define _IQ16toIQ8(A) ((_iq8 )(A) >> (8)) #define _IQ16toIQ7(A) ((_iq7 )(A) >> (9)) #define _IQ16toIQ6(A) ((_iq6 )(A) >> (10)) #define _IQ16toIQ5(A) ((_iq5 )(A) >> (11)) #define _IQ16toIQ4(A) ((_iq4 )(A) >> (12)) #define _IQ16toIQ3(A) ((_iq3 )(A) >> (13)) #define _IQ16toIQ2(A) ((_iq2 )(A) >> (14)) #define _IQ16toIQ1(A) ((_iq1 )(A) >> (15)) /* IQ17 to IQN */ #define _IQ17toIQ30(A) ((_iq30)(A) << (13)) #define _IQ17toIQ29(A) ((_iq29)(A) << (12)) #define _IQ17toIQ28(A) ((_iq28)(A) << (11)) #define _IQ17toIQ27(A) ((_iq27)(A) << (10)) #define _IQ17toIQ26(A) ((_iq26)(A) << (9)) #define _IQ17toIQ25(A) ((_iq25)(A) << (8)) #define _IQ17toIQ24(A) ((_iq24)(A) << (7)) #define _IQ17toIQ23(A) ((_iq23)(A) << (6)) #define _IQ17toIQ22(A) ((_iq22)(A) << (5)) #define _IQ17toIQ21(A) ((_iq21)(A) << (4)) #define _IQ17toIQ20(A) ((_iq20)(A) << (3)) #define _IQ17toIQ19(A) ((_iq19)(A) << (2)) #define _IQ17toIQ18(A) ((_iq18)(A) << (1)) #define _IQ17toIQ16(A) ((_iq16)(A) >> (1)) #define _IQ17toIQ15(A) ((_iq15)(A) >> (2)) #define _IQ17toIQ14(A) ((_iq14)(A) >> (3)) #define _IQ17toIQ13(A) ((_iq13)(A) >> (4)) #define _IQ17toIQ12(A) ((_iq12)(A) >> (5)) #define _IQ17toIQ11(A) ((_iq11)(A) >> (6)) #define _IQ17toIQ10(A) ((_iq10)(A) >> (7)) #define _IQ17toIQ9(A) ((_iq9 )(A) >> (8)) #define _IQ17toIQ8(A) ((_iq8 )(A) >> (9)) #define _IQ17toIQ7(A) ((_iq7 )(A) >> (10)) #define _IQ17toIQ6(A) ((_iq6 )(A) >> (11)) #define _IQ17toIQ5(A) ((_iq5 )(A) >> (12)) #define _IQ17toIQ4(A) ((_iq4 )(A) >> (13)) #define _IQ17toIQ3(A) ((_iq3 )(A) >> (14)) #define _IQ17toIQ2(A) ((_iq2 )(A) >> (15)) #define _IQ17toIQ1(A) ((_iq1 )(A) >> (16)) /* IQ18 to IQN */ #define _IQ18toIQ30(A) ((_iq30)(A) << (12)) #define _IQ18toIQ29(A) ((_iq29)(A) << (11)) #define _IQ18toIQ28(A) ((_iq28)(A) << (10)) #define _IQ18toIQ27(A) ((_iq27)(A) << (9)) #define _IQ18toIQ26(A) ((_iq26)(A) << (8)) #define _IQ18toIQ25(A) ((_iq25)(A) << (7)) #define _IQ18toIQ24(A) ((_iq24)(A) << (6)) #define _IQ18toIQ23(A) ((_iq23)(A) << (5)) #define _IQ18toIQ22(A) ((_iq22)(A) << (4)) #define _IQ18toIQ21(A) ((_iq21)(A) << (3)) #define _IQ18toIQ20(A) ((_iq20)(A) << (2)) #define _IQ18toIQ19(A) ((_iq19)(A) << (1)) #define _IQ18toIQ17(A) ((_iq17)(A) >> (1)) #define _IQ18toIQ16(A) ((_iq16)(A) >> (2)) #define _IQ18toIQ15(A) ((_iq15)(A) >> (3)) #define _IQ18toIQ14(A) ((_iq14)(A) >> (4)) #define _IQ18toIQ13(A) ((_iq13)(A) >> (5)) #define _IQ18toIQ12(A) ((_iq12)(A) >> (6)) #define _IQ18toIQ11(A) ((_iq11)(A) >> (7)) #define _IQ18toIQ10(A) ((_iq10)(A) >> (8)) #define _IQ18toIQ9(A) ((_iq9 )(A) >> (9)) #define _IQ18toIQ8(A) ((_iq8 )(A) >> (10)) #define _IQ18toIQ7(A) ((_iq7 )(A) >> (11)) #define _IQ18toIQ6(A) ((_iq6 )(A) >> (12)) #define _IQ18toIQ5(A) ((_iq5 )(A) >> (13)) #define _IQ18toIQ4(A) ((_iq4 )(A) >> (14)) #define _IQ18toIQ3(A) ((_iq3 )(A) >> (15)) #define _IQ18toIQ2(A) ((_iq2 )(A) >> (16)) #define _IQ18toIQ1(A) ((_iq1 )(A) >> (17)) /* IQ19 to IQN */ #define _IQ19toIQ30(A) ((_iq30)(A) << (11)) #define _IQ19toIQ29(A) ((_iq29)(A) << (10)) #define _IQ19toIQ28(A) ((_iq28)(A) << (9)) #define _IQ19toIQ27(A) ((_iq27)(A) << (8)) #define _IQ19toIQ26(A) ((_iq26)(A) << (7)) #define _IQ19toIQ25(A) ((_iq25)(A) << (6)) #define _IQ19toIQ24(A) ((_iq24)(A) << (5)) #define _IQ19toIQ23(A) ((_iq23)(A) << (4)) #define _IQ19toIQ22(A) ((_iq22)(A) << (3)) #define _IQ19toIQ21(A) ((_iq21)(A) << (2)) #define _IQ19toIQ20(A) ((_iq20)(A) << (1)) #define _IQ19toIQ18(A) ((_iq18)(A) >> (1)) #define _IQ19toIQ17(A) ((_iq17)(A) >> (2)) #define _IQ19toIQ16(A) ((_iq16)(A) >> (3)) #define _IQ19toIQ15(A) ((_iq15)(A) >> (4)) #define _IQ19toIQ14(A) ((_iq14)(A) >> (5)) #define _IQ19toIQ13(A) ((_iq13)(A) >> (6)) #define _IQ19toIQ12(A) ((_iq12)(A) >> (7)) #define _IQ19toIQ11(A) ((_iq11)(A) >> (8)) #define _IQ19toIQ10(A) ((_iq10)(A) >> (9)) #define _IQ19toIQ9(A) ((_iq9 )(A) >> (10)) #define _IQ19toIQ8(A) ((_iq8 )(A) >> (11)) #define _IQ19toIQ7(A) ((_iq7 )(A) >> (12)) #define _IQ19toIQ6(A) ((_iq6 )(A) >> (13)) #define _IQ19toIQ5(A) ((_iq5 )(A) >> (14)) #define _IQ19toIQ4(A) ((_iq4 )(A) >> (15)) #define _IQ19toIQ3(A) ((_iq3 )(A) >> (16)) #define _IQ19toIQ2(A) ((_iq2 )(A) >> (17)) #define _IQ19toIQ1(A) ((_iq1 )(A) >> (18)) /* IQ20 to IQN */ #define _IQ20toIQ30(A) ((_iq30)(A) << (10)) #define _IQ20toIQ29(A) ((_iq29)(A) << (9)) #define _IQ20toIQ28(A) ((_iq28)(A) << (8)) #define _IQ20toIQ27(A) ((_iq27)(A) << (7)) #define _IQ20toIQ26(A) ((_iq26)(A) << (6)) #define _IQ20toIQ25(A) ((_iq25)(A) << (5)) #define _IQ20toIQ24(A) ((_iq24)(A) << (4)) #define _IQ20toIQ23(A) ((_iq23)(A) << (3)) #define _IQ20toIQ22(A) ((_iq22)(A) << (2)) #define _IQ20toIQ21(A) ((_iq21)(A) << (1)) #define _IQ20toIQ19(A) ((_iq19)(A) >> (1)) #define _IQ20toIQ18(A) ((_iq18)(A) >> (2)) #define _IQ20toIQ17(A) ((_iq17)(A) >> (3)) #define _IQ20toIQ16(A) ((_iq16)(A) >> (4)) #define _IQ20toIQ15(A) ((_iq15)(A) >> (5)) #define _IQ20toIQ14(A) ((_iq14)(A) >> (6)) #define _IQ20toIQ13(A) ((_iq13)(A) >> (7)) #define _IQ20toIQ12(A) ((_iq12)(A) >> (8)) #define _IQ20toIQ11(A) ((_iq11)(A) >> (9)) #define _IQ20toIQ10(A) ((_iq10)(A) >> (10)) #define _IQ20toIQ9(A) ((_iq9 )(A) >> (11)) #define _IQ20toIQ8(A) ((_iq8 )(A) >> (12)) #define _IQ20toIQ7(A) ((_iq7 )(A) >> (13)) #define _IQ20toIQ6(A) ((_iq6 )(A) >> (14)) #define _IQ20toIQ5(A) ((_iq5 )(A) >> (15)) #define _IQ20toIQ4(A) ((_iq4 )(A) >> (16)) #define _IQ20toIQ3(A) ((_iq3 )(A) >> (17)) #define _IQ20toIQ2(A) ((_iq2 )(A) >> (18)) #define _IQ20toIQ1(A) ((_iq1 )(A) >> (19)) /* IQ21 to IQN */ #define _IQ21toIQ30(A) ((_iq30)(A) << (9)) #define _IQ21toIQ29(A) ((_iq29)(A) << (8)) #define _IQ21toIQ28(A) ((_iq28)(A) << (7)) #define _IQ21toIQ27(A) ((_iq27)(A) << (6)) #define _IQ21toIQ26(A) ((_iq26)(A) << (5)) #define _IQ21toIQ25(A) ((_iq25)(A) << (4)) #define _IQ21toIQ24(A) ((_iq24)(A) << (3)) #define _IQ21toIQ23(A) ((_iq23)(A) << (2)) #define _IQ21toIQ22(A) ((_iq22)(A) << (1)) #define _IQ21toIQ20(A) ((_iq20)(A) >> (1)) #define _IQ21toIQ19(A) ((_iq19)(A) >> (2)) #define _IQ21toIQ18(A) ((_iq18)(A) >> (3)) #define _IQ21toIQ17(A) ((_iq17)(A) >> (4)) #define _IQ21toIQ16(A) ((_iq16)(A) >> (5)) #define _IQ21toIQ15(A) ((_iq15)(A) >> (6)) #define _IQ21toIQ14(A) ((_iq14)(A) >> (7)) #define _IQ21toIQ13(A) ((_iq13)(A) >> (8)) #define _IQ21toIQ12(A) ((_iq12)(A) >> (9)) #define _IQ21toIQ11(A) ((_iq11)(A) >> (10)) #define _IQ21toIQ10(A) ((_iq10)(A) >> (11)) #define _IQ21toIQ9(A) ((_iq9 )(A) >> (12)) #define _IQ21toIQ8(A) ((_iq8 )(A) >> (13)) #define _IQ21toIQ7(A) ((_iq7 )(A) >> (14)) #define _IQ21toIQ6(A) ((_iq6 )(A) >> (15)) #define _IQ21toIQ5(A) ((_iq5 )(A) >> (16)) #define _IQ21toIQ4(A) ((_iq4 )(A) >> (17)) #define _IQ21toIQ3(A) ((_iq3 )(A) >> (18)) #define _IQ21toIQ2(A) ((_iq2 )(A) >> (19)) #define _IQ21toIQ1(A) ((_iq1 )(A) >> (20)) /* IQ22 to IQN */ #define _IQ22toIQ30(A) ((_iq30)(A) << (8)) #define _IQ22toIQ29(A) ((_iq29)(A) << (7)) #define _IQ22toIQ28(A) ((_iq28)(A) << (6)) #define _IQ22toIQ27(A) ((_iq27)(A) << (5)) #define _IQ22toIQ26(A) ((_iq26)(A) << (4)) #define _IQ22toIQ25(A) ((_iq25)(A) << (3)) #define _IQ22toIQ24(A) ((_iq24)(A) << (2)) #define _IQ22toIQ23(A) ((_iq23)(A) << (1)) #define _IQ22toIQ21(A) ((_iq21)(A) >> (1)) #define _IQ22toIQ20(A) ((_iq20)(A) >> (2)) #define _IQ22toIQ19(A) ((_iq19)(A) >> (3)) #define _IQ22toIQ18(A) ((_iq18)(A) >> (4)) #define _IQ22toIQ17(A) ((_iq17)(A) >> (5)) #define _IQ22toIQ16(A) ((_iq16)(A) >> (6)) #define _IQ22toIQ15(A) ((_iq15)(A) >> (7)) #define _IQ22toIQ14(A) ((_iq14)(A) >> (8)) #define _IQ22toIQ13(A) ((_iq13)(A) >> (9)) #define _IQ22toIQ12(A) ((_iq12)(A) >> (10)) #define _IQ22toIQ11(A) ((_iq11)(A) >> (11)) #define _IQ22toIQ10(A) ((_iq10)(A) >> (12)) #define _IQ22toIQ9(A) ((_iq9 )(A) >> (13)) #define _IQ22toIQ8(A) ((_iq8 )(A) >> (14)) #define _IQ22toIQ7(A) ((_iq7 )(A) >> (15)) #define _IQ22toIQ6(A) ((_iq6 )(A) >> (16)) #define _IQ22toIQ5(A) ((_iq5 )(A) >> (17)) #define _IQ22toIQ4(A) ((_iq4 )(A) >> (18)) #define _IQ22toIQ3(A) ((_iq3 )(A) >> (19)) #define _IQ22toIQ2(A) ((_iq2 )(A) >> (20)) #define _IQ22toIQ1(A) ((_iq1 )(A) >> (21)) /* IQ23 to IQN */ #define _IQ23toIQ30(A) ((_iq30)(A) << (7)) #define _IQ23toIQ29(A) ((_iq29)(A) << (6)) #define _IQ23toIQ28(A) ((_iq28)(A) << (5)) #define _IQ23toIQ27(A) ((_iq27)(A) << (4)) #define _IQ23toIQ26(A) ((_iq26)(A) << (3)) #define _IQ23toIQ25(A) ((_iq25)(A) << (2)) #define _IQ23toIQ24(A) ((_iq24)(A) << (1)) #define _IQ23toIQ22(A) ((_iq22)(A) >> (1)) #define _IQ23toIQ21(A) ((_iq21)(A) >> (2)) #define _IQ23toIQ20(A) ((_iq20)(A) >> (3)) #define _IQ23toIQ19(A) ((_iq19)(A) >> (4)) #define _IQ23toIQ18(A) ((_iq18)(A) >> (5)) #define _IQ23toIQ17(A) ((_iq17)(A) >> (6)) #define _IQ23toIQ16(A) ((_iq16)(A) >> (7)) #define _IQ23toIQ15(A) ((_iq15)(A) >> (8)) #define _IQ23toIQ14(A) ((_iq14)(A) >> (9)) #define _IQ23toIQ13(A) ((_iq13)(A) >> (10)) #define _IQ23toIQ12(A) ((_iq12)(A) >> (11)) #define _IQ23toIQ11(A) ((_iq11)(A) >> (12)) #define _IQ23toIQ10(A) ((_iq10)(A) >> (13)) #define _IQ23toIQ9(A) ((_iq9 )(A) >> (14)) #define _IQ23toIQ8(A) ((_iq8 )(A) >> (15)) #define _IQ23toIQ7(A) ((_iq7 )(A) >> (16)) #define _IQ23toIQ6(A) ((_iq6 )(A) >> (17)) #define _IQ23toIQ5(A) ((_iq5 )(A) >> (18)) #define _IQ23toIQ4(A) ((_iq4 )(A) >> (19)) #define _IQ23toIQ3(A) ((_iq3 )(A) >> (20)) #define _IQ23toIQ2(A) ((_iq2 )(A) >> (21)) #define _IQ23toIQ1(A) ((_iq1 )(A) >> (22)) /* IQ24 to IQN */ #define _IQ24toIQ30(A) ((_iq30)(A) << (6)) #define _IQ24toIQ29(A) ((_iq29)(A) << (5)) #define _IQ24toIQ28(A) ((_iq28)(A) << (4)) #define _IQ24toIQ27(A) ((_iq27)(A) << (3)) #define _IQ24toIQ26(A) ((_iq26)(A) << (2)) #define _IQ24toIQ25(A) ((_iq25)(A) << (1)) #define _IQ24toIQ23(A) ((_iq23)(A) >> (1)) #define _IQ24toIQ22(A) ((_iq22)(A) >> (2)) #define _IQ24toIQ21(A) ((_iq21)(A) >> (3)) #define _IQ24toIQ20(A) ((_iq20)(A) >> (4)) #define _IQ24toIQ19(A) ((_iq19)(A) >> (5)) #define _IQ24toIQ18(A) ((_iq18)(A) >> (6)) #define _IQ24toIQ17(A) ((_iq17)(A) >> (7)) #define _IQ24toIQ16(A) ((_iq16)(A) >> (8)) #define _IQ24toIQ15(A) ((_iq15)(A) >> (9)) #define _IQ24toIQ14(A) ((_iq14)(A) >> (10)) #define _IQ24toIQ13(A) ((_iq13)(A) >> (11)) #define _IQ24toIQ12(A) ((_iq12)(A) >> (12)) #define _IQ24toIQ11(A) ((_iq11)(A) >> (13)) #define _IQ24toIQ10(A) ((_iq10)(A) >> (14)) #define _IQ24toIQ9(A) ((_iq9 )(A) >> (15)) #define _IQ24toIQ8(A) ((_iq8 )(A) >> (16)) #define _IQ24toIQ7(A) ((_iq7 )(A) >> (17)) #define _IQ24toIQ6(A) ((_iq6 )(A) >> (18)) #define _IQ24toIQ5(A) ((_iq5 )(A) >> (19)) #define _IQ24toIQ4(A) ((_iq4 )(A) >> (20)) #define _IQ24toIQ3(A) ((_iq3 )(A) >> (21)) #define _IQ24toIQ2(A) ((_iq2 )(A) >> (22)) #define _IQ24toIQ1(A) ((_iq1 )(A) >> (23)) /* IQ25 to IQN */ #define _IQ25toIQ30(A) ((_iq30)(A) << (5)) #define _IQ25toIQ29(A) ((_iq29)(A) << (4)) #define _IQ25toIQ28(A) ((_iq28)(A) << (3)) #define _IQ25toIQ27(A) ((_iq27)(A) << (2)) #define _IQ25toIQ26(A) ((_iq26)(A) << (1)) #define _IQ25toIQ24(A) ((_iq24)(A) >> (1)) #define _IQ25toIQ23(A) ((_iq23)(A) >> (2)) #define _IQ25toIQ22(A) ((_iq22)(A) >> (3)) #define _IQ25toIQ21(A) ((_iq21)(A) >> (4)) #define _IQ25toIQ20(A) ((_iq20)(A) >> (5)) #define _IQ25toIQ19(A) ((_iq19)(A) >> (6)) #define _IQ25toIQ18(A) ((_iq18)(A) >> (7)) #define _IQ25toIQ17(A) ((_iq17)(A) >> (8)) #define _IQ25toIQ16(A) ((_iq16)(A) >> (9)) #define _IQ25toIQ15(A) ((_iq15)(A) >> (10)) #define _IQ25toIQ14(A) ((_iq14)(A) >> (11)) #define _IQ25toIQ13(A) ((_iq13)(A) >> (12)) #define _IQ25toIQ12(A) ((_iq12)(A) >> (13)) #define _IQ25toIQ11(A) ((_iq11)(A) >> (14)) #define _IQ25toIQ10(A) ((_iq10)(A) >> (15)) #define _IQ25toIQ9(A) ((_iq9 )(A) >> (16)) #define _IQ25toIQ8(A) ((_iq8 )(A) >> (17)) #define _IQ25toIQ7(A) ((_iq7 )(A) >> (18)) #define _IQ25toIQ6(A) ((_iq6 )(A) >> (19)) #define _IQ25toIQ5(A) ((_iq5 )(A) >> (20)) #define _IQ25toIQ4(A) ((_iq4 )(A) >> (21)) #define _IQ25toIQ3(A) ((_iq3 )(A) >> (22)) #define _IQ25toIQ2(A) ((_iq2 )(A) >> (23)) #define _IQ25toIQ1(A) ((_iq1 )(A) >> (24)) /* IQ26 to IQN */ #define _IQ26toIQ30(A) ((_iq30)(A) << (4)) #define _IQ26toIQ29(A) ((_iq29)(A) << (3)) #define _IQ26toIQ28(A) ((_iq28)(A) << (2)) #define _IQ26toIQ27(A) ((_iq27)(A) << (1)) #define _IQ26toIQ25(A) ((_iq25)(A) >> (1)) #define _IQ26toIQ24(A) ((_iq24)(A) >> (2)) #define _IQ26toIQ23(A) ((_iq23)(A) >> (3)) #define _IQ26toIQ22(A) ((_iq22)(A) >> (4)) #define _IQ26toIQ21(A) ((_iq21)(A) >> (5)) #define _IQ26toIQ20(A) ((_iq20)(A) >> (6)) #define _IQ26toIQ19(A) ((_iq19)(A) >> (7)) #define _IQ26toIQ18(A) ((_iq18)(A) >> (8)) #define _IQ26toIQ17(A) ((_iq17)(A) >> (9)) #define _IQ26toIQ16(A) ((_iq16)(A) >> (10)) #define _IQ26toIQ15(A) ((_iq15)(A) >> (11)) #define _IQ26toIQ14(A) ((_iq14)(A) >> (12)) #define _IQ26toIQ13(A) ((_iq13)(A) >> (13)) #define _IQ26toIQ12(A) ((_iq12)(A) >> (14)) #define _IQ26toIQ11(A) ((_iq11)(A) >> (15)) #define _IQ26toIQ10(A) ((_iq10)(A) >> (16)) #define _IQ26toIQ9(A) ((_iq9 )(A) >> (17)) #define _IQ26toIQ8(A) ((_iq8 )(A) >> (18)) #define _IQ26toIQ7(A) ((_iq7 )(A) >> (19)) #define _IQ26toIQ6(A) ((_iq6 )(A) >> (20)) #define _IQ26toIQ5(A) ((_iq5 )(A) >> (21)) #define _IQ26toIQ4(A) ((_iq4 )(A) >> (22)) #define _IQ26toIQ3(A) ((_iq3 )(A) >> (23)) #define _IQ26toIQ2(A) ((_iq2 )(A) >> (24)) #define _IQ26toIQ1(A) ((_iq1 )(A) >> (25)) /* IQ27 to IQN */ #define _IQ27toIQ30(A) ((_iq30)(A) << (3)) #define _IQ27toIQ29(A) ((_iq29)(A) << (2)) #define _IQ27toIQ28(A) ((_iq28)(A) << (1)) #define _IQ27toIQ26(A) ((_iq26)(A) >> (1)) #define _IQ27toIQ25(A) ((_iq25)(A) >> (2)) #define _IQ27toIQ24(A) ((_iq24)(A) >> (3)) #define _IQ27toIQ23(A) ((_iq23)(A) >> (4)) #define _IQ27toIQ22(A) ((_iq22)(A) >> (5)) #define _IQ27toIQ21(A) ((_iq21)(A) >> (6)) #define _IQ27toIQ20(A) ((_iq20)(A) >> (7)) #define _IQ27toIQ19(A) ((_iq19)(A) >> (8)) #define _IQ27toIQ18(A) ((_iq18)(A) >> (9)) #define _IQ27toIQ17(A) ((_iq17)(A) >> (10)) #define _IQ27toIQ16(A) ((_iq16)(A) >> (11)) #define _IQ27toIQ15(A) ((_iq15)(A) >> (12)) #define _IQ27toIQ14(A) ((_iq14)(A) >> (13)) #define _IQ27toIQ13(A) ((_iq13)(A) >> (14)) #define _IQ27toIQ12(A) ((_iq12)(A) >> (15)) #define _IQ27toIQ11(A) ((_iq11)(A) >> (16)) #define _IQ27toIQ10(A) ((_iq10)(A) >> (17)) #define _IQ27toIQ9(A) ((_iq9 )(A) >> (18)) #define _IQ27toIQ8(A) ((_iq8 )(A) >> (19)) #define _IQ27toIQ7(A) ((_iq7 )(A) >> (20)) #define _IQ27toIQ6(A) ((_iq6 )(A) >> (21)) #define _IQ27toIQ5(A) ((_iq5 )(A) >> (22)) #define _IQ27toIQ4(A) ((_iq4 )(A) >> (23)) #define _IQ27toIQ3(A) ((_iq3 )(A) >> (24)) #define _IQ27toIQ2(A) ((_iq2 )(A) >> (25)) #define _IQ27toIQ1(A) ((_iq1 )(A) >> (26)) /* IQ28 to IQN */ #define _IQ28toIQ30(A) ((_iq30)(A) << (2)) #define _IQ28toIQ29(A) ((_iq29)(A) << (1)) #define _IQ28toIQ27(A) ((_iq27)(A) >> (1)) #define _IQ28toIQ26(A) ((_iq26)(A) >> (2)) #define _IQ28toIQ25(A) ((_iq25)(A) >> (3)) #define _IQ28toIQ24(A) ((_iq24)(A) >> (4)) #define _IQ28toIQ23(A) ((_iq23)(A) >> (5)) #define _IQ28toIQ22(A) ((_iq22)(A) >> (6)) #define _IQ28toIQ21(A) ((_iq21)(A) >> (7)) #define _IQ28toIQ20(A) ((_iq20)(A) >> (8)) #define _IQ28toIQ19(A) ((_iq19)(A) >> (9)) #define _IQ28toIQ18(A) ((_iq18)(A) >> (10)) #define _IQ28toIQ17(A) ((_iq17)(A) >> (11)) #define _IQ28toIQ16(A) ((_iq16)(A) >> (12)) #define _IQ28toIQ15(A) ((_iq15)(A) >> (13)) #define _IQ28toIQ14(A) ((_iq14)(A) >> (14)) #define _IQ28toIQ13(A) ((_iq13)(A) >> (15)) #define _IQ28toIQ12(A) ((_iq12)(A) >> (16)) #define _IQ28toIQ11(A) ((_iq11)(A) >> (17)) #define _IQ28toIQ10(A) ((_iq10)(A) >> (18)) #define _IQ28toIQ9(A) ((_iq9 )(A) >> (19)) #define _IQ28toIQ8(A) ((_iq8 )(A) >> (20)) #define _IQ28toIQ7(A) ((_iq7 )(A) >> (21)) #define _IQ28toIQ6(A) ((_iq6 )(A) >> (22)) #define _IQ28toIQ5(A) ((_iq5 )(A) >> (23)) #define _IQ28toIQ4(A) ((_iq4 )(A) >> (24)) #define _IQ28toIQ3(A) ((_iq3 )(A) >> (25)) #define _IQ28toIQ2(A) ((_iq2 )(A) >> (26)) #define _IQ28toIQ1(A) ((_iq1 )(A) >> (27)) /* IQ29 to IQN */ #define _IQ29toIQ30(A) ((_iq30)(A) << (1)) #define _IQ29toIQ28(A) ((_iq28)(A) >> (1)) #define _IQ29toIQ27(A) ((_iq27)(A) >> (2)) #define _IQ29toIQ26(A) ((_iq26)(A) >> (3)) #define _IQ29toIQ25(A) ((_iq25)(A) >> (4)) #define _IQ29toIQ24(A) ((_iq24)(A) >> (5)) #define _IQ29toIQ23(A) ((_iq23)(A) >> (6)) #define _IQ29toIQ22(A) ((_iq22)(A) >> (7)) #define _IQ29toIQ21(A) ((_iq21)(A) >> (8)) #define _IQ29toIQ20(A) ((_iq20)(A) >> (9)) #define _IQ29toIQ19(A) ((_iq19)(A) >> (10)) #define _IQ29toIQ18(A) ((_iq18)(A) >> (11)) #define _IQ29toIQ17(A) ((_iq17)(A) >> (12)) #define _IQ29toIQ16(A) ((_iq16)(A) >> (13)) #define _IQ29toIQ15(A) ((_iq15)(A) >> (14)) #define _IQ29toIQ14(A) ((_iq14)(A) >> (15)) #define _IQ29toIQ13(A) ((_iq13)(A) >> (16)) #define _IQ29toIQ12(A) ((_iq12)(A) >> (17)) #define _IQ29toIQ11(A) ((_iq11)(A) >> (18)) #define _IQ29toIQ10(A) ((_iq10)(A) >> (19)) #define _IQ29toIQ9(A) ((_iq9 )(A) >> (20)) #define _IQ29toIQ8(A) ((_iq8 )(A) >> (21)) #define _IQ29toIQ7(A) ((_iq7 )(A) >> (22)) #define _IQ29toIQ6(A) ((_iq6 )(A) >> (23)) #define _IQ29toIQ5(A) ((_iq5 )(A) >> (24)) #define _IQ29toIQ4(A) ((_iq4 )(A) >> (25)) #define _IQ29toIQ3(A) ((_iq3 )(A) >> (26)) #define _IQ29toIQ2(A) ((_iq2 )(A) >> (27)) #define _IQ29toIQ1(A) ((_iq1 )(A) >> (28)) /* IQ30 to IQN */ #define _IQ30toIQ29(A) ((_iq29)(A) >> (1)) #define _IQ30toIQ28(A) ((_iq28)(A) >> (2)) #define _IQ30toIQ27(A) ((_iq27)(A) >> (3)) #define _IQ30toIQ26(A) ((_iq26)(A) >> (4)) #define _IQ30toIQ25(A) ((_iq25)(A) >> (5)) #define _IQ30toIQ24(A) ((_iq24)(A) >> (6)) #define _IQ30toIQ23(A) ((_iq23)(A) >> (7)) #define _IQ30toIQ22(A) ((_iq22)(A) >> (8)) #define _IQ30toIQ21(A) ((_iq21)(A) >> (9)) #define _IQ30toIQ20(A) ((_iq20)(A) >> (10)) #define _IQ30toIQ19(A) ((_iq19)(A) >> (11)) #define _IQ30toIQ18(A) ((_iq18)(A) >> (12)) #define _IQ30toIQ17(A) ((_iq17)(A) >> (13)) #define _IQ30toIQ16(A) ((_iq16)(A) >> (14)) #define _IQ30toIQ15(A) ((_iq15)(A) >> (15)) #define _IQ30toIQ14(A) ((_iq14)(A) >> (16)) #define _IQ30toIQ13(A) ((_iq13)(A) >> (17)) #define _IQ30toIQ12(A) ((_iq12)(A) >> (18)) #define _IQ30toIQ11(A) ((_iq11)(A) >> (19)) #define _IQ30toIQ10(A) ((_iq10)(A) >> (20)) #define _IQ30toIQ9(A) ((_iq9 )(A) >> (21)) #define _IQ30toIQ8(A) ((_iq8 )(A) >> (22)) #define _IQ30toIQ7(A) ((_iq7 )(A) >> (23)) #define _IQ30toIQ6(A) ((_iq6 )(A) >> (24)) #define _IQ30toIQ5(A) ((_iq5 )(A) >> (25)) #define _IQ30toIQ4(A) ((_iq4 )(A) >> (26)) #define _IQ30toIQ3(A) ((_iq3 )(A) >> (27)) #define _IQ30toIQ2(A) ((_iq2 )(A) >> (28)) #define _IQ30toIQ1(A) ((_iq1 )(A) >> (29)) #endif /* DOXYGEN_SHOULD_SKIP_THIS */ #ifndef DOXYGEN_SHOULD_SKIP_THIS //***************************************************************************** // // Converts a number between IQ format and 16-bit Qn format. // //***************************************************************************** #if (GLOBAL_IQ >= 15) #define _IQtoQ15(A) ((int32_t)(A) >> (GLOBAL_IQ - 15)) #define _Q15toIQ(A) ((_iq15)(A) << (GLOBAL_IQ - 15)) #else #define _IQtoQ15(A) ((int32_t)(A) << (15 - GLOBAL_IQ)) #define _Q15toIQ(A) ((_iq15)(A) >> (15 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 14) #define _IQtoQ14(A) ((int32_t)(A) >> (GLOBAL_IQ - 14)) #define _Q14toIQ(A) ((_iq14)(A) << (GLOBAL_IQ - 14)) #else #define _IQtoQ14(A) ((int32_t)(A) << (14 - GLOBAL_IQ)) #define _Q14toIQ(A) ((_iq14)(A) >> (14 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 13) #define _IQtoQ13(A) ((int32_t)(A) >> (GLOBAL_IQ - 13)) #define _Q13toIQ(A) ((_iq13)(A) << (GLOBAL_IQ - 13)) #else #define _IQtoQ13(A) ((int32_t)(A) << (13 - GLOBAL_IQ)) #define _Q13toIQ(A) ((_iq13)(A) >> (13 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 12) #define _IQtoQ12(A) ((int32_t)(A) >> (GLOBAL_IQ - 12)) #define _Q12toIQ(A) ((_iq12)(A) << (GLOBAL_IQ - 12)) #else #define _IQtoQ12(A) ((int32_t)(A) << (12 - GLOBAL_IQ)) #define _Q12toIQ(A) ((_iq12)(A) >> (12 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 11) #define _IQtoQ11(A) ((int32_t)(A) >> (GLOBAL_IQ - 11)) #define _Q11toIQ(A) ((_iq11)(A) << (GLOBAL_IQ - 11)) #else #define _IQtoQ11(A) ((int32_t)(A) << (11 - GLOBAL_IQ)) #define _Q11toIQ(A) ((_iq11)(A) >> (11 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 10) #define _IQtoQ10(A) ((int32_t)(A) >> (GLOBAL_IQ - 10)) #define _Q10toIQ(A) ((_iq10)(A) << (GLOBAL_IQ - 10)) #else #define _IQtoQ10(A) ((int32_t)(A) << (10 - GLOBAL_IQ)) #define _Q10toIQ(A) ((_iq10)(A) >> (10 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 9) #define _IQtoQ9(A) ((int32_t)(A) >> (GLOBAL_IQ - 9)) #define _Q9toIQ(A) ((_iq9)(A) << (GLOBAL_IQ - 9)) #else #define _IQtoQ9(A) ((int32_t)(A) << (9 - GLOBAL_IQ)) #define _Q9toIQ(A) ((_iq9)(A) >> (9 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 8) #define _IQtoQ8(A) ((int32_t)(A) >> (GLOBAL_IQ - 8)) #define _Q8toIQ(A) ((_iq8)(A) << (GLOBAL_IQ - 8)) #else #define _IQtoQ8(A) ((int32_t)(A) << (8 - GLOBAL_IQ)) #define _Q8toIQ(A) ((_iq8)(A) >> (8 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 7) #define _IQtoQ7(A) ((int32_t)(A) >> (GLOBAL_IQ - 7)) #define _Q7toIQ(A) ((_iq7)(A) << (GLOBAL_IQ - 7)) #else #define _IQtoQ7(A) ((int32_t)(A) << (7 - GLOBAL_IQ)) #define _Q7toIQ(A) ((_iq7)(A) >> (7 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 6) #define _IQtoQ6(A) ((int32_t)(A) >> (GLOBAL_IQ - 6)) #define _Q6toIQ(A) ((_iq6)(A) << (GLOBAL_IQ - 6)) #else #define _IQtoQ6(A) ((int32_t)(A) << (6 - GLOBAL_IQ)) #define _Q6toIQ(A) ((_iq6)(A) >> (6 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 5) #define _IQtoQ5(A) ((int32_t)(A) >> (GLOBAL_IQ - 5)) #define _Q5toIQ(A) ((_iq5)(A) << (GLOBAL_IQ - 5)) #else #define _IQtoQ5(A) ((int32_t)(A) << (5 - GLOBAL_IQ)) #define _Q5toIQ(A) ((_iq5)(A) >> (5 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 4) #define _IQtoQ4(A) ((int32_t)(A) >> (GLOBAL_IQ - 4)) #define _Q4toIQ(A) ((_iq4)(A) << (GLOBAL_IQ - 4)) #else #define _IQtoQ4(A) ((int32_t)(A) << (4 - GLOBAL_IQ)) #define _Q4toIQ(A) ((_iq4)(A) >> (4 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 3) #define _IQtoQ3(A) ((int32_t)(A) >> (GLOBAL_IQ - 3)) #define _Q3toIQ(A) ((_iq3)(A) << (GLOBAL_IQ - 3)) #else #define _IQtoQ3(A) ((int32_t)(A) << (3 - GLOBAL_IQ)) #define _Q3toIQ(A) ((_iq3)(A) >> (3 - GLOBAL_IQ)) #endif #if (GLOBAL_IQ >= 2) #define _IQtoQ2(A) ((int32_t)(A) >> (GLOBAL_IQ - 2)) #define _Q2toIQ(A) ((_iq2)(A) << (GLOBAL_IQ - 2)) #else #define _IQtoQ2(A) ((int32_t)(A) << (2 - GLOBAL_IQ)) #define _Q2toIQ(A) ((_iq2)(A) >> (2 - GLOBAL_IQ)) #endif #define _IQtoQ1(A) ((int32_t)(A) >> (GLOBAL_IQ - 1)) #define _Q1toIQ(A) ((_iq1)(A) << (GLOBAL_IQ - 1)) #endif /* DOXYGEN_SHOULD_SKIP_THIS */ //***************************************************************************** // // Multiplies two IQ numbers. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30mpy(_iq30 A, _iq30 B); extern _iq29 _IQ29mpy(_iq29 A, _iq29 B); extern _iq28 _IQ28mpy(_iq28 A, _iq28 B); extern _iq27 _IQ27mpy(_iq27 A, _iq27 B); extern _iq26 _IQ26mpy(_iq26 A, _iq26 B); extern _iq25 _IQ25mpy(_iq25 A, _iq25 B); extern _iq24 _IQ24mpy(_iq24 A, _iq24 B); extern _iq23 _IQ23mpy(_iq23 A, _iq23 B); extern _iq22 _IQ22mpy(_iq22 A, _iq22 B); extern _iq21 _IQ21mpy(_iq21 A, _iq21 B); extern _iq20 _IQ20mpy(_iq20 A, _iq20 B); extern _iq19 _IQ19mpy(_iq19 A, _iq19 B); extern _iq18 _IQ18mpy(_iq18 A, _iq18 B); extern _iq17 _IQ17mpy(_iq17 A, _iq17 B); extern _iq16 _IQ16mpy(_iq16 A, _iq16 B); extern _iq15 _IQ15mpy(_iq15 A, _iq15 B); extern _iq14 _IQ14mpy(_iq14 A, _iq14 B); extern _iq13 _IQ13mpy(_iq13 A, _iq13 B); extern _iq12 _IQ12mpy(_iq12 A, _iq12 B); extern _iq11 _IQ11mpy(_iq11 A, _iq11 B); extern _iq10 _IQ10mpy(_iq10 A, _iq10 B); extern _iq9 _IQ9mpy(_iq9 A, _iq9 B); extern _iq8 _IQ8mpy(_iq8 A, _iq8 B); extern _iq7 _IQ7mpy(_iq7 A, _iq7 B); extern _iq6 _IQ6mpy(_iq6 A, _iq6 B); extern _iq5 _IQ5mpy(_iq5 A, _iq5 B); extern _iq4 _IQ4mpy(_iq4 A, _iq4 B); extern _iq3 _IQ3mpy(_iq3 A, _iq3 B); extern _iq2 _IQ2mpy(_iq2 A, _iq2 B); extern _iq1 _IQ1mpy(_iq1 A, _iq1 B); //***************************************************************************** // // MathACL Repeat Operation: repeats same settings as last call (same IQ value, same operation). // This version is only compatible with IQmpy and IQdiv. // //***************************************************************************** #if ((defined (__IQMATH_USE_MATHACL__)) && (defined (__MSPM0_HAS_MATHACL__))) extern int32_t _IQrepeat(int32_t A, int32_t B); #endif #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Multiplies two global IQ format numbers. * * @param A Global IQ format number to be multiplied. * @param B Global IQ format number to be multiplied. * * @return Global IQ type result of multiplication. */ #if GLOBAL_IQ == 30 #define _IQmpy(A, B) _IQ30mpy(A, B) #endif #if GLOBAL_IQ == 29 #define _IQmpy(A, B) _IQ29mpy(A, B) #endif #if GLOBAL_IQ == 28 #define _IQmpy(A, B) _IQ28mpy(A, B) #endif #if GLOBAL_IQ == 27 #define _IQmpy(A, B) _IQ27mpy(A, B) #endif #if GLOBAL_IQ == 26 #define _IQmpy(A, B) _IQ26mpy(A, B) #endif #if GLOBAL_IQ == 25 #define _IQmpy(A, B) _IQ25mpy(A, B) #endif #if GLOBAL_IQ == 24 #define _IQmpy(A, B) _IQ24mpy(A, B) #endif #if GLOBAL_IQ == 23 #define _IQmpy(A, B) _IQ23mpy(A, B) #endif #if GLOBAL_IQ == 22 #define _IQmpy(A, B) _IQ22mpy(A, B) #endif #if GLOBAL_IQ == 21 #define _IQmpy(A, B) _IQ21mpy(A, B) #endif #if GLOBAL_IQ == 20 #define _IQmpy(A, B) _IQ20mpy(A, B) #endif #if GLOBAL_IQ == 19 #define _IQmpy(A, B) _IQ19mpy(A, B) #endif #if GLOBAL_IQ == 18 #define _IQmpy(A, B) _IQ18mpy(A, B) #endif #if GLOBAL_IQ == 17 #define _IQmpy(A, B) _IQ17mpy(A, B) #endif #if GLOBAL_IQ == 16 #define _IQmpy(A, B) _IQ16mpy(A, B) #endif #if GLOBAL_IQ == 15 #define _IQmpy(A, B) _IQ15mpy(A, B) #endif #if GLOBAL_IQ == 14 #define _IQmpy(A, B) _IQ14mpy(A, B) #endif #if GLOBAL_IQ == 13 #define _IQmpy(A, B) _IQ13mpy(A, B) #endif #if GLOBAL_IQ == 12 #define _IQmpy(A, B) _IQ12mpy(A, B) #endif #if GLOBAL_IQ == 11 #define _IQmpy(A, B) _IQ11mpy(A, B) #endif #if GLOBAL_IQ == 10 #define _IQmpy(A, B) _IQ10mpy(A, B) #endif #if GLOBAL_IQ == 9 #define _IQmpy(A, B) _IQ9mpy(A, B) #endif #if GLOBAL_IQ == 8 #define _IQmpy(A, B) _IQ8mpy(A, B) #endif #if GLOBAL_IQ == 7 #define _IQmpy(A, B) _IQ7mpy(A, B) #endif #if GLOBAL_IQ == 6 #define _IQmpy(A, B) _IQ6mpy(A, B) #endif #if GLOBAL_IQ == 5 #define _IQmpy(A, B) _IQ5mpy(A, B) #endif #if GLOBAL_IQ == 4 #define _IQmpy(A, B) _IQ4mpy(A, B) #endif #if GLOBAL_IQ == 3 #define _IQmpy(A, B) _IQ3mpy(A, B) #endif #if GLOBAL_IQ == 2 #define _IQmpy(A, B) _IQ2mpy(A, B) #endif #if GLOBAL_IQ == 1 #define _IQmpy(A, B) _IQ1mpy(A, B) #endif //***************************************************************************** // // Multiplies two IQ numbers, with rounding. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30rmpy(_iq30 A, _iq30 B); extern _iq29 _IQ29rmpy(_iq29 A, _iq29 B); extern _iq28 _IQ28rmpy(_iq28 A, _iq28 B); extern _iq27 _IQ27rmpy(_iq27 A, _iq27 B); extern _iq26 _IQ26rmpy(_iq26 A, _iq26 B); extern _iq25 _IQ25rmpy(_iq25 A, _iq25 B); extern _iq24 _IQ24rmpy(_iq24 A, _iq24 B); extern _iq23 _IQ23rmpy(_iq23 A, _iq23 B); extern _iq22 _IQ22rmpy(_iq22 A, _iq22 B); extern _iq21 _IQ21rmpy(_iq21 A, _iq21 B); extern _iq20 _IQ20rmpy(_iq20 A, _iq20 B); extern _iq19 _IQ19rmpy(_iq19 A, _iq19 B); extern _iq18 _IQ18rmpy(_iq18 A, _iq18 B); extern _iq17 _IQ17rmpy(_iq17 A, _iq17 B); extern _iq16 _IQ16rmpy(_iq16 A, _iq16 B); extern _iq15 _IQ15rmpy(_iq15 A, _iq15 B); extern _iq14 _IQ14rmpy(_iq14 A, _iq14 B); extern _iq13 _IQ13rmpy(_iq13 A, _iq13 B); extern _iq12 _IQ12rmpy(_iq12 A, _iq12 B); extern _iq11 _IQ11rmpy(_iq11 A, _iq11 B); extern _iq10 _IQ10rmpy(_iq10 A, _iq10 B); extern _iq9 _IQ9rmpy(_iq9 A, _iq9 B); extern _iq8 _IQ8rmpy(_iq8 A, _iq8 B); extern _iq7 _IQ7rmpy(_iq7 A, _iq7 B); extern _iq6 _IQ6rmpy(_iq6 A, _iq6 B); extern _iq5 _IQ5rmpy(_iq5 A, _iq5 B); extern _iq4 _IQ4rmpy(_iq4 A, _iq4 B); extern _iq3 _IQ3rmpy(_iq3 A, _iq3 B); extern _iq2 _IQ2rmpy(_iq2 A, _iq2 B); extern _iq1 _IQ1rmpy(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Multiplies two global IQ format numbers, with rounding * * @param A Global IQ format number to be multiplied. * @param B Global IQ format number to be multiplied. * * @return Global IQ type result of multiplication. */ #if GLOBAL_IQ == 30 #define _IQrmpy(A, B) _IQ30rmpy(A, B) #endif #if GLOBAL_IQ == 29 #define _IQrmpy(A, B) _IQ29rmpy(A, B) #endif #if GLOBAL_IQ == 28 #define _IQrmpy(A, B) _IQ28rmpy(A, B) #endif #if GLOBAL_IQ == 27 #define _IQrmpy(A, B) _IQ27rmpy(A, B) #endif #if GLOBAL_IQ == 26 #define _IQrmpy(A, B) _IQ26rmpy(A, B) #endif #if GLOBAL_IQ == 25 #define _IQrmpy(A, B) _IQ25rmpy(A, B) #endif #if GLOBAL_IQ == 24 #define _IQrmpy(A, B) _IQ24rmpy(A, B) #endif #if GLOBAL_IQ == 23 #define _IQrmpy(A, B) _IQ23rmpy(A, B) #endif #if GLOBAL_IQ == 22 #define _IQrmpy(A, B) _IQ22rmpy(A, B) #endif #if GLOBAL_IQ == 21 #define _IQrmpy(A, B) _IQ21rmpy(A, B) #endif #if GLOBAL_IQ == 20 #define _IQrmpy(A, B) _IQ20rmpy(A, B) #endif #if GLOBAL_IQ == 19 #define _IQrmpy(A, B) _IQ19rmpy(A, B) #endif #if GLOBAL_IQ == 18 #define _IQrmpy(A, B) _IQ18rmpy(A, B) #endif #if GLOBAL_IQ == 17 #define _IQrmpy(A, B) _IQ17rmpy(A, B) #endif #if GLOBAL_IQ == 16 #define _IQrmpy(A, B) _IQ16rmpy(A, B) #endif #if GLOBAL_IQ == 15 #define _IQrmpy(A, B) _IQ15rmpy(A, B) #endif #if GLOBAL_IQ == 14 #define _IQrmpy(A, B) _IQ14rmpy(A, B) #endif #if GLOBAL_IQ == 13 #define _IQrmpy(A, B) _IQ13rmpy(A, B) #endif #if GLOBAL_IQ == 12 #define _IQrmpy(A, B) _IQ12rmpy(A, B) #endif #if GLOBAL_IQ == 11 #define _IQrmpy(A, B) _IQ11rmpy(A, B) #endif #if GLOBAL_IQ == 10 #define _IQrmpy(A, B) _IQ10rmpy(A, B) #endif #if GLOBAL_IQ == 9 #define _IQrmpy(A, B) _IQ9rmpy(A, B) #endif #if GLOBAL_IQ == 8 #define _IQrmpy(A, B) _IQ8rmpy(A, B) #endif #if GLOBAL_IQ == 7 #define _IQrmpy(A, B) _IQ7rmpy(A, B) #endif #if GLOBAL_IQ == 6 #define _IQrmpy(A, B) _IQ6rmpy(A, B) #endif #if GLOBAL_IQ == 5 #define _IQrmpy(A, B) _IQ5rmpy(A, B) #endif #if GLOBAL_IQ == 4 #define _IQrmpy(A, B) _IQ4rmpy(A, B) #endif #if GLOBAL_IQ == 3 #define _IQrmpy(A, B) _IQ3rmpy(A, B) #endif #if GLOBAL_IQ == 2 #define _IQrmpy(A, B) _IQ2rmpy(A, B) #endif #if GLOBAL_IQ == 1 #define _IQrmpy(A, B) _IQ1rmpy(A, B) #endif //***************************************************************************** // // Multiplies two IQ numbers, with rounding and saturation. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30rsmpy(_iq30 A, _iq30 B); extern _iq29 _IQ29rsmpy(_iq29 A, _iq29 B); extern _iq28 _IQ28rsmpy(_iq28 A, _iq28 B); extern _iq27 _IQ27rsmpy(_iq27 A, _iq27 B); extern _iq26 _IQ26rsmpy(_iq26 A, _iq26 B); extern _iq25 _IQ25rsmpy(_iq25 A, _iq25 B); extern _iq24 _IQ24rsmpy(_iq24 A, _iq24 B); extern _iq23 _IQ23rsmpy(_iq23 A, _iq23 B); extern _iq22 _IQ22rsmpy(_iq22 A, _iq22 B); extern _iq21 _IQ21rsmpy(_iq21 A, _iq21 B); extern _iq20 _IQ20rsmpy(_iq20 A, _iq20 B); extern _iq19 _IQ19rsmpy(_iq19 A, _iq19 B); extern _iq18 _IQ18rsmpy(_iq18 A, _iq18 B); extern _iq17 _IQ17rsmpy(_iq17 A, _iq17 B); extern _iq16 _IQ16rsmpy(_iq16 A, _iq16 B); extern _iq15 _IQ15rsmpy(_iq15 A, _iq15 B); extern _iq14 _IQ14rsmpy(_iq14 A, _iq14 B); extern _iq13 _IQ13rsmpy(_iq13 A, _iq13 B); extern _iq12 _IQ12rsmpy(_iq12 A, _iq12 B); extern _iq11 _IQ11rsmpy(_iq11 A, _iq11 B); extern _iq10 _IQ10rsmpy(_iq10 A, _iq10 B); extern _iq9 _IQ9rsmpy(_iq9 A, _iq9 B); extern _iq8 _IQ8rsmpy(_iq8 A, _iq8 B); extern _iq7 _IQ7rsmpy(_iq7 A, _iq7 B); extern _iq6 _IQ6rsmpy(_iq6 A, _iq6 B); extern _iq5 _IQ5rsmpy(_iq5 A, _iq5 B); extern _iq4 _IQ4rsmpy(_iq4 A, _iq4 B); extern _iq3 _IQ3rsmpy(_iq3 A, _iq3 B); extern _iq2 _IQ2rsmpy(_iq2 A, _iq2 B); extern _iq1 _IQ1rsmpy(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Multiplies two global IQ format numbers, with rounding and saturation. * * @param A Global IQ format number to be multiplied. * @param B Global IQ format number to be multiplied. * * @return Global IQ type result of multiplication. */ #if GLOBAL_IQ == 30 #define _IQrsmpy(A, B) _IQ30rsmpy(A, B) #endif #if GLOBAL_IQ == 29 #define _IQrsmpy(A, B) _IQ29rsmpy(A, B) #endif #if GLOBAL_IQ == 28 #define _IQrsmpy(A, B) _IQ28rsmpy(A, B) #endif #if GLOBAL_IQ == 27 #define _IQrsmpy(A, B) _IQ27rsmpy(A, B) #endif #if GLOBAL_IQ == 26 #define _IQrsmpy(A, B) _IQ26rsmpy(A, B) #endif #if GLOBAL_IQ == 25 #define _IQrsmpy(A, B) _IQ25rsmpy(A, B) #endif #if GLOBAL_IQ == 24 #define _IQrsmpy(A, B) _IQ24rsmpy(A, B) #endif #if GLOBAL_IQ == 23 #define _IQrsmpy(A, B) _IQ23rsmpy(A, B) #endif #if GLOBAL_IQ == 22 #define _IQrsmpy(A, B) _IQ22rsmpy(A, B) #endif #if GLOBAL_IQ == 21 #define _IQrsmpy(A, B) _IQ21rsmpy(A, B) #endif #if GLOBAL_IQ == 20 #define _IQrsmpy(A, B) _IQ20rsmpy(A, B) #endif #if GLOBAL_IQ == 19 #define _IQrsmpy(A, B) _IQ19rsmpy(A, B) #endif #if GLOBAL_IQ == 18 #define _IQrsmpy(A, B) _IQ18rsmpy(A, B) #endif #if GLOBAL_IQ == 17 #define _IQrsmpy(A, B) _IQ17rsmpy(A, B) #endif #if GLOBAL_IQ == 16 #define _IQrsmpy(A, B) _IQ16rsmpy(A, B) #endif #if GLOBAL_IQ == 15 #define _IQrsmpy(A, B) _IQ15rsmpy(A, B) #endif #if GLOBAL_IQ == 14 #define _IQrsmpy(A, B) _IQ14rsmpy(A, B) #endif #if GLOBAL_IQ == 13 #define _IQrsmpy(A, B) _IQ13rsmpy(A, B) #endif #if GLOBAL_IQ == 12 #define _IQrsmpy(A, B) _IQ12rsmpy(A, B) #endif #if GLOBAL_IQ == 11 #define _IQrsmpy(A, B) _IQ11rsmpy(A, B) #endif #if GLOBAL_IQ == 10 #define _IQrsmpy(A, B) _IQ10rsmpy(A, B) #endif #if GLOBAL_IQ == 9 #define _IQrsmpy(A, B) _IQ9rsmpy(A, B) #endif #if GLOBAL_IQ == 8 #define _IQrsmpy(A, B) _IQ8rsmpy(A, B) #endif #if GLOBAL_IQ == 7 #define _IQrsmpy(A, B) _IQ7rsmpy(A, B) #endif #if GLOBAL_IQ == 6 #define _IQrsmpy(A, B) _IQ6rsmpy(A, B) #endif #if GLOBAL_IQ == 5 #define _IQrsmpy(A, B) _IQ5rsmpy(A, B) #endif #if GLOBAL_IQ == 4 #define _IQrsmpy(A, B) _IQ4rsmpy(A, B) #endif #if GLOBAL_IQ == 3 #define _IQrsmpy(A, B) _IQ3rsmpy(A, B) #endif #if GLOBAL_IQ == 2 #define _IQrsmpy(A, B) _IQ2rsmpy(A, B) #endif #if GLOBAL_IQ == 1 #define _IQrsmpy(A, B) _IQ1rsmpy(A, B) #endif //***************************************************************************** // // Divides two IQ numbers. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30div(_iq30 A, _iq30 B); extern _iq29 _IQ29div(_iq29 A, _iq29 B); extern _iq28 _IQ28div(_iq28 A, _iq28 B); extern _iq27 _IQ27div(_iq27 A, _iq27 B); extern _iq26 _IQ26div(_iq26 A, _iq26 B); extern _iq25 _IQ25div(_iq25 A, _iq25 B); extern _iq24 _IQ24div(_iq24 A, _iq24 B); extern _iq23 _IQ23div(_iq23 A, _iq23 B); extern _iq22 _IQ22div(_iq22 A, _iq22 B); extern _iq21 _IQ21div(_iq21 A, _iq21 B); extern _iq20 _IQ20div(_iq20 A, _iq20 B); extern _iq19 _IQ19div(_iq19 A, _iq19 B); extern _iq18 _IQ18div(_iq18 A, _iq18 B); extern _iq17 _IQ17div(_iq17 A, _iq17 B); extern _iq16 _IQ16div(_iq16 A, _iq16 B); extern _iq15 _IQ15div(_iq15 A, _iq15 B); extern _iq14 _IQ14div(_iq14 A, _iq14 B); extern _iq13 _IQ13div(_iq13 A, _iq13 B); extern _iq12 _IQ12div(_iq12 A, _iq12 B); extern _iq11 _IQ11div(_iq11 A, _iq11 B); extern _iq10 _IQ10div(_iq10 A, _iq10 B); extern _iq9 _IQ9div(_iq9 A, _iq9 B); extern _iq8 _IQ8div(_iq8 A, _iq8 B); extern _iq7 _IQ7div(_iq7 A, _iq7 B); extern _iq6 _IQ6div(_iq6 A, _iq6 B); extern _iq5 _IQ5div(_iq5 A, _iq5 B); extern _iq4 _IQ4div(_iq4 A, _iq4 B); extern _iq3 _IQ3div(_iq3 A, _iq3 B); extern _iq2 _IQ2div(_iq2 A, _iq2 B); extern _iq1 _IQ1div(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Divides two global IQ format numbers. * * @param A Global IQ format numerator to be divided. * @param B Global IQ format denominator to divide by. * * @return Global IQ type result of division. */ #if GLOBAL_IQ == 30 #define _IQdiv(A, B) _IQ30div(A, B) #endif #if GLOBAL_IQ == 29 #define _IQdiv(A, B) _IQ29div(A, B) #endif #if GLOBAL_IQ == 28 #define _IQdiv(A, B) _IQ28div(A, B) #endif #if GLOBAL_IQ == 27 #define _IQdiv(A, B) _IQ27div(A, B) #endif #if GLOBAL_IQ == 26 #define _IQdiv(A, B) _IQ26div(A, B) #endif #if GLOBAL_IQ == 25 #define _IQdiv(A, B) _IQ25div(A, B) #endif #if GLOBAL_IQ == 24 #define _IQdiv(A, B) _IQ24div(A, B) #endif #if GLOBAL_IQ == 23 #define _IQdiv(A, B) _IQ23div(A, B) #endif #if GLOBAL_IQ == 22 #define _IQdiv(A, B) _IQ22div(A, B) #endif #if GLOBAL_IQ == 21 #define _IQdiv(A, B) _IQ21div(A, B) #endif #if GLOBAL_IQ == 20 #define _IQdiv(A, B) _IQ20div(A, B) #endif #if GLOBAL_IQ == 19 #define _IQdiv(A, B) _IQ19div(A, B) #endif #if GLOBAL_IQ == 18 #define _IQdiv(A, B) _IQ18div(A, B) #endif #if GLOBAL_IQ == 17 #define _IQdiv(A, B) _IQ17div(A, B) #endif #if GLOBAL_IQ == 16 #define _IQdiv(A, B) _IQ16div(A, B) #endif #if GLOBAL_IQ == 15 #define _IQdiv(A, B) _IQ15div(A, B) #endif #if GLOBAL_IQ == 14 #define _IQdiv(A, B) _IQ14div(A, B) #endif #if GLOBAL_IQ == 13 #define _IQdiv(A, B) _IQ13div(A, B) #endif #if GLOBAL_IQ == 12 #define _IQdiv(A, B) _IQ12div(A, B) #endif #if GLOBAL_IQ == 11 #define _IQdiv(A, B) _IQ11div(A, B) #endif #if GLOBAL_IQ == 10 #define _IQdiv(A, B) _IQ10div(A, B) #endif #if GLOBAL_IQ == 9 #define _IQdiv(A, B) _IQ9div(A, B) #endif #if GLOBAL_IQ == 8 #define _IQdiv(A, B) _IQ8div(A, B) #endif #if GLOBAL_IQ == 7 #define _IQdiv(A, B) _IQ7div(A, B) #endif #if GLOBAL_IQ == 6 #define _IQdiv(A, B) _IQ6div(A, B) #endif #if GLOBAL_IQ == 5 #define _IQdiv(A, B) _IQ5div(A, B) #endif #if GLOBAL_IQ == 4 #define _IQdiv(A, B) _IQ4div(A, B) #endif #if GLOBAL_IQ == 3 #define _IQdiv(A, B) _IQ3div(A, B) #endif #if GLOBAL_IQ == 2 #define _IQdiv(A, B) _IQ2div(A, B) #endif #if GLOBAL_IQ == 1 #define _IQdiv(A, B) _IQ1div(A, B) #endif //***************************************************************************** // // Computes the sin of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq29 _IQ29sin(_iq29 A); extern _iq28 _IQ28sin(_iq28 A); extern _iq27 _IQ27sin(_iq27 A); extern _iq26 _IQ26sin(_iq26 A); extern _iq25 _IQ25sin(_iq25 A); extern _iq24 _IQ24sin(_iq24 A); extern _iq23 _IQ23sin(_iq23 A); extern _iq22 _IQ22sin(_iq22 A); extern _iq21 _IQ21sin(_iq21 A); extern _iq20 _IQ20sin(_iq20 A); extern _iq19 _IQ19sin(_iq19 A); extern _iq18 _IQ18sin(_iq18 A); extern _iq17 _IQ17sin(_iq17 A); extern _iq16 _IQ16sin(_iq16 A); extern _iq15 _IQ15sin(_iq15 A); extern _iq14 _IQ14sin(_iq14 A); extern _iq13 _IQ13sin(_iq13 A); extern _iq12 _IQ12sin(_iq12 A); extern _iq11 _IQ11sin(_iq11 A); extern _iq10 _IQ10sin(_iq10 A); extern _iq9 _IQ9sin(_iq9 A); extern _iq8 _IQ8sin(_iq8 A); extern _iq7 _IQ7sin(_iq7 A); extern _iq6 _IQ6sin(_iq6 A); extern _iq5 _IQ5sin(_iq5 A); extern _iq4 _IQ4sin(_iq4 A); extern _iq3 _IQ3sin(_iq3 A); extern _iq2 _IQ2sin(_iq2 A); extern _iq1 _IQ1sin(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the sine of a global IQ format input, in radians. * * @param A Global IQ format input. * * @return Global IQ type result of sine operation. */ #if GLOBAL_IQ == 29 #define _IQsin(A) _IQ29sin(A) #endif #if GLOBAL_IQ == 28 #define _IQsin(A) _IQ28sin(A) #endif #if GLOBAL_IQ == 27 #define _IQsin(A) _IQ27sin(A) #endif #if GLOBAL_IQ == 26 #define _IQsin(A) _IQ26sin(A) #endif #if GLOBAL_IQ == 25 #define _IQsin(A) _IQ25sin(A) #endif #if GLOBAL_IQ == 24 #define _IQsin(A) _IQ24sin(A) #endif #if GLOBAL_IQ == 23 #define _IQsin(A) _IQ23sin(A) #endif #if GLOBAL_IQ == 22 #define _IQsin(A) _IQ22sin(A) #endif #if GLOBAL_IQ == 21 #define _IQsin(A) _IQ21sin(A) #endif #if GLOBAL_IQ == 20 #define _IQsin(A) _IQ20sin(A) #endif #if GLOBAL_IQ == 19 #define _IQsin(A) _IQ19sin(A) #endif #if GLOBAL_IQ == 18 #define _IQsin(A) _IQ18sin(A) #endif #if GLOBAL_IQ == 17 #define _IQsin(A) _IQ17sin(A) #endif #if GLOBAL_IQ == 16 #define _IQsin(A) _IQ16sin(A) #endif #if GLOBAL_IQ == 15 #define _IQsin(A) _IQ15sin(A) #endif #if GLOBAL_IQ == 14 #define _IQsin(A) _IQ14sin(A) #endif #if GLOBAL_IQ == 13 #define _IQsin(A) _IQ13sin(A) #endif #if GLOBAL_IQ == 12 #define _IQsin(A) _IQ12sin(A) #endif #if GLOBAL_IQ == 11 #define _IQsin(A) _IQ11sin(A) #endif #if GLOBAL_IQ == 10 #define _IQsin(A) _IQ10sin(A) #endif #if GLOBAL_IQ == 9 #define _IQsin(A) _IQ9sin(A) #endif #if GLOBAL_IQ == 8 #define _IQsin(A) _IQ8sin(A) #endif #if GLOBAL_IQ == 7 #define _IQsin(A) _IQ7sin(A) #endif #if GLOBAL_IQ == 6 #define _IQsin(A) _IQ6sin(A) #endif #if GLOBAL_IQ == 5 #define _IQsin(A) _IQ5sin(A) #endif #if GLOBAL_IQ == 4 #define _IQsin(A) _IQ4sin(A) #endif #if GLOBAL_IQ == 3 #define _IQsin(A) _IQ3sin(A) #endif #if GLOBAL_IQ == 2 #define _IQsin(A) _IQ2sin(A) #endif #if GLOBAL_IQ == 1 #define _IQsin(A) _IQ1sin(A) #endif //***************************************************************************** // // Computes the sin of an IQ number, using cycles per unit instead of radians. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30sinPU(_iq30 A); extern _iq29 _IQ29sinPU(_iq29 A); extern _iq28 _IQ28sinPU(_iq28 A); extern _iq27 _IQ27sinPU(_iq27 A); extern _iq26 _IQ26sinPU(_iq26 A); extern _iq25 _IQ25sinPU(_iq25 A); extern _iq24 _IQ24sinPU(_iq24 A); extern _iq23 _IQ23sinPU(_iq23 A); extern _iq22 _IQ22sinPU(_iq22 A); extern _iq21 _IQ21sinPU(_iq21 A); extern _iq20 _IQ20sinPU(_iq20 A); extern _iq19 _IQ19sinPU(_iq19 A); extern _iq18 _IQ18sinPU(_iq18 A); extern _iq17 _IQ17sinPU(_iq17 A); extern _iq16 _IQ16sinPU(_iq16 A); extern _iq15 _IQ15sinPU(_iq15 A); extern _iq14 _IQ14sinPU(_iq14 A); extern _iq13 _IQ13sinPU(_iq13 A); extern _iq12 _IQ12sinPU(_iq12 A); extern _iq11 _IQ11sinPU(_iq11 A); extern _iq10 _IQ10sinPU(_iq10 A); extern _iq9 _IQ9sinPU(_iq9 A); extern _iq8 _IQ8sinPU(_iq8 A); extern _iq7 _IQ7sinPU(_iq7 A); extern _iq6 _IQ6sinPU(_iq6 A); extern _iq5 _IQ5sinPU(_iq5 A); extern _iq4 _IQ4sinPU(_iq4 A); extern _iq3 _IQ3sinPU(_iq3 A); extern _iq2 _IQ2sinPU(_iq2 A); extern _iq1 _IQ1sinPU(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the sine of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type per-unit result of sine operation. */ #if GLOBAL_IQ == 30 #define _IQsinPU(A) _IQ30sinPU(A) #endif #if GLOBAL_IQ == 29 #define _IQsinPU(A) _IQ29sinPU(A) #endif #if GLOBAL_IQ == 28 #define _IQsinPU(A) _IQ28sinPU(A) #endif #if GLOBAL_IQ == 27 #define _IQsinPU(A) _IQ27sinPU(A) #endif #if GLOBAL_IQ == 26 #define _IQsinPU(A) _IQ26sinPU(A) #endif #if GLOBAL_IQ == 25 #define _IQsinPU(A) _IQ25sinPU(A) #endif #if GLOBAL_IQ == 24 #define _IQsinPU(A) _IQ24sinPU(A) #endif #if GLOBAL_IQ == 23 #define _IQsinPU(A) _IQ23sinPU(A) #endif #if GLOBAL_IQ == 22 #define _IQsinPU(A) _IQ22sinPU(A) #endif #if GLOBAL_IQ == 21 #define _IQsinPU(A) _IQ21sinPU(A) #endif #if GLOBAL_IQ == 20 #define _IQsinPU(A) _IQ20sinPU(A) #endif #if GLOBAL_IQ == 19 #define _IQsinPU(A) _IQ19sinPU(A) #endif #if GLOBAL_IQ == 18 #define _IQsinPU(A) _IQ18sinPU(A) #endif #if GLOBAL_IQ == 17 #define _IQsinPU(A) _IQ17sinPU(A) #endif #if GLOBAL_IQ == 16 #define _IQsinPU(A) _IQ16sinPU(A) #endif #if GLOBAL_IQ == 15 #define _IQsinPU(A) _IQ15sinPU(A) #endif #if GLOBAL_IQ == 14 #define _IQsinPU(A) _IQ14sinPU(A) #endif #if GLOBAL_IQ == 13 #define _IQsinPU(A) _IQ13sinPU(A) #endif #if GLOBAL_IQ == 12 #define _IQsinPU(A) _IQ12sinPU(A) #endif #if GLOBAL_IQ == 11 #define _IQsinPU(A) _IQ11sinPU(A) #endif #if GLOBAL_IQ == 10 #define _IQsinPU(A) _IQ10sinPU(A) #endif #if GLOBAL_IQ == 9 #define _IQsinPU(A) _IQ9sinPU(A) #endif #if GLOBAL_IQ == 8 #define _IQsinPU(A) _IQ8sinPU(A) #endif #if GLOBAL_IQ == 7 #define _IQsinPU(A) _IQ7sinPU(A) #endif #if GLOBAL_IQ == 6 #define _IQsinPU(A) _IQ6sinPU(A) #endif #if GLOBAL_IQ == 5 #define _IQsinPU(A) _IQ5sinPU(A) #endif #if GLOBAL_IQ == 4 #define _IQsinPU(A) _IQ4sinPU(A) #endif #if GLOBAL_IQ == 3 #define _IQsinPU(A) _IQ3sinPU(A) #endif #if GLOBAL_IQ == 2 #define _IQsinPU(A) _IQ2sinPU(A) #endif #if GLOBAL_IQ == 1 #define _IQsinPU(A) _IQ1sinPU(A) #endif //***************************************************************************** // // Computes the arcsin of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq29 _IQ29asin(_iq29 A); extern _iq28 _IQ28asin(_iq28 A); extern _iq27 _IQ27asin(_iq27 A); extern _iq26 _IQ26asin(_iq26 A); extern _iq25 _IQ25asin(_iq25 A); extern _iq24 _IQ24asin(_iq24 A); extern _iq23 _IQ23asin(_iq23 A); extern _iq22 _IQ22asin(_iq22 A); extern _iq21 _IQ21asin(_iq21 A); extern _iq20 _IQ20asin(_iq20 A); extern _iq19 _IQ19asin(_iq19 A); extern _iq18 _IQ18asin(_iq18 A); extern _iq17 _IQ17asin(_iq17 A); extern _iq16 _IQ16asin(_iq16 A); extern _iq15 _IQ15asin(_iq15 A); extern _iq14 _IQ14asin(_iq14 A); extern _iq13 _IQ13asin(_iq13 A); extern _iq12 _IQ12asin(_iq12 A); extern _iq11 _IQ11asin(_iq11 A); extern _iq10 _IQ10asin(_iq10 A); extern _iq9 _IQ9asin(_iq9 A); extern _iq8 _IQ8asin(_iq8 A); extern _iq7 _IQ7asin(_iq7 A); extern _iq6 _IQ6asin(_iq6 A); extern _iq5 _IQ5asin(_iq5 A); extern _iq4 _IQ4asin(_iq4 A); extern _iq3 _IQ3asin(_iq3 A); extern _iq2 _IQ2asin(_iq2 A); extern _iq1 _IQ1asin(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the inverse sine of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of inverse sine operation. */ #if GLOBAL_IQ == 29 #define _IQasin(A) _IQ29asin(A) #endif #if GLOBAL_IQ == 28 #define _IQasin(A) _IQ28asin(A) #endif #if GLOBAL_IQ == 27 #define _IQasin(A) _IQ27asin(A) #endif #if GLOBAL_IQ == 26 #define _IQasin(A) _IQ26asin(A) #endif #if GLOBAL_IQ == 25 #define _IQasin(A) _IQ25asin(A) #endif #if GLOBAL_IQ == 24 #define _IQasin(A) _IQ24asin(A) #endif #if GLOBAL_IQ == 23 #define _IQasin(A) _IQ23asin(A) #endif #if GLOBAL_IQ == 22 #define _IQasin(A) _IQ22asin(A) #endif #if GLOBAL_IQ == 21 #define _IQasin(A) _IQ21asin(A) #endif #if GLOBAL_IQ == 20 #define _IQasin(A) _IQ20asin(A) #endif #if GLOBAL_IQ == 19 #define _IQasin(A) _IQ19asin(A) #endif #if GLOBAL_IQ == 18 #define _IQasin(A) _IQ18asin(A) #endif #if GLOBAL_IQ == 17 #define _IQasin(A) _IQ17asin(A) #endif #if GLOBAL_IQ == 16 #define _IQasin(A) _IQ16asin(A) #endif #if GLOBAL_IQ == 15 #define _IQasin(A) _IQ15asin(A) #endif #if GLOBAL_IQ == 14 #define _IQasin(A) _IQ14asin(A) #endif #if GLOBAL_IQ == 13 #define _IQasin(A) _IQ13asin(A) #endif #if GLOBAL_IQ == 12 #define _IQasin(A) _IQ12asin(A) #endif #if GLOBAL_IQ == 11 #define _IQasin(A) _IQ11asin(A) #endif #if GLOBAL_IQ == 10 #define _IQasin(A) _IQ10asin(A) #endif #if GLOBAL_IQ == 9 #define _IQasin(A) _IQ9asin(A) #endif #if GLOBAL_IQ == 8 #define _IQasin(A) _IQ8asin(A) #endif #if GLOBAL_IQ == 7 #define _IQasin(A) _IQ7asin(A) #endif #if GLOBAL_IQ == 6 #define _IQasin(A) _IQ6asin(A) #endif #if GLOBAL_IQ == 5 #define _IQasin(A) _IQ5asin(A) #endif #if GLOBAL_IQ == 4 #define _IQasin(A) _IQ4asin(A) #endif #if GLOBAL_IQ == 3 #define _IQasin(A) _IQ3asin(A) #endif #if GLOBAL_IQ == 2 #define _IQasin(A) _IQ2asin(A) #endif #if GLOBAL_IQ == 1 #define _IQasin(A) _IQ1asin(A) #endif //***************************************************************************** // // Computes the cos of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq29 _IQ29cos(_iq29 A); extern _iq28 _IQ28cos(_iq28 A); extern _iq27 _IQ27cos(_iq27 A); extern _iq26 _IQ26cos(_iq26 A); extern _iq25 _IQ25cos(_iq25 A); extern _iq24 _IQ24cos(_iq24 A); extern _iq23 _IQ23cos(_iq23 A); extern _iq22 _IQ22cos(_iq22 A); extern _iq21 _IQ21cos(_iq21 A); extern _iq20 _IQ20cos(_iq20 A); extern _iq19 _IQ19cos(_iq19 A); extern _iq18 _IQ18cos(_iq18 A); extern _iq17 _IQ17cos(_iq17 A); extern _iq16 _IQ16cos(_iq16 A); extern _iq15 _IQ15cos(_iq15 A); extern _iq14 _IQ14cos(_iq14 A); extern _iq13 _IQ13cos(_iq13 A); extern _iq12 _IQ12cos(_iq12 A); extern _iq11 _IQ11cos(_iq11 A); extern _iq10 _IQ10cos(_iq10 A); extern _iq9 _IQ9cos(_iq9 A); extern _iq8 _IQ8cos(_iq8 A); extern _iq7 _IQ7cos(_iq7 A); extern _iq6 _IQ6cos(_iq6 A); extern _iq5 _IQ5cos(_iq5 A); extern _iq4 _IQ4cos(_iq4 A); extern _iq3 _IQ3cos(_iq3 A); extern _iq2 _IQ2cos(_iq2 A); extern _iq1 _IQ1cos(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the cosine of a global IQ format input, in radians. * * @param A Global IQ format input. * * @return Global IQ type result of cosine operation. */ #if GLOBAL_IQ == 29 #define _IQcos(A) _IQ29cos(A) #endif #if GLOBAL_IQ == 28 #define _IQcos(A) _IQ28cos(A) #endif #if GLOBAL_IQ == 27 #define _IQcos(A) _IQ27cos(A) #endif #if GLOBAL_IQ == 26 #define _IQcos(A) _IQ26cos(A) #endif #if GLOBAL_IQ == 25 #define _IQcos(A) _IQ25cos(A) #endif #if GLOBAL_IQ == 24 #define _IQcos(A) _IQ24cos(A) #endif #if GLOBAL_IQ == 23 #define _IQcos(A) _IQ23cos(A) #endif #if GLOBAL_IQ == 22 #define _IQcos(A) _IQ22cos(A) #endif #if GLOBAL_IQ == 21 #define _IQcos(A) _IQ21cos(A) #endif #if GLOBAL_IQ == 20 #define _IQcos(A) _IQ20cos(A) #endif #if GLOBAL_IQ == 19 #define _IQcos(A) _IQ19cos(A) #endif #if GLOBAL_IQ == 18 #define _IQcos(A) _IQ18cos(A) #endif #if GLOBAL_IQ == 17 #define _IQcos(A) _IQ17cos(A) #endif #if GLOBAL_IQ == 16 #define _IQcos(A) _IQ16cos(A) #endif #if GLOBAL_IQ == 15 #define _IQcos(A) _IQ15cos(A) #endif #if GLOBAL_IQ == 14 #define _IQcos(A) _IQ14cos(A) #endif #if GLOBAL_IQ == 13 #define _IQcos(A) _IQ13cos(A) #endif #if GLOBAL_IQ == 12 #define _IQcos(A) _IQ12cos(A) #endif #if GLOBAL_IQ == 11 #define _IQcos(A) _IQ11cos(A) #endif #if GLOBAL_IQ == 10 #define _IQcos(A) _IQ10cos(A) #endif #if GLOBAL_IQ == 9 #define _IQcos(A) _IQ9cos(A) #endif #if GLOBAL_IQ == 8 #define _IQcos(A) _IQ8cos(A) #endif #if GLOBAL_IQ == 7 #define _IQcos(A) _IQ7cos(A) #endif #if GLOBAL_IQ == 6 #define _IQcos(A) _IQ6cos(A) #endif #if GLOBAL_IQ == 5 #define _IQcos(A) _IQ5cos(A) #endif #if GLOBAL_IQ == 4 #define _IQcos(A) _IQ4cos(A) #endif #if GLOBAL_IQ == 3 #define _IQcos(A) _IQ3cos(A) #endif #if GLOBAL_IQ == 2 #define _IQcos(A) _IQ2cos(A) #endif #if GLOBAL_IQ == 1 #define _IQcos(A) _IQ1cos(A) #endif //***************************************************************************** // // Computes the cos of an IQ number, using cycles per unit instead of radians. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30cosPU(_iq30 A); extern _iq29 _IQ29cosPU(_iq29 A); extern _iq28 _IQ28cosPU(_iq28 A); extern _iq27 _IQ27cosPU(_iq27 A); extern _iq26 _IQ26cosPU(_iq26 A); extern _iq25 _IQ25cosPU(_iq25 A); extern _iq24 _IQ24cosPU(_iq24 A); extern _iq23 _IQ23cosPU(_iq23 A); extern _iq22 _IQ22cosPU(_iq22 A); extern _iq21 _IQ21cosPU(_iq21 A); extern _iq20 _IQ20cosPU(_iq20 A); extern _iq19 _IQ19cosPU(_iq19 A); extern _iq18 _IQ18cosPU(_iq18 A); extern _iq17 _IQ17cosPU(_iq17 A); extern _iq16 _IQ16cosPU(_iq16 A); extern _iq15 _IQ15cosPU(_iq15 A); extern _iq14 _IQ14cosPU(_iq14 A); extern _iq13 _IQ13cosPU(_iq13 A); extern _iq12 _IQ12cosPU(_iq12 A); extern _iq11 _IQ11cosPU(_iq11 A); extern _iq10 _IQ10cosPU(_iq10 A); extern _iq9 _IQ9cosPU(_iq9 A); extern _iq8 _IQ8cosPU(_iq8 A); extern _iq7 _IQ7cosPU(_iq7 A); extern _iq6 _IQ6cosPU(_iq6 A); extern _iq5 _IQ5cosPU(_iq5 A); extern _iq4 _IQ4cosPU(_iq4 A); extern _iq3 _IQ3cosPU(_iq3 A); extern _iq2 _IQ2cosPU(_iq2 A); extern _iq1 _IQ1cosPU(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the cossine of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type per-unit result of cosine operation. */ #if GLOBAL_IQ == 30 #define _IQcosPU(A) _IQ30cosPU(A) #endif #if GLOBAL_IQ == 29 #define _IQcosPU(A) _IQ29cosPU(A) #endif #if GLOBAL_IQ == 28 #define _IQcosPU(A) _IQ28cosPU(A) #endif #if GLOBAL_IQ == 27 #define _IQcosPU(A) _IQ27cosPU(A) #endif #if GLOBAL_IQ == 26 #define _IQcosPU(A) _IQ26cosPU(A) #endif #if GLOBAL_IQ == 25 #define _IQcosPU(A) _IQ25cosPU(A) #endif #if GLOBAL_IQ == 24 #define _IQcosPU(A) _IQ24cosPU(A) #endif #if GLOBAL_IQ == 23 #define _IQcosPU(A) _IQ23cosPU(A) #endif #if GLOBAL_IQ == 22 #define _IQcosPU(A) _IQ22cosPU(A) #endif #if GLOBAL_IQ == 21 #define _IQcosPU(A) _IQ21cosPU(A) #endif #if GLOBAL_IQ == 20 #define _IQcosPU(A) _IQ20cosPU(A) #endif #if GLOBAL_IQ == 19 #define _IQcosPU(A) _IQ19cosPU(A) #endif #if GLOBAL_IQ == 18 #define _IQcosPU(A) _IQ18cosPU(A) #endif #if GLOBAL_IQ == 17 #define _IQcosPU(A) _IQ17cosPU(A) #endif #if GLOBAL_IQ == 16 #define _IQcosPU(A) _IQ16cosPU(A) #endif #if GLOBAL_IQ == 15 #define _IQcosPU(A) _IQ15cosPU(A) #endif #if GLOBAL_IQ == 14 #define _IQcosPU(A) _IQ14cosPU(A) #endif #if GLOBAL_IQ == 13 #define _IQcosPU(A) _IQ13cosPU(A) #endif #if GLOBAL_IQ == 12 #define _IQcosPU(A) _IQ12cosPU(A) #endif #if GLOBAL_IQ == 11 #define _IQcosPU(A) _IQ11cosPU(A) #endif #if GLOBAL_IQ == 10 #define _IQcosPU(A) _IQ10cosPU(A) #endif #if GLOBAL_IQ == 9 #define _IQcosPU(A) _IQ9cosPU(A) #endif #if GLOBAL_IQ == 8 #define _IQcosPU(A) _IQ8cosPU(A) #endif #if GLOBAL_IQ == 7 #define _IQcosPU(A) _IQ7cosPU(A) #endif #if GLOBAL_IQ == 6 #define _IQcosPU(A) _IQ6cosPU(A) #endif #if GLOBAL_IQ == 5 #define _IQcosPU(A) _IQ5cosPU(A) #endif #if GLOBAL_IQ == 4 #define _IQcosPU(A) _IQ4cosPU(A) #endif #if GLOBAL_IQ == 3 #define _IQcosPU(A) _IQ3cosPU(A) #endif #if GLOBAL_IQ == 2 #define _IQcosPU(A) _IQ2cosPU(A) #endif #if GLOBAL_IQ == 1 #define _IQcosPU(A) _IQ1cosPU(A) #endif //***************************************************************************** // // Computes the arccos of an IQ number. // //***************************************************************************** /** * @brief Computes the inverse cosine of an IQ29 type input. * * @param A IQ29 input. * * @return IQ29 type result of inverse cosine operation. */ #define _IQ29acos(A) (_IQ29(1.570796327) - _IQ29asin(A)) /** * @brief Computes the inverse cosine of an IQ28 type input. * * @param A IQ28 input. * * @return IQ28 type result of inverse cosine operation. */ #define _IQ28acos(A) (_IQ28(1.570796327) - _IQ28asin(A)) /** * @brief Computes the inverse cosine of an IQ27 type input. * * @param A IQ27 input. * * @return IQ27 type result of inverse cosine operation. */ #define _IQ27acos(A) (_IQ27(1.570796327) - _IQ27asin(A)) /** * @brief Computes the inverse cosine of an IQ26 type input. * * @param A IQ26 input. * * @return IQ26 type result of inverse cosine operation. */ #define _IQ26acos(A) (_IQ26(1.570796327) - _IQ26asin(A)) /** * @brief Computes the inverse cosine of an IQ25 type input. * * @param A IQ25 input. * * @return IQ25 type result of inverse cosine operation. */ #define _IQ25acos(A) (_IQ25(1.570796327) - _IQ25asin(A)) /** * @brief Computes the inverse cosine of an IQ24 type input. * * @param A IQ24 input. * * @return IQ24 type result of inverse cosine operation. */ #define _IQ24acos(A) (_IQ24(1.570796327) - _IQ24asin(A)) /** * @brief Computes the inverse cosine of an IQ23 type input. * * @param A IQ23 input. * * @return IQ23 type result of inverse cosine operation. */ #define _IQ23acos(A) (_IQ23(1.570796327) - _IQ23asin(A)) /** * @brief Computes the inverse cosine of an IQ22 type input. * * @param A IQ22 input. * * @return IQ22 type result of inverse cosine operation. */ #define _IQ22acos(A) (_IQ22(1.570796327) - _IQ22asin(A)) /** * @brief Computes the inverse cosine of an IQ21 type input. * * @param A IQ21 input. * * @return IQ21 type result of inverse cosine operation. */ #define _IQ21acos(A) (_IQ21(1.570796327) - _IQ21asin(A)) /** * @brief Computes the inverse cosine of an IQ20 type input. * * @param A IQ20 input. * * @return IQ20 type result of inverse cosine operation. */ #define _IQ20acos(A) (_IQ20(1.570796327) - _IQ20asin(A)) /** * @brief Computes the inverse cosine of an IQ19 type input. * * @param A IQ19 input. * * @return IQ19 type result of inverse cosine operation. */ #define _IQ19acos(A) (_IQ19(1.570796327) - _IQ19asin(A)) /** * @brief Computes the inverse cosine of an IQ18 type input. * * @param A IQ18 input. * * @return IQ18 type result of inverse cosine operation. */ #define _IQ18acos(A) (_IQ18(1.570796327) - _IQ18asin(A)) /** * @brief Computes the inverse cosine of an IQ17 type input. * * @param A IQ17 input. * * @return IQ17 type result of inverse cosine operation. */ #define _IQ17acos(A) (_IQ17(1.570796327) - _IQ17asin(A)) /** * @brief Computes the inverse cosine of an IQ16 type input. * * @param A IQ16 input. * * @return IQ16 type result of inverse cosine operation. */ #define _IQ16acos(A) (_IQ16(1.570796327) - _IQ16asin(A)) /** * @brief Computes the inverse cosine of an IQ15 type input. * * @param A IQ15 input. * * @return IQ15 type result of inverse cosine operation. */ #define _IQ15acos(A) (_IQ15(1.570796327) - _IQ15asin(A)) /** * @brief Computes the inverse cosine of an IQ14 type input. * * @param A IQ14 input. * * @return IQ14 type result of inverse cosine operation. */ #define _IQ14acos(A) (_IQ14(1.570796327) - _IQ14asin(A)) /** * @brief Computes the inverse cosine of an IQ13 type input. * * @param A IQ13 input. * * @return IQ13 type result of inverse cosine operation. */ #define _IQ13acos(A) (_IQ13(1.570796327) - _IQ13asin(A)) /** * @brief Computes the inverse cosine of an IQ12 type input. * * @param A IQ12 input. * * @return IQ12 type result of inverse cosine operation. */ #define _IQ12acos(A) (_IQ12(1.570796327) - _IQ12asin(A)) /** * @brief Computes the inverse cosine of an IQ11 type input. * * @param A IQ11 input. * * @return IQ11 type result of inverse cosine operation. */ #define _IQ11acos(A) (_IQ11(1.570796327) - _IQ11asin(A)) /** * @brief Computes the inverse cosine of an IQ10 type input. * * @param A IQ10 input. * * @return IQ10 type result of inverse cosine operation. */ #define _IQ10acos(A) (_IQ10(1.570796327) - _IQ10asin(A)) /** * @brief Computes the inverse cosine of an IQ9 type input. * * @param A IQ9 input. * * @return IQ9 type result of inverse cosine operation. */ #define _IQ9acos(A) (_IQ9(1.570796327) - _IQ9asin(A)) /** * @brief Computes the inverse cosine of an IQ8 type input. * * @param A IQ8 input. * * @return IQ8 type result of inverse cosine operation. */ #define _IQ8acos(A) (_IQ8(1.570796327) - _IQ8asin(A)) /** * @brief Computes the inverse cosine of an IQ7 type input. * * @param A IQ7 input. * * @return IQ7 type result of inverse cosine operation. */ #define _IQ7acos(A) (_IQ7(1.570796327) - _IQ7asin(A)) /** * @brief Computes the inverse cosine of an IQ6 type input. * * @param A IQ6 input. * * @return IQ6 type result of inverse cosine operation. */ #define _IQ6acos(A) (_IQ6(1.570796327) - _IQ6asin(A)) /** * @brief Computes the inverse cosine of an IQ5 type input. * * @param A IQ5 input. * * @return IQ5 type result of inverse cosine operation. */ #define _IQ5acos(A) (_IQ5(1.570796327) - _IQ5asin(A)) /** * @brief Computes the inverse cosine of an IQ4 type input. * * @param A IQ4 input. * * @return IQ4 type result of inverse cosine operation. */ #define _IQ4acos(A) (_IQ4(1.570796327) - _IQ4asin(A)) /** * @brief Computes the inverse cosine of an IQ3 type input. * * @param A IQ3 input. * * @return IQ3 type result of inverse cosine operation. */ #define _IQ3acos(A) (_IQ3(1.570796327) - _IQ3asin(A)) /** * @brief Computes the inverse cosine of an IQ2 type input. * * @param A IQ2 input. * * @return IQ2 type result of inverse cosine operation. */ #define _IQ2acos(A) (_IQ2(1.570796327) - _IQ2asin(A)) /** * @brief Computes the inverse cosine of an IQ1 type input. * * @param A IQ1 input. * * @return IQ1 type result of inverse cosine operation. */ #define _IQ1acos(A) (_IQ1(1.570796327) - _IQ1asin(A)) /** * @brief Computes the inverse cosine of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of inverse cosine operation. */ #if GLOBAL_IQ == 29 #define _IQacos(A) _IQ29acos(A) #endif #if GLOBAL_IQ == 28 #define _IQacos(A) _IQ28acos(A) #endif #if GLOBAL_IQ == 27 #define _IQacos(A) _IQ27acos(A) #endif #if GLOBAL_IQ == 26 #define _IQacos(A) _IQ26acos(A) #endif #if GLOBAL_IQ == 25 #define _IQacos(A) _IQ25acos(A) #endif #if GLOBAL_IQ == 24 #define _IQacos(A) _IQ24acos(A) #endif #if GLOBAL_IQ == 23 #define _IQacos(A) _IQ23acos(A) #endif #if GLOBAL_IQ == 22 #define _IQacos(A) _IQ22acos(A) #endif #if GLOBAL_IQ == 21 #define _IQacos(A) _IQ21acos(A) #endif #if GLOBAL_IQ == 20 #define _IQacos(A) _IQ20acos(A) #endif #if GLOBAL_IQ == 19 #define _IQacos(A) _IQ19acos(A) #endif #if GLOBAL_IQ == 18 #define _IQacos(A) _IQ18acos(A) #endif #if GLOBAL_IQ == 17 #define _IQacos(A) _IQ17acos(A) #endif #if GLOBAL_IQ == 16 #define _IQacos(A) _IQ16acos(A) #endif #if GLOBAL_IQ == 15 #define _IQacos(A) _IQ15acos(A) #endif #if GLOBAL_IQ == 14 #define _IQacos(A) _IQ14acos(A) #endif #if GLOBAL_IQ == 13 #define _IQacos(A) _IQ13acos(A) #endif #if GLOBAL_IQ == 12 #define _IQacos(A) _IQ12acos(A) #endif #if GLOBAL_IQ == 11 #define _IQacos(A) _IQ11acos(A) #endif #if GLOBAL_IQ == 10 #define _IQacos(A) _IQ10acos(A) #endif #if GLOBAL_IQ == 9 #define _IQacos(A) _IQ9acos(A) #endif #if GLOBAL_IQ == 8 #define _IQacos(A) _IQ8acos(A) #endif #if GLOBAL_IQ == 7 #define _IQacos(A) _IQ7acos(A) #endif #if GLOBAL_IQ == 6 #define _IQacos(A) _IQ6acos(A) #endif #if GLOBAL_IQ == 5 #define _IQacos(A) _IQ5acos(A) #endif #if GLOBAL_IQ == 4 #define _IQacos(A) _IQ4acos(A) #endif #if GLOBAL_IQ == 3 #define _IQacos(A) _IQ3acos(A) #endif #if GLOBAL_IQ == 2 #define _IQacos(A) _IQ2acos(A) #endif #if GLOBAL_IQ == 1 #define _IQacos(A) _IQ1acos(A) #endif //***************************************************************************** // // Computes the arctan of a coordinate specified by two IQ numbers. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq29 _IQ29atan2(_iq29 A, _iq29 B); extern _iq28 _IQ28atan2(_iq28 A, _iq28 B); extern _iq27 _IQ27atan2(_iq27 A, _iq27 B); extern _iq26 _IQ26atan2(_iq26 A, _iq26 B); extern _iq25 _IQ25atan2(_iq25 A, _iq25 B); extern _iq24 _IQ24atan2(_iq24 A, _iq24 B); extern _iq23 _IQ23atan2(_iq23 A, _iq23 B); extern _iq22 _IQ22atan2(_iq22 A, _iq22 B); extern _iq21 _IQ21atan2(_iq21 A, _iq21 B); extern _iq20 _IQ20atan2(_iq20 A, _iq20 B); extern _iq19 _IQ19atan2(_iq19 A, _iq19 B); extern _iq18 _IQ18atan2(_iq18 A, _iq18 B); extern _iq17 _IQ17atan2(_iq17 A, _iq17 B); extern _iq16 _IQ16atan2(_iq16 A, _iq16 B); extern _iq15 _IQ15atan2(_iq15 A, _iq15 B); extern _iq14 _IQ14atan2(_iq14 A, _iq14 B); extern _iq13 _IQ13atan2(_iq13 A, _iq13 B); extern _iq12 _IQ12atan2(_iq12 A, _iq12 B); extern _iq11 _IQ11atan2(_iq11 A, _iq11 B); extern _iq10 _IQ10atan2(_iq10 A, _iq10 B); extern _iq9 _IQ9atan2(_iq9 A, _iq9 B); extern _iq8 _IQ8atan2(_iq8 A, _iq8 B); extern _iq7 _IQ7atan2(_iq7 A, _iq7 B); extern _iq6 _IQ6atan2(_iq6 A, _iq6 B); extern _iq5 _IQ5atan2(_iq5 A, _iq5 B); extern _iq4 _IQ4atan2(_iq4 A, _iq4 B); extern _iq3 _IQ3atan2(_iq3 A, _iq3 B); extern _iq2 _IQ2atan2(_iq2 A, _iq2 B); extern _iq1 _IQ1atan2(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Compute the 4-quadrant arctangent of a * global IQ format input, in radians. * * @param A Global IQ format input. * @param B Global IQ format input. * * @return Global IQ type result of * 4-quadrant arctangent. */ #if GLOBAL_IQ == 29 #define _IQatan2(A, B) _IQ29atan2(A, B) #endif #if GLOBAL_IQ == 28 #define _IQatan2(A, B) _IQ28atan2(A, B) #endif #if GLOBAL_IQ == 27 #define _IQatan2(A, B) _IQ27atan2(A, B) #endif #if GLOBAL_IQ == 26 #define _IQatan2(A, B) _IQ26atan2(A, B) #endif #if GLOBAL_IQ == 25 #define _IQatan2(A, B) _IQ25atan2(A, B) #endif #if GLOBAL_IQ == 24 #define _IQatan2(A, B) _IQ24atan2(A, B) #endif #if GLOBAL_IQ == 23 #define _IQatan2(A, B) _IQ23atan2(A, B) #endif #if GLOBAL_IQ == 22 #define _IQatan2(A, B) _IQ22atan2(A, B) #endif #if GLOBAL_IQ == 21 #define _IQatan2(A, B) _IQ21atan2(A, B) #endif #if GLOBAL_IQ == 20 #define _IQatan2(A, B) _IQ20atan2(A, B) #endif #if GLOBAL_IQ == 19 #define _IQatan2(A, B) _IQ19atan2(A, B) #endif #if GLOBAL_IQ == 18 #define _IQatan2(A, B) _IQ18atan2(A, B) #endif #if GLOBAL_IQ == 17 #define _IQatan2(A, B) _IQ17atan2(A, B) #endif #if GLOBAL_IQ == 16 #define _IQatan2(A, B) _IQ16atan2(A, B) #endif #if GLOBAL_IQ == 15 #define _IQatan2(A, B) _IQ15atan2(A, B) #endif #if GLOBAL_IQ == 14 #define _IQatan2(A, B) _IQ14atan2(A, B) #endif #if GLOBAL_IQ == 13 #define _IQatan2(A, B) _IQ13atan2(A, B) #endif #if GLOBAL_IQ == 12 #define _IQatan2(A, B) _IQ12atan2(A, B) #endif #if GLOBAL_IQ == 11 #define _IQatan2(A, B) _IQ11atan2(A, B) #endif #if GLOBAL_IQ == 10 #define _IQatan2(A, B) _IQ10atan2(A, B) #endif #if GLOBAL_IQ == 9 #define _IQatan2(A, B) _IQ9atan2(A, B) #endif #if GLOBAL_IQ == 8 #define _IQatan2(A, B) _IQ8atan2(A, B) #endif #if GLOBAL_IQ == 7 #define _IQatan2(A, B) _IQ7atan2(A, B) #endif #if GLOBAL_IQ == 6 #define _IQatan2(A, B) _IQ6atan2(A, B) #endif #if GLOBAL_IQ == 5 #define _IQatan2(A, B) _IQ5atan2(A, B) #endif #if GLOBAL_IQ == 4 #define _IQatan2(A, B) _IQ4atan2(A, B) #endif #if GLOBAL_IQ == 3 #define _IQatan2(A, B) _IQ3atan2(A, B) #endif #if GLOBAL_IQ == 2 #define _IQatan2(A, B) _IQ2atan2(A, B) #endif #if GLOBAL_IQ == 1 #define _IQatan2(A, B) _IQ1atan2(A, B) #endif //***************************************************************************** // // Computes the arctan of a coordinate specified by two IQ numbers, returning // the value in cycles per unit instead of radians. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30atan2PU(_iq30 A, _iq30 B); extern _iq29 _IQ29atan2PU(_iq29 A, _iq29 B); extern _iq28 _IQ28atan2PU(_iq28 A, _iq28 B); extern _iq27 _IQ27atan2PU(_iq27 A, _iq27 B); extern _iq26 _IQ26atan2PU(_iq26 A, _iq26 B); extern _iq25 _IQ25atan2PU(_iq25 A, _iq25 B); extern _iq24 _IQ24atan2PU(_iq24 A, _iq24 B); extern _iq23 _IQ23atan2PU(_iq23 A, _iq23 B); extern _iq22 _IQ22atan2PU(_iq22 A, _iq22 B); extern _iq21 _IQ21atan2PU(_iq21 A, _iq21 B); extern _iq20 _IQ20atan2PU(_iq20 A, _iq20 B); extern _iq19 _IQ19atan2PU(_iq19 A, _iq19 B); extern _iq18 _IQ18atan2PU(_iq18 A, _iq18 B); extern _iq17 _IQ17atan2PU(_iq17 A, _iq17 B); extern _iq16 _IQ16atan2PU(_iq16 A, _iq16 B); extern _iq15 _IQ15atan2PU(_iq15 A, _iq15 B); extern _iq14 _IQ14atan2PU(_iq14 A, _iq14 B); extern _iq13 _IQ13atan2PU(_iq13 A, _iq13 B); extern _iq12 _IQ12atan2PU(_iq12 A, _iq12 B); extern _iq11 _IQ11atan2PU(_iq11 A, _iq11 B); extern _iq10 _IQ10atan2PU(_iq10 A, _iq10 B); extern _iq9 _IQ9atan2PU(_iq9 A, _iq9 B); extern _iq8 _IQ8atan2PU(_iq8 A, _iq8 B); extern _iq7 _IQ7atan2PU(_iq7 A, _iq7 B); extern _iq6 _IQ6atan2PU(_iq6 A, _iq6 B); extern _iq5 _IQ5atan2PU(_iq5 A, _iq5 B); extern _iq4 _IQ4atan2PU(_iq4 A, _iq4 B); extern _iq3 _IQ3atan2PU(_iq3 A, _iq3 B); extern _iq2 _IQ2atan2PU(_iq2 A, _iq2 B); extern _iq1 _IQ1atan2PU(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Compute the 4-quadrant arctangent of a * global IQ format input. * * @param A Global IQ format input. * @param B Global IQ format input. * * @return Global IQ type per-unit result of * 4-quadrant arctangent. */ #if GLOBAL_IQ == 30 #define _IQatan2PU(A, B) _IQ30atan2PU(A, B) #endif #if GLOBAL_IQ == 29 #define _IQatan2PU(A, B) _IQ29atan2PU(A, B) #endif #if GLOBAL_IQ == 28 #define _IQatan2PU(A, B) _IQ28atan2PU(A, B) #endif #if GLOBAL_IQ == 27 #define _IQatan2PU(A, B) _IQ27atan2PU(A, B) #endif #if GLOBAL_IQ == 26 #define _IQatan2PU(A, B) _IQ26atan2PU(A, B) #endif #if GLOBAL_IQ == 25 #define _IQatan2PU(A, B) _IQ25atan2PU(A, B) #endif #if GLOBAL_IQ == 24 #define _IQatan2PU(A, B) _IQ24atan2PU(A, B) #endif #if GLOBAL_IQ == 23 #define _IQatan2PU(A, B) _IQ23atan2PU(A, B) #endif #if GLOBAL_IQ == 22 #define _IQatan2PU(A, B) _IQ22atan2PU(A, B) #endif #if GLOBAL_IQ == 21 #define _IQatan2PU(A, B) _IQ21atan2PU(A, B) #endif #if GLOBAL_IQ == 20 #define _IQatan2PU(A, B) _IQ20atan2PU(A, B) #endif #if GLOBAL_IQ == 19 #define _IQatan2PU(A, B) _IQ19atan2PU(A, B) #endif #if GLOBAL_IQ == 18 #define _IQatan2PU(A, B) _IQ18atan2PU(A, B) #endif #if GLOBAL_IQ == 17 #define _IQatan2PU(A, B) _IQ17atan2PU(A, B) #endif #if GLOBAL_IQ == 16 #define _IQatan2PU(A, B) _IQ16atan2PU(A, B) #endif #if GLOBAL_IQ == 15 #define _IQatan2PU(A, B) _IQ15atan2PU(A, B) #endif #if GLOBAL_IQ == 14 #define _IQatan2PU(A, B) _IQ14atan2PU(A, B) #endif #if GLOBAL_IQ == 13 #define _IQatan2PU(A, B) _IQ13atan2PU(A, B) #endif #if GLOBAL_IQ == 12 #define _IQatan2PU(A, B) _IQ12atan2PU(A, B) #endif #if GLOBAL_IQ == 11 #define _IQatan2PU(A, B) _IQ11atan2PU(A, B) #endif #if GLOBAL_IQ == 10 #define _IQatan2PU(A, B) _IQ10atan2PU(A, B) #endif #if GLOBAL_IQ == 9 #define _IQatan2PU(A, B) _IQ9atan2PU(A, B) #endif #if GLOBAL_IQ == 8 #define _IQatan2PU(A, B) _IQ8atan2PU(A, B) #endif #if GLOBAL_IQ == 7 #define _IQatan2PU(A, B) _IQ7atan2PU(A, B) #endif #if GLOBAL_IQ == 6 #define _IQatan2PU(A, B) _IQ6atan2PU(A, B) #endif #if GLOBAL_IQ == 5 #define _IQatan2PU(A, B) _IQ5atan2PU(A, B) #endif #if GLOBAL_IQ == 4 #define _IQatan2PU(A, B) _IQ4atan2PU(A, B) #endif #if GLOBAL_IQ == 3 #define _IQatan2PU(A, B) _IQ3atan2PU(A, B) #endif #if GLOBAL_IQ == 2 #define _IQatan2PU(A, B) _IQ2atan2PU(A, B) #endif #if GLOBAL_IQ == 1 #define _IQatan2PU(A, B) _IQ1atan2PU(A, B) #endif //***************************************************************************** // // Computes the arctan of an IQ number. // //***************************************************************************** /** * @brief Computes the inverse tangnet of an IQ29 format input. * * @param A IQ29 format input. * * @return IQ29 type result of inverse tangent operation. */ #define _IQ29atan(A) _IQ29atan2(A, _IQ29(1.0)) /** * @brief Computes the inverse tangnet of an IQ28 format input. * * @param A IQ28 format input. * * @return IQ28 type result of inverse tangent operation. */ #define _IQ28atan(A) _IQ28atan2(A, _IQ28(1.0)) /** * @brief Computes the inverse tangnet of an IQ27 format input. * * @param A IQ27 format input. * * @return IQ27 type result of inverse tangent operation. */ #define _IQ27atan(A) _IQ27atan2(A, _IQ27(1.0)) /** * @brief Computes the inverse tangnet of an IQ26 format input. * * @param A IQ26 format input. * * @return IQ26 type result of inverse tangent operation. */ #define _IQ26atan(A) _IQ26atan2(A, _IQ26(1.0)) /** * @brief Computes the inverse tangnet of an IQ25 format input. * * @param A IQ25 format input. * * @return IQ25 type result of inverse tangent operation. */ #define _IQ25atan(A) _IQ25atan2(A, _IQ25(1.0)) /** * @brief Computes the inverse tangnet of an IQ24 format input. * * @param A IQ24 format input. * * @return IQ24 type result of inverse tangent operation. */ #define _IQ24atan(A) _IQ24atan2(A, _IQ24(1.0)) /** * @brief Computes the inverse tangnet of an IQ23 format input. * * @param A IQ23 format input. * * @return IQ23 type result of inverse tangent operation. */ #define _IQ23atan(A) _IQ23atan2(A, _IQ23(1.0)) /** * @brief Computes the inverse tangnet of an IQ22 format input. * * @param A IQ22 format input. * * @return IQ22 type result of inverse tangent operation. */ #define _IQ22atan(A) _IQ22atan2(A, _IQ22(1.0)) /** * @brief Computes the inverse tangnet of an IQ21 format input. * * @param A IQ21 format input. * * @return IQ21 type result of inverse tangent operation. */ #define _IQ21atan(A) _IQ21atan2(A, _IQ21(1.0)) /** * @brief Computes the inverse tangnet of an IQ20 format input. * * @param A IQ20 format input. * * @return IQ20 type result of inverse tangent operation. */ #define _IQ20atan(A) _IQ20atan2(A, _IQ20(1.0)) /** * @brief Computes the inverse tangnet of an IQ19 format input. * * @param A IQ19 format input. * * @return IQ19 type result of inverse tangent operation. */ #define _IQ19atan(A) _IQ19atan2(A, _IQ19(1.0)) /** * @brief Computes the inverse tangnet of an IQ18 format input. * * @param A IQ18 format input. * * @return IQ18 type result of inverse tangent operation. */ #define _IQ18atan(A) _IQ18atan2(A, _IQ18(1.0)) /** * @brief Computes the inverse tangnet of an IQ17 format input. * * @param A IQ17 format input. * * @return IQ17 type result of inverse tangent operation. */ #define _IQ17atan(A) _IQ17atan2(A, _IQ17(1.0)) /** * @brief Computes the inverse tangnet of an IQ16 format input. * * @param A IQ16 format input. * * @return IQ16 type result of inverse tangent operation. */ #define _IQ16atan(A) _IQ16atan2(A, _IQ16(1.0)) /** * @brief Computes the inverse tangnet of an IQ15 format input. * * @param A IQ15 format input. * * @return IQ15 type result of inverse tangent operation. */ #define _IQ15atan(A) _IQ15atan2(A, _IQ15(1.0)) /** * @brief Computes the inverse tangnet of an IQ14 format input. * * @param A IQ14 format input. * * @return IQ14 type result of inverse tangent operation. */ #define _IQ14atan(A) _IQ14atan2(A, _IQ14(1.0)) /** * @brief Computes the inverse tangnet of an IQ13 format input. * * @param A IQ13 format input. * * @return IQ13 type result of inverse tangent operation. */ #define _IQ13atan(A) _IQ13atan2(A, _IQ13(1.0)) /** * @brief Computes the inverse tangnet of an IQ12 format input. * * @param A IQ12 format input. * * @return IQ12 type result of inverse tangent operation. */ #define _IQ12atan(A) _IQ12atan2(A, _IQ12(1.0)) /** * @brief Computes the inverse tangnet of an IQ11 format input. * * @param A IQ11 format input. * * @return IQ11 type result of inverse tangent operation. */ #define _IQ11atan(A) _IQ11atan2(A, _IQ11(1.0)) /** * @brief Computes the inverse tangnet of an IQ10 format input. * * @param A IQ10 format input. * * @return IQ10 type result of inverse tangent operation. */ #define _IQ10atan(A) _IQ10atan2(A, _IQ10(1.0)) /** * @brief Computes the inverse tangnet of an IQ9 format input. * * @param A IQ9 format input. * * @return IQ9 type result of inverse tangent operation. */ #define _IQ9atan(A) _IQ9atan2(A, _IQ9(1.0)) /** * @brief Computes the inverse tangnet of an IQ8 format input. * * @param A IQ8 format input. * * @return IQ8 type result of inverse tangent operation. */ #define _IQ8atan(A) _IQ8atan2(A, _IQ8(1.0)) /** * @brief Computes the inverse tangnet of an IQ7 format input. * * @param A IQ7 format input. * * @return IQ7 type result of inverse tangent operation. */ #define _IQ7atan(A) _IQ7atan2(A, _IQ7(1.0)) /** * @brief Computes the inverse tangnet of an IQ6 format input. * * @param A IQ6 format input. * * @return IQ6 type result of inverse tangent operation. */ #define _IQ6atan(A) _IQ6atan2(A, _IQ6(1.0)) /** * @brief Computes the inverse tangnet of an IQ5 format input. * * @param A IQ5 format input. * * @return IQ5 type result of inverse tangent operation. */ #define _IQ5atan(A) _IQ5atan2(A, _IQ5(1.0)) /** * @brief Computes the inverse tangnet of an IQ4 format input. * * @param A IQ4 format input. * * @return IQ4 type result of inverse tangent operation. */ #define _IQ4atan(A) _IQ4atan2(A, _IQ4(1.0)) /** * @brief Computes the inverse tangnet of an IQ3 format input. * * @param A IQ3 format input. * * @return IQ3 type result of inverse tangent operation. */ #define _IQ3atan(A) _IQ3atan2(A, _IQ3(1.0)) /** * @brief Computes the inverse tangnet of an IQ2 format input. * * @param A IQ2 format input. * * @return IQ2 type result of inverse tangent operation. */ #define _IQ2atan(A) _IQ2atan2(A, _IQ2(1.0)) /** * @brief Computes the inverse tangnet of an IQ1 format input. * * @param A IQ1 format input. * * @return IQ1 type result of inverse tangent operation. */ #define _IQ1atan(A) _IQ1atan2(A, _IQ1(1.0)) /** * @brief Computes the inverse tangent of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of the inverse tangent. */ #if GLOBAL_IQ == 29 #define _IQatan(A) _IQ29atan2(A, _IQ29(1.0)) #endif #if GLOBAL_IQ == 28 #define _IQatan(A) _IQ28atan2(A, _IQ28(1.0)) #endif #if GLOBAL_IQ == 27 #define _IQatan(A) _IQ27atan2(A, _IQ27(1.0)) #endif #if GLOBAL_IQ == 26 #define _IQatan(A) _IQ26atan2(A, _IQ26(1.0)) #endif #if GLOBAL_IQ == 25 #define _IQatan(A) _IQ25atan2(A, _IQ25(1.0)) #endif #if GLOBAL_IQ == 24 #define _IQatan(A) _IQ24atan2(A, _IQ24(1.0)) #endif #if GLOBAL_IQ == 23 #define _IQatan(A) _IQ23atan2(A, _IQ23(1.0)) #endif #if GLOBAL_IQ == 22 #define _IQatan(A) _IQ22atan2(A, _IQ22(1.0)) #endif #if GLOBAL_IQ == 21 #define _IQatan(A) _IQ21atan2(A, _IQ21(1.0)) #endif #if GLOBAL_IQ == 20 #define _IQatan(A) _IQ20atan2(A, _IQ20(1.0)) #endif #if GLOBAL_IQ == 19 #define _IQatan(A) _IQ19atan2(A, _IQ19(1.0)) #endif #if GLOBAL_IQ == 18 #define _IQatan(A) _IQ18atan2(A, _IQ18(1.0)) #endif #if GLOBAL_IQ == 17 #define _IQatan(A) _IQ17atan2(A, _IQ17(1.0)) #endif #if GLOBAL_IQ == 16 #define _IQatan(A) _IQ16atan2(A, _IQ16(1.0)) #endif #if GLOBAL_IQ == 15 #define _IQatan(A) _IQ15atan2(A, _IQ15(1.0)) #endif #if GLOBAL_IQ == 14 #define _IQatan(A) _IQ14atan2(A, _IQ14(1.0)) #endif #if GLOBAL_IQ == 13 #define _IQatan(A) _IQ13atan2(A, _IQ13(1.0)) #endif #if GLOBAL_IQ == 12 #define _IQatan(A) _IQ12atan2(A, _IQ12(1.0)) #endif #if GLOBAL_IQ == 11 #define _IQatan(A) _IQ11atan2(A, _IQ11(1.0)) #endif #if GLOBAL_IQ == 10 #define _IQatan(A) _IQ10atan2(A, _IQ10(1.0)) #endif #if GLOBAL_IQ == 9 #define _IQatan(A) _IQ9atan2(A, _IQ9(1.0)) #endif #if GLOBAL_IQ == 8 #define _IQatan(A) _IQ8atan2(A, _IQ8(1.0)) #endif #if GLOBAL_IQ == 7 #define _IQatan(A) _IQ7atan2(A, _IQ7(1.0)) #endif #if GLOBAL_IQ == 6 #define _IQatan(A) _IQ6atan2(A, _IQ6(1.0)) #endif #if GLOBAL_IQ == 5 #define _IQatan(A) _IQ5atan2(A, _IQ5(1.0)) #endif #if GLOBAL_IQ == 4 #define _IQatan(A) _IQ4atan2(A, _IQ4(1.0)) #endif #if GLOBAL_IQ == 3 #define _IQatan(A) _IQ3atan2(A, _IQ3(1.0)) #endif #if GLOBAL_IQ == 2 #define _IQatan(A) _IQ2atan2(A, _IQ2(1.0)) #endif #if GLOBAL_IQ == 1 #define _IQatan(A) _IQ1atan2(A, _IQ1(1.0)) #endif //***************************************************************************** // // Computes the square root of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30sqrt(_iq30 A); extern _iq29 _IQ29sqrt(_iq29 A); extern _iq28 _IQ28sqrt(_iq28 A); extern _iq27 _IQ27sqrt(_iq27 A); extern _iq26 _IQ26sqrt(_iq26 A); extern _iq25 _IQ25sqrt(_iq25 A); extern _iq24 _IQ24sqrt(_iq24 A); extern _iq23 _IQ23sqrt(_iq23 A); extern _iq22 _IQ22sqrt(_iq22 A); extern _iq21 _IQ21sqrt(_iq21 A); extern _iq20 _IQ20sqrt(_iq20 A); extern _iq19 _IQ19sqrt(_iq19 A); extern _iq18 _IQ18sqrt(_iq18 A); extern _iq17 _IQ17sqrt(_iq17 A); extern _iq16 _IQ16sqrt(_iq16 A); extern _iq15 _IQ15sqrt(_iq15 A); extern _iq14 _IQ14sqrt(_iq14 A); extern _iq13 _IQ13sqrt(_iq13 A); extern _iq12 _IQ12sqrt(_iq12 A); extern _iq11 _IQ11sqrt(_iq11 A); extern _iq10 _IQ10sqrt(_iq10 A); extern _iq9 _IQ9sqrt(_iq9 A); extern _iq8 _IQ8sqrt(_iq8 A); extern _iq7 _IQ7sqrt(_iq7 A); extern _iq6 _IQ6sqrt(_iq6 A); extern _iq5 _IQ5sqrt(_iq5 A); extern _iq4 _IQ4sqrt(_iq4 A); extern _iq3 _IQ3sqrt(_iq3 A); extern _iq2 _IQ2sqrt(_iq2 A); extern _iq1 _IQ1sqrt(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Calculate square root of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of the square root operation. */ #if GLOBAL_IQ == 30 #define _IQsqrt(A) _IQ30sqrt(A) #endif #if GLOBAL_IQ == 29 #define _IQsqrt(A) _IQ29sqrt(A) #endif #if GLOBAL_IQ == 28 #define _IQsqrt(A) _IQ28sqrt(A) #endif #if GLOBAL_IQ == 27 #define _IQsqrt(A) _IQ27sqrt(A) #endif #if GLOBAL_IQ == 26 #define _IQsqrt(A) _IQ26sqrt(A) #endif #if GLOBAL_IQ == 25 #define _IQsqrt(A) _IQ25sqrt(A) #endif #if GLOBAL_IQ == 24 #define _IQsqrt(A) _IQ24sqrt(A) #endif #if GLOBAL_IQ == 23 #define _IQsqrt(A) _IQ23sqrt(A) #endif #if GLOBAL_IQ == 22 #define _IQsqrt(A) _IQ22sqrt(A) #endif #if GLOBAL_IQ == 21 #define _IQsqrt(A) _IQ21sqrt(A) #endif #if GLOBAL_IQ == 20 #define _IQsqrt(A) _IQ20sqrt(A) #endif #if GLOBAL_IQ == 19 #define _IQsqrt(A) _IQ19sqrt(A) #endif #if GLOBAL_IQ == 18 #define _IQsqrt(A) _IQ18sqrt(A) #endif #if GLOBAL_IQ == 17 #define _IQsqrt(A) _IQ17sqrt(A) #endif #if GLOBAL_IQ == 16 #define _IQsqrt(A) _IQ16sqrt(A) #endif #if GLOBAL_IQ == 15 #define _IQsqrt(A) _IQ15sqrt(A) #endif #if GLOBAL_IQ == 14 #define _IQsqrt(A) _IQ14sqrt(A) #endif #if GLOBAL_IQ == 13 #define _IQsqrt(A) _IQ13sqrt(A) #endif #if GLOBAL_IQ == 12 #define _IQsqrt(A) _IQ12sqrt(A) #endif #if GLOBAL_IQ == 11 #define _IQsqrt(A) _IQ11sqrt(A) #endif #if GLOBAL_IQ == 10 #define _IQsqrt(A) _IQ10sqrt(A) #endif #if GLOBAL_IQ == 9 #define _IQsqrt(A) _IQ9sqrt(A) #endif #if GLOBAL_IQ == 8 #define _IQsqrt(A) _IQ8sqrt(A) #endif #if GLOBAL_IQ == 7 #define _IQsqrt(A) _IQ7sqrt(A) #endif #if GLOBAL_IQ == 6 #define _IQsqrt(A) _IQ6sqrt(A) #endif #if GLOBAL_IQ == 5 #define _IQsqrt(A) _IQ5sqrt(A) #endif #if GLOBAL_IQ == 4 #define _IQsqrt(A) _IQ4sqrt(A) #endif #if GLOBAL_IQ == 3 #define _IQsqrt(A) _IQ3sqrt(A) #endif #if GLOBAL_IQ == 2 #define _IQsqrt(A) _IQ2sqrt(A) #endif #if GLOBAL_IQ == 1 #define _IQsqrt(A) _IQ1sqrt(A) #endif //***************************************************************************** // // Computes 1 over the square root of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30isqrt(_iq30 A); extern _iq29 _IQ29isqrt(_iq29 A); extern _iq28 _IQ28isqrt(_iq28 A); extern _iq27 _IQ27isqrt(_iq27 A); extern _iq26 _IQ26isqrt(_iq26 A); extern _iq25 _IQ25isqrt(_iq25 A); extern _iq24 _IQ24isqrt(_iq24 A); extern _iq23 _IQ23isqrt(_iq23 A); extern _iq22 _IQ22isqrt(_iq22 A); extern _iq21 _IQ21isqrt(_iq21 A); extern _iq20 _IQ20isqrt(_iq20 A); extern _iq19 _IQ19isqrt(_iq19 A); extern _iq18 _IQ18isqrt(_iq18 A); extern _iq17 _IQ17isqrt(_iq17 A); extern _iq16 _IQ16isqrt(_iq16 A); extern _iq15 _IQ15isqrt(_iq15 A); extern _iq14 _IQ14isqrt(_iq14 A); extern _iq13 _IQ13isqrt(_iq13 A); extern _iq12 _IQ12isqrt(_iq12 A); extern _iq11 _IQ11isqrt(_iq11 A); extern _iq10 _IQ10isqrt(_iq10 A); extern _iq9 _IQ9isqrt(_iq9 A); extern _iq8 _IQ8isqrt(_iq8 A); extern _iq7 _IQ7isqrt(_iq7 A); extern _iq6 _IQ6isqrt(_iq6 A); extern _iq5 _IQ5isqrt(_iq5 A); extern _iq4 _IQ4isqrt(_iq4 A); extern _iq3 _IQ3isqrt(_iq3 A); extern _iq2 _IQ2isqrt(_iq2 A); extern _iq1 _IQ1isqrt(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes 1 over the square root of a global IQ format number. * * @param A Global IQ format input. * * @return Global IQ type result of inverse square root operation. */ #if GLOBAL_IQ == 30 #define _IQisqrt(A) _IQ30isqrt(A) #endif #if GLOBAL_IQ == 29 #define _IQisqrt(A) _IQ29isqrt(A) #endif #if GLOBAL_IQ == 28 #define _IQisqrt(A) _IQ28isqrt(A) #endif #if GLOBAL_IQ == 27 #define _IQisqrt(A) _IQ27isqrt(A) #endif #if GLOBAL_IQ == 26 #define _IQisqrt(A) _IQ26isqrt(A) #endif #if GLOBAL_IQ == 25 #define _IQisqrt(A) _IQ25isqrt(A) #endif #if GLOBAL_IQ == 24 #define _IQisqrt(A) _IQ24isqrt(A) #endif #if GLOBAL_IQ == 23 #define _IQisqrt(A) _IQ23isqrt(A) #endif #if GLOBAL_IQ == 22 #define _IQisqrt(A) _IQ22isqrt(A) #endif #if GLOBAL_IQ == 21 #define _IQisqrt(A) _IQ21isqrt(A) #endif #if GLOBAL_IQ == 20 #define _IQisqrt(A) _IQ20isqrt(A) #endif #if GLOBAL_IQ == 19 #define _IQisqrt(A) _IQ19isqrt(A) #endif #if GLOBAL_IQ == 18 #define _IQisqrt(A) _IQ18isqrt(A) #endif #if GLOBAL_IQ == 17 #define _IQisqrt(A) _IQ17isqrt(A) #endif #if GLOBAL_IQ == 16 #define _IQisqrt(A) _IQ16isqrt(A) #endif #if GLOBAL_IQ == 15 #define _IQisqrt(A) _IQ15isqrt(A) #endif #if GLOBAL_IQ == 14 #define _IQisqrt(A) _IQ14isqrt(A) #endif #if GLOBAL_IQ == 13 #define _IQisqrt(A) _IQ13isqrt(A) #endif #if GLOBAL_IQ == 12 #define _IQisqrt(A) _IQ12isqrt(A) #endif #if GLOBAL_IQ == 11 #define _IQisqrt(A) _IQ11isqrt(A) #endif #if GLOBAL_IQ == 10 #define _IQisqrt(A) _IQ10isqrt(A) #endif #if GLOBAL_IQ == 9 #define _IQisqrt(A) _IQ9isqrt(A) #endif #if GLOBAL_IQ == 8 #define _IQisqrt(A) _IQ8isqrt(A) #endif #if GLOBAL_IQ == 7 #define _IQisqrt(A) _IQ7isqrt(A) #endif #if GLOBAL_IQ == 6 #define _IQisqrt(A) _IQ6isqrt(A) #endif #if GLOBAL_IQ == 5 #define _IQisqrt(A) _IQ5isqrt(A) #endif #if GLOBAL_IQ == 4 #define _IQisqrt(A) _IQ4isqrt(A) #endif #if GLOBAL_IQ == 3 #define _IQisqrt(A) _IQ3isqrt(A) #endif #if GLOBAL_IQ == 2 #define _IQisqrt(A) _IQ2isqrt(A) #endif #if GLOBAL_IQ == 1 #define _IQisqrt(A) _IQ1isqrt(A) #endif //***************************************************************************** // // Computes e^x of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30exp(_iq30 A); extern _iq29 _IQ29exp(_iq29 A); extern _iq28 _IQ28exp(_iq28 A); extern _iq27 _IQ27exp(_iq27 A); extern _iq26 _IQ26exp(_iq26 A); extern _iq25 _IQ25exp(_iq25 A); extern _iq24 _IQ24exp(_iq24 A); extern _iq23 _IQ23exp(_iq23 A); extern _iq22 _IQ22exp(_iq22 A); extern _iq21 _IQ21exp(_iq21 A); extern _iq20 _IQ20exp(_iq20 A); extern _iq19 _IQ19exp(_iq19 A); extern _iq18 _IQ18exp(_iq18 A); extern _iq17 _IQ17exp(_iq17 A); extern _iq16 _IQ16exp(_iq16 A); extern _iq15 _IQ15exp(_iq15 A); extern _iq14 _IQ14exp(_iq14 A); extern _iq13 _IQ13exp(_iq13 A); extern _iq12 _IQ12exp(_iq12 A); extern _iq11 _IQ11exp(_iq11 A); extern _iq10 _IQ10exp(_iq10 A); extern _iq9 _IQ9exp(_iq9 A); extern _iq8 _IQ8exp(_iq8 A); extern _iq7 _IQ7exp(_iq7 A); extern _iq6 _IQ6exp(_iq6 A); extern _iq5 _IQ5exp(_iq5 A); extern _iq4 _IQ4exp(_iq4 A); extern _iq3 _IQ3exp(_iq3 A); extern _iq2 _IQ2exp(_iq2 A); extern _iq1 _IQ1exp(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the exponential of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of exponential. */ #if GLOBAL_IQ == 30 #define _IQexp(A) _IQ30exp(A) #endif #if GLOBAL_IQ == 29 #define _IQexp(A) _IQ29exp(A) #endif #if GLOBAL_IQ == 28 #define _IQexp(A) _IQ28exp(A) #endif #if GLOBAL_IQ == 27 #define _IQexp(A) _IQ27exp(A) #endif #if GLOBAL_IQ == 26 #define _IQexp(A) _IQ26exp(A) #endif #if GLOBAL_IQ == 25 #define _IQexp(A) _IQ25exp(A) #endif #if GLOBAL_IQ == 24 #define _IQexp(A) _IQ24exp(A) #endif #if GLOBAL_IQ == 23 #define _IQexp(A) _IQ23exp(A) #endif #if GLOBAL_IQ == 22 #define _IQexp(A) _IQ22exp(A) #endif #if GLOBAL_IQ == 21 #define _IQexp(A) _IQ21exp(A) #endif #if GLOBAL_IQ == 20 #define _IQexp(A) _IQ20exp(A) #endif #if GLOBAL_IQ == 19 #define _IQexp(A) _IQ19exp(A) #endif #if GLOBAL_IQ == 18 #define _IQexp(A) _IQ18exp(A) #endif #if GLOBAL_IQ == 17 #define _IQexp(A) _IQ17exp(A) #endif #if GLOBAL_IQ == 16 #define _IQexp(A) _IQ16exp(A) #endif #if GLOBAL_IQ == 15 #define _IQexp(A) _IQ15exp(A) #endif #if GLOBAL_IQ == 14 #define _IQexp(A) _IQ14exp(A) #endif #if GLOBAL_IQ == 13 #define _IQexp(A) _IQ13exp(A) #endif #if GLOBAL_IQ == 12 #define _IQexp(A) _IQ12exp(A) #endif #if GLOBAL_IQ == 11 #define _IQexp(A) _IQ11exp(A) #endif #if GLOBAL_IQ == 10 #define _IQexp(A) _IQ10exp(A) #endif #if GLOBAL_IQ == 9 #define _IQexp(A) _IQ9exp(A) #endif #if GLOBAL_IQ == 8 #define _IQexp(A) _IQ8exp(A) #endif #if GLOBAL_IQ == 7 #define _IQexp(A) _IQ7exp(A) #endif #if GLOBAL_IQ == 6 #define _IQexp(A) _IQ6exp(A) #endif #if GLOBAL_IQ == 5 #define _IQexp(A) _IQ5exp(A) #endif #if GLOBAL_IQ == 4 #define _IQexp(A) _IQ4exp(A) #endif #if GLOBAL_IQ == 3 #define _IQexp(A) _IQ3exp(A) #endif #if GLOBAL_IQ == 2 #define _IQexp(A) _IQ2exp(A) #endif #if GLOBAL_IQ == 1 #define _IQexp(A) _IQ1exp(A) #endif //***************************************************************************** // // Computes log(x) of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30log(_iq30 A); extern _iq29 _IQ29log(_iq29 A); extern _iq28 _IQ28log(_iq28 A); extern _iq27 _IQ27log(_iq27 A); extern _iq26 _IQ26log(_iq26 A); extern _iq25 _IQ25log(_iq25 A); extern _iq24 _IQ24log(_iq24 A); extern _iq23 _IQ23log(_iq23 A); extern _iq22 _IQ22log(_iq22 A); extern _iq21 _IQ21log(_iq21 A); extern _iq20 _IQ20log(_iq20 A); extern _iq19 _IQ19log(_iq19 A); extern _iq18 _IQ18log(_iq18 A); extern _iq17 _IQ17log(_iq17 A); extern _iq16 _IQ16log(_iq16 A); extern _iq15 _IQ15log(_iq15 A); extern _iq14 _IQ14log(_iq14 A); extern _iq13 _IQ13log(_iq13 A); extern _iq12 _IQ12log(_iq12 A); extern _iq11 _IQ11log(_iq11 A); extern _iq10 _IQ10log(_iq10 A); extern _iq9 _IQ9log(_iq9 A); extern _iq8 _IQ8log(_iq8 A); extern _iq7 _IQ7log(_iq7 A); extern _iq6 _IQ6log(_iq6 A); extern _iq5 _IQ5log(_iq5 A); extern _iq4 _IQ4log(_iq4 A); extern _iq3 _IQ3log(_iq3 A); extern _iq2 _IQ2log(_iq2 A); extern _iq1 _IQ1log(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the base-e logarithm of a global IQ format input. * * @param A Global IQ format input. * * @return Global IQ type result of base-e logarithm. */ #if GLOBAL_IQ == 30 #define _IQlog(A) _IQ30log(A) #endif #if GLOBAL_IQ == 29 #define _IQlog(A) _IQ29log(A) #endif #if GLOBAL_IQ == 28 #define _IQlog(A) _IQ28log(A) #endif #if GLOBAL_IQ == 27 #define _IQlog(A) _IQ27log(A) #endif #if GLOBAL_IQ == 26 #define _IQlog(A) _IQ26log(A) #endif #if GLOBAL_IQ == 25 #define _IQlog(A) _IQ25log(A) #endif #if GLOBAL_IQ == 24 #define _IQlog(A) _IQ24log(A) #endif #if GLOBAL_IQ == 23 #define _IQlog(A) _IQ23log(A) #endif #if GLOBAL_IQ == 22 #define _IQlog(A) _IQ22log(A) #endif #if GLOBAL_IQ == 21 #define _IQlog(A) _IQ21log(A) #endif #if GLOBAL_IQ == 20 #define _IQlog(A) _IQ20log(A) #endif #if GLOBAL_IQ == 19 #define _IQlog(A) _IQ19log(A) #endif #if GLOBAL_IQ == 18 #define _IQlog(A) _IQ18log(A) #endif #if GLOBAL_IQ == 17 #define _IQlog(A) _IQ17log(A) #endif #if GLOBAL_IQ == 16 #define _IQlog(A) _IQ16log(A) #endif #if GLOBAL_IQ == 15 #define _IQlog(A) _IQ15log(A) #endif #if GLOBAL_IQ == 14 #define _IQlog(A) _IQ14log(A) #endif #if GLOBAL_IQ == 13 #define _IQlog(A) _IQ13log(A) #endif #if GLOBAL_IQ == 12 #define _IQlog(A) _IQ12log(A) #endif #if GLOBAL_IQ == 11 #define _IQlog(A) _IQ11log(A) #endif #if GLOBAL_IQ == 10 #define _IQlog(A) _IQ10log(A) #endif #if GLOBAL_IQ == 9 #define _IQlog(A) _IQ9log(A) #endif #if GLOBAL_IQ == 8 #define _IQlog(A) _IQ8log(A) #endif #if GLOBAL_IQ == 7 #define _IQlog(A) _IQ7log(A) #endif #if GLOBAL_IQ == 6 #define _IQlog(A) _IQ6log(A) #endif #if GLOBAL_IQ == 5 #define _IQlog(A) _IQ5log(A) #endif #if GLOBAL_IQ == 4 #define _IQlog(A) _IQ4log(A) #endif #if GLOBAL_IQ == 3 #define _IQlog(A) _IQ3log(A) #endif #if GLOBAL_IQ == 2 #define _IQlog(A) _IQ2log(A) #endif #if GLOBAL_IQ == 1 #define _IQlog(A) _IQ1log(A) #endif //***************************************************************************** // // Returns the integer portion of an IQ number. // //***************************************************************************** /** * @brief Returns the integer portion of an IQ30 type number. * * @param A IQ30 type input. * * @return Iinteger portion of input. */ #define _IQ30int(A) ((A) >> 30) /** * @brief Returns the integer portion of an IQ29 type number. * * @param A IQ29 type input. * * @return Iinteger portion of input. */ #define _IQ29int(A) ((A) >> 29) /** * @brief Returns the integer portion of an IQ28 type number. * * @param A IQ28 type input. * * @return Iinteger portion of input. */ #define _IQ28int(A) ((A) >> 28) /** * @brief Returns the integer portion of an IQ27 type number. * * @param A IQ27 type input. * * @return Iinteger portion of input. */ #define _IQ27int(A) ((A) >> 27) /** * @brief Returns the integer portion of an IQ26 type number. * * @param A IQ26 type input. * * @return Iinteger portion of input. */ #define _IQ26int(A) ((A) >> 26) /** * @brief Returns the integer portion of an IQ25 type number. * * @param A IQ25 type input. * * @return Iinteger portion of input. */ #define _IQ25int(A) ((A) >> 25) /** * @brief Returns the integer portion of an IQ24 type number. * * @param A IQ24 type input. * * @return Iinteger portion of input. */ #define _IQ24int(A) ((A) >> 24) /** * @brief Returns the integer portion of an IQ23 type number. * * @param A IQ23 type input. * * @return Iinteger portion of input. */ #define _IQ23int(A) ((A) >> 23) /** * @brief Returns the integer portion of an IQ22 type number. * * @param A IQ22 type input. * * @return Iinteger portion of input. */ #define _IQ22int(A) ((A) >> 22) /** * @brief Returns the integer portion of an IQ21 type number. * * @param A IQ21 type input. * * @return Iinteger portion of input. */ #define _IQ21int(A) ((A) >> 21) /** * @brief Returns the integer portion of an IQ20 type number. * * @param A IQ20 type input. * * @return Iinteger portion of input. */ #define _IQ20int(A) ((A) >> 20) /** * @brief Returns the integer portion of an IQ19 type number. * * @param A IQ19 type input. * * @return Iinteger portion of input. */ #define _IQ19int(A) ((A) >> 19) /** * @brief Returns the integer portion of an IQ18 type number. * * @param A IQ18 type input. * * @return Iinteger portion of input. */ #define _IQ18int(A) ((A) >> 18) /** * @brief Returns the integer portion of an IQ17 type number. * * @param A IQ17 type input. * * @return Iinteger portion of input. */ #define _IQ17int(A) ((A) >> 17) /** * @brief Returns the integer portion of an IQ16 type number. * * @param A IQ16 type input. * * @return Iinteger portion of input. */ #define _IQ16int(A) ((A) >> 16) /** * @brief Returns the integer portion of an IQ15 type number. * * @param A IQ15 type input. * * @return Iinteger portion of input. */ #define _IQ15int(A) ((A) >> 15) /** * @brief Returns the integer portion of an IQ14 type number. * * @param A IQ14 type input. * * @return Iinteger portion of input. */ #define _IQ14int(A) ((A) >> 14) /** * @brief Returns the integer portion of an IQ13 type number. * * @param A IQ13 type input. * * @return Iinteger portion of input. */ #define _IQ13int(A) ((A) >> 13) /** * @brief Returns the integer portion of an IQ12 type number. * * @param A IQ12 type input. * * @return Iinteger portion of input. */ #define _IQ12int(A) ((A) >> 12) /** * @brief Returns the integer portion of an IQ11 type number. * * @param A IQ11 type input. * * @return Iinteger portion of input. */ #define _IQ11int(A) ((A) >> 11) /** * @brief Returns the integer portion of an IQ10 type number. * * @param A IQ10 type input. * * @return Iinteger portion of input. */ #define _IQ10int(A) ((A) >> 10) /** * @brief Returns the integer portion of an IQ9 type number. * * @param A IQ9 type input. * * @return Integer portion of input. */ #define _IQ9int(A) ((A) >> 9) /** * @brief Returns the integer portion of an IQ8 type number. * * @param A IQ8 type input. * * @return Integer portion of input. */ #define _IQ8int(A) ((A) >> 8) /** * @brief Returns the integer portion of an IQ7 type number. * * @param A IQ7 type input. * * @return Integer portion of input. */ #define _IQ7int(A) ((A) >> 7) /** * @brief Returns the integer portion of an IQ6 type number. * * @param A IQ6 type input. * * @return Integer portion of input. */ #define _IQ6int(A) ((A) >> 6) /** * @brief Returns the integer portion of an IQ5 type number. * * @param A IQ5 type input. * * @return Integer portion of input. */ #define _IQ5int(A) ((A) >> 5) /** * @brief Returns the integer portion of an IQ4 type number. * * @param A IQ4 type input. * * @return Integer portion of input. */ #define _IQ4int(A) ((A) >> 4) /** * @brief Returns the integer portion of an IQ3 type number. * * @param A IQ3 type input. * * @return Integer portion of input. */ #define _IQ3int(A) ((A) >> 3) /** * @brief Returns the integer portion of an IQ2 type number. * * @param A IQ2 type input. * * @return Integer portion of input. */ #define _IQ2int(A) ((A) >> 2) /** * @brief Returns the integer portion of an IQ1 type number. * * @param A IQ1 type input. * * @return Integer portion of input. */ #define _IQ1int(A) ((A) >> 1) /** * @brief Returns the integer portion of a global IQ format number. * * @param A Global IQ format input. * * @return Integer portion of input. */ #define _IQint(A) ((A) >> GLOBAL_IQ) //***************************************************************************** // // Computes the fractional portion of an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _IQ30frac(_iq30 A); extern _iq29 _IQ29frac(_iq29 A); extern _iq28 _IQ28frac(_iq28 A); extern _iq27 _IQ27frac(_iq27 A); extern _iq26 _IQ26frac(_iq26 A); extern _iq25 _IQ25frac(_iq25 A); extern _iq24 _IQ24frac(_iq24 A); extern _iq23 _IQ23frac(_iq23 A); extern _iq22 _IQ22frac(_iq22 A); extern _iq21 _IQ21frac(_iq21 A); extern _iq20 _IQ20frac(_iq20 A); extern _iq19 _IQ19frac(_iq19 A); extern _iq18 _IQ18frac(_iq18 A); extern _iq17 _IQ17frac(_iq17 A); extern _iq16 _IQ16frac(_iq16 A); extern _iq15 _IQ15frac(_iq15 A); extern _iq14 _IQ14frac(_iq14 A); extern _iq13 _IQ13frac(_iq13 A); extern _iq12 _IQ12frac(_iq12 A); extern _iq11 _IQ11frac(_iq11 A); extern _iq10 _IQ10frac(_iq10 A); extern _iq9 _IQ9frac(_iq9 A); extern _iq8 _IQ8frac(_iq8 A); extern _iq7 _IQ7frac(_iq7 A); extern _iq6 _IQ6frac(_iq6 A); extern _iq5 _IQ5frac(_iq5 A); extern _iq4 _IQ4frac(_iq4 A); extern _iq3 _IQ3frac(_iq3 A); extern _iq2 _IQ2frac(_iq2 A); extern _iq1 _IQ1frac(_iq1 A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the fractional portion a global IQ format number. * * @param A Global IQ format input. * * @return Global IQ format fractional portion of input. */ #if GLOBAL_IQ == 30 #define _IQfrac(A) _IQ30frac(A) #endif #if GLOBAL_IQ == 29 #define _IQfrac(A) _IQ29frac(A) #endif #if GLOBAL_IQ == 28 #define _IQfrac(A) _IQ28frac(A) #endif #if GLOBAL_IQ == 27 #define _IQfrac(A) _IQ27frac(A) #endif #if GLOBAL_IQ == 26 #define _IQfrac(A) _IQ26frac(A) #endif #if GLOBAL_IQ == 25 #define _IQfrac(A) _IQ25frac(A) #endif #if GLOBAL_IQ == 24 #define _IQfrac(A) _IQ24frac(A) #endif #if GLOBAL_IQ == 23 #define _IQfrac(A) _IQ23frac(A) #endif #if GLOBAL_IQ == 22 #define _IQfrac(A) _IQ22frac(A) #endif #if GLOBAL_IQ == 21 #define _IQfrac(A) _IQ21frac(A) #endif #if GLOBAL_IQ == 20 #define _IQfrac(A) _IQ20frac(A) #endif #if GLOBAL_IQ == 19 #define _IQfrac(A) _IQ19frac(A) #endif #if GLOBAL_IQ == 18 #define _IQfrac(A) _IQ18frac(A) #endif #if GLOBAL_IQ == 17 #define _IQfrac(A) _IQ17frac(A) #endif #if GLOBAL_IQ == 16 #define _IQfrac(A) _IQ16frac(A) #endif #if GLOBAL_IQ == 15 #define _IQfrac(A) _IQ15frac(A) #endif #if GLOBAL_IQ == 14 #define _IQfrac(A) _IQ14frac(A) #endif #if GLOBAL_IQ == 13 #define _IQfrac(A) _IQ13frac(A) #endif #if GLOBAL_IQ == 12 #define _IQfrac(A) _IQ12frac(A) #endif #if GLOBAL_IQ == 11 #define _IQfrac(A) _IQ11frac(A) #endif #if GLOBAL_IQ == 10 #define _IQfrac(A) _IQ10frac(A) #endif #if GLOBAL_IQ == 9 #define _IQfrac(A) _IQ9frac(A) #endif #if GLOBAL_IQ == 8 #define _IQfrac(A) _IQ8frac(A) #endif #if GLOBAL_IQ == 7 #define _IQfrac(A) _IQ7frac(A) #endif #if GLOBAL_IQ == 6 #define _IQfrac(A) _IQ6frac(A) #endif #if GLOBAL_IQ == 5 #define _IQfrac(A) _IQ5frac(A) #endif #if GLOBAL_IQ == 4 #define _IQfrac(A) _IQ4frac(A) #endif #if GLOBAL_IQ == 3 #define _IQfrac(A) _IQ3frac(A) #endif #if GLOBAL_IQ == 2 #define _IQfrac(A) _IQ2frac(A) #endif #if GLOBAL_IQ == 1 #define _IQfrac(A) _IQ1frac(A) #endif //***************************************************************************** // // Multiplies two IQ numbers in the specified iQ formats, returning the result // in another IQ format. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern int32_t _IQ30mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ29mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ28mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ27mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ26mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ25mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ24mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ23mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ22mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ21mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ20mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ19mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ18mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ17mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ16mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ15mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ14mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ13mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ12mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ11mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ10mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ9mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ8mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ7mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ6mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ5mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ4mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ3mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ2mpyIQX(int32_t A, int n1, int32_t B, int n2); extern int32_t _IQ1mpyIQX(int32_t A, int n1, int32_t B, int n2); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Multiply two IQ numbers in different IQ formats, * returning the product in global IQ format. * * @param A IQN1 format input to be multiplied. * @param n1 IQ format for first value. * @param B IQN2 format input to be multiplied. * @param n2 IQ format for second value. * * * @return Global IQ format result of the multiplication. */ #if GLOBAL_IQ == 30 #define _IQmpyIQX(A, n1, B, n2) _IQ30mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 29 #define _IQmpyIQX(A, n1, B, n2) _IQ29mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 28 #define _IQmpyIQX(A, n1, B, n2) _IQ28mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 27 #define _IQmpyIQX(A, n1, B, n2) _IQ27mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 26 #define _IQmpyIQX(A, n1, B, n2) _IQ26mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 25 #define _IQmpyIQX(A, n1, B, n2) _IQ25mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 24 #define _IQmpyIQX(A, n1, B, n2) _IQ24mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 23 #define _IQmpyIQX(A, n1, B, n2) _IQ23mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 22 #define _IQmpyIQX(A, n1, B, n2) _IQ22mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 21 #define _IQmpyIQX(A, n1, B, n2) _IQ21mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 20 #define _IQmpyIQX(A, n1, B, n2) _IQ20mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 19 #define _IQmpyIQX(A, n1, B, n2) _IQ19mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 18 #define _IQmpyIQX(A, n1, B, n2) _IQ18mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 17 #define _IQmpyIQX(A, n1, B, n2) _IQ17mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 16 #define _IQmpyIQX(A, n1, B, n2) _IQ16mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 15 #define _IQmpyIQX(A, n1, B, n2) _IQ15mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 14 #define _IQmpyIQX(A, n1, B, n2) _IQ14mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 13 #define _IQmpyIQX(A, n1, B, n2) _IQ13mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 12 #define _IQmpyIQX(A, n1, B, n2) _IQ12mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 11 #define _IQmpyIQX(A, n1, B, n2) _IQ11mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 10 #define _IQmpyIQX(A, n1, B, n2) _IQ10mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 9 #define _IQmpyIQX(A, n1, B, n2) _IQ9mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 8 #define _IQmpyIQX(A, n1, B, n2) _IQ8mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 7 #define _IQmpyIQX(A, n1, B, n2) _IQ7mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 6 #define _IQmpyIQX(A, n1, B, n2) _IQ6mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 5 #define _IQmpyIQX(A, n1, B, n2) _IQ5mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 4 #define _IQmpyIQX(A, n1, B, n2) _IQ4mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 3 #define _IQmpyIQX(A, n1, B, n2) _IQ3mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 2 #define _IQmpyIQX(A, n1, B, n2) _IQ2mpyIQX(A, n1, B, n2) #endif #if GLOBAL_IQ == 1 #define _IQmpyIQX(A, n1, B, n2) _IQ1mpyIQX(A, n1, B, n2) #endif #ifndef DOXYGEN_SHOULD_SKIP_THIS //***************************************************************************** // // Multiplies an IQ number by an integer. // //***************************************************************************** #define _IQ30mpyI32(A, B) ((A) * (B)) #define _IQ29mpyI32(A, B) ((A) * (B)) #define _IQ28mpyI32(A, B) ((A) * (B)) #define _IQ27mpyI32(A, B) ((A) * (B)) #define _IQ26mpyI32(A, B) ((A) * (B)) #define _IQ25mpyI32(A, B) ((A) * (B)) #define _IQ24mpyI32(A, B) ((A) * (B)) #define _IQ23mpyI32(A, B) ((A) * (B)) #define _IQ22mpyI32(A, B) ((A) * (B)) #define _IQ21mpyI32(A, B) ((A) * (B)) #define _IQ20mpyI32(A, B) ((A) * (B)) #define _IQ19mpyI32(A, B) ((A) * (B)) #define _IQ18mpyI32(A, B) ((A) * (B)) #define _IQ17mpyI32(A, B) ((A) * (B)) #define _IQ16mpyI32(A, B) ((A) * (B)) #define _IQ15mpyI32(A, B) ((A) * (B)) #define _IQ14mpyI32(A, B) ((A) * (B)) #define _IQ13mpyI32(A, B) ((A) * (B)) #define _IQ12mpyI32(A, B) ((A) * (B)) #define _IQ11mpyI32(A, B) ((A) * (B)) #define _IQ10mpyI32(A, B) ((A) * (B)) #define _IQ9mpyI32(A, B) ((A) * (B)) #define _IQ8mpyI32(A, B) ((A) * (B)) #define _IQ7mpyI32(A, B) ((A) * (B)) #define _IQ6mpyI32(A, B) ((A) * (B)) #define _IQ5mpyI32(A, B) ((A) * (B)) #define _IQ4mpyI32(A, B) ((A) * (B)) #define _IQ3mpyI32(A, B) ((A) * (B)) #define _IQ2mpyI32(A, B) ((A) * (B)) #define _IQ1mpyI32(A, B) ((A) * (B)) #define _IQmpyI32(A, B) ((A) * (B)) //***************************************************************************** // // Multiplies an IQ number by an integer, and returns the integer portion. // //***************************************************************************** #define _IQ30mpyI32int(A, B) _IQ30int(_IQ30mpyI32(A, B)) #define _IQ29mpyI32int(A, B) _IQ29int(_IQ29mpyI32(A, B)) #define _IQ28mpyI32int(A, B) _IQ28int(_IQ28mpyI32(A, B)) #define _IQ27mpyI32int(A, B) _IQ27int(_IQ27mpyI32(A, B)) #define _IQ26mpyI32int(A, B) _IQ26int(_IQ26mpyI32(A, B)) #define _IQ25mpyI32int(A, B) _IQ25int(_IQ25mpyI32(A, B)) #define _IQ24mpyI32int(A, B) _IQ24int(_IQ24mpyI32(A, B)) #define _IQ23mpyI32int(A, B) _IQ23int(_IQ23mpyI32(A, B)) #define _IQ22mpyI32int(A, B) _IQ22int(_IQ22mpyI32(A, B)) #define _IQ21mpyI32int(A, B) _IQ21int(_IQ21mpyI32(A, B)) #define _IQ20mpyI32int(A, B) _IQ20int(_IQ20mpyI32(A, B)) #define _IQ19mpyI32int(A, B) _IQ19int(_IQ19mpyI32(A, B)) #define _IQ18mpyI32int(A, B) _IQ18int(_IQ18mpyI32(A, B)) #define _IQ17mpyI32int(A, B) _IQ17int(_IQ17mpyI32(A, B)) #define _IQ16mpyI32int(A, B) _IQ16int(_IQ16mpyI32(A, B)) #define _IQ15mpyI32int(A, B) _IQ15int(_IQ15mpyI32(A, B)) #define _IQ14mpyI32int(A, B) _IQ14int(_IQ14mpyI32(A, B)) #define _IQ13mpyI32int(A, B) _IQ13int(_IQ13mpyI32(A, B)) #define _IQ12mpyI32int(A, B) _IQ12int(_IQ12mpyI32(A, B)) #define _IQ11mpyI32int(A, B) _IQ11int(_IQ11mpyI32(A, B)) #define _IQ10mpyI32int(A, B) _IQ10int(_IQ10mpyI32(A, B)) #define _IQ9mpyI32int(A, B) _IQ9int(_IQ9mpyI32(A, B)) #define _IQ8mpyI32int(A, B) _IQ8int(_IQ8mpyI32(A, B)) #define _IQ7mpyI32int(A, B) _IQ7int(_IQ7mpyI32(A, B)) #define _IQ6mpyI32int(A, B) _IQ6int(_IQ6mpyI32(A, B)) #define _IQ5mpyI32int(A, B) _IQ5int(_IQ5mpyI32(A, B)) #define _IQ4mpyI32int(A, B) _IQ4int(_IQ4mpyI32(A, B)) #define _IQ3mpyI32int(A, B) _IQ3int(_IQ3mpyI32(A, B)) #define _IQ2mpyI32int(A, B) _IQ2int(_IQ2mpyI32(A, B)) #define _IQ1mpyI32int(A, B) _IQ1int(_IQ1mpyI32(A, B)) #if GLOBAL_IQ == 30 #define _IQmpyI32int(A, B) _IQ30mpyI32int(A, B) #endif #if GLOBAL_IQ == 29 #define _IQmpyI32int(A, B) _IQ29mpyI32int(A, B) #endif #if GLOBAL_IQ == 28 #define _IQmpyI32int(A, B) _IQ28mpyI32int(A, B) #endif #if GLOBAL_IQ == 27 #define _IQmpyI32int(A, B) _IQ27mpyI32int(A, B) #endif #if GLOBAL_IQ == 26 #define _IQmpyI32int(A, B) _IQ26mpyI32int(A, B) #endif #if GLOBAL_IQ == 25 #define _IQmpyI32int(A, B) _IQ25mpyI32int(A, B) #endif #if GLOBAL_IQ == 24 #define _IQmpyI32int(A, B) _IQ24mpyI32int(A, B) #endif #if GLOBAL_IQ == 23 #define _IQmpyI32int(A, B) _IQ23mpyI32int(A, B) #endif #if GLOBAL_IQ == 22 #define _IQmpyI32int(A, B) _IQ22mpyI32int(A, B) #endif #if GLOBAL_IQ == 21 #define _IQmpyI32int(A, B) _IQ21mpyI32int(A, B) #endif #if GLOBAL_IQ == 20 #define _IQmpyI32int(A, B) _IQ20mpyI32int(A, B) #endif #if GLOBAL_IQ == 19 #define _IQmpyI32int(A, B) _IQ19mpyI32int(A, B) #endif #if GLOBAL_IQ == 18 #define _IQmpyI32int(A, B) _IQ18mpyI32int(A, B) #endif #if GLOBAL_IQ == 17 #define _IQmpyI32int(A, B) _IQ17mpyI32int(A, B) #endif #if GLOBAL_IQ == 16 #define _IQmpyI32int(A, B) _IQ16mpyI32int(A, B) #endif #if GLOBAL_IQ == 15 #define _IQmpyI32int(A, B) _IQ15mpyI32int(A, B) #endif #if GLOBAL_IQ == 14 #define _IQmpyI32int(A, B) _IQ14mpyI32int(A, B) #endif #if GLOBAL_IQ == 13 #define _IQmpyI32int(A, B) _IQ13mpyI32int(A, B) #endif #if GLOBAL_IQ == 12 #define _IQmpyI32int(A, B) _IQ12mpyI32int(A, B) #endif #if GLOBAL_IQ == 11 #define _IQmpyI32int(A, B) _IQ11mpyI32int(A, B) #endif #if GLOBAL_IQ == 10 #define _IQmpyI32int(A, B) _IQ10mpyI32int(A, B) #endif #if GLOBAL_IQ == 9 #define _IQmpyI32int(A, B) _IQ9mpyI32int(A, B) #endif #if GLOBAL_IQ == 8 #define _IQmpyI32int(A, B) _IQ8mpyI32int(A, B) #endif #if GLOBAL_IQ == 7 #define _IQmpyI32int(A, B) _IQ7mpyI32int(A, B) #endif #if GLOBAL_IQ == 6 #define _IQmpyI32int(A, B) _IQ6mpyI32int(A, B) #endif #if GLOBAL_IQ == 5 #define _IQmpyI32int(A, B) _IQ5mpyI32int(A, B) #endif #if GLOBAL_IQ == 4 #define _IQmpyI32int(A, B) _IQ4mpyI32int(A, B) #endif #if GLOBAL_IQ == 3 #define _IQmpyI32int(A, B) _IQ3mpyI32int(A, B) #endif #if GLOBAL_IQ == 2 #define _IQmpyI32int(A, B) _IQ2mpyI32int(A, B) #endif #if GLOBAL_IQ == 1 #define _IQmpyI32int(A, B) _IQ1mpyI32int(A, B) #endif //***************************************************************************** // // Multiplies an IQ number by an integer, and returns the fractional portion. // //***************************************************************************** #define _IQ30mpyI32frac(A, B) _IQ30frac(_IQ30mpyI32(A, B)) #define _IQ29mpyI32frac(A, B) _IQ29frac(_IQ29mpyI32(A, B)) #define _IQ28mpyI32frac(A, B) _IQ28frac(_IQ28mpyI32(A, B)) #define _IQ27mpyI32frac(A, B) _IQ27frac(_IQ27mpyI32(A, B)) #define _IQ26mpyI32frac(A, B) _IQ26frac(_IQ26mpyI32(A, B)) #define _IQ25mpyI32frac(A, B) _IQ25frac(_IQ25mpyI32(A, B)) #define _IQ24mpyI32frac(A, B) _IQ24frac(_IQ24mpyI32(A, B)) #define _IQ23mpyI32frac(A, B) _IQ23frac(_IQ23mpyI32(A, B)) #define _IQ22mpyI32frac(A, B) _IQ22frac(_IQ22mpyI32(A, B)) #define _IQ21mpyI32frac(A, B) _IQ21frac(_IQ21mpyI32(A, B)) #define _IQ20mpyI32frac(A, B) _IQ20frac(_IQ20mpyI32(A, B)) #define _IQ19mpyI32frac(A, B) _IQ19frac(_IQ19mpyI32(A, B)) #define _IQ18mpyI32frac(A, B) _IQ18frac(_IQ18mpyI32(A, B)) #define _IQ17mpyI32frac(A, B) _IQ17frac(_IQ17mpyI32(A, B)) #define _IQ16mpyI32frac(A, B) _IQ16frac(_IQ16mpyI32(A, B)) #define _IQ15mpyI32frac(A, B) _IQ15frac(_IQ15mpyI32(A, B)) #define _IQ14mpyI32frac(A, B) _IQ14frac(_IQ14mpyI32(A, B)) #define _IQ13mpyI32frac(A, B) _IQ13frac(_IQ13mpyI32(A, B)) #define _IQ12mpyI32frac(A, B) _IQ12frac(_IQ12mpyI32(A, B)) #define _IQ11mpyI32frac(A, B) _IQ11frac(_IQ11mpyI32(A, B)) #define _IQ10mpyI32frac(A, B) _IQ10frac(_IQ10mpyI32(A, B)) #define _IQ9mpyI32frac(A, B) _IQ9frac(_IQ9mpyI32(A, B)) #define _IQ8mpyI32frac(A, B) _IQ8frac(_IQ8mpyI32(A, B)) #define _IQ7mpyI32frac(A, B) _IQ7frac(_IQ7mpyI32(A, B)) #define _IQ6mpyI32frac(A, B) _IQ6frac(_IQ6mpyI32(A, B)) #define _IQ5mpyI32frac(A, B) _IQ5frac(_IQ5mpyI32(A, B)) #define _IQ4mpyI32frac(A, B) _IQ4frac(_IQ4mpyI32(A, B)) #define _IQ3mpyI32frac(A, B) _IQ3frac(_IQ3mpyI32(A, B)) #define _IQ2mpyI32frac(A, B) _IQ2frac(_IQ2mpyI32(A, B)) #define _IQ1mpyI32frac(A, B) _IQ1frac(_IQ1mpyI32(A, B)) #if GLOBAL_IQ == 30 #define _IQmpyI32frac(A, B) _IQ30mpyI32frac(A, B) #endif #if GLOBAL_IQ == 29 #define _IQmpyI32frac(A, B) _IQ29mpyI32frac(A, B) #endif #if GLOBAL_IQ == 28 #define _IQmpyI32frac(A, B) _IQ28mpyI32frac(A, B) #endif #if GLOBAL_IQ == 27 #define _IQmpyI32frac(A, B) _IQ27mpyI32frac(A, B) #endif #if GLOBAL_IQ == 26 #define _IQmpyI32frac(A, B) _IQ26mpyI32frac(A, B) #endif #if GLOBAL_IQ == 25 #define _IQmpyI32frac(A, B) _IQ25mpyI32frac(A, B) #endif #if GLOBAL_IQ == 24 #define _IQmpyI32frac(A, B) _IQ24mpyI32frac(A, B) #endif #if GLOBAL_IQ == 23 #define _IQmpyI32frac(A, B) _IQ23mpyI32frac(A, B) #endif #if GLOBAL_IQ == 22 #define _IQmpyI32frac(A, B) _IQ22mpyI32frac(A, B) #endif #if GLOBAL_IQ == 21 #define _IQmpyI32frac(A, B) _IQ21mpyI32frac(A, B) #endif #if GLOBAL_IQ == 20 #define _IQmpyI32frac(A, B) _IQ20mpyI32frac(A, B) #endif #if GLOBAL_IQ == 19 #define _IQmpyI32frac(A, B) _IQ19mpyI32frac(A, B) #endif #if GLOBAL_IQ == 18 #define _IQmpyI32frac(A, B) _IQ18mpyI32frac(A, B) #endif #if GLOBAL_IQ == 17 #define _IQmpyI32frac(A, B) _IQ17mpyI32frac(A, B) #endif #if GLOBAL_IQ == 16 #define _IQmpyI32frac(A, B) _IQ16mpyI32frac(A, B) #endif #if GLOBAL_IQ == 15 #define _IQmpyI32frac(A, B) _IQ15mpyI32frac(A, B) #endif #if GLOBAL_IQ == 14 #define _IQmpyI32frac(A, B) _IQ14mpyI32frac(A, B) #endif #if GLOBAL_IQ == 13 #define _IQmpyI32frac(A, B) _IQ13mpyI32frac(A, B) #endif #if GLOBAL_IQ == 12 #define _IQmpyI32frac(A, B) _IQ12mpyI32frac(A, B) #endif #if GLOBAL_IQ == 11 #define _IQmpyI32frac(A, B) _IQ11mpyI32frac(A, B) #endif #if GLOBAL_IQ == 10 #define _IQmpyI32frac(A, B) _IQ10mpyI32frac(A, B) #endif #if GLOBAL_IQ == 9 #define _IQmpyI32frac(A, B) _IQ9mpyI32frac(A, B) #endif #if GLOBAL_IQ == 8 #define _IQmpyI32frac(A, B) _IQ8mpyI32frac(A, B) #endif #if GLOBAL_IQ == 7 #define _IQmpyI32frac(A, B) _IQ7mpyI32frac(A, B) #endif #if GLOBAL_IQ == 6 #define _IQmpyI32frac(A, B) _IQ6mpyI32frac(A, B) #endif #if GLOBAL_IQ == 5 #define _IQmpyI32frac(A, B) _IQ5mpyI32frac(A, B) #endif #if GLOBAL_IQ == 4 #define _IQmpyI32frac(A, B) _IQ4mpyI32frac(A, B) #endif #if GLOBAL_IQ == 3 #define _IQmpyI32frac(A, B) _IQ3mpyI32frac(A, B) #endif #if GLOBAL_IQ == 2 #define _IQmpyI32frac(A, B) _IQ2mpyI32frac(A, B) #endif #if GLOBAL_IQ == 1 #define _IQmpyI32frac(A, B) _IQ1mpyI32frac(A, B) #endif #endif /* DOXYGEN_SHOULD_SKIP_THIS */ //***************************************************************************** // // Computes the square root of A^2 + B^2 using IQ numbers. // //***************************************************************************** extern int32_t _IQmag(int32_t A, int32_t B); /** * @brief Computes the square root of A^2 + B^2 using IQ30 numbers. * * @param A IQ30 type input. * @param B IQ30 type input * * @return IQ30 result of magnitude operation. */ #define _IQ30mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ29 numbers. * * @param A IQ29 type input. * @param B IQ29 type input * * @return IQ29 result of magnitude operation. */ #define _IQ29mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ28 numbers. * * @param A IQ28 type input. * @param B IQ28 type input * * @return IQ28 result of magnitude operation. */ #define _IQ28mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ27 numbers. * * @param A IQ27 type input. * @param B IQ27 type input * * @return IQ27 result of magnitude operation. */ #define _IQ27mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ26 numbers. * * @param A IQ26 type input. * @param B IQ26 type input * * @return IQ26 result of magnitude operation. */ #define _IQ26mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ25 numbers. * * @param A IQ25 type input. * @param B IQ25 type input * * @return IQ25 result of magnitude operation. */ #define _IQ25mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ24 numbers. * * @param A IQ24 type input. * @param B IQ24 type input * * @return IQ24 result of magnitude operation. */ #define _IQ24mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ23 numbers. * * @param A IQ23 type input. * @param B IQ23 type input * * @return IQ23 result of magnitude operation. */ #define _IQ23mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ22 numbers. * * @param A IQ22 type input. * @param B IQ22 type input * * @return IQ22 result of magnitude operation. */ #define _IQ22mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ21 numbers. * * @param A IQ21 type input. * @param B IQ21 type input * * @return IQ21 result of magnitude operation. */ #define _IQ21mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ20 numbers. * * @param A IQ20 type input. * @param B IQ20 type input * * @return IQ20 result of magnitude operation. */ #define _IQ20mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ19 numbers. * * @param A IQ19 type input. * @param B IQ19 type input * * @return IQ19 result of magnitude operation. */ #define _IQ19mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ18 numbers. * * @param A IQ18 type input. * @param B IQ18 type input * * @return IQ18 result of magnitude operation. */ #define _IQ18mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ17 numbers. * * @param A IQ17 type input. * @param B IQ17 type input * * @return IQ17 result of magnitude operation. */ #define _IQ17mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ16 numbers. * * @param A IQ16 type input. * @param B IQ16 type input * * @return IQ16 result of magnitude operation. */ #define _IQ16mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ15 numbers. * * @param A IQ15 type input. * @param B IQ15 type input * * @return IQ15 result of magnitude operation. */ #define _IQ15mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ14 numbers. * * @param A IQ14 type input. * @param B IQ14 type input * * @return IQ14 result of magnitude operation. */ #define _IQ14mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ13 numbers. * * @param A IQ13 type input. * @param B IQ13 type input * * @return IQ13 result of magnitude operation. */ #define _IQ13mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ12 numbers. * * @param A IQ12 type input. * @param B IQ12 type input * * @return IQ12 result of magnitude operation. */ #define _IQ12mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ11 numbers. * * @param A IQ11 type input. * @param B IQ11 type input * * @return IQ11 result of magnitude operation. */ #define _IQ11mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ10 numbers. * * @param A IQ10 type input. * @param B IQ10 type input * * @return IQ10 result of magnitude operation. */ #define _IQ10mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ9 numbers. * * @param A IQ9 type input. * @param B IQ9 type input * * @return IQ9 result of magnitude operation. */ #define _IQ9mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ8 numbers. * * @param A IQ8 type input. * @param B IQ8 type input * * @return IQ8 result of magnitude operation. */ #define _IQ8mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ7 numbers. * * @param A IQ7 type input. * @param B IQ7 type input * * @return IQ7 result of magnitude operation. */ #define _IQ7mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ6 numbers. * * @param A IQ6 type input. * @param B IQ6 type input * * @return IQ6 result of magnitude operation. */ #define _IQ6mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ5 numbers. * * @param A IQ5 type input. * @param B IQ5 type input * * @return IQ5 result of magnitude operation. */ #define _IQ5mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ4 numbers. * * @param A IQ4 type input. * @param B IQ4 type input * * @return IQ4 result of magnitude operation. */ #define _IQ4mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ3 numbers. * * @param A IQ3 type input. * @param B IQ3 type input * * @return IQ3 result of magnitude operation. */ #define _IQ3mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ2 numbers. * * @param A IQ2 type input. * @param B IQ2 type input * * @return IQ2 result of magnitude operation. */ #define _IQ2mag(A, B) _IQmag(A, B) /** * @brief Computes the square root of A^2 + B^2 using IQ1 numbers. * * @param A IQ1 type input. * @param B IQ1 type input * * @return IQ1 result of magnitude operation. */ #define _IQ1mag(A, B) _IQmag(A, B) //***************************************************************************** // // Computes the inverse square root of A^2 + B^2 using IQ numbers. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq29 _IQ30imag(_iq30 A, _iq30 B); extern _iq29 _IQ29imag(_iq29 A, _iq29 B); extern _iq28 _IQ28imag(_iq28 A, _iq28 B); extern _iq27 _IQ27imag(_iq27 A, _iq27 B); extern _iq26 _IQ26imag(_iq26 A, _iq26 B); extern _iq25 _IQ25imag(_iq25 A, _iq25 B); extern _iq24 _IQ24imag(_iq24 A, _iq24 B); extern _iq23 _IQ23imag(_iq23 A, _iq23 B); extern _iq22 _IQ22imag(_iq22 A, _iq22 B); extern _iq21 _IQ21imag(_iq21 A, _iq21 B); extern _iq20 _IQ20imag(_iq20 A, _iq20 B); extern _iq19 _IQ19imag(_iq19 A, _iq19 B); extern _iq18 _IQ18imag(_iq18 A, _iq18 B); extern _iq17 _IQ17imag(_iq17 A, _iq17 B); extern _iq16 _IQ16imag(_iq16 A, _iq16 B); extern _iq15 _IQ15imag(_iq15 A, _iq15 B); extern _iq14 _IQ14imag(_iq14 A, _iq14 B); extern _iq13 _IQ13imag(_iq13 A, _iq13 B); extern _iq12 _IQ12imag(_iq12 A, _iq12 B); extern _iq11 _IQ11imag(_iq11 A, _iq11 B); extern _iq10 _IQ10imag(_iq10 A, _iq10 B); extern _iq9 _IQ9imag(_iq9 A, _iq9 B); extern _iq8 _IQ8imag(_iq8 A, _iq8 B); extern _iq7 _IQ7imag(_iq7 A, _iq7 B); extern _iq6 _IQ6imag(_iq6 A, _iq6 B); extern _iq5 _IQ5imag(_iq5 A, _iq5 B); extern _iq4 _IQ4imag(_iq4 A, _iq4 B); extern _iq3 _IQ3imag(_iq3 A, _iq3 B); extern _iq2 _IQ2imag(_iq2 A, _iq2 B); extern _iq1 _IQ1imag(_iq1 A, _iq1 B); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Computes the inverse square root of A^2 + B^2 using IQ1 numbers. * * @param A Global IQ format input. * @param B Global IQ format input * * @return Global IQ format result of inverse * magnitude operation. */ #if GLOBAL_IQ == 30 #define _IQimag(A, B) _IQ30imag(A, B) #endif #if GLOBAL_IQ == 29 #define _IQimag(A, B) _IQ29imag(A, B) #endif #if GLOBAL_IQ == 28 #define _IQimag(A, B) _IQ28imag(A, B) #endif #if GLOBAL_IQ == 27 #define _IQimag(A, B) _IQ27imag(A, B) #endif #if GLOBAL_IQ == 26 #define _IQimag(A, B) _IQ26imag(A, B) #endif #if GLOBAL_IQ == 25 #define _IQimag(A, B) _IQ25imag(A, B) #endif #if GLOBAL_IQ == 24 #define _IQimag(A, B) _IQ24imag(A, B) #endif #if GLOBAL_IQ == 23 #define _IQimag(A, B) _IQ23imag(A, B) #endif #if GLOBAL_IQ == 22 #define _IQimag(A, B) _IQ22imag(A, B) #endif #if GLOBAL_IQ == 21 #define _IQimag(A, B) _IQ21imag(A, B) #endif #if GLOBAL_IQ == 20 #define _IQimag(A, B) _IQ20imag(A, B) #endif #if GLOBAL_IQ == 19 #define _IQimag(A, B) _IQ19imag(A, B) #endif #if GLOBAL_IQ == 18 #define _IQimag(A, B) _IQ18imag(A, B) #endif #if GLOBAL_IQ == 17 #define _IQimag(A, B) _IQ17imag(A, B) #endif #if GLOBAL_IQ == 16 #define _IQimag(A, B) _IQ16imag(A, B) #endif #if GLOBAL_IQ == 15 #define _IQimag(A, B) _IQ15imag(A, B) #endif #if GLOBAL_IQ == 14 #define _IQimag(A, B) _IQ14imag(A, B) #endif #if GLOBAL_IQ == 13 #define _IQimag(A, B) _IQ13imag(A, B) #endif #if GLOBAL_IQ == 12 #define _IQimag(A, B) _IQ12imag(A, B) #endif #if GLOBAL_IQ == 11 #define _IQimag(A, B) _IQ11imag(A, B) #endif #if GLOBAL_IQ == 10 #define _IQimag(A, B) _IQ10imag(A, B) #endif #if GLOBAL_IQ == 9 #define _IQimag(A, B) _IQ9imag(A, B) #endif #if GLOBAL_IQ == 8 #define _IQimag(A, B) _IQ8imag(A, B) #endif #if GLOBAL_IQ == 7 #define _IQimag(A, B) _IQ7imag(A, B) #endif #if GLOBAL_IQ == 6 #define _IQimag(A, B) _IQ6imag(A, B) #endif #if GLOBAL_IQ == 5 #define _IQimag(A, B) _IQ5imag(A, B) #endif #if GLOBAL_IQ == 4 #define _IQimag(A, B) _IQ4imag(A, B) #endif #if GLOBAL_IQ == 3 #define _IQimag(A, B) _IQ3imag(A, B) #endif #if GLOBAL_IQ == 2 #define _IQimag(A, B) _IQ2imag(A, B) #endif #if GLOBAL_IQ == 1 #define _IQimag(A, B) _IQ1imag(A, B) #endif //***************************************************************************** // // Converts a string into an IQ number. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern _iq30 _atoIQ30(const char *A); extern _iq29 _atoIQ29(const char *A); extern _iq28 _atoIQ28(const char *A); extern _iq27 _atoIQ27(const char *A); extern _iq26 _atoIQ26(const char *A); extern _iq25 _atoIQ25(const char *A); extern _iq24 _atoIQ24(const char *A); extern _iq23 _atoIQ23(const char *A); extern _iq22 _atoIQ22(const char *A); extern _iq21 _atoIQ21(const char *A); extern _iq20 _atoIQ20(const char *A); extern _iq19 _atoIQ19(const char *A); extern _iq18 _atoIQ18(const char *A); extern _iq17 _atoIQ17(const char *A); extern _iq16 _atoIQ16(const char *A); extern _iq15 _atoIQ15(const char *A); extern _iq14 _atoIQ14(const char *A); extern _iq13 _atoIQ13(const char *A); extern _iq12 _atoIQ12(const char *A); extern _iq11 _atoIQ11(const char *A); extern _iq10 _atoIQ10(const char *A); extern _iq9 _atoIQ9(const char *A); extern _iq8 _atoIQ8(const char *A); extern _iq7 _atoIQ7(const char *A); extern _iq6 _atoIQ6(const char *A); extern _iq5 _atoIQ5(const char *A); extern _iq4 _atoIQ4(const char *A); extern _iq3 _atoIQ3(const char *A); extern _iq2 _atoIQ2(const char *A); extern _iq1 _atoIQ1(const char *A); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Converts a string into a global IQ format number. * * @param A String input. * * @return Global IQ format result of conversion. */ #if GLOBAL_IQ == 30 #define _atoIQ(A) _atoIQ30(A) #endif #if GLOBAL_IQ == 29 #define _atoIQ(A) _atoIQ29(A) #endif #if GLOBAL_IQ == 28 #define _atoIQ(A) _atoIQ28(A) #endif #if GLOBAL_IQ == 27 #define _atoIQ(A) _atoIQ27(A) #endif #if GLOBAL_IQ == 26 #define _atoIQ(A) _atoIQ26(A) #endif #if GLOBAL_IQ == 25 #define _atoIQ(A) _atoIQ25(A) #endif #if GLOBAL_IQ == 24 #define _atoIQ(A) _atoIQ24(A) #endif #if GLOBAL_IQ == 23 #define _atoIQ(A) _atoIQ23(A) #endif #if GLOBAL_IQ == 22 #define _atoIQ(A) _atoIQ22(A) #endif #if GLOBAL_IQ == 21 #define _atoIQ(A) _atoIQ21(A) #endif #if GLOBAL_IQ == 20 #define _atoIQ(A) _atoIQ20(A) #endif #if GLOBAL_IQ == 19 #define _atoIQ(A) _atoIQ19(A) #endif #if GLOBAL_IQ == 18 #define _atoIQ(A) _atoIQ18(A) #endif #if GLOBAL_IQ == 17 #define _atoIQ(A) _atoIQ17(A) #endif #if GLOBAL_IQ == 16 #define _atoIQ(A) _atoIQ16(A) #endif #if GLOBAL_IQ == 15 #define _atoIQ(A) _atoIQ15(A) #endif #if GLOBAL_IQ == 14 #define _atoIQ(A) _atoIQ14(A) #endif #if GLOBAL_IQ == 13 #define _atoIQ(A) _atoIQ13(A) #endif #if GLOBAL_IQ == 12 #define _atoIQ(A) _atoIQ12(A) #endif #if GLOBAL_IQ == 11 #define _atoIQ(A) _atoIQ11(A) #endif #if GLOBAL_IQ == 10 #define _atoIQ(A) _atoIQ10(A) #endif #if GLOBAL_IQ == 9 #define _atoIQ(A) _atoIQ9(A) #endif #if GLOBAL_IQ == 8 #define _atoIQ(A) _atoIQ8(A) #endif #if GLOBAL_IQ == 7 #define _atoIQ(A) _atoIQ7(A) #endif #if GLOBAL_IQ == 6 #define _atoIQ(A) _atoIQ6(A) #endif #if GLOBAL_IQ == 5 #define _atoIQ(A) _atoIQ5(A) #endif #if GLOBAL_IQ == 4 #define _atoIQ(A) _atoIQ4(A) #endif #if GLOBAL_IQ == 3 #define _atoIQ(A) _atoIQ3(A) #endif #if GLOBAL_IQ == 2 #define _atoIQ(A) _atoIQ2(A) #endif #if GLOBAL_IQ == 1 #define _atoIQ(A) _atoIQ1(A) #endif //***************************************************************************** // // Converts an IQ number into a string. // //***************************************************************************** #ifndef DOXYGEN_SHOULD_SKIP_THIS extern int16_t _IQ30toa(char *string, const char *format, _iq30 input); extern int16_t _IQ29toa(char *string, const char *format, _iq29 input); extern int16_t _IQ28toa(char *string, const char *format, _iq28 input); extern int16_t _IQ27toa(char *string, const char *format, _iq27 input); extern int16_t _IQ26toa(char *string, const char *format, _iq26 input); extern int16_t _IQ25toa(char *string, const char *format, _iq25 input); extern int16_t _IQ24toa(char *string, const char *format, _iq24 input); extern int16_t _IQ23toa(char *string, const char *format, _iq23 input); extern int16_t _IQ22toa(char *string, const char *format, _iq22 input); extern int16_t _IQ21toa(char *string, const char *format, _iq21 input); extern int16_t _IQ20toa(char *string, const char *format, _iq20 input); extern int16_t _IQ19toa(char *string, const char *format, _iq19 input); extern int16_t _IQ18toa(char *string, const char *format, _iq18 input); extern int16_t _IQ17toa(char *string, const char *format, _iq17 input); extern int16_t _IQ16toa(char *string, const char *format, _iq16 input); extern int16_t _IQ15toa(char *string, const char *format, _iq15 input); extern int16_t _IQ14toa(char *string, const char *format, _iq14 input); extern int16_t _IQ13toa(char *string, const char *format, _iq13 input); extern int16_t _IQ12toa(char *string, const char *format, _iq12 input); extern int16_t _IQ11toa(char *string, const char *format, _iq11 input); extern int16_t _IQ10toa(char *string, const char *format, _iq10 input); extern int16_t _IQ9toa(char *string, const char *format, _iq9 input); extern int16_t _IQ8toa(char *string, const char *format, _iq8 input); extern int16_t _IQ7toa(char *string, const char *format, _iq7 input); extern int16_t _IQ6toa(char *string, const char *format, _iq6 input); extern int16_t _IQ5toa(char *string, const char *format, _iq5 input); extern int16_t _IQ4toa(char *string, const char *format, _iq4 input); extern int16_t _IQ3toa(char *string, const char *format, _iq3 input); extern int16_t _IQ2toa(char *string, const char *format, _iq2 input); extern int16_t _IQ1toa(char *string, const char *format, _iq1 input); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * @brief Converts a global IQ format input into a string. * * @param A Pointer to the buffer to store the converted IQ number. * @param B The format string specifying how to convert the IQ number. * @param C Global IQ format input. * * @return Returns 0 if there is no error, 1 if the width is too small to hold the integer * characters, and 2 if an illegal format was specified. */ #if GLOBAL_IQ == 30 #define _IQtoa(A, B, C) _IQ30toa(A, B, C) #endif #if GLOBAL_IQ == 29 #define _IQtoa(A, B, C) _IQ29toa(A, B, C) #endif #if GLOBAL_IQ == 28 #define _IQtoa(A, B, C) _IQ28toa(A, B, C) #endif #if GLOBAL_IQ == 27 #define _IQtoa(A, B, C) _IQ27toa(A, B, C) #endif #if GLOBAL_IQ == 26 #define _IQtoa(A, B, C) _IQ26toa(A, B, C) #endif #if GLOBAL_IQ == 25 #define _IQtoa(A, B, C) _IQ25toa(A, B, C) #endif #if GLOBAL_IQ == 24 #define _IQtoa(A, B, C) _IQ24toa(A, B, C) #endif #if GLOBAL_IQ == 23 #define _IQtoa(A, B, C) _IQ23toa(A, B, C) #endif #if GLOBAL_IQ == 22 #define _IQtoa(A, B, C) _IQ22toa(A, B, C) #endif #if GLOBAL_IQ == 21 #define _IQtoa(A, B, C) _IQ21toa(A, B, C) #endif #if GLOBAL_IQ == 20 #define _IQtoa(A, B, C) _IQ20toa(A, B, C) #endif #if GLOBAL_IQ == 19 #define _IQtoa(A, B, C) _IQ19toa(A, B, C) #endif #if GLOBAL_IQ == 18 #define _IQtoa(A, B, C) _IQ18toa(A, B, C) #endif #if GLOBAL_IQ == 17 #define _IQtoa(A, B, C) _IQ17toa(A, B, C) #endif #if GLOBAL_IQ == 16 #define _IQtoa(A, B, C) _IQ16toa(A, B, C) #endif #if GLOBAL_IQ == 15 #define _IQtoa(A, B, C) _IQ15toa(A, B, C) #endif #if GLOBAL_IQ == 14 #define _IQtoa(A, B, C) _IQ14toa(A, B, C) #endif #if GLOBAL_IQ == 13 #define _IQtoa(A, B, C) _IQ13toa(A, B, C) #endif #if GLOBAL_IQ == 12 #define _IQtoa(A, B, C) _IQ12toa(A, B, C) #endif #if GLOBAL_IQ == 11 #define _IQtoa(A, B, C) _IQ11toa(A, B, C) #endif #if GLOBAL_IQ == 10 #define _IQtoa(A, B, C) _IQ10toa(A, B, C) #endif #if GLOBAL_IQ == 9 #define _IQtoa(A, B, C) _IQ9toa(A, B, C) #endif #if GLOBAL_IQ == 8 #define _IQtoa(A, B, C) _IQ8toa(A, B, C) #endif #if GLOBAL_IQ == 7 #define _IQtoa(A, B, C) _IQ7toa(A, B, C) #endif #if GLOBAL_IQ == 6 #define _IQtoa(A, B, C) _IQ6toa(A, B, C) #endif #if GLOBAL_IQ == 5 #define _IQtoa(A, B, C) _IQ5toa(A, B, C) #endif #if GLOBAL_IQ == 4 #define _IQtoa(A, B, C) _IQ4toa(A, B, C) #endif #if GLOBAL_IQ == 3 #define _IQtoa(A, B, C) _IQ3toa(A, B, C) #endif #if GLOBAL_IQ == 2 #define _IQtoa(A, B, C) _IQ2toa(A, B, C) #endif #if GLOBAL_IQ == 1 #define _IQtoa(A, B, C) _IQ1toa(A, B, C) #endif //***************************************************************************** // // Computes the absolute value of an IQ number. // //***************************************************************************** /** * @brief Computes the absolute value of an IQ30 number. * * @param A IQ30 type input. * * @return IQ30 type absolute value of input. */ #define _IQ30abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ29 number. * * @param A IQ29 type input. * * @return IQ29 type absolute value of input. */ #define _IQ29abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ28 number. * * @param A IQ28 type input. * * @return IQ28 type absolute value of input. */ #define _IQ28abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ27 number. * * @param A IQ27 type input. * * @return IQ27 type absolute value of input. */ #define _IQ27abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ26 number. * * @param A IQ26 type input. * * @return IQ26 type absolute value of input. */ #define _IQ26abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ25 number. * * @param A IQ25 type input. * * @return IQ25 type absolute value of input. */ #define _IQ25abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ24 number. * * @param A IQ24 type input. * * @return IQ24 type absolute value of input. */ #define _IQ24abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ23 number. * * @param A IQ23 type input. * * @return IQ23 type absolute value of input. */ #define _IQ23abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ22 number. * * @param A IQ22 type input. * * @return IQ22 type absolute value of input. */ #define _IQ22abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ21 number. * * @param A IQ21 type input. * * @return IQ21 type absolute value of input. */ #define _IQ21abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ20 number. * * @param A IQ20 type input. * * @return IQ20 type absolute value of input. */ #define _IQ20abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ19 number. * * @param A IQ19 type input. * * @return IQ19 type absolute value of input. */ #define _IQ19abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ18 number. * * @param A IQ18 type input. * * @return IQ18 type absolute value of input. */ #define _IQ18abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ17 number. * * @param A IQ17 type input. * * @return IQ17 type absolute value of input. */ #define _IQ17abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ16 number. * * @param A IQ16 type input. * * @return IQ16 type absolute value of input. */ #define _IQ16abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ15 number. * * @param A IQ15 type input. * * @return IQ15 type absolute value of input. */ #define _IQ15abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ14 number. * * @param A IQ14 type input. * * @return IQ14 type absolute value of input. */ #define _IQ14abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ13 number. * * @param A IQ13 type input. * * @return IQ13 type absolute value of input. */ #define _IQ13abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ12 number. * * @param A IQ12 type input. * * @return IQ12 type absolute value of input. */ #define _IQ12abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ11 number. * * @param A IQ11 type input. * * @return IQ11 type absolute value of input. */ #define _IQ11abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ10 number. * * @param A IQ10 type input. * * @return IQ10 type absolute value of input. */ #define _IQ10abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ9 number. * * @param A IQ9 type input. * * @return IQ9 type absolute value of input. */ #define _IQ9abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ8 number. * * @param A IQ8 type input. * * @return IQ8 type absolute value of input. */ #define _IQ8abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ7 number. * * @param A IQ7 type input. * * @return IQ7 type absolute value of input. */ #define _IQ7abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ6 number. * * @param A IQ6 type input. * * @return IQ6 type absolute value of input. */ #define _IQ6abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ5 number. * * @param A IQ5 type input. * * @return IQ5 type absolute value of input. */ #define _IQ5abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ4 number. * * @param A IQ4 type input. * * @return IQ4 type absolute value of input. */ #define _IQ4abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ3 number. * * @param A IQ3 type input. * * @return IQ3 type absolute value of input. */ #define _IQ3abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ2 number. * * @param A IQ2 type input. * * @return IQ2 type absolute value of input. */ #define _IQ2abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an IQ1 number. * * @param A IQ1 type input. * * @return IQ1 type absolute value of input. */ #define _IQ1abs(A) (((A) < 0) ? - (A) : (A)) /** * @brief Computes the absolute value of an global IQ format number. * * @param A Global IQ format input. * * @return GlobalIQ format absolute value of input. */ #define _IQabs(A) (((A) < 0) ? - (A) : (A)) //***************************************************************************** // // Mark the end of the C bindings section for C++ compilers. // //***************************************************************************** #ifdef __cplusplus } #endif #endif // __IQMATHLIB_H__ ================================================ FILE: iqmath/support/RTS_support.h ================================================ #ifndef __RTS_SUPPORTH__ #define __RTS_SUPPORTH__ //////////////////////////////////////////////////////////// // // // MPY32 control functions. // // // //////////////////////////////////////////////////////////// #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_start) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpy_start(uint_fast16_t *ui16IntState, uint_fast16_t *ui16MPYState) { /* Do nothing. */ return; } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_start) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpyf_start(uint_fast16_t *ui16IntState, uint_fast16_t *ui16MPYState) { /* Do nothing. */ return; } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyfs_start) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpyfs_start(uint_fast16_t *ui16IntState, uint_fast16_t *ui16MPYState) { /* Do nothing. */ return; } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_clear_ctl0) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpy_clear_ctl0(void) { /* Do nothing. */ return; } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_set_frac) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpy_set_frac(void) { /* Do nothing. */ return; } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_stop) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline void __mpy_stop(uint_fast16_t *ui16IntState, uint_fast16_t *ui16MPYState) { /* Do nothing. */ return; } //////////////////////////////////////////////////////////// // // // 16-bit functions // // // //////////////////////////////////////////////////////////// #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_w) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast16_t __mpy_w(int_fast16_t arg1, int_fast16_t arg2) { return (arg1 * arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_uw) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast16_t __mpy_uw(uint_fast16_t arg1, uint_fast16_t arg2) { return (arg1 * arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyx_w) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyx_w(int_fast16_t arg1, int_fast16_t arg2) { return ((int_fast32_t)arg1 * (int_fast32_t)arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyx_uw) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast32_t __mpyx_uw(uint_fast16_t arg1, uint_fast16_t arg2) { return ((uint_fast32_t)arg1 * (uint_fast32_t)arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_w) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast16_t __mpyf_w(int_fast16_t arg1, int_fast16_t arg2) { return (int_fast16_t)(((int_fast32_t)arg1 * (int_fast32_t)arg2) >> 15); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_w_reuse_arg1) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast16_t __mpyf_w_reuse_arg1(int_fast16_t arg1, int_fast16_t arg2) { /* This is identical to __mpyf_w */ return (int_fast16_t)(((int_fast32_t)arg1 * (int_fast32_t)arg2) >> 15); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_uw) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast16_t __mpyf_uw(uint_fast16_t arg1, uint_fast16_t arg2) { return (uint_fast16_t)(((uint_fast32_t)arg1 * (uint_fast32_t)arg2) >> 15); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_uw_reuse_arg1) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast16_t __mpyf_uw_reuse_arg1(uint_fast16_t arg1, uint_fast16_t arg2) { /* This is identical to __mpyf_uw */ return (uint_fast16_t)(((uint_fast32_t)arg1 * (uint_fast32_t)arg2) >> 15); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyfx_w) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyfx_w(int_fast16_t arg1, int_fast16_t arg2) { return (((int_fast32_t)arg1 * (int_fast32_t)arg2) << 1); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyfx_uw) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyfx_uw(uint_fast16_t arg1, uint_fast16_t arg2) { return (((uint_fast32_t)arg1 * (uint_fast32_t)arg2) << 1); } //////////////////////////////////////////////////////////// // // // 32-bit functions // // // //////////////////////////////////////////////////////////// #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_l) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpy_l(int_fast32_t arg1, int_fast32_t arg2) { return (arg1 * arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpy_ul) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast32_t __mpy_ul(uint_fast32_t arg1, uint_fast32_t arg2) { return (arg1 * arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyx) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast64_t __mpyx(int_fast32_t arg1, int_fast32_t arg2) { return ((int_fast64_t)arg1 * (int_fast64_t)arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyx_u) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast64_t __mpyx_u(uint_fast32_t arg1, uint_fast32_t arg2) { return ((uint_fast64_t)arg1 * (uint_fast64_t)arg2); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_l) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyf_l(int_fast32_t arg1, int_fast32_t arg2) { return (int_fast32_t)(((int_fast64_t)arg1 * (int_fast64_t)arg2) >> 31); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_l_reuse_arg1) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyf_l_reuse_arg1(int_fast32_t arg1, int_fast32_t arg2) { /* This is identical to __mpyf_l */ return (int_fast32_t)(((int_fast64_t)arg1 * (int_fast64_t)arg2) >> 31); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_ul) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast32_t __mpyf_ul(uint_fast32_t arg1, uint_fast32_t arg2) { return (uint_fast32_t)(((uint_fast64_t)arg1 * (uint_fast64_t)arg2) >> 31); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyf_ul_reuse_arg1) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast32_t __mpyf_ul_reuse_arg1(uint_fast32_t arg1, uint_fast32_t arg2) { /* This is identical to __mpyf_ul */ return (uint_fast32_t)(((uint_fast64_t)arg1 * (uint_fast64_t)arg2) >> 31); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyfx) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline int_fast64_t __mpyfx(int_fast32_t arg1, int_fast32_t arg2) { return (((int_fast64_t)arg1 * (int_fast64_t)arg2) << 1); } #if defined (__TI_COMPILER_VERSION__) #pragma FUNC_ALWAYS_INLINE(__mpyfx_u) #elif defined(__IAR_SYSTEMS_ICC__) #pragma inline=forced #endif static inline uint_fast64_t __mpyfx_u(uint_fast32_t arg1, uint_fast32_t arg2) { return (((uint_fast64_t)arg1 * (uint_fast64_t)arg2) << 1); } #endif //__RTS_SUPPORTH__ ================================================ FILE: iqmath/support/support.h ================================================ #ifndef __SUPPORTH__ #define __SUPPORTH__ #include #include "esp_attr.h" #include "RTS_support.h" #define __STATIC_INLINE FORCE_INLINE_ATTR /* Common value defines. */ #define q15_ln2 0x58b9 #define q13_pi 0x6488 #define q14_pi 0xc910 #define q14_halfPi 0x6488 #define q14_quarterPi 0x3244 #define q15_halfPi 0xc910 #define q15_quarterPi 0x6488 #define q15_invRoot2 0x5a82 #define q15_tanSmall 0x0021 #define q15_pointOne 0x0ccd #define q15_oneTenth 0x0ccd #define iq28_twoPi 0x6487ed51 #define iq29_pi 0x6487ed51 #define iq29_halfPi 0x3243f6a8 #define iq30_pi 0xc90fdaa2 #define iq30_halfPi 0x6487ed51 #define iq30_quarterPi 0x3243f6a8 #define iq31_halfPi 0xc90fdaa2 #define iq31_quarterPi 0x6487ed51 #define iq31_invRoot2 0x5a82799a #define iq31_tanSmall 0x0020c49b #define iq31_ln2 0x58b90bfc #define iq31_twoThird 0x55555555 #define iq31_pointOne 0x0ccccccd #define iq31_oneTenth 0x0ccccccd #define iq31_one 0x7fffffff #endif //__SUPPORTH__ ================================================ FILE: iqmath/test_apps/CMakeLists.txt ================================================ # This is the project CMakeLists.txt file for the test subproject cmake_minimum_required(VERSION 3.5) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(iqmath_test) ================================================ FILE: iqmath/test_apps/main/CMakeLists.txt ================================================ # This is the project CMakeLists.txt file for the test subproject set(src "test_app_main.c" "test_iqmath.c") set(priv_reqs unity) idf_component_register(SRCS ${src} PRIV_REQUIRES ${priv_reqs} WHOLE_ARCHIVE ) ================================================ FILE: iqmath/test_apps/main/idf_component.yml ================================================ dependencies: espressif/iqmath: version: '*' override_path: '../../' ================================================ FILE: iqmath/test_apps/main/test_app_main.c ================================================ /* * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(20); } void app_main(void) { printf("Running IQmath component tests\n"); unity_run_menu(); } ================================================ FILE: iqmath/test_apps/main/test_iqmath.c ================================================ /* * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include "unity.h" #include "esp_log.h" #include "IQmathLib.h" #define ERROR_WITHIN_TOLERANCE(result, expected, tolerance) \ (((result) >= ((expected) - ((expected) * (tolerance)))) && \ ((result) <= ((expected) + ((expected) * (tolerance))))) static const char *TAG = "iqmath_test"; TEST_CASE("Test IQmath basic arithmetic", "[iqmath]") { const float error_tolerance = 0.01; float res; /* IQ variables using global type */ _iq qA = _IQ(1.5); _iq qB = _IQ(2.5); _iq qC; qC = qA + qB; res = _IQtoF(qC); ESP_LOGI(TAG, "Addition: %f + %f = %f", _IQtoF(qA), _IQtoF(qB), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 4.0, error_tolerance)); qC = _IQmpy(qA, qB); res = _IQtoF(qC); ESP_LOGI(TAG, "Multiplication: %f * %f = %f", _IQtoF(qA), _IQtoF(qB), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 3.75, error_tolerance)); qC = qB - qA; res = _IQtoF(qC); ESP_LOGI(TAG, "Subtraction: %f - %f = %f", _IQtoF(qB), _IQtoF(qA), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 1.0, error_tolerance)); qC = _IQdiv(qB, qA); res = _IQtoF(qC); ESP_LOGI(TAG, "Division: %f / %f = %f", _IQtoF(qB), _IQtoF(qA), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 1.666667, error_tolerance)); } TEST_CASE("Test IQmath mathematical functions", "[iqmath]") { const float error_tolerance = 0.01; float res; _iq qA = _IQ(2.5); _iq qC; // Test square root qC = _IQsqrt(qA); res = _IQtoF(qC); ESP_LOGI(TAG, "Square root of %f = %f", _IQtoF(qA), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 1.58113885, error_tolerance)); // Test trigonometric functions qA = _IQ(M_PI / 4.0); // 45 degrees // Test sin qC = _IQsin(qA); res = _IQtoF(qC); ESP_LOGI(TAG, "sin(pi/4) = %f", res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 0.707106781, error_tolerance)); // Test cos qC = _IQcos(qA); res = _IQtoF(qC); ESP_LOGI(TAG, "cos(pi/4) = %f", res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 0.707106781, error_tolerance)); } TEST_CASE("Test IQ8 type operations", "[iqmath]") { const float error_tolerance = 0.01; float res; /* IQ variables using IQ8 type */ _iq8 q8A = _IQ8(1.5); _iq8 q8B = _IQ8(2.5); _iq8 q8C; // Test IQ8 addition q8C = q8A + q8B; res = _IQ8toF(q8C); ESP_LOGI(TAG, "IQ8 Addition: %f + %f = %f", _IQ8toF(q8A), _IQ8toF(q8B), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 4.0, error_tolerance)); // Test IQ8 multiplication q8C = _IQ8mpy(q8A, q8B); res = _IQ8toF(q8C); ESP_LOGI(TAG, "IQ8 Multiplication: %f * %f = %f", _IQ8toF(q8A), _IQ8toF(q8B), res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 3.75, error_tolerance)); } TEST_CASE("Test IQ conversion and saturation", "[iqmath]") { const float error_tolerance = 0.01; float res; // Test float to IQ conversion float test_val = 3.14159; _iq iq_val = _IQ(test_val); res = _IQtoF(iq_val); ESP_LOGI(TAG, "Float to IQ conversion: %f -> %f", test_val, res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, test_val, error_tolerance)); // Test IQ saturation _iq8 q8A = _IQ8(16.0); _iq qC = _IQ8toIQ(_IQsat(q8A, _IQtoQ8(MAX_IQ_POS), _IQtoQ8(MAX_IQ_NEG))); res = _IQtoF(qC); ESP_LOGI(TAG, "IQ saturation test: %f", res); TEST_ASSERT(ERROR_WITHIN_TOLERANCE(res, 16.0, error_tolerance)); } ================================================ FILE: iqmath/test_apps/pytest_iqmath.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest @pytest.mark.generic def test_iqmath(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: iqmath/test_apps/sdkconfig.defaults ================================================ CONFIG_UNITY_ENABLE_FIXTURE=n CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: jsmn/.build-test-rules.yml ================================================ jsmn/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: jsmn/CMakeLists.txt ================================================ idf_component_register(INCLUDE_DIRS "include") if(CONFIG_JSMN_PARENT_LINKS) target_compile_definitions(${COMPONENT_LIB} INTERFACE "-DJSMN_PARENT_LINKS") endif() if(CONFIG_JSMN_STRICT) target_compile_definitions(${COMPONENT_LIB} INTERFACE "-DJSMN_STRICT") endif() if(CONFIG_JSMN_STATIC) target_compile_definitions(${COMPONENT_LIB} INTERFACE "-DJSMN_STATIC") endif() ================================================ FILE: jsmn/Kconfig ================================================ menu "jsmn" config JSMN_PARENT_LINKS bool "Enable parent links" default n help You can access parent node of parsed json config JSMN_STRICT bool "Enable strict mode" default n help In strict mode primitives are: numbers and booleans config JSMN_STATIC bool "Declare JSMN API as static" default node help Declare JSMN API as static (instead of extern) endmenu ================================================ FILE: jsmn/LICENSE ================================================ Copyright (c) 2010 Serge A. Zaitsev 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: jsmn/README.md ================================================ JSMN ==== [![Build Status](https://travis-ci.org/zserge/jsmn.svg?branch=master)](https://travis-ci.org/zserge/jsmn) [![Component Registry](https://components.espressif.com/components/espressif/jsmn/badge.svg)](https://components.espressif.com/components/espressif/jsmn) jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. You can find more information about JSON format at [json.org][1] Library sources are available at https://github.com/zserge/jsmn The web page with some information about jsmn can be found at [http://zserge.com/jsmn.html][2] Philosophy ---------- Most JSON parsers offer you a bunch of functions to load JSON data, parse it and extract any value by its name. jsmn proves that checking the correctness of every JSON packet or allocating temporary objects to store parsed JSON fields often is an overkill. JSON format itself is extremely simple, so why should we complicate it? jsmn is designed to be **robust** (it should work fine even with erroneous data), **fast** (it should parse data on the fly), **portable** (no superfluous dependencies or non-standard C extensions). And of course, **simplicity** is a key feature - simple code style, simple algorithm, simple integration into other projects. Features -------- * compatible with C89 * no dependencies (even libc!) * highly portable (tested on x86/amd64, ARM, AVR) * about 200 lines of code * extremely small code footprint * API contains only 2 functions * no dynamic memory allocation * incremental single-pass parsing * library code is covered with unit-tests Design ------ The rudimentary jsmn object is a **token**. Let's consider a JSON string: '{ "name" : "Jack", "age" : 27 }' It holds the following tokens: * Object: `{ "name" : "Jack", "age" : 27}` (the whole object) * Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) * Number: `27` In jsmn, tokens do not hold any data, but point to token boundaries in JSON string instead. In the example above jsmn will create tokens like: Object [0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. Every jsmn token has a type, which indicates the type of corresponding JSON token. jsmn supports the following token types: * Object - a container of key-value pairs, e.g.: `{ "foo":"bar", "x":0.3 }` * Array - a sequence of values, e.g.: `[ 1, 2, 3 ]` * String - a quoted sequence of chars, e.g.: `"foo"` * Primitive - a number, a boolean (`true`, `false`) or `null` Besides start/end positions, jsmn tokens for complex types (like arrays or objects) also contain a number of child items, so you can easily follow object hierarchy. This approach provides enough information for parsing any JSON data and makes it possible to use zero-copy techniques. Usage ----- Download `jsmn.h`, include it, done. ``` #include "jsmn.h" ... jsmn_parser p; jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */ jsmn_init(&p); r = jsmn_parse(&p, s, strlen(s), t, 128); // "s" is the char array holding the json content ``` Since jsmn is a single-header, header-only library, for more complex use cases you might need to define additional macros. `#define JSMN_STATIC` hides all jsmn API symbols by making them static. Also, if you want to include `jsmn.h` from multiple C files, to avoid duplication of symbols you may define `JSMN_HEADER` macro. ``` /* In every .c file that uses jsmn include only declarations: */ #define JSMN_HEADER #include "jsmn.h" /* Additionally, create one jsmn.c file for jsmn implementation: */ #include "jsmn.h" ``` API --- Token types are described by `jsmntype_t`: typedef enum { JSMN_UNDEFINED = 0, JSMN_OBJECT = 1 << 0, JSMN_ARRAY = 1 << 1, JSMN_STRING = 1 << 2, JSMN_PRIMITIVE = 1 << 3 } jsmntype_t; **Note:** Unlike JSON data types, primitive tokens are not divided into numbers, booleans and null, because one can easily tell the type using the first character: * 't', 'f' - boolean * 'n' - null * '-', '0'..'9' - number Token is an object of `jsmntok_t` type: typedef struct { jsmntype_t type; // Token type int start; // Token start position int end; // Token end position int size; // Number of child (nested) tokens } jsmntok_t; **Note:** string tokens point to the first character after the opening quote and the previous symbol before final quote. This was made to simplify string extraction from JSON data. All job is done by `jsmn_parser` object. You can initialize a new parser using: jsmn_parser parser; jsmntok_t tokens[10]; jsmn_init(&parser); // js - pointer to JSON string // tokens - an array of tokens available // 10 - number of tokens available jsmn_parse(&parser, js, strlen(js), tokens, 10); This will create a parser, and then it tries to parse up to 10 JSON tokens from the `js` string. A non-negative return value of `jsmn_parse` is the number of tokens actually used by the parser. Passing NULL instead of the tokens array would not store parsing results, but instead the function will return the number of tokens needed to parse the given string. This can be useful if you don't know yet how many tokens to allocate. If something goes wrong, you will get an error. Error will be one of these: * `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted * `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large * `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data If you get `JSMN_ERROR_NOMEM`, you can re-allocate more tokens and call `jsmn_parse` once more. If you read json data from the stream, you can periodically call `jsmn_parse` and check if return value is `JSMN_ERROR_PART`. You will get this error until you reach the end of JSON data. Other info ---------- This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), so feel free to integrate it in your commercial products. [1]: http://www.json.org/ [2]: http://zserge.com/jsmn.html ================================================ FILE: jsmn/idf_component.yml ================================================ version: "1.1.0" description: "JSMN: minimalistic JSON parser in C" url: https://github.com/espressif/idf-extra-components/tree/master/jsmn dependencies: idf: ">=4.3" ================================================ FILE: jsmn/include/jsmn.h ================================================ /* * MIT License * * Copyright (c) 2010 Serge Zaitsev * * 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: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * 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. */ #ifndef JSMN_H #define JSMN_H #include #ifdef __cplusplus extern "C" { #endif #ifdef JSMN_STATIC #define JSMN_API static #else #define JSMN_API extern #endif /** * JSON type identifier. Basic types are: * o Object * o Array * o String * o Other primitive: number, boolean (true/false) or null */ typedef enum { JSMN_UNDEFINED = 0, JSMN_OBJECT = 1 << 0, JSMN_ARRAY = 1 << 1, JSMN_STRING = 1 << 2, JSMN_PRIMITIVE = 1 << 3 } jsmntype_t; enum jsmnerr { /* Not enough tokens were provided */ JSMN_ERROR_NOMEM = -1, /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3 }; /** * JSON token description. * type type (object, array, string etc.) * start start position in JSON data string * end end position in JSON data string */ typedef struct jsmntok { jsmntype_t type; int start; int end; int size; #ifdef JSMN_PARENT_LINKS int parent; #endif } jsmntok_t; /** * JSON parser. Contains an array of token blocks available. Also stores * the string being parsed now and current position in that string. */ typedef struct jsmn_parser { unsigned int pos; /* offset in the JSON string */ unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g. parent object or array */ } jsmn_parser; /** * Create JSON parser over an array of tokens */ JSMN_API void jsmn_init(jsmn_parser *parser); /** * Run JSON parser. It parses a JSON data string into and array of tokens, each * describing * a single JSON object. */ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens); #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, const size_t num_tokens) { jsmntok_t *tok; if (parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; tok->start = tok->end = -1; tok->size = 0; #ifdef JSMN_PARENT_LINKS tok->parent = -1; #endif return tok; } /** * Fills token type and boundaries. */ static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, const int start, const int end) { token->type = type; token->start = start; token->end = end; token->size = 0; } /** * Fills next available token with JSON primitive. */ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { jsmntok_t *token; int start; start = parser->pos; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ case ':': #endif case '\t': case '\r': case '\n': case ' ': case ',': case ']': case '}': goto found; default: /* to quiet a warning from gcc*/ break; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { parser->pos = start; return JSMN_ERROR_INVAL; } } #ifdef JSMN_STRICT /* In strict mode primitive must be followed by a comma/object/array */ parser->pos = start; return JSMN_ERROR_PART; #endif found: if (tokens == NULL) { parser->pos--; return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif parser->pos--; return 0; } /** * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { jsmntok_t *token; int start = parser->pos; /* Skip starting quote */ parser->pos++; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { if (tokens == NULL) { return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif return 0; } /* Backslash: Quoted symbol expected */ if (c == '\\' && parser->pos + 1 < len) { int i; parser->pos++; switch (js[parser->pos]) { /* Allowed escaped symbols */ case '\"': case '/': case '\\': case 'b': case 'f': case 'r': case 'n': case 't': break; /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ parser->pos = start; return JSMN_ERROR_INVAL; } parser->pos++; } parser->pos--; break; /* Unexpected symbol */ default: parser->pos = start; return JSMN_ERROR_INVAL; } } } parser->pos = start; return JSMN_ERROR_PART; } /** * Parse JSON string and fill tokens. */ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens) { int r; int i; jsmntok_t *token; int count = parser->toknext; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; c = js[parser->pos]; switch (c) { case '{': case '[': count++; if (tokens == NULL) { break; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { return JSMN_ERROR_NOMEM; } if (parser->toksuper != -1) { jsmntok_t *t = &tokens[parser->toksuper]; #ifdef JSMN_STRICT /* In strict mode an object or array can't become a key */ if (t->type == JSMN_OBJECT) { return JSMN_ERROR_INVAL; } #endif t->size++; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; parser->toksuper = parser->toknext - 1; break; case '}': case ']': if (tokens == NULL) { break; } type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { return JSMN_ERROR_INVAL; } token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; parser->toksuper = token->parent; break; } if (token->parent == -1) { if (token->type != type || parser->toksuper == -1) { return JSMN_ERROR_INVAL; } break; } token = &tokens[token->parent]; } #else for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } parser->toksuper = -1; token->end = parser->pos + 1; break; } } /* Error if unmatched closing bracket */ if (i == -1) { return JSMN_ERROR_INVAL; } for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { parser->toksuper = i; break; } } #endif break; case '\"': r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) { return r; } count++; if (parser->toksuper != -1 && tokens != NULL) { tokens[parser->toksuper].size++; } break; case '\t': case '\r': case '\n': case ' ': break; case ':': parser->toksuper = parser->toknext - 1; break; case ',': if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && tokens[parser->toksuper].type != JSMN_OBJECT) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i >= 0; i--) { if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { if (tokens[i].start != -1 && tokens[i].end == -1) { parser->toksuper = i; break; } } } #endif } break; #ifdef JSMN_STRICT /* In strict mode primitives are: numbers and booleans */ case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 't': case 'f': case 'n': /* And they must not be keys of the object */ if (tokens != NULL && parser->toksuper != -1) { const jsmntok_t *t = &tokens[parser->toksuper]; if (t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) { return JSMN_ERROR_INVAL; } } #else /* In non-strict mode every unquoted value is a primitive */ default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r < 0) { return r; } count++; if (parser->toksuper != -1 && tokens != NULL) { tokens[parser->toksuper].size++; } break; #ifdef JSMN_STRICT /* Unexpected char in strict mode */ default: return JSMN_ERROR_INVAL; #endif } } if (tokens != NULL) { for (i = parser->toknext - 1; i >= 0; i--) { /* Unmatched opened object or array */ if (tokens[i].start != -1 && tokens[i].end == -1) { return JSMN_ERROR_PART; } } } return count; } /** * Creates a new parser based over a given buffer with an array of tokens * available. */ JSMN_API void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; } #endif /* JSMN_HEADER */ #ifdef __cplusplus } #endif #endif /* JSMN_H */ ================================================ FILE: jsmn/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(jsmn_test) ================================================ FILE: jsmn/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "jsmn_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: jsmn/test_apps/main/idf_component.yml ================================================ dependencies: espressif/jsmn: version: "*" override_path: "../.." ================================================ FILE: jsmn/test_apps/main/jsmn_test.c ================================================ #include void app_main(void) { } ================================================ FILE: json_generator/.build-test-rules.yml ================================================ json_generator/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: json_generator/CMakeLists.txt ================================================ idf_component_register(SRCS "src/json_generator.c" INCLUDE_DIRS "include" ) ================================================ FILE: json_generator/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Piyush Shah Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: json_generator/README.md ================================================ # JSON Generator [![Component Registry](https://components.espressif.com/components/espressif/json_generator/badge.svg)](https://components.espressif.com/components/espressif/json_generator) A simple JSON (JavasScript Object Notation) generator with flushing capability. Details of JSON can be found at [http://www.json.org/](http://www.json.org/). The JSON strings generated can be validated using any standard JSON validator. Eg. [https://jsonlint.com/](https://jsonlint.com/) # Files - `src/json_generator.c`: Actual source file for the JSON generator with implementation of all APIS - `include/json_generator.h`: Header file documenting and exposing all available APIs # Usage Include the C and H files in your project's build system and that should be enough. `json_generator` requires only standard library functions for compilation ================================================ FILE: json_generator/idf_component.yml ================================================ version: "1.2.0" description: A simple JSON (JavasScript Object Notation) generator with flushing capability url: https://github.com/espressif/json_generator ================================================ FILE: json_generator/include/json_generator.h ================================================ /* * Copyright 2020 Piyush Shah * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * JSON String Generator * * This module can be used to create JSON strings with a facility * to flush out data if the destination buffer is full. All commas * and colons as required are automatically added by the APIs * */ #ifndef _JSON_GENERATOR_H #define _JSON_GENERATOR_H #include #include #ifdef __cplusplus extern "C" { #endif /** Float precision i.e. number of digits after decimal point */ #ifndef JSON_FLOAT_PRECISION #define JSON_FLOAT_PRECISION 5 #endif /** JSON string flush callback prototype * * This is a prototype of the function that needs to be passed to * json_gen_str_start() and which will be invoked by the JSON generator * module either when the buffer is full or json_gen_str_end() ins invoked. * * \param[in] buf Pointer to a NULL terminated JSON string * \param[in] priv Private data to be passed to the flush callback. Will * be the same as the one passed to json_gen_str_start() */ typedef void (*json_gen_flush_cb_t) (char *buf, void *priv); /** JSON String structure * * Please do not set/modify any elements. * Just define this structure and pass a pointer to it in the APIs below */ typedef struct { /** Pointer to the JSON buffer provided by the calling function */ char *buf; /** Size of the above buffer */ int buf_size; /** (Optional) callback function to invoke when the buffer gets full */ json_gen_flush_cb_t flush_cb; /** (Optional) Private data to pass to the callback function */ void *priv; /** (For Internal use only) */ bool comma_req; /** (For Internal use only) */ char *free_ptr; /** Total length */ int total_len; } json_gen_str_t; /** Start a JSON String * * This is the first function to be called for creating a JSON string. * It initializes the internal data structures. After the JSON string * generation is over, the json_gen_str_end() function should be called. * * \param[out] jstr Pointer to an allocated \ref json_gen_str_t structure. * This will be initialised internally and needs to be passed to all * subsequent function calls * \param[out] buf Pointer to an allocated buffer into which the JSON * string will be written * \param[in] buf_size Size of the buffer * \param[in] flush_cb Pointer to the flushing function of type \ref json_gen_flush_cb_t * which will be invoked either when the buffer is full or when json_gen_str_end() * is invoked. Can be left NULL. * \param[in] priv Private data to be passed to the flushing function callback. * Can be something like a session identifier (Eg. socket). Can be left NULL. */ void json_gen_str_start(json_gen_str_t *jstr, char *buf, int buf_size, json_gen_flush_cb_t flush_cb, void *priv); /** End JSON string * * This should be the last function to be called after the entire JSON string * has been generated. * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return Total length of the JSON created, including the NULL termination byte. */ int json_gen_str_end(json_gen_str_t *jstr); /** Start a JSON object * * This starts a JSON object by adding a '{' * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_start_object(json_gen_str_t *jstr); /** End a JSON object * * This ends a JSON object by adding a '}' * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_end_object(json_gen_str_t *jstr); /** Start a JSON array * * This starts a JSON object by adding a '[' * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_start_array(json_gen_str_t *jstr); /** End a JSON object * * This ends a JSON object by adding a ']' * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_end_array(json_gen_str_t *jstr); /** Push a named JSON object * * This adds a JSON object like "name":{ * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the object * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_push_object(json_gen_str_t *jstr, const char *name); /** Pop a named JSON object * * This ends a JSON object by adding a '}'. This is basically same as * json_gen_end_object() but included so as to complement json_gen_push_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_pop_object(json_gen_str_t *jstr); /** Push a JSON object string * * This adds a complete pre-formatted JSON object string to the JSON object. * * Eg. json_gen_push_object_str(jstr, "pre-formatted", "{\"a\":1,\"b\":2}"); * This will add "pre-formatted":{"a":1,"b":2} * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the JSON object string * \param[in] object_str The pre-formatted JSON object string * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that. */ int json_gen_push_object_str(json_gen_str_t *jstr, const char *name, const char *object_str); /** Push a named JSON array * * This adds a JSON array like "name":[ * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the array * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_push_array(json_gen_str_t *jstr, const char *name); /** Pop a named JSON array * * This ends a JSON array by adding a ']'. This is basically same as * json_gen_end_array() but included so as to complement json_gen_push_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_pop_array(json_gen_str_t *jstr); /** Push a JSON array string * * This adds a complete pre-formatted JSON array string to the JSON object. * * Eg. json_gen_push_object_str(jstr, "pre-formatted", "[1,2,3]"); * This will add "pre-formatted":[1,2,3] * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the JSON array string * \param[in] array_str The pre-formatted JSON array string * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that. */ int json_gen_push_array_str(json_gen_str_t *jstr, const char *name, const char *array_str); /** Add a boolean element to an object * * This adds a boolean element to an object. Eg. "bool_val":true * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val Boolean value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_bool(json_gen_str_t *jstr, const char *name, bool val); /** Add an integer element to an object * * This adds an integer element to an object. Eg. "int_val":28 * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val Integer value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_int(json_gen_str_t *jstr, const char *name, int val); /** Add a 64 bit integer element to an object * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val 64 bit integer value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_int64(json_gen_str_t *jstr, const char *name, int64_t val); /** Add a float element to an object * * This adds a float element to an object. Eg. "float_val":23.8 * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val Float value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_float(json_gen_str_t *jstr, const char *name, float val); /** Add a string element to an object * * This adds a string element to an object. Eg. "string_val":"my_string" * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val Null terminated string value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_string(json_gen_str_t *jstr, const char *name, const char *val); /** Add a NULL element to an object * * This adds a NULL element to an object. Eg. "null_val":null * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_set_null(json_gen_str_t *jstr, const char *name); /** Add a boolean element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val Boolean value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_bool(json_gen_str_t *jstr, bool val); /** Add an integer element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val Integer value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_int(json_gen_str_t *jstr, int val); /** Add a 64 bit integer element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val 64 bit integer value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_int64(json_gen_str_t *jstr, int64_t val); /** Add a float element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val Float value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_float(json_gen_str_t *jstr, float val); /** Add a string element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val Null terminated string value of the element * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_string(json_gen_str_t *jstr, const char *val); /** Add a NULL element to an array * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_set_null(json_gen_str_t *jstr); /** Start a Long string in an object * * This starts a string in an object, but does not end it (i.e., does not add the * terminating quotes. This is useful for long strings. Eg. "string_val":"my_string. * The API json_gen_add_to_long_string() must be used to add to this string and the API * json_gen_end_long_string() must be used to terminate it (i.e. add the ending quotes). * * \note This must be called between json_gen_start_object()/json_gen_push_object() * and json_gen_end_object()/json_gen_pop_object() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] name Name of the element * \param[in] val Null terminated initial part of the string value. It can also be NULL * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_obj_start_long_string(json_gen_str_t *jstr, const char *name, const char *val); /** Start a Long string in an array * * This starts a string in an arrayt, but does not end it (i.e., does not add the * terminating quotes. This is useful for long strings. * The API json_gen_add_to_long_string() must be used to add to this string and the API * json_gen_end_long_string() must be used to terminate it (i.e. add the ending quotes). * * \note This must be called between json_gen_start_array()/json_gen_push_array() * and json_gen_end_array()/json_gen_pop_array() * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by * json_gen_str_start() * \param[in] val Null terminated initial part of the string value. It can also be NULL * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_arr_start_long_string(json_gen_str_t *jstr, const char *val); /** Add to a JSON Long string * * This extends the string initialised by json_gen_obj_start_long_string() or * json_gen_arr_start_long_string(). After the entire string is created, it should be terminated * with json_gen_end_long_string(). * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by json_gen_str_start() * \param[in] val Null terminated extending part of the string value. * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_add_to_long_string(json_gen_str_t *jstr, const char *val); /** End a JSON Long string * * This ends the string initialised by json_gen_obj_start_long_string() or * json_gen_arr_start_long_string() by adding the ending quotes. * * \param[in] jstr Pointer to the \ref json_gen_str_t structure initialised by json_gen_str_start() * * * \return 0 on Success * \return -1 if buffer is out of space (possible only if no callback function * is passed to json_gen_str_start(). Else, buffer will be flushed out and new data * added after that */ int json_gen_end_long_string(json_gen_str_t *jstr); #ifdef __cplusplus } #endif #endif ================================================ FILE: json_generator/src/json_generator.c ================================================ /* * Copyright 2020 Piyush Shah * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #define MAX_INT_IN_STR 24 #define MAX_INT64_IN_STR 24 #define MAX_FLOAT_IN_STR 30 static inline int json_gen_get_empty_len(json_gen_str_t *jstr) { return (jstr->buf_size - (jstr->free_ptr - jstr->buf) - 1); } /* This will add the incoming string to the JSON string buffer * and flush it out if the buffer is full. Note that the data being * flushed out will always be equal to the size of the buffer unless * this is the last chunk being flushed out on json_gen_end_str() */ static int json_gen_add_to_str(json_gen_str_t *jstr, const char *str) { if (!str) { return 0; } int len = strlen(str); jstr->total_len += len; if (jstr->buf == NULL) { return 0; } const char *cur_ptr = str; while (1) { int len_remaining = json_gen_get_empty_len(jstr); int copy_len = len_remaining > len ? len : len_remaining; memmove(jstr->free_ptr, cur_ptr, copy_len); cur_ptr += copy_len; jstr->free_ptr += copy_len; len -= copy_len; if (len) { *jstr->free_ptr = '\0'; /* Report error if the buffer is full and no flush callback * is registered */ if (!jstr->flush_cb) { return -1; } jstr->flush_cb(jstr->buf, jstr->priv); jstr->free_ptr = jstr->buf; } else { break; } } return 0; } void json_gen_str_start(json_gen_str_t *jstr, char *buf, int buf_size, json_gen_flush_cb_t flush_cb, void *priv) { memset(jstr, 0, sizeof(json_gen_str_t)); jstr->buf = buf; jstr->buf_size = buf_size; jstr->flush_cb = flush_cb; jstr->free_ptr = buf; jstr->priv = priv; } int json_gen_str_end(json_gen_str_t *jstr) { int total_len = jstr->total_len; if (jstr->buf) { *jstr->free_ptr = '\0'; if (jstr->flush_cb) { jstr->flush_cb(jstr->buf, jstr->priv); } } memset(jstr, 0, sizeof(json_gen_str_t)); return total_len + 1; /* +1 for the NULL termination */ } static inline void json_gen_handle_comma(json_gen_str_t *jstr) { if (jstr->comma_req) { json_gen_add_to_str(jstr, ","); } } static int json_gen_handle_name(json_gen_str_t *jstr, const char *name) { json_gen_add_to_str(jstr, "\""); json_gen_add_to_str(jstr, name); return json_gen_add_to_str(jstr, "\":"); } int json_gen_start_object(json_gen_str_t *jstr) { json_gen_handle_comma(jstr); jstr->comma_req = false; return json_gen_add_to_str(jstr, "{"); } int json_gen_end_object(json_gen_str_t *jstr) { jstr->comma_req = true; return json_gen_add_to_str(jstr, "}"); } int json_gen_start_array(json_gen_str_t *jstr) { json_gen_handle_comma(jstr); jstr->comma_req = false; return json_gen_add_to_str(jstr, "["); } int json_gen_end_array(json_gen_str_t *jstr) { jstr->comma_req = true; return json_gen_add_to_str(jstr, "]"); } int json_gen_push_object(json_gen_str_t *jstr, const char *name) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); jstr->comma_req = false; return json_gen_add_to_str(jstr, "{"); } int json_gen_pop_object(json_gen_str_t *jstr) { jstr->comma_req = true; return json_gen_add_to_str(jstr, "}"); } int json_gen_push_object_str(json_gen_str_t *jstr, const char *name, const char *object_str) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); jstr->comma_req = true; return json_gen_add_to_str(jstr, object_str); } int json_gen_push_array(json_gen_str_t *jstr, const char *name) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); jstr->comma_req = false; return json_gen_add_to_str(jstr, "["); } int json_gen_pop_array(json_gen_str_t *jstr) { jstr->comma_req = true; return json_gen_add_to_str(jstr, "]"); } int json_gen_push_array_str(json_gen_str_t *jstr, const char *name, const char *array_str) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); jstr->comma_req = true; return json_gen_add_to_str(jstr, array_str); } static int json_gen_set_bool(json_gen_str_t *jstr, bool val) { jstr->comma_req = true; if (val) { return json_gen_add_to_str(jstr, "true"); } else { return json_gen_add_to_str(jstr, "false"); } } int json_gen_obj_set_bool(json_gen_str_t *jstr, const char *name, bool val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_bool(jstr, val); } int json_gen_arr_set_bool(json_gen_str_t *jstr, bool val) { json_gen_handle_comma(jstr); return json_gen_set_bool(jstr, val); } static int json_gen_set_int(json_gen_str_t *jstr, int val) { jstr->comma_req = true; char str[MAX_INT_IN_STR]; snprintf(str, MAX_INT_IN_STR, "%d", val); return json_gen_add_to_str(jstr, str); } int json_gen_obj_set_int(json_gen_str_t *jstr, const char *name, int val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_int(jstr, val); } int json_gen_arr_set_int(json_gen_str_t *jstr, int val) { json_gen_handle_comma(jstr); return json_gen_set_int(jstr, val); } static int json_gen_set_int64(json_gen_str_t *jstr, int64_t val) { jstr->comma_req = true; char str[MAX_INT64_IN_STR]; snprintf(str, MAX_INT64_IN_STR, "%" PRId64, val); return json_gen_add_to_str(jstr, str); } int json_gen_obj_set_int64(json_gen_str_t *jstr, const char *name, int64_t val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_int64(jstr, val); } int json_gen_arr_set_int64(json_gen_str_t *jstr, int64_t val) { json_gen_handle_comma(jstr); return json_gen_set_int64(jstr, val); } static int json_gen_set_float(json_gen_str_t *jstr, float val) { jstr->comma_req = true; char str[MAX_FLOAT_IN_STR]; snprintf(str, MAX_FLOAT_IN_STR, "%.*f", JSON_FLOAT_PRECISION, val); return json_gen_add_to_str(jstr, str); } int json_gen_obj_set_float(json_gen_str_t *jstr, const char *name, float val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_float(jstr, val); } int json_gen_arr_set_float(json_gen_str_t *jstr, float val) { json_gen_handle_comma(jstr); return json_gen_set_float(jstr, val); } static int json_gen_set_string(json_gen_str_t *jstr, const char *val) { jstr->comma_req = true; json_gen_add_to_str(jstr, "\""); json_gen_add_to_str(jstr, val); return json_gen_add_to_str(jstr, "\""); } int json_gen_obj_set_string(json_gen_str_t *jstr, const char *name, const char *val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_string(jstr, val); } int json_gen_arr_set_string(json_gen_str_t *jstr, const char *val) { json_gen_handle_comma(jstr); return json_gen_set_string(jstr, val); } static int json_gen_set_long_string(json_gen_str_t *jstr, const char *val) { jstr->comma_req = true; json_gen_add_to_str(jstr, "\""); return json_gen_add_to_str(jstr, val); } int json_gen_obj_start_long_string(json_gen_str_t *jstr, const char *name, const char *val) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_long_string(jstr, val); } int json_gen_arr_start_long_string(json_gen_str_t *jstr, const char *val) { json_gen_handle_comma(jstr); return json_gen_set_long_string(jstr, val); } int json_gen_add_to_long_string(json_gen_str_t *jstr, const char *val) { return json_gen_add_to_str(jstr, val); } int json_gen_end_long_string(json_gen_str_t *jstr) { return json_gen_add_to_str(jstr, "\""); } static int json_gen_set_null(json_gen_str_t *jstr) { jstr->comma_req = true; return json_gen_add_to_str(jstr, "null"); } int json_gen_obj_set_null(json_gen_str_t *jstr, const char *name) { json_gen_handle_comma(jstr); json_gen_handle_name(jstr, name); return json_gen_set_null(jstr); } int json_gen_arr_set_null(json_gen_str_t *jstr) { json_gen_handle_comma(jstr); return json_gen_set_null(jstr); } ================================================ FILE: json_generator/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(json_generator_test) ================================================ FILE: json_generator/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "json_generator_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: json_generator/test_apps/main/idf_component.yml ================================================ dependencies: espressif/json_generator: version: "*" override_path: "../.." ================================================ FILE: json_generator/test_apps/main/json_generator_test.c ================================================ #include void app_main(void) { } ================================================ FILE: json_parser/.build-test-rules.yml ================================================ json_parser/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: json_parser/CMakeLists.txt ================================================ idf_component_register(SRCS "src/json_parser.c" INCLUDE_DIRS "include" REQUIRES "jsmn" ) ================================================ FILE: json_parser/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Piyush Shah Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: json_parser/README.md ================================================ # JSON Parser [![Component Registry](https://components.espressif.com/components/espressif/json_parser/badge.svg)](https://components.espressif.com/components/espressif/json_parser) This is a simple, light weight JSON parser built on top of [jsmn](https://github.com/zserge/jsmn). Files - `src/json_parser.c`: Source file which has all the logic for implementing the APIs built on top of JSMN - `include/json_parser.h`: Header file that exposes all APIs ================================================ FILE: json_parser/idf_component.yml ================================================ version: "1.0.3" description: This is a simple, light weight JSON parser built on top of jsmn url: https://github.com/espressif/json_parser dependencies: jsmn: version: "~1.1" rules: - if: "idf_version >=5.0" override_path: "../jsmn/" ================================================ FILE: json_parser/include/json_parser.h ================================================ /* * Copyright 2020 Piyush Shah * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _JSON_PARSER_H_ #define _JSON_PARSER_H_ #define JSMN_PARENT_LINKS #define JSMN_HEADER #include #include #include #ifdef __cplusplus extern "C" { #endif #define OS_SUCCESS 0 #define OS_FAIL -1 typedef jsmn_parser json_parser_t; typedef jsmntok_t json_tok_t; typedef struct { json_parser_t parser; const char *js; json_tok_t *tokens; json_tok_t *cur; int num_tokens; } jparse_ctx_t; int json_parse_start(jparse_ctx_t *jctx, const char *js, int len); int json_parse_end(jparse_ctx_t *jctx); int json_parse_start_static(jparse_ctx_t *jctx, const char *js, int len, json_tok_t *buffer_tokens, int buffer_tokens_max_count); int json_parse_end_static(jparse_ctx_t *jctx); int json_obj_get_array(jparse_ctx_t *jctx, const char *name, int *num_elem); int json_obj_leave_array(jparse_ctx_t *jctx); int json_obj_get_object(jparse_ctx_t *jctx, const char *name); int json_obj_leave_object(jparse_ctx_t *jctx); int json_obj_get_bool(jparse_ctx_t *jctx, const char *name, bool *val); int json_obj_get_int(jparse_ctx_t *jctx, const char *name, int *val); int json_obj_get_int64(jparse_ctx_t *jctx, const char *name, int64_t *val); int json_obj_get_float(jparse_ctx_t *jctx, const char *name, float *val); int json_obj_get_string(jparse_ctx_t *jctx, const char *name, char *val, int size); int json_obj_get_strlen(jparse_ctx_t *jctx, const char *name, int *strlen); int json_obj_get_object_str(jparse_ctx_t *jctx, const char *name, char *val, int size); int json_obj_get_object_strlen(jparse_ctx_t *jctx, const char *name, int *strlen); int json_obj_get_array_str(jparse_ctx_t *jctx, const char *name, char *val, int size); int json_obj_get_array_strlen(jparse_ctx_t *jctx, const char *name, int *strlen); int json_arr_get_array(jparse_ctx_t *jctx, uint32_t index); int json_arr_leave_array(jparse_ctx_t *jctx); int json_arr_get_object(jparse_ctx_t *jctx, uint32_t index); int json_arr_leave_object(jparse_ctx_t *jctx); int json_arr_get_bool(jparse_ctx_t *jctx, uint32_t index, bool *val); int json_arr_get_int(jparse_ctx_t *jctx, uint32_t index, int *val); int json_arr_get_int64(jparse_ctx_t *jctx, uint32_t index, int64_t *val); int json_arr_get_float(jparse_ctx_t *jctx, uint32_t index, float *val); int json_arr_get_string(jparse_ctx_t *jctx, uint32_t index, char *val, int size); int json_arr_get_strlen(jparse_ctx_t *jctx, uint32_t index, int *strlen); #ifdef __cplusplus } #endif #endif /* _JSON_PARSER_H_ */ ================================================ FILE: json_parser/src/json_parser.c ================================================ /* * Copyright 2020 Piyush Shah * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #define JSMN_PARENT_LINKS #define JSMN_STRICT #define JSMN_STATIC #include #include static bool token_matches_str(jparse_ctx_t *ctx, json_tok_t *tok, const char *str) { const char *js = ctx->js; return ((strncmp(js + tok->start, str, strlen(str)) == 0) && (strlen(str) == (size_t) (tok->end - tok->start))); } static json_tok_t *json_skip_elem(json_tok_t *token) { json_tok_t *cur = token; int cnt = cur->size; while (cnt--) { cur++; cur = json_skip_elem(cur); } return cur; } static int json_tok_to_bool(jparse_ctx_t *jctx, json_tok_t *tok, bool *val) { if (token_matches_str(jctx, tok, "true") || token_matches_str(jctx, tok, "1")) { *val = true; } else if (token_matches_str(jctx, tok, "false") || token_matches_str(jctx, tok, "0")) { *val = false; } else { return -OS_FAIL; } return OS_SUCCESS; } static int json_tok_to_int(jparse_ctx_t *jctx, json_tok_t *tok, int *val) { const char *tok_start = &jctx->js[tok->start]; const char *tok_end = &jctx->js[tok->end]; char *endptr; int i = strtoul(tok_start, &endptr, 10); if (endptr == tok_end) { *val = i; return OS_SUCCESS; } return -OS_FAIL; } static int json_tok_to_int64(jparse_ctx_t *jctx, json_tok_t *tok, int64_t *val) { const char *tok_start = &jctx->js[tok->start]; const char *tok_end = &jctx->js[tok->end]; char *endptr; int64_t i64 = strtoull(tok_start, &endptr, 10); if (endptr == tok_end) { *val = i64; return OS_SUCCESS; } return -OS_FAIL; } static int json_tok_to_float(jparse_ctx_t *jctx, json_tok_t *tok, float *val) { const char *tok_start = &jctx->js[tok->start]; const char *tok_end = &jctx->js[tok->end]; char *endptr; float f = strtof(tok_start, &endptr); if (endptr == tok_end) { *val = f; return OS_SUCCESS; } return -OS_FAIL; } static int json_tok_to_string(jparse_ctx_t *jctx, json_tok_t *tok, char *val, int size) { if ((tok->end - tok->start) > (size - 1)) { return -OS_FAIL; } strncpy(val, jctx->js + tok->start, tok->end - tok->start); val[tok->end - tok->start] = 0; return OS_SUCCESS; } static json_tok_t *json_obj_search(jparse_ctx_t *jctx, const char *key) { json_tok_t *tok = jctx->cur; int size = tok->size; if (size <= 0) { return NULL; } if (tok->type != JSMN_OBJECT) { return NULL; } while (size--) { tok++; if (token_matches_str(jctx, tok, key)) { return tok; } tok = json_skip_elem(tok); } return NULL; } static json_tok_t *json_obj_get_val_tok(jparse_ctx_t *jctx, const char *name, jsmntype_t type) { json_tok_t *tok = json_obj_search(jctx, name); if (!tok) { return NULL; } tok++; if (tok->type != type) { return NULL; } return tok; } int json_obj_get_array(jparse_ctx_t *jctx, const char *name, int *num_elem) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_ARRAY); if (!tok) { return -OS_FAIL; } jctx->cur = tok; *num_elem = tok->size; return OS_SUCCESS; } int json_obj_leave_array(jparse_ctx_t *jctx) { /* The array's parent will be the key */ if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; /* The key's parent will be the actual parent object */ if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; return OS_SUCCESS; } int json_obj_get_object(jparse_ctx_t *jctx, const char *name) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_OBJECT); if (!tok) { return -OS_FAIL; } jctx->cur = tok; return OS_SUCCESS; } int json_obj_leave_object(jparse_ctx_t *jctx) { /* The objects's parent will be the key */ if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; /* The key's parent will be the actual parent object */ if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; return OS_SUCCESS; } int json_obj_get_bool(jparse_ctx_t *jctx, const char *name, bool *val) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_bool(jctx, tok, val); } int json_obj_get_int(jparse_ctx_t *jctx, const char *name, int *val) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_int(jctx, tok, val); } int json_obj_get_int64(jparse_ctx_t *jctx, const char *name, int64_t *val) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_int64(jctx, tok, val); } int json_obj_get_float(jparse_ctx_t *jctx, const char *name, float *val) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_float(jctx, tok, val); } int json_obj_get_string(jparse_ctx_t *jctx, const char *name, char *val, int size) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_STRING); if (!tok) { return -OS_FAIL; } return json_tok_to_string(jctx, tok, val, size); } int json_obj_get_strlen(jparse_ctx_t *jctx, const char *name, int *strlen) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_STRING); if (!tok) { return -OS_FAIL; } *strlen = tok->end - tok->start; return OS_SUCCESS; } int json_obj_get_object_str(jparse_ctx_t *jctx, const char *name, char *val, int size) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_OBJECT); if (!tok) { return -OS_FAIL; } return json_tok_to_string(jctx, tok, val, size); } int json_obj_get_object_strlen(jparse_ctx_t *jctx, const char *name, int *strlen) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_OBJECT); if (!tok) { return -OS_FAIL; } *strlen = tok->end - tok->start; return OS_SUCCESS; } int json_obj_get_array_str(jparse_ctx_t *jctx, const char *name, char *val, int size) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_ARRAY); if (!tok) { return -OS_FAIL; } return json_tok_to_string(jctx, tok, val, size); } int json_obj_get_array_strlen(jparse_ctx_t *jctx, const char *name, int *strlen) { json_tok_t *tok = json_obj_get_val_tok(jctx, name, JSMN_ARRAY); if (!tok) { return -OS_FAIL; } *strlen = tok->end - tok->start; return OS_SUCCESS; } static json_tok_t *json_arr_search(jparse_ctx_t *ctx, uint32_t index) { json_tok_t *tok = ctx->cur; if ((tok->type != JSMN_ARRAY) || (tok->size <= 0)) { return NULL; } if (index > (uint32_t)(tok->size - 1)) { return NULL; } /* Increment by 1, so that token points to index 0 */ tok++; while (index--) { tok = json_skip_elem(tok); tok++; } return tok; } static json_tok_t *json_arr_get_val_tok(jparse_ctx_t *jctx, uint32_t index, jsmntype_t type) { json_tok_t *tok = json_arr_search(jctx, index); if (!tok) { return NULL; } if (tok->type != type) { return NULL; } return tok; } int json_arr_get_array(jparse_ctx_t *jctx, uint32_t index) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_ARRAY); if (!tok) { return -OS_FAIL; } jctx->cur = tok; return OS_SUCCESS; } int json_arr_leave_array(jparse_ctx_t *jctx) { if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; return OS_SUCCESS; } int json_arr_get_object(jparse_ctx_t *jctx, uint32_t index) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_OBJECT); if (!tok) { return -OS_FAIL; } jctx->cur = tok; return OS_SUCCESS; } int json_arr_leave_object(jparse_ctx_t *jctx) { if (jctx->cur->parent < 0) { return -OS_FAIL; } jctx->cur = &jctx->tokens[jctx->cur->parent]; return OS_SUCCESS; } int json_arr_get_bool(jparse_ctx_t *jctx, uint32_t index, bool *val) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_bool(jctx, tok, val); } int json_arr_get_int(jparse_ctx_t *jctx, uint32_t index, int *val) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_int(jctx, tok, val); } int json_arr_get_int64(jparse_ctx_t *jctx, uint32_t index, int64_t *val) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_int64(jctx, tok, val); } int json_arr_get_float(jparse_ctx_t *jctx, uint32_t index, float *val) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_PRIMITIVE); if (!tok) { return -OS_FAIL; } return json_tok_to_float(jctx, tok, val); } int json_arr_get_string(jparse_ctx_t *jctx, uint32_t index, char *val, int size) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_STRING); if (!tok) { return -OS_FAIL; } return json_tok_to_string(jctx, tok, val, size); } int json_arr_get_strlen(jparse_ctx_t *jctx, uint32_t index, int *strlen) { json_tok_t *tok = json_arr_get_val_tok(jctx, index, JSMN_STRING); if (!tok) { return -OS_FAIL; } *strlen = tok->end - tok->start; return OS_SUCCESS; } int json_parse_start(jparse_ctx_t *jctx, const char *js, int len) { memset(jctx, 0, sizeof(jparse_ctx_t)); jsmn_init(&jctx->parser); int num_tokens = jsmn_parse(&jctx->parser, js, len, NULL, 0); if (num_tokens <= 0) { return -OS_FAIL; } jctx->num_tokens = num_tokens; jctx->tokens = calloc(num_tokens, sizeof(json_tok_t)); if (!jctx->tokens) { return -OS_FAIL; } jctx->js = js; jsmn_init(&jctx->parser); int ret = jsmn_parse(&jctx->parser, js, len, jctx->tokens, jctx->num_tokens); if (ret <= 0) { free(jctx->tokens); memset(jctx, 0, sizeof(jparse_ctx_t)); return -OS_FAIL; } jctx->cur = jctx->tokens; return OS_SUCCESS; } int json_parse_end(jparse_ctx_t *jctx) { if (jctx->tokens) { free(jctx->tokens); } memset(jctx, 0, sizeof(jparse_ctx_t)); return OS_SUCCESS; } int json_parse_start_static(jparse_ctx_t *jctx, const char *js, int len, json_tok_t *buffer_tokens, int buffer_tokens_max_count) { // Init memset(buffer_tokens, 0, buffer_tokens_max_count * sizeof(json_tok_t)); memset(jctx, 0, sizeof(jparse_ctx_t)); // Check fit jsmn_init(&jctx->parser); int num_tokens = jsmn_parse(&jctx->parser, js, len, NULL, 0); if (num_tokens <= 0 || num_tokens > buffer_tokens_max_count) { return -OS_FAIL; } // Set struct jctx->num_tokens = num_tokens; jctx->tokens = buffer_tokens; jctx->js = js; // Parse jsmn_init(&jctx->parser); int ret = jsmn_parse(&jctx->parser, js, len, jctx->tokens, jctx->num_tokens); if (ret <= 0) { memset(jctx, 0, sizeof(jparse_ctx_t)); return -OS_FAIL; } jctx->cur = jctx->tokens; return OS_SUCCESS; } int json_parse_end_static(jparse_ctx_t *jctx) { memset(jctx, 0, sizeof(jparse_ctx_t)); return OS_SUCCESS; } ================================================ FILE: json_parser/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(json_parser_test) ================================================ FILE: json_parser/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS test_main.c test_json_parser.c PRIV_REQUIRES unity WHOLE_ARCHIVE) ================================================ FILE: json_parser/test_apps/main/idf_component.yml ================================================ dependencies: espressif/json_parser: version: "*" override_path: "../.." ================================================ FILE: json_parser/test_apps/main/test_json_parser.c ================================================ #include #include #include "json_parser.h" #include "unity.h" #define json_test_str "{\n\"str_val\" : \"JSON Parser\",\n" \ "\t\"float_val\" : 2.0,\n" \ "\"int_val\" : 2017,\n" \ "\"bool_val\" : false,\n" \ "\"supported_el\" :\t [\"bool\",\"int\","\ "\"float\",\"str\"" \ ",\"object\",\"array\"],\n" \ "\"features\" : { \"objects\":true, "\ "\"arrays\":\"yes\"},\n"\ "\"int_64\":109174583252}" TEST_CASE("json_parser basic tests", "[json_parser]") { jparse_ctx_t jctx; int ret = json_parse_start(&jctx, json_test_str, strlen(json_test_str)); TEST_ASSERT_EQUAL(OS_SUCCESS, ret); char str_val[64]; int int_val, num_elem; int64_t int64_val; bool bool_val; float float_val; TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_string(&jctx, "str_val", str_val, sizeof(str_val))); TEST_ASSERT_EQUAL_STRING("JSON Parser", str_val); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_float(&jctx, "float_val", &float_val)); TEST_ASSERT(fabs(float_val - 2.0f) < 0.0001f); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_int(&jctx, "int_val", &int_val)); TEST_ASSERT_EQUAL_INT(2017, int_val); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_bool(&jctx, "bool_val", &bool_val)); TEST_ASSERT_EQUAL(false, bool_val); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_array(&jctx, "supported_el", &num_elem)); const char *expected_values[] = {"bool", "int", "float", "str", "object", "array"}; TEST_ASSERT_EQUAL(sizeof(expected_values) / sizeof(expected_values[0]), num_elem); for (int i = 0; i < num_elem; ++i) { TEST_ASSERT_EQUAL(OS_SUCCESS, json_arr_get_string(&jctx, i, str_val, sizeof(str_val))); TEST_ASSERT_EQUAL_STRING(expected_values[i], str_val); } json_obj_leave_array(&jctx); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_object(&jctx, "features")); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_bool(&jctx, "objects", &bool_val)); TEST_ASSERT_EQUAL(true, bool_val); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_string(&jctx, "arrays", str_val, sizeof(str_val))); TEST_ASSERT_EQUAL_STRING("yes", str_val); json_obj_leave_object(&jctx); TEST_ASSERT_EQUAL(OS_SUCCESS, json_obj_get_int64(&jctx, "int_64", &int64_val)); TEST_ASSERT(int64_val == 109174583252); json_parse_end(&jctx); } ================================================ FILE: json_parser/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running json_parser component tests\n"); unity_run_menu(); } ================================================ FILE: json_parser/test_apps/pytest_json_parser.py ================================================ import pytest @pytest.mark.generic def test_json_parser(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: json_parser/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: led_strip/CHANGELOG.md ================================================ ## 3.0.3 - Support WS2816 with 16-bit color ## 3.0.1 - Support WS2811 bit timing ## 3.0.0 - Discontinued support for ESP-IDF v4.x - Added configuration for user-defined color component format ## 2.5.5 - Simplified the led_strip component dependency, the time of full build with ESP-IDF v5.3 can now be shorter. ## 2.5.4 - Inserted extra delay when initialize the SPI LED device, to ensure all LEDs are in the reset state correctly ## 2.5.3 - Extend reset time (280us) to support WS2812B-V5 ## 2.5.2 - Added API reference doc (api.md) ## 2.5.0 - Enabled support for IDF4.4 and above - with RMT backend only - Added API `led_strip_set_pixel_hsv` ## 2.4.0 - Support configurable SPI mode to control leds - recommend enabling DMA when using SPI mode ## 2.3.0 - Support configurable RMT channel size by setting `mem_block_symbols` ## 2.2.0 - Support for 4 components RGBW leds (SK6812): - in led_strip_config_t new fields led_pixel_format, controlling byte format (LED_PIXEL_FORMAT_GRB, LED_PIXEL_FORMAT_GRBW) led_model, used to configure bit timing (LED_MODEL_WS2812, LED_MODEL_SK6812) - new API led_strip_set_pixel_rgbw - new interface type set_pixel_rgbw ## 2.1.0 - Support DMA feature, which offloads the CPU by a lot when it comes to drive a bunch of LEDs - Support various RMT clock sources - Acquire and release the power management lock before and after each refresh - New driver flag: `invert_out` which can invert the led control signal by hardware ## 2.0.0 - Reimplemented the driver using the new RMT driver (`driver/rmt_tx.h`) ## 1.0.0 - Initial driver version, based on the legacy RMT driver (`driver/rmt.h`) ================================================ FILE: led_strip/CMakeLists.txt ================================================ include($ENV{IDF_PATH}/tools/cmake/version.cmake) set(srcs "src/led_strip_api.c") set(public_requires) if(CONFIG_SOC_RMT_SUPPORTED) list(APPEND srcs "src/led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c") endif() # the SPI backend driver relies on some feature that was available in IDF 5.1 if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.1") if(CONFIG_SOC_GPSPI_SUPPORTED) list(APPEND srcs "src/led_strip_spi_dev.c") endif() endif() # Starting from esp-idf v5.3, the RMT and SPI drivers are moved to separate components if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3") list(APPEND public_requires "esp_driver_rmt" "esp_driver_spi") else() list(APPEND public_requires "driver") endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" "interface" REQUIRES ${public_requires}) ================================================ FILE: led_strip/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: led_strip/README.md ================================================ # LED Strip Driver [![Component Registry](https://components.espressif.com/components/espressif/led_strip/badge.svg)](https://components.espressif.com/components/espressif/led_strip) This driver is designed for addressable LEDs like [WS2812](http://world-semi.com/ws2812-family/), where each LED is controlled by a single data line. ## Supported Backend Peripherals The LED strip driver supports two different backend peripherals to generate the timing signals required by addressable LEDs: ### The [RMT](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html) Peripheral This is the most economical way to drive the LEDs because it only consumes one RMT channel, leaving other channels free to use. However, the memory usage increases dramatically with the number of LEDs. If the RMT hardware can't be assist by DMA, the driver will going into interrupt very frequently, thus result in a high CPU usage. What's worse, if the RMT interrupt is delayed or not serviced in time (e.g. if Wi-Fi interrupt happens on the same CPU core), the RMT transaction will be corrupted and the LEDs will display incorrect colors. If you want to use RMT to drive a large number of LEDs, you'd better to enable the DMA feature if possible [^1]. ### The [SPI](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) Peripheral SPI peripheral can also be used to generate the timing required by the LED strip, in a so-called "Clock-less" mode. However this backend is not as economical as the RMT one, because it will take up the whole **bus**. You **CANNOT** connect other devices to the same SPI bus if it's been used by the led_strip, because the led_strip doesn't have the concept of "Chip Select". ## Documentation For detailed information about the LED Strip component, including API reference and user guides, please visit: - **Programming Guide & API Reference**: [LED Strip Documentation](https://espressif.github.io/idf-extra-components/latest/led_strip/index.html) ================================================ FILE: led_strip/docs/Doxyfile ================================================ # Set this to the header file you want INPUT = \ ../include/ \ ../interface/ # The output directory for the generated XML documentation OUTPUT_DIRECTORY = doxygen_output # Warning-related settings, it's recommended to keep them enabled WARN_IF_UNDOC_ENUM_VAL = YES WARN_AS_ERROR = YES # Other common settings FULL_PATH_NAMES = YES STRIP_FROM_PATH = ../ STRIP_FROM_INC_PATH = ../ ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES OPTIMIZE_OUTPUT_FOR_C = YES EXPAND_ONLY_PREDEF = YES EXTRACT_ALL = YES PREDEFINED = $(ENV_DOXYGEN_DEFINES) HAVE_DOT = NO GENERATE_XML = YES XML_OUTPUT = xml GENERATE_HTML = NO HAVE_DOT = NO GENERATE_LATEX = NO QUIET = YES MARKDOWN_SUPPORT = YES ================================================ FILE: led_strip/docs/book.toml ================================================ [book] title = "LED Strip Documentation" language = "en" [output.html] default-theme = "light" git-repository-url = "https://github.com/espressif/idf-extra-components/tree/master/led_strip" edit-url-template = "https://github.com/espressif/idf-extra-components/edit/master/led_strip/docs/{path}" ================================================ FILE: led_strip/docs/src/SUMMARY.md ================================================ # Summary --- # Programming Guide - [LED Strip](index.md) --- # API Reference - [API Reference](api.md) ================================================ FILE: led_strip/docs/src/api.md ================================================ # API Reference
This file is automatically generated by esp-doxybook. DO NOT edit it manually.
================================================ FILE: led_strip/docs/src/index.md ================================================ # LED Strip Programming Guide ## Allocate LED Strip Object with RMT Backend ```c #define BLINK_GPIO 0 /// LED strip common configuration led_strip_config_t strip_config = { .strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line .max_leds = 1, // The number of LEDs in the strip, .led_model = LED_MODEL_WS2812, // LED strip model, it determines the bit timing .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color component format is G-R-B .flags = { .invert_out = false, // don't invert the output signal } }; /// RMT backend specific configuration led_strip_rmt_config_t rmt_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption .resolution_hz = 10 * 1000 * 1000, // RMT counter clock frequency: 10MHz .mem_block_symbols = 64, // the memory size of each RMT channel, in words (4 bytes) .flags = { .with_dma = false, // DMA feature is available on chips like ESP32-S3/P4 } }; /// Create the LED strip object led_strip_handle_t led_strip = NULL; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); ``` --- You can create multiple LED strip objects with different GPIOs and pixel numbers. The backend driver will automatically allocate sufficient RMT channels for you wherever possible. If the RMT channels are not enough, the [led_strip_new_rmt_device](api.md#function-led_strip_new_rmt_device) will return an error. ## Allocate LED Strip Object with SPI Backend ```c #define BLINK_GPIO 0 /// LED strip common configuration led_strip_config_t strip_config = { .strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line .max_leds = 1, // The number of LEDs in the strip, .led_model = LED_MODEL_WS2812, // LED strip model, it determines the bit timing .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color component format is G-R-B .flags = { .invert_out = false, // don't invert the output signal } }; /// SPI backend specific configuration led_strip_spi_config_t spi_config = { .clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption .spi_bus = SPI2_HOST, // SPI bus ID .flags = { .with_dma = true, // Using DMA can improve performance and help drive more LEDs } }; /// Create the LED strip object led_strip_handle_t led_strip = NULL; ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip)); ``` --- The number of LED strip objects can be created depends on how many free SPI controllers are free to use in your project. ## FAQ - How to set the brightness of the LED strip? - You can tune the brightness by scaling the value of each R-G-B element with a **same** factor. But pay attention to the overflow of the value. ================================================ FILE: led_strip/examples/led_strip_rmt_ws2812/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(led_strip_rmt_ws2812) ================================================ FILE: led_strip/examples/led_strip_rmt_ws2812/README.md ================================================ # LED Strip Example (RMT backend + WS2812) This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component. ## How to Use Example ### Hardware Required * A development board with Espressif SoC * A USB cable for Power supply and programming * WS2812 LED strip ### Configure the Example Before project configuration and build, be sure to set the correct chip target using `idf.py set-target `. Then assign the proper GPIO in the [source file](main/led_strip_rmt_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number. ### Build and Flash Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. (To exit the serial monitor, type ``Ctrl-]``.) See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. ## Example Output ```text I (299) gpio: GPIO[8]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (309) example: Created LED strip object with RMT backend I (309) example: Start blinking LED strip ``` ================================================ FILE: led_strip/examples/led_strip_rmt_ws2812/main/CMakeLists.txt ================================================ idf_component_register(SRCS "led_strip_rmt_ws2812_main.c" INCLUDE_DIRS ".") ================================================ FILE: led_strip/examples/led_strip_rmt_ws2812/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/led_strip: version: '^3' override_path: '../../../' ================================================ FILE: led_strip/examples/led_strip_rmt_ws2812/main/led_strip_rmt_ws2812_main.c ================================================ /* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "led_strip.h" #include "esp_log.h" #include "esp_err.h" // Set to 1 to use DMA for driving the LED strip, 0 otherwise // Please note the RMT DMA feature is only available on chips e.g. ESP32-S3/P4 #define LED_STRIP_USE_DMA 0 #if LED_STRIP_USE_DMA // Numbers of the LED in the strip #define LED_STRIP_LED_COUNT 256 #define LED_STRIP_MEMORY_BLOCK_WORDS 1024 // this determines the DMA block size #else // Numbers of the LED in the strip #define LED_STRIP_LED_COUNT 24 #define LED_STRIP_MEMORY_BLOCK_WORDS 0 // let the driver choose a proper memory block size automatically #endif // LED_STRIP_USE_DMA // GPIO assignment #define LED_STRIP_GPIO_PIN 2 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution) #define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000) static const char *TAG = "example"; led_strip_handle_t configure_led(void) { // LED strip general initialization, according to your led board design led_strip_config_t strip_config = { .strip_gpio_num = LED_STRIP_GPIO_PIN, // The GPIO that connected to the LED strip's data line .max_leds = LED_STRIP_LED_COUNT, // The number of LEDs in the strip, .led_model = LED_MODEL_WS2812, // LED strip model .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color order of the strip: GRB .flags = { .invert_out = false, // don't invert the output signal } }; // LED strip backend configuration: RMT led_strip_rmt_config_t rmt_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption .resolution_hz = LED_STRIP_RMT_RES_HZ, // RMT counter clock frequency .mem_block_symbols = LED_STRIP_MEMORY_BLOCK_WORDS, // the memory block size used by the RMT channel .flags = { .with_dma = LED_STRIP_USE_DMA, // Using DMA can improve performance when driving more LEDs } }; // LED Strip object handle led_strip_handle_t led_strip; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); ESP_LOGI(TAG, "Created LED strip object with RMT backend"); return led_strip; } void app_main(void) { led_strip_handle_t led_strip = configure_led(); bool led_on_off = false; ESP_LOGI(TAG, "Start blinking LED strip"); while (1) { if (led_on_off) { /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */ for (int i = 0; i < LED_STRIP_LED_COUNT; i++) { ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5)); } /* Refresh the strip to send data */ ESP_ERROR_CHECK(led_strip_refresh(led_strip)); ESP_LOGI(TAG, "LED ON!"); } else { /* Set all LED off to clear all pixels */ ESP_ERROR_CHECK(led_strip_clear(led_strip)); ESP_LOGI(TAG, "LED OFF!"); } led_on_off = !led_on_off; vTaskDelay(pdMS_TO_TICKS(500)); } } ================================================ FILE: led_strip/examples/led_strip_spi_ws2812/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(led_strip_spi_ws2812) ================================================ FILE: led_strip/examples/led_strip_spi_ws2812/README.md ================================================ # LED Strip Example (SPI backend + WS2812) This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component. ## How to Use Example ### Hardware Required * A development board with Espressif SoC * A USB cable for Power supply and programming * WS2812 LED strip ### Configure the Example Before project configuration and build, be sure to set the correct chip target using `idf.py set-target `. Then assign the proper GPIO in the [source file](main/led_strip_spi_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number. ### Build and Flash Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. (To exit the serial monitor, type ``Ctrl-]``.) See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. ## Example Output ```text I (299) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (309) example: Created LED strip object with SPI backend I (309) example: Start blinking LED strip ``` ================================================ FILE: led_strip/examples/led_strip_spi_ws2812/main/CMakeLists.txt ================================================ idf_component_register(SRCS "led_strip_spi_ws2812_main.c" INCLUDE_DIRS ".") ================================================ FILE: led_strip/examples/led_strip_spi_ws2812/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/led_strip: version: '^3' override_path: '../../../' idf: ">=5.1" ================================================ FILE: led_strip/examples/led_strip_spi_ws2812/main/led_strip_spi_ws2812_main.c ================================================ /* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "led_strip.h" #include "esp_log.h" #include "esp_err.h" // GPIO assignment #define LED_STRIP_GPIO_PIN 2 // Numbers of the LED in the strip #define LED_STRIP_LED_COUNT 24 static const char *TAG = "example"; led_strip_handle_t configure_led(void) { // LED strip general initialization, according to your led board design led_strip_config_t strip_config = { .strip_gpio_num = LED_STRIP_GPIO_PIN, // The GPIO that connected to the LED strip's data line .max_leds = LED_STRIP_LED_COUNT, // The number of LEDs in the strip, .led_model = LED_MODEL_WS2812, // LED strip model // set the color order of the strip: GRB .color_component_format = { .format = { .r_pos = 1, // red is the second byte in the color data .g_pos = 0, // green is the first byte in the color data .b_pos = 2, // blue is the third byte in the color data .num_components = 3, // total 3 color components }, }, .flags = { .invert_out = false, // don't invert the output signal } }; // LED strip backend configuration: SPI led_strip_spi_config_t spi_config = { .clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption .spi_bus = SPI2_HOST, // SPI bus ID .flags = { .with_dma = true, // Using DMA can improve performance and help drive more LEDs } }; // LED Strip object handle led_strip_handle_t led_strip; ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip)); ESP_LOGI(TAG, "Created LED strip object with SPI backend"); return led_strip; } void app_main(void) { led_strip_handle_t led_strip = configure_led(); bool led_on_off = false; ESP_LOGI(TAG, "Start blinking LED strip"); while (1) { if (led_on_off) { /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */ for (int i = 0; i < LED_STRIP_LED_COUNT; i++) { ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5)); } /* Refresh the strip to send data */ ESP_ERROR_CHECK(led_strip_refresh(led_strip)); ESP_LOGI(TAG, "LED ON!"); } else { /* Set all LED off to clear all pixels */ ESP_ERROR_CHECK(led_strip_clear(led_strip)); ESP_LOGI(TAG, "LED OFF!"); } led_on_off = !led_on_off; vTaskDelay(pdMS_TO_TICKS(500)); } } ================================================ FILE: led_strip/idf_component.yml ================================================ version: "3.0.3" description: Driver for Addressable LED Strip (WS2812, etc) url: https://github.com/espressif/idf-extra-components/tree/master/led_strip repository: https://github.com/espressif/idf-extra-components.git documentation: https://espressif.github.io/idf-extra-components/latest/led_strip/index.html issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.0" ================================================ FILE: led_strip/include/led_strip.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "led_strip_rmt.h" #include "led_strip_spi.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Set RGB for a specific pixel * * @param strip: LED strip * @param index: index of pixel to set * @param red: red part of color * @param green: green part of color * @param blue: blue part of color * * @return * - ESP_OK: Set RGB for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred */ esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); /** * @brief Set RGBW for a specific pixel * * @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW) * @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component * * @param strip: LED strip * @param index: index of pixel to set * @param red: red part of color * @param green: green part of color * @param blue: blue part of color * @param white: separate white component * * @return * - ESP_OK: Set RGBW color for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument * - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred */ esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white); /** * @brief Set HSV for a specific pixel * * @param strip: LED strip * @param index: index of pixel to set * @param hue: hue part of color (0 - 360) * @param saturation: saturation part of color (0 - 255, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 127) * @param value: value part of color (0 - 255, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 127) * * @return * - ESP_OK: Set HSV color for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument * - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred */ esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value); /** * @brief Set HSV for a specific pixel in 16-bit resolution * * @param strip: LED strip * @param index: index of pixel to set * @param hue: hue part of color (0 - 360) * @param saturation: saturation part of color (0 - 65535, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 32767) * @param value: value part of color (0 - 65535, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 32767) * * @return * - ESP_OK: Set HSV color for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument * - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred */ esp_err_t led_strip_set_pixel_hsv_16(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint16_t saturation, uint16_t value); /** * @brief Refresh memory colors to LEDs * * @param strip: LED strip * * @return * - ESP_OK: Refresh successfully * - ESP_FAIL: Refresh failed because some other error occurred * * @note: * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. */ esp_err_t led_strip_refresh(led_strip_handle_t strip); /** * @brief Clear LED strip (turn off all LEDs) * * @param strip: LED strip * * @return * - ESP_OK: Clear LEDs successfully * - ESP_FAIL: Clear LEDs failed because some other error occurred */ esp_err_t led_strip_clear(led_strip_handle_t strip); /** * @brief Free LED strip resources * * @param strip: LED strip * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t led_strip_del(led_strip_handle_t strip); #ifdef __cplusplus } #endif ================================================ FILE: led_strip/include/led_strip_rmt.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "led_strip_types.h" #include "esp_idf_version.h" #include "driver/rmt_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief LED Strip RMT specific configuration */ typedef struct { rmt_clock_source_t clk_src; /*!< RMT clock source */ uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */ size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */ /*!< Extra RMT specific driver flags */ struct led_strip_rmt_extra_config { uint32_t with_dma: 1; /*!< Use DMA to transmit data */ } flags; /*!< Extra driver flags */ } led_strip_rmt_config_t; /** * @brief Create LED strip based on RMT TX channel * * @param led_config LED strip configuration * @param rmt_config RMT specific configuration * @param ret_strip Returned LED strip handle * @return * - ESP_OK: create LED strip handle successfully * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory * - ESP_FAIL: create LED strip handle failed because some other error */ esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip); #ifdef __cplusplus } #endif ================================================ FILE: led_strip/include/led_strip_spi.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "driver/spi_master.h" #include "led_strip_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief LED Strip SPI specific configuration */ typedef struct { spi_clock_source_t clk_src; /*!< SPI clock source */ spi_host_device_t spi_bus; /*!< SPI bus ID. Which buses are available depends on the specific chip */ struct { uint32_t with_dma: 1; /*!< Use DMA to transmit data */ } flags; /*!< Extra driver flags */ } led_strip_spi_config_t; /** * @brief Create LED strip based on SPI MOSI channel * * @note Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes. * * @param led_config LED strip configuration * @param spi_config SPI specific configuration * @param ret_strip Returned LED strip handle * @return * - ESP_OK: create LED strip handle successfully * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument * - ESP_ERR_NOT_SUPPORTED: create LED strip handle failed because of unsupported configuration * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory * - ESP_FAIL: create LED strip handle failed because some other error */ esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip); #ifdef __cplusplus } #endif ================================================ FILE: led_strip/include/led_strip_types.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #ifdef __cplusplus extern "C" { #endif /** * @brief Type of LED strip handle */ typedef struct led_strip_t *led_strip_handle_t; /** * @brief LED strip model * @note Different led model may have different timing parameters, so we need to distinguish them. */ typedef enum { LED_MODEL_WS2812, /*!< LED strip model: WS2812 */ LED_MODEL_SK6812, /*!< LED strip model: SK6812 */ LED_MODEL_WS2811, /*!< LED strip model: WS2811 */ LED_MODEL_WS2816, /*!< LED strip model: WS2816 */ LED_MODEL_INVALID /*!< Invalid LED strip model */ } led_model_t; /** * @brief LED color component format * @note The format is used to specify the order of color components in each pixel, also the number of color components. */ typedef union { struct format_layout { uint32_t r_pos: 2; /*!< Position of the red channel in the color order: 0~3 */ uint32_t g_pos: 2; /*!< Position of the green channel in the color order: 0~3 */ uint32_t b_pos: 2; /*!< Position of the blue channel in the color order: 0~3 */ uint32_t w_pos: 2; /*!< Position of the white channel in the color order: 0~3 */ uint32_t reserved: 19; /*!< Reserved */ uint32_t bytes_per_color: 2; /*!< Bytes per color component: 1 or 2. If set to 0, it will fallback to 1 */ uint32_t num_components: 3; /*!< Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3 */ } format; /*!< Format layout */ uint32_t format_id; /*!< Format ID */ } led_color_component_format_t; /// Helper macros to set the color component format #define LED_STRIP_COLOR_COMPONENT_FMT_GRB (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}} #define LED_STRIP_COLOR_COMPONENT_FMT_GRB_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}} #define LED_STRIP_COLOR_COMPONENT_FMT_GRBW (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}} #define LED_STRIP_COLOR_COMPONENT_FMT_GRBW_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}} #define LED_STRIP_COLOR_COMPONENT_FMT_RGB (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}} #define LED_STRIP_COLOR_COMPONENT_FMT_RGB_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}} #define LED_STRIP_COLOR_COMPONENT_FMT_RGBW (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}} #define LED_STRIP_COLOR_COMPONENT_FMT_RGBW_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}} /** * @brief LED Strip common configurations * The common configurations are not specific to any backend peripheral. */ typedef struct { int strip_gpio_num; /*!< GPIO number that used by LED strip */ uint32_t max_leds; /*!< Maximum number of LEDs that can be controlled in a single strip */ led_model_t led_model; /*!< Specifies the LED strip model (e.g., WS2812, SK6812) */ led_color_component_format_t color_component_format; /*!< Specifies the order of color components in each pixel. Use helper macros like `LED_STRIP_COLOR_COMPONENT_FMT_GRB` to set the format */ /*!< LED strip extra driver flags */ struct led_strip_extra_flags { uint32_t invert_out: 1; /*!< Invert output signal */ } flags; /*!< Extra driver flags */ } led_strip_config_t; #ifdef __cplusplus } #endif ================================================ FILE: led_strip/interface/led_strip_interface.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */ /** * @brief LED strip interface definition */ struct led_strip_t { /** * @brief Set RGB for a specific pixel * * @param strip: LED strip * @param index: index of pixel to set * @param red: red part of color * @param green: green part of color * @param blue: blue part of color * * @return * - ESP_OK: Set RGB for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred */ esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); /** * @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component * * @param strip: LED strip * @param index: index of pixel to set * @param red: red part of color * @param green: green part of color * @param blue: blue part of color * @param white: separate white component * * @return * - ESP_OK: Set RGBW color for a specific pixel successfully * - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument * - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred */ esp_err_t (*set_pixel_rgbw)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white); /** * @brief Refresh memory colors to LEDs * * @param strip: LED strip * @param timeout_ms: timeout value for refreshing task * * @return * - ESP_OK: Refresh successfully * - ESP_FAIL: Refresh failed because some other error occurred * * @note: * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. */ esp_err_t (*refresh)(led_strip_t *strip); /** * @brief Clear LED strip (turn off all LEDs) * * @param strip: LED strip * @param timeout_ms: timeout value for clearing task * * @return * - ESP_OK: Clear LEDs successfully * - ESP_FAIL: Clear LEDs failed because some other error occurred */ esp_err_t (*clear)(led_strip_t *strip); /** * @brief Free LED strip resources * * @param strip: LED strip * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t (*del)(led_strip_t *strip); }; #ifdef __cplusplus } #endif ================================================ FILE: led_strip/src/led_strip_api.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_log.h" #include "esp_check.h" #include "led_strip.h" #include "led_strip_interface.h" static const char *TAG = "led_strip"; esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return strip->set_pixel(strip, index, red, green, blue); } esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); uint32_t red = 0; uint32_t green = 0; uint32_t blue = 0; uint32_t rgb_max = value; uint32_t rgb_min = rgb_max * (255 - saturation) / 255; uint32_t i = hue / 60; uint32_t diff = hue % 60; // RGB adjustment amount by hue uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; switch (i) { case 0: red = rgb_max; green = rgb_min + rgb_adj; blue = rgb_min; break; case 1: red = rgb_max - rgb_adj; green = rgb_max; blue = rgb_min; break; case 2: red = rgb_min; green = rgb_max; blue = rgb_min + rgb_adj; break; case 3: red = rgb_min; green = rgb_max - rgb_adj; blue = rgb_max; break; case 4: red = rgb_min + rgb_adj; green = rgb_min; blue = rgb_max; break; default: red = rgb_max; green = rgb_min; blue = rgb_max - rgb_adj; break; } return strip->set_pixel(strip, index, red, green, blue); } esp_err_t led_strip_set_pixel_hsv_16(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint16_t saturation, uint16_t value) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); uint32_t red = 0; uint32_t green = 0; uint32_t blue = 0; uint32_t rgb_max = value; uint32_t rgb_min = rgb_max * (65535 - saturation) / 65535; uint32_t i = hue / 60; uint32_t diff = hue % 60; // RGB adjustment amount by hue uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; switch (i) { case 0: red = rgb_max; green = rgb_min + rgb_adj; blue = rgb_min; break; case 1: red = rgb_max - rgb_adj; green = rgb_max; blue = rgb_min; break; case 2: red = rgb_min; green = rgb_max; blue = rgb_min + rgb_adj; break; case 3: red = rgb_min; green = rgb_max - rgb_adj; blue = rgb_max; break; case 4: red = rgb_min + rgb_adj; green = rgb_min; blue = rgb_max; break; default: red = rgb_max; green = rgb_min; blue = rgb_max - rgb_adj; break; } return strip->set_pixel(strip, index, red, green, blue); } esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return strip->set_pixel_rgbw(strip, index, red, green, blue, white); } esp_err_t led_strip_refresh(led_strip_handle_t strip) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return strip->refresh(strip); } esp_err_t led_strip_clear(led_strip_handle_t strip) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return strip->clear(strip); } esp_err_t led_strip_del(led_strip_handle_t strip) { ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return strip->del(strip); } ================================================ FILE: led_strip/src/led_strip_rmt_dev.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "esp_check.h" #include "driver/rmt_tx.h" #include "led_strip.h" #include "led_strip_interface.h" #include "led_strip_rmt_encoder.h" #define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution #define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4 // the memory size of each RMT channel, in words (4 bytes) #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64 #else #define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48 #endif static const char *TAG = "led_strip_rmt"; typedef struct { led_strip_t base; rmt_channel_handle_t rmt_chan; rmt_encoder_handle_t strip_encoder; uint32_t strip_len; uint8_t bytes_per_pixel; led_color_component_format_t component_fmt; uint8_t pixel_buf[]; } led_strip_rmt_obj; static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) { led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); struct format_layout format = rmt_strip->component_fmt.format; uint32_t start = index * rmt_strip->bytes_per_pixel; uint8_t *pixel_buf = rmt_strip->pixel_buf; uint8_t pos_bytes = format.bytes_per_color; for (uint8_t i = 0; i < format.bytes_per_color; i++) { uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i); pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF; pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF; pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF; if (format.num_components > 3) { pixel_buf[start + format.w_pos * pos_bytes + i] = 0; } } return ESP_OK; } static esp_err_t led_strip_rmt_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) { led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); struct format_layout format = rmt_strip->component_fmt.format; ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); ESP_RETURN_ON_FALSE(format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components"); uint32_t start = index * rmt_strip->bytes_per_pixel; uint8_t *pixel_buf = rmt_strip->pixel_buf; uint8_t pos_bytes = format.bytes_per_color; for (uint8_t i = 0; i < format.bytes_per_color; i++) { uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i); pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF; pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF; pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF; pixel_buf[start + format.w_pos * pos_bytes + i] = (white >> color_shift) & 0xFF; } return ESP_OK; } static esp_err_t led_strip_rmt_refresh(led_strip_t *strip) { led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); rmt_transmit_config_t tx_conf = { .loop_count = 0, }; ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed"); ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf, rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed"); ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed"); ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed"); return ESP_OK; } static esp_err_t led_strip_rmt_clear(led_strip_t *strip) { led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); // Write zero to turn off all leds memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel); return led_strip_rmt_refresh(strip); } static esp_err_t led_strip_rmt_del(led_strip_t *strip) { led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed"); ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed"); free(rmt_strip); return ESP_OK; } esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip) { led_strip_rmt_obj *rmt_strip = NULL; esp_err_t ret = ESP_OK; ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); led_color_component_format_t component_fmt = led_config->color_component_format; // If R/G/B order is not specified, set default GRB order as fallback if (component_fmt.format_id == 0) { component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB; } if (led_config->led_model == LED_MODEL_WS2816) { component_fmt.format.bytes_per_color = 2; } if (component_fmt.format.bytes_per_color == 0) { component_fmt.format.bytes_per_color = 1; } // check the validation of the color component format uint8_t mask = 0; if (component_fmt.format.num_components == 3) { mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos); // Check for invalid values ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); } else if (component_fmt.format.num_components == 4) { mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos); // Check for invalid values ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); } else { ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components); } uint8_t bytes_per_pixel = component_fmt.format.num_components; if (component_fmt.format.bytes_per_color > 1) { bytes_per_pixel *= component_fmt.format.bytes_per_color; } rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel); ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip"); uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION; // for backward compatibility, if the user does not set the clk_src, use the default value rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT; if (rmt_config->clk_src) { clk_src = rmt_config->clk_src; } size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS; // override the default value if the user sets it if (rmt_config->mem_block_symbols) { mem_block_symbols = rmt_config->mem_block_symbols; } rmt_tx_channel_config_t rmt_chan_config = { .clk_src = clk_src, .gpio_num = led_config->strip_gpio_num, .mem_block_symbols = mem_block_symbols, .resolution_hz = resolution, .trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE, .flags.with_dma = rmt_config->flags.with_dma, .flags.invert_out = led_config->flags.invert_out, }; ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed"); led_strip_encoder_config_t strip_encoder_conf = { .resolution = resolution, .led_model = led_config->led_model }; ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed"); rmt_strip->component_fmt = component_fmt; rmt_strip->bytes_per_pixel = bytes_per_pixel; rmt_strip->strip_len = led_config->max_leds; rmt_strip->base.set_pixel = led_strip_rmt_set_pixel; rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw; rmt_strip->base.refresh = led_strip_rmt_refresh; rmt_strip->base.clear = led_strip_rmt_clear; rmt_strip->base.del = led_strip_rmt_del; *ret_strip = &rmt_strip->base; return ESP_OK; err: if (rmt_strip) { if (rmt_strip->rmt_chan) { rmt_del_channel(rmt_strip->rmt_chan); } if (rmt_strip->strip_encoder) { rmt_del_encoder(rmt_strip->strip_encoder); } free(rmt_strip); } return ret; } ================================================ FILE: led_strip/src/led_strip_rmt_encoder.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include "esp_idf_version.h" #include "esp_check.h" #include "esp_attr.h" #include "led_strip_rmt_encoder.h" static const char *TAG = "led_rmt_encoder"; #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) #if CONFIG_RMT_ISR_IRAM_SAFE #define RMT_ENCODER_FUNC_ATTR IRAM_ATTR #else #define RMT_ENCODER_FUNC_ATTR #endif // CONFIG_RMT_ISR_IRAM_SAFE #endif // ESP_IDF_VERSION typedef struct { rmt_encoder_t base; rmt_encoder_t *bytes_encoder; rmt_encoder_t *copy_encoder; int state; rmt_symbol_word_t reset_code; } rmt_led_strip_encoder_t; RMT_ENCODER_FUNC_ATTR static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) { rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; rmt_encode_state_t session_state = 0; rmt_encode_state_t state = 0; size_t encoded_symbols = 0; switch (led_encoder->state) { case 0: // send RGB data encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); if (session_state & RMT_ENCODING_COMPLETE) { led_encoder->state = 1; // switch to next state when current encoding session finished } if (session_state & RMT_ENCODING_MEM_FULL) { state |= RMT_ENCODING_MEM_FULL; goto out; // yield if there's no free space for encoding artifacts } // fall-through case 1: // send reset code encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, sizeof(led_encoder->reset_code), &session_state); if (session_state & RMT_ENCODING_COMPLETE) { led_encoder->state = 0; // back to the initial encoding session state |= RMT_ENCODING_COMPLETE; } if (session_state & RMT_ENCODING_MEM_FULL) { state |= RMT_ENCODING_MEM_FULL; goto out; // yield if there's no free space for encoding artifacts } } out: *ret_state = state; return encoded_symbols; } static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) { rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); rmt_del_encoder(led_encoder->bytes_encoder); rmt_del_encoder(led_encoder->copy_encoder); free(led_encoder); return ESP_OK; } RMT_ENCODER_FUNC_ATTR static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) { rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); rmt_encoder_reset(led_encoder->bytes_encoder); rmt_encoder_reset(led_encoder->copy_encoder); led_encoder->state = 0; return ESP_OK; } esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) { esp_err_t ret = ESP_OK; rmt_led_strip_encoder_t *led_encoder = NULL; ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model"); led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); led_encoder->base.encode = rmt_encode_led_strip; led_encoder->base.del = rmt_del_led_strip_encoder; led_encoder->base.reset = rmt_led_strip_encoder_reset; rmt_bytes_encoder_config_t bytes_encoder_config; uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accommodate WS2812B-V5 if (config->led_model == LED_MODEL_SK6812) { bytes_encoder_config = (rmt_bytes_encoder_config_t) { .bit0 = { .level0 = 1, .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us .level1 = 0, .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us }, .bit1 = { .level0 = 1, .duration0 = 0.6 * config->resolution / 1000000, // T1H=0.6us .level1 = 0, .duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us }, .flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0) }; } else if (config->led_model == LED_MODEL_WS2812) { // different led strip might have its own timing requirements, following parameter is for WS2812 bytes_encoder_config = (rmt_bytes_encoder_config_t) { .bit0 = { .level0 = 1, .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us .level1 = 0, .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us }, .bit1 = { .level0 = 1, .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us .level1 = 0, .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us }, .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 }; } else if (config->led_model == LED_MODEL_WS2811) { // different led strip might have its own timing requirements, following parameter is for WS2811 bytes_encoder_config = (rmt_bytes_encoder_config_t) { .bit0 = { .level0 = 1, .duration0 = 0.5 * config->resolution / 1000000., // T0H=0.5us .level1 = 0, .duration1 = 2.0 * config->resolution / 1000000., // T0L=2.0us }, .bit1 = { .level0 = 1, .duration0 = 1.2 * config->resolution / 1000000., // T1H=1.2us .level1 = 0, .duration1 = 1.3 * config->resolution / 1000000., // T1L=1.3us }, .flags.msb_first = 1 }; reset_ticks = config->resolution / 1000000 * 50 / 2; // divide by 2... signal is sent twice } else if (config->led_model == LED_MODEL_WS2816) { // different led strip might have its own timing requirements, following parameter is for WS2816 bytes_encoder_config = (rmt_bytes_encoder_config_t) { .bit0 = { .level0 = 1, .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us .level1 = 0, .duration1 = 0.95 * config->resolution / 1000000, // T0L=0.95us }, .bit1 = { .level0 = 1, .duration0 = 0.75 * config->resolution / 1000000, // T1H=0.75us .level1 = 0, .duration1 = 0.5 * config->resolution / 1000000, // T1L=0.5us }, .flags.msb_first = 1 }; } else { assert(false); } ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); rmt_copy_encoder_config_t copy_encoder_config = {}; ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); led_encoder->reset_code = (rmt_symbol_word_t) { .level0 = 0, .duration0 = reset_ticks, .level1 = 0, .duration1 = reset_ticks, }; *ret_encoder = &led_encoder->base; return ESP_OK; err: if (led_encoder) { if (led_encoder->bytes_encoder) { rmt_del_encoder(led_encoder->bytes_encoder); } if (led_encoder->copy_encoder) { rmt_del_encoder(led_encoder->copy_encoder); } free(led_encoder); } return ret; } ================================================ FILE: led_strip/src/led_strip_rmt_encoder.h ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "driver/rmt_encoder.h" #include "led_strip_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Type of led strip encoder configuration */ typedef struct { uint32_t resolution; /*!< Encoder resolution, in Hz */ led_model_t led_model; /*!< LED model */ } led_strip_encoder_config_t; /** * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols * * @param[in] config Encoder configuration * @param[out] ret_encoder Returned encoder handle * @return * - ESP_ERR_INVALID_ARG for any invalid arguments * - ESP_ERR_NO_MEM out of memory when creating led strip encoder * - ESP_OK if creating encoder successfully */ esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); #ifdef __cplusplus } #endif ================================================ FILE: led_strip/src/led_strip_spi_dev.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "esp_check.h" #include "esp_rom_gpio.h" #include "soc/spi_periph.h" #include "led_strip.h" #include "led_strip_interface.h" #include "esp_heap_caps.h" #define LED_STRIP_SPI_DEFAULT_RESOLUTION (2.5 * 1000 * 1000) // 2.5MHz resolution #define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4 #define SPI_BYTES_PER_COLOR_BYTE 3 #define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8) static const char *TAG = "led_strip_spi"; typedef struct { led_strip_t base; spi_host_device_t spi_host; spi_device_handle_t spi_device; uint32_t strip_len; uint8_t bytes_per_pixel; led_color_component_format_t component_fmt; uint8_t pixel_buf[]; } led_strip_spi_obj; // please make sure to zero-initialize the buf before calling this function static void __led_strip_spi_bit(uint8_t data, uint8_t *buf) { // Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110 // So a color byte occupies 3 bytes of SPI. *(buf + 2) |= data & BIT(0) ? BIT(2) | BIT(1) : BIT(2); *(buf + 2) |= data & BIT(1) ? BIT(5) | BIT(4) : BIT(5); *(buf + 2) |= data & BIT(2) ? BIT(7) : 0x00; *(buf + 1) |= BIT(0); *(buf + 1) |= data & BIT(3) ? BIT(3) | BIT(2) : BIT(3); *(buf + 1) |= data & BIT(4) ? BIT(6) | BIT(5) : BIT(6); *(buf + 0) |= data & BIT(5) ? BIT(1) | BIT(0) : BIT(1); *(buf + 0) |= data & BIT(6) ? BIT(4) | BIT(3) : BIT(4); *(buf + 0) |= data & BIT(7) ? BIT(7) | BIT(6) : BIT(7); } static esp_err_t led_strip_spi_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) { led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); // 3 pixels take 72bits(9bytes) uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE; uint8_t *pixel_buf = spi_strip->pixel_buf; struct format_layout format = spi_strip->component_fmt.format; memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); uint8_t pos_bytes = format.bytes_per_color; for (uint8_t i = 0; i < format.bytes_per_color; i++) { uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i); __led_strip_spi_bit((red >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.r_pos * pos_bytes + i)]); __led_strip_spi_bit((green >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.g_pos * pos_bytes + i)]); __led_strip_spi_bit((blue >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.b_pos * pos_bytes + i)]); if (format.num_components > 3) { __led_strip_spi_bit(0, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.w_pos * pos_bytes + i)]); } } return ESP_OK; } static esp_err_t led_strip_spi_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) { led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); struct format_layout format = spi_strip->component_fmt.format; ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); ESP_RETURN_ON_FALSE(format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components"); // LED_PIXEL_FORMAT_GRBW takes 96bits(12bytes) uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE; uint8_t *pixel_buf = spi_strip->pixel_buf; memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); uint8_t pos_bytes = format.bytes_per_color; for (uint8_t i = 0; i < format.bytes_per_color; i++) { uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i); __led_strip_spi_bit((red >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.r_pos * pos_bytes + i)]); __led_strip_spi_bit((green >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.g_pos * pos_bytes + i)]); __led_strip_spi_bit((blue >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.b_pos * pos_bytes + i)]); __led_strip_spi_bit((white >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.w_pos * pos_bytes + i)]); } return ESP_OK; } static esp_err_t led_strip_spi_refresh(led_strip_t *strip) { led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); spi_transaction_t tx_conf; memset(&tx_conf, 0, sizeof(tx_conf)); tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE; tx_conf.tx_buffer = spi_strip->pixel_buf; tx_conf.rx_buffer = NULL; ESP_RETURN_ON_ERROR(spi_device_transmit(spi_strip->spi_device, &tx_conf), TAG, "transmit pixels by SPI failed"); return ESP_OK; } static esp_err_t led_strip_spi_clear(led_strip_t *strip) { led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); //Write zero to turn off all leds memset(spi_strip->pixel_buf, 0, spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); uint8_t *buf = spi_strip->pixel_buf; for (int index = 0; index < spi_strip->strip_len * spi_strip->bytes_per_pixel; index++) { __led_strip_spi_bit(0, buf); buf += SPI_BYTES_PER_COLOR_BYTE; } return led_strip_spi_refresh(strip); } static esp_err_t led_strip_spi_del(led_strip_t *strip) { led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); ESP_RETURN_ON_ERROR(spi_bus_remove_device(spi_strip->spi_device), TAG, "delete spi device failed"); ESP_RETURN_ON_ERROR(spi_bus_free(spi_strip->spi_host), TAG, "free spi bus failed"); free(spi_strip); return ESP_OK; } esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip) { led_strip_spi_obj *spi_strip = NULL; esp_err_t ret = ESP_OK; ESP_GOTO_ON_FALSE(led_config && spi_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); led_color_component_format_t component_fmt = led_config->color_component_format; // If R/G/B order is not specified, set default GRB order as fallback if (component_fmt.format_id == 0) { component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB; } if (led_config->led_model == LED_MODEL_WS2816) { component_fmt.format.bytes_per_color = 2; } if (component_fmt.format.bytes_per_color == 0) { component_fmt.format.bytes_per_color = 1; } uint8_t mask = 0; if (component_fmt.format.num_components == 3) { mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos); // Check for invalid values ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); } else if (component_fmt.format.num_components == 4) { mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos); // Check for invalid values ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); } else { ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components); } uint8_t bytes_per_pixel = component_fmt.format.num_components; if (component_fmt.format.bytes_per_color > 1) { bytes_per_pixel *= component_fmt.format.bytes_per_color; } uint32_t mem_caps = MALLOC_CAP_DEFAULT; if (spi_config->flags.with_dma) { // DMA buffer must be placed in internal SRAM mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; } spi_strip = heap_caps_calloc(1, sizeof(led_strip_spi_obj) + led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, mem_caps); ESP_GOTO_ON_FALSE(spi_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for spi strip"); spi_strip->spi_host = spi_config->spi_bus; // for backward compatibility, if the user does not set the clk_src, use the default value spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT; if (spi_config->clk_src) { clk_src = spi_config->clk_src; } spi_bus_config_t spi_bus_cfg = { .mosi_io_num = led_config->strip_gpio_num, //Only use MOSI to generate the signal, set -1 when other pins are not used. .miso_io_num = -1, .sclk_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, }; ESP_GOTO_ON_ERROR(spi_bus_initialize(spi_strip->spi_host, &spi_bus_cfg, spi_config->flags.with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED), err, TAG, "create SPI bus failed"); if (led_config->flags.invert_out == true) { esp_rom_gpio_connect_out_signal(led_config->strip_gpio_num, spi_periph_signal[spi_strip->spi_host].spid_out, true, false); } spi_device_interface_config_t spi_dev_cfg = { .clock_source = clk_src, .command_bits = 0, .address_bits = 0, .dummy_bits = 0, .clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION, .mode = 0, //set -1 when CS is not used .spics_io_num = -1, .queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE, }; ESP_GOTO_ON_ERROR(spi_bus_add_device(spi_strip->spi_host, &spi_dev_cfg, &spi_strip->spi_device), err, TAG, "Failed to add spi device"); //ensure the reset time is enough esp_rom_delay_us(10); int clock_resolution_khz = 0; spi_device_get_actual_freq(spi_strip->spi_device, &clock_resolution_khz); // TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution // But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION // clock_resolution between 2.2MHz to 2.8MHz is supported ESP_GOTO_ON_FALSE((clock_resolution_khz < LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 + 300) && (clock_resolution_khz > LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 - 300), ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported clock resolution:%dKHz", clock_resolution_khz); if (led_config->led_model != LED_MODEL_WS2812) { ESP_LOGW(TAG, "Only support WS2812. The timing requirements for other models may not be met"); } spi_strip->component_fmt = component_fmt; spi_strip->bytes_per_pixel = bytes_per_pixel; spi_strip->strip_len = led_config->max_leds; spi_strip->base.set_pixel = led_strip_spi_set_pixel; spi_strip->base.set_pixel_rgbw = led_strip_spi_set_pixel_rgbw; spi_strip->base.refresh = led_strip_spi_refresh; spi_strip->base.clear = led_strip_spi_clear; spi_strip->base.del = led_strip_spi_del; *ret_strip = &spi_strip->base; return ESP_OK; err: if (spi_strip) { if (spi_strip->spi_device) { spi_bus_remove_device(spi_strip->spi_device); } if (spi_strip->spi_host) { spi_bus_free(spi_strip->spi_host); } free(spi_strip); } return ret; } ================================================ FILE: libjpeg-turbo/.build-test-rules.yml ================================================ libjpeg-turbo/examples/hello_jpeg: enable: - if: (IDF_VERSION_MAJOR >= 5) and (IDF_TARGET in ["esp32", "esp32c3"]) reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: libjpeg-turbo/CMakeLists.txt ================================================ idf_component_register( # We need the dummy source file so that the component # library is not an interface library. This allows to # get the list of include directories from other components # via INCLUDE_DIRECTORIES property later on. SRCS dummy.c) # Determine compilation flags used for building Jpeg-turbo # Flags inherited from IDF build system and other IDF components: set(idf_include_directories $) set(includes "-I$") set(c_flags "${includes} ${extra_defines} ") set(cxx_flags "${includes} ${extra_defines} ") set(common_flags "-ggdb -ffunction-sections -fdata-sections -lm") if(CONFIG_IDF_TARGET_ARCH_XTENSA) set(assert_flags "${assert_flags} -mlongcalls") endif() # We redefine the flags to apply common flags, # like -ffunction-sections -fdata-sections. if(CONFIG_COMPILER_OPTIMIZATION_DEFAULT) set(opt_flags "-Og ${common_flags} ${assert_flags}") set(opt_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE=${opt_flags}) elseif(CONFIG_COMPILER_OPTIMIZATION_SIZE) set(opt_flags "-Os ${common_flags} ${assert_flags}") set(opt_args -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS_MINSIZEREL=${opt_flags}) elseif(CONFIG_COMPILER_OPTIMIZATION_PERF) set(opt_flags "-O3 ${common_flags} ${assert_flags}") set(opt_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE=${opt_flags}) elseif(COMPILER_OPTIMIZATION_NONE) set(opt_flags "-O0 ${common_flags} ${assert_flags}") set(opt_args -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS_DEBUG=${opt_flags}) else() message(FATAL_ERROR "Unsupported optimization level") endif() include(ExternalProject) # Build jpeg-turbo in this directory: set(BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-build) set(lib_path ${BINARY_DIR}/install/lib/libjpeg.a) add_prebuilt_library(libjpeg-turbo ${lib_path}) # Add jpeg-turbo as a subproject. ExternalProject_Add(jpegturbo_proj SOURCE_DIR ${COMPONENT_DIR}/libjpeg-turbo BINARY_DIR ${BINARY_DIR} BUILD_BYPRODUCTS ${lib_path} # These two options are set so that Ninja immediately outputs # the subproject build to the terminal. Otherwise it looks like the # build process "hangs" for too long until jpeg-turbo build is complete. USES_TERMINAL_CONFIGURE TRUE USES_TERMINAL_BUILD TRUE CMAKE_POSITION_INDEPENDENT_CODE ON # Arguments to pass to jpeg-turbo CMake invocation: CMAKE_ARGS -DCMAKE_C_FLAGS=${c_flags} -DCMAKE_CXX_FLAGS=${cxx_flags} ${opt_args} -DCMAKE_INSTALL_PREFIX=${BINARY_DIR}/install -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DWITH_TURBOJPEG=FALSE -DWITH_SIMD=FALSE -DCMAKE_SYSTEM_PROCESSOR=esp32 # Without this parameter the lib make an error -DWITH_ARITH_DEC=TRUE -DWITH_ARITH_ENC=TRUE -DWITH_JPEG8=TRUE -DWITH_JPEG7=TRUE -DENABLE_SHARED=FALSE -DENABLE_STATIC=TRUE -DWITH_TOOLS=FALSE -DWITH_TESTS=FALSE ) # Attach header files to the component library: set_target_properties(${COMPONENT_LIB} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${BINARY_DIR}/install/include) # Make sure the subproject is built before the component library: add_dependencies(${COMPONENT_LIB} jpegturbo_proj) # Finally, link the interface library to the component library: # Attach IDF component dependencies to jpeg libraries foreach(dep ${deps}) target_link_libraries(libjpeg-turbo INTERFACE idf::${dep}) endforeach() # Attach jpeg-turbo libraries to the component library target_link_libraries(${COMPONENT_LIB} INTERFACE libjpeg-turbo) ================================================ FILE: libjpeg-turbo/LICENSE ================================================ libjpeg-turbo Licenses ====================== libjpeg-turbo is covered by two compatible BSD-style open source licenses: - The IJG (Independent JPEG Group) License, which is listed in [README.ijg](README.ijg) This license applies to the libjpeg API library and associated programs, including any code inherited from libjpeg and any modifications to that code. Note that the libjpeg-turbo SIMD source code bears the [zlib License](https://opensource.org/licenses/Zlib), but in the context of the overall libjpeg API library, the terms of the zlib License are subsumed by the terms of the IJG License. - The Modified (3-clause) BSD License, which is listed below This license applies to the TurboJPEG API library and associated programs, as well as the build system. Note that the TurboJPEG API library wraps the libjpeg API library, so in the context of the overall TurboJPEG API library, both the terms of the IJG License and the terms of the Modified (3-clause) BSD License apply. Complying with the libjpeg-turbo Licenses ========================================= This section provides a roll-up of the libjpeg-turbo licensing terms, to the best of our understanding. This is not a license in and of itself. It is intended solely for clarification. 1. If you are distributing a modified version of the libjpeg-turbo source, then: 1. You cannot alter or remove any existing copyright or license notices from the source. **Origin** - Clause 1 of the IJG License - Clause 1 of the Modified BSD License - Clauses 1 and 3 of the zlib License 2. You must add your own copyright notice to the header of each source file you modified, so others can tell that you modified that file. (If there is not an existing copyright header in that file, then you can simply add a notice stating that you modified the file.) **Origin** - Clause 1 of the IJG License - Clause 2 of the zlib License 3. You must include the IJG README file, and you must not alter any of the copyright or license text in that file. **Origin** - Clause 1 of the IJG License 2. If you are distributing only libjpeg-turbo binaries without the source, or if you are distributing an application that statically links with libjpeg-turbo, then: 1. Your product documentation must include a message stating: This software is based in part on the work of the Independent JPEG Group. **Origin** - Clause 2 of the IJG license 2. If your binary distribution includes or uses the TurboJPEG API, then your product documentation must include the text of the Modified BSD License (see below.) **Origin** - Clause 2 of the Modified BSD License 3. You cannot use the name of the IJG or The libjpeg-turbo Project or the contributors thereof in advertising, publicity, etc. **Origin** - IJG License - Clause 3 of the Modified BSD License 4. The IJG and The libjpeg-turbo Project do not warrant libjpeg-turbo to be free of defects, nor do we accept any liability for undesirable consequences resulting from your use of the software. **Origin** - IJG License - Modified BSD License - zlib License The Modified (3-clause) BSD License =================================== Copyright (C)2009-2024 D. R. Commander. All Rights Reserved.
Copyright (C)2015 Viktor Szathmáry. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the libjpeg-turbo Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Why Two Licenses? ================= The zlib License could have been used instead of the Modified (3-clause) BSD License, and since the IJG License effectively subsumes the distribution conditions of the zlib License, this would have effectively placed libjpeg-turbo binary distributions under the IJG License. However, the IJG License specifically refers to the Independent JPEG Group and does not extend attribution and endorsement protections to other entities. Thus, it was desirable to choose a license that granted us the same protections for new code that were granted to the IJG for code derived from their software. ================================================ FILE: libjpeg-turbo/README.md ================================================ # Jpeg-turbo for ESP-IDF [![Component Registry](https://components.espressif.com/components/espressif/libjpeg-turbo/badge.svg)](https://components.espressif.com/components/espressif/libjpeg-turbo) This component ports libjpeg-turbo library into the esp-idf. **libjpeg-turbo is a JPEG image codec** For more information go to https://github.com/libjpeg-turbo/libjpeg-turbo. For ***pull request***, ***bug reports***, and ***feature requests***, go to https://github.com/libjpeg-turbo/libjpeg-turbo. ================================================ FILE: libjpeg-turbo/dummy.c ================================================ /* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(hello-libjpeg) ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/main/CMakeLists.txt ================================================ idf_component_register(SRCS "main.c" "decode_image.c" INCLUDE_DIRS "." EMBED_FILES image32x32.jpg image.jpg REQUIRES esp_timer) ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/main/decode_image.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "decode_image.h" #include "esp_log.h" #include "esp_check.h" #include "freertos/FreeRTOS.h" #include "esp_cpu.h" #include "jpeglib.h" #include "jerror.h" /* Reference the binary-included jpeg file */ extern const uint8_t image_jpg_start[] asm("_binary_image_jpg_start"); extern const uint8_t image_jpg_end[] asm("_binary_image_jpg_end"); extern const uint8_t image32x32_jpg_start[] asm("_binary_image32x32_jpg_start"); extern const uint8_t image32x32_jpg_end[] asm("_binary_image32x32_jpg_end"); /* Define the height and width of the jpeg file. Make sure this matches the actual jpeg dimensions. */ const char *TAG = "ImageDec"; struct my_error_mgr { struct jpeg_error_mgr pub; /* "public" fields */ void *setjmp_buffer; /* for return to caller */ }; typedef struct my_error_mgr *my_error_ptr; void my_error_exit(j_common_ptr cinfo) { printf("Error - my_error_exit()! \n"); } /* Decode the embedded image into pixel lines that can be used with the rest of the logic. */ esp_err_t decode_image(uint16_t **pixels) { *pixels = NULL; esp_err_t ret = ESP_OK; struct my_error_mgr jerr; JSAMPARRAY buffer = NULL; /* Output row buffer */ struct jpeg_decompress_struct jpeg_info; struct jpeg_decompress_struct *cinfo = &jpeg_info; cinfo->err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; struct jpeg_decompress_struct jpeg_info_print; struct jpeg_decompress_struct *cinfo_print = &jpeg_info_print; cinfo_print->err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; jpeg_create_decompress(cinfo); jpeg_mem_src(cinfo, image_jpg_start, (image_jpg_end - image_jpg_start)); (void)jpeg_read_header(cinfo, TRUE); /* We can ignore the return value from jpeg_read_header since * (a) suspension is not possible with the stdio data source, and * (b) we passed TRUE to reject a tables-only JPEG file as an error. * See libjpeg.txt for more info. */ /* emit header for raw PPM format */ printf("P6\n%u %u\n%d\n", cinfo->image_width, cinfo->image_height, cinfo->data_precision == 12 ? MAXJ12SAMPLE : MAXJSAMPLE); printf("jpeg_start_decompress\n"); unsigned int start_b = esp_cpu_get_cycle_count(); jpeg_start_decompress(cinfo); int row_stride = cinfo->output_width * cinfo->output_components; buffer = (*cinfo->mem->alloc_sarray) ((j_common_ptr)cinfo, JPOOL_IMAGE, row_stride, 1); while (cinfo->output_scanline < cinfo->output_height) { /* jpeg_read_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could ask for * more than one scanline at a time if that's more convenient. */ (void)jpeg_read_scanlines(cinfo, buffer, 1); } unsigned int end_b = esp_cpu_get_cycle_count(); printf("jpeg_finish_decompress, time = %i\n", ((unsigned int)(end_b - start_b) / 100000) * 100000); jpeg_finish_decompress(cinfo); printf("jpeg_destroy_decompress\n"); jpeg_destroy_decompress(cinfo); jpeg_create_decompress(cinfo_print); jpeg_mem_src(cinfo_print, image32x32_jpg_start, (image32x32_jpg_end - image32x32_jpg_start)); jpeg_read_header(cinfo_print, TRUE); printf("P6\n%u %u\n%d\n", cinfo_print->image_width, cinfo_print->image_height, cinfo_print->data_precision == 12 ? MAXJ12SAMPLE : MAXJSAMPLE); jpeg_start_decompress(cinfo_print); row_stride = cinfo_print->output_width * cinfo_print->output_components; buffer = (*cinfo_print->mem->alloc_sarray) ((j_common_ptr)cinfo_print, JPOOL_IMAGE, row_stride, 1); printf("Decoded image 32x32:\n"); while (cinfo_print->output_scanline < cinfo_print->output_height) { (void)jpeg_read_scanlines(cinfo_print, buffer, 1); for (int ix = 0; ix < cinfo_print->image_width; ix++) { int val = 0; for (int i = 0; i < cinfo_print->output_components; i++) { val += buffer[0][ix * cinfo_print->output_components + i]; } val = val / cinfo->output_components; if (val > 127) { putchar('#'); } else if (val > 64) { putchar('+'); } else if (val > 32) { putchar('.'); } else { putchar(' '); } } putchar('\n'); } jpeg_finish_decompress(cinfo_print); jpeg_destroy_decompress(cinfo_print); printf("done\n"); return ret; } ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/main/decode_image.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #pragma once #include #include "esp_err.h" #define IMAGE_W 320 #define IMAGE_H 240 #ifdef __cplusplus extern "C" { #endif /** * @brief Decode the jpeg ``image.jpg`` embedded into the program file into pixel data and measure the performance. * * @param pixels A pointer to a pointer for an array of rows, which themselves are an array of pixels. * @return - ESP_OK on successful decode */ esp_err_t decode_image(uint16_t **pixels); #ifdef __cplusplus } #endif ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File dependencies: espressif/libjpeg-turbo: version: "^3.1.0" # This line define the local path of the jpeg-turbo component because this # example is part of the jpeg-turbo component. This line is optional. override_path: "../../.." ## Required IDF version idf: version: ">=5.0.0" ================================================ FILE: libjpeg-turbo/examples/hello_jpeg/main/main.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "decode_image.h" uint16_t *pixels; void app_main(void) { printf("app_main started\n"); decode_image(&pixels); } ================================================ FILE: libjpeg-turbo/idf_component.yml ================================================ version: "3.1.1~1" description: Jpeg-turbo port to ESP url: https://github.com/espressif/idf-extra-components/tree/master/libjpeg-turbo dependencies: idf: ">=5.0.0" ================================================ FILE: libjpeg-turbo/sbom_libjpeg.yml ================================================ name: libjpeg-turbo version: 3.1.1 cpe: cpe:3.1.1:a:libjpeg:libjpeg:{}:*:*:*:*:*:*:* supplier: 'Organization: libjpeg-turbo' description: libjpeg-turbo is a JPEG image codec library url: https://github.com/libjpeg-turbo/libjpeg-turbo hash: 98c458381f0938b2cea222f5b278b4ced6f70805 ================================================ FILE: libpng/.build-test-rules.yml ================================================ libpng/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: libpng/CMakeLists.txt ================================================ idf_component_register(INCLUDE_DIRS . libpng SRC_DIRS libpng) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-maybe-uninitialized) ================================================ FILE: libpng/LICENSE ================================================ COPYRIGHT NOTICE, DISCLAIMER, and LICENSE ========================================= PNG Reference Library License version 2 --------------------------------------- * Copyright (c) 1995-2019 The PNG Reference Library Authors. * Copyright (c) 2018-2019 Cosmin Truta. * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. * Copyright (c) 1996-1997 Andreas Dilger. * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. The software is supplied "as is", without warranty of any kind, express or implied, including, without limitation, the warranties of merchantability, fitness for a particular purpose, title, and non-infringement. In no event shall the Copyright owners, or anyone distributing the software, be liable for any damages or other liability, whether in contract, tort or otherwise, arising from, out of, or in connection with the software, or the use or other dealings in the software, even if advised of the possibility of such damage. Permission is hereby granted to use, copy, modify, and distribute this software, or portions hereof, for any purpose, without fee, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated, but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This Copyright notice may not be removed or altered from any source or altered source distribution. PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35) ----------------------------------------------------------------------- libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are derived from libpng-1.0.6, and are distributed according to the same disclaimer and license as libpng-1.0.6 with the following individuals added to the list of Contributing Authors: Simon-Pierre Cadieux Eric S. Raymond Mans Rullgard Cosmin Truta Gilles Vollant James Yu Mandar Sahastrabuddhe Google Inc. Vadim Barkov and with the following additions to the disclaimer: There is no warranty against interference with your enjoyment of the library or against infringement. There is no warranty that our efforts or the library will fulfill any of your particular purposes or needs. This library is provided with all faults, and the entire risk of satisfactory quality, performance, accuracy, and effort is with the user. Some files in the "contrib" directory and some configure-generated files that are distributed with libpng have other copyright owners, and are released under other open source licenses. libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from libpng-0.96, and are distributed according to the same disclaimer and license as libpng-0.96, with the following individuals added to the list of Contributing Authors: Tom Lane Glenn Randers-Pehrson Willem van Schaik libpng versions 0.89, June 1996, through 0.96, May 1997, are Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, and are distributed according to the same disclaimer and license as libpng-0.88, with the following individuals added to the list of Contributing Authors: John Bowler Kevin Bracey Sam Bushell Magnus Holmgren Greg Roelofs Tom Tanner Some files in the "scripts" directory have other copyright owners, but are released under this license. libpng versions 0.5, May 1995, through 0.88, January 1996, are Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. For the purposes of this copyright and license, "Contributing Authors" is defined as the following set of individuals: Andreas Dilger Dave Martindale Guy Eric Schalnat Paul Schmidt Tim Wegner The PNG Reference Library is supplied "AS IS". The Contributing Authors and Group 42, Inc. disclaim all warranties, expressed or implied, including, without limitation, the warranties of merchantability and of fitness for any purpose. The Contributing Authors and Group 42, Inc. assume no liability for direct, indirect, incidental, special, exemplary, or consequential damages, which may result from the use of the PNG Reference Library, even if advised of the possibility of such damage. Permission is hereby granted to use, copy, modify, and distribute this source code, or portions hereof, for any purpose, without fee, subject to the following restrictions: 1. The origin of this source code must not be misrepresented. 2. Altered versions must be plainly marked as such and must not be misrepresented as being the original source. 3. This Copyright notice may not be removed or altered from any source or altered source distribution. The Contributing Authors and Group 42, Inc. specifically permit, without fee, and encourage the use of this source code as a component to supporting the PNG file format in commercial products. If you use this source code in a product, acknowledgment is not required but would be appreciated. ================================================ FILE: libpng/README.md ================================================ # PNG image encoding and decoding library [![Component Registry](https://components.espressif.com/components/espressif/libpng/badge.svg)](https://components.espressif.com/components/espressif/libpng) This is an IDF component for libpng library. For usage instructions, please refer to the official documentation: http://www.libpng.org/pub/png/libpng.html ================================================ FILE: libpng/idf_component.yml ================================================ version: "1.6.58" description: Portable Network Graphics(png) C library url: https://github.com/espressif/idf-extra-components/tree/master/libpng repository: "https://github.com/espressif/idf-extra-components.git" repository_info: path: "libpng" documentation: "http://www.libpng.org/pub/png/pngdocs.html" issues: "https://github.com/espressif/idf-extra-components/issues" dependencies: idf: ">=5.0" zlib: version: "^1.2.13" override_path: "../zlib" sbom: manifests: - path: sbom_libpng.yml dest: libpng ================================================ FILE: libpng/pnglibconf.h ================================================ /* pnglibconf.h - library build configuration */ /* libpng version 1.6.58 */ /* Copyright (c) 2018-2025 Cosmin Truta */ /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */ /* This code is released under the libpng license. */ /* For conditions of distribution and use, see the disclaimer */ /* and license in png.h */ /* pnglibconf.h */ /* Machine generated file: DO NOT EDIT */ /* Derived from: scripts/pnglibconf.dfa */ #ifndef PNGLCONF_H #define PNGLCONF_H /* options */ #define PNG_16BIT_SUPPORTED #define PNG_ALIGNED_MEMORY_SUPPORTED /*#undef PNG_ARM_NEON_API_SUPPORTED*/ /*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ #define PNG_BENIGN_ERRORS_SUPPORTED #define PNG_BENIGN_READ_ERRORS_SUPPORTED /*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ #define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED #define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED #define PNG_COLORSPACE_SUPPORTED #define PNG_CONSOLE_IO_SUPPORTED #define PNG_CONVERT_tIME_SUPPORTED /*#undef PNG_DISABLE_ADLER32_CHECK_SUPPORTED*/ #define PNG_EASY_ACCESS_SUPPORTED /*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ #define PNG_ERROR_TEXT_SUPPORTED #define PNG_FIXED_POINT_SUPPORTED #define PNG_FLOATING_ARITHMETIC_SUPPORTED #define PNG_FLOATING_POINT_SUPPORTED #define PNG_FORMAT_AFIRST_SUPPORTED #define PNG_FORMAT_BGR_SUPPORTED #define PNG_GAMMA_SUPPORTED #define PNG_GET_PALETTE_MAX_SUPPORTED #define PNG_HANDLE_AS_UNKNOWN_SUPPORTED #define PNG_INCH_CONVERSIONS_SUPPORTED #define PNG_INFO_IMAGE_SUPPORTED #define PNG_IO_STATE_SUPPORTED /*#undef PNG_MIPS_MMI_API_SUPPORTED*/ /*#undef PNG_MIPS_MMI_CHECK_SUPPORTED*/ /*#undef PNG_MIPS_MSA_API_SUPPORTED*/ /*#undef PNG_MIPS_MSA_CHECK_SUPPORTED*/ #define PNG_MNG_FEATURES_SUPPORTED #define PNG_POINTER_INDEXING_SUPPORTED /*#undef PNG_POWERPC_VSX_API_SUPPORTED*/ /*#undef PNG_POWERPC_VSX_CHECK_SUPPORTED*/ #define PNG_PROGRESSIVE_READ_SUPPORTED #define PNG_READ_16BIT_SUPPORTED #define PNG_READ_ALPHA_MODE_SUPPORTED #define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED #define PNG_READ_BACKGROUND_SUPPORTED #define PNG_READ_BGR_SUPPORTED #define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED #define PNG_READ_COMPOSITE_NODIV_SUPPORTED #define PNG_READ_COMPRESSED_TEXT_SUPPORTED #define PNG_READ_EXPAND_16_SUPPORTED #define PNG_READ_EXPAND_SUPPORTED #define PNG_READ_FILLER_SUPPORTED #define PNG_READ_GAMMA_SUPPORTED #define PNG_READ_GET_PALETTE_MAX_SUPPORTED #define PNG_READ_GRAY_TO_RGB_SUPPORTED #define PNG_READ_INTERLACING_SUPPORTED #define PNG_READ_INT_FUNCTIONS_SUPPORTED #define PNG_READ_INVERT_ALPHA_SUPPORTED #define PNG_READ_INVERT_SUPPORTED #define PNG_READ_OPT_PLTE_SUPPORTED #define PNG_READ_PACKSWAP_SUPPORTED #define PNG_READ_PACK_SUPPORTED #define PNG_READ_QUANTIZE_SUPPORTED #define PNG_READ_RGB_TO_GRAY_SUPPORTED #define PNG_READ_SCALE_16_TO_8_SUPPORTED #define PNG_READ_SHIFT_SUPPORTED #define PNG_READ_STRIP_16_TO_8_SUPPORTED #define PNG_READ_STRIP_ALPHA_SUPPORTED #define PNG_READ_SUPPORTED #define PNG_READ_SWAP_ALPHA_SUPPORTED #define PNG_READ_SWAP_SUPPORTED #define PNG_READ_TEXT_SUPPORTED #define PNG_READ_TRANSFORMS_SUPPORTED #define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED #define PNG_READ_USER_CHUNKS_SUPPORTED #define PNG_READ_USER_TRANSFORM_SUPPORTED #define PNG_READ_bKGD_SUPPORTED #define PNG_READ_cHRM_SUPPORTED #define PNG_READ_cICP_SUPPORTED #define PNG_READ_cLLI_SUPPORTED #define PNG_READ_eXIf_SUPPORTED #define PNG_READ_gAMA_SUPPORTED #define PNG_READ_hIST_SUPPORTED #define PNG_READ_iCCP_SUPPORTED #define PNG_READ_iTXt_SUPPORTED #define PNG_READ_mDCV_SUPPORTED #define PNG_READ_oFFs_SUPPORTED #define PNG_READ_pCAL_SUPPORTED #define PNG_READ_pHYs_SUPPORTED #define PNG_READ_sBIT_SUPPORTED #define PNG_READ_sCAL_SUPPORTED #define PNG_READ_sPLT_SUPPORTED #define PNG_READ_sRGB_SUPPORTED #define PNG_READ_tEXt_SUPPORTED #define PNG_READ_tIME_SUPPORTED #define PNG_READ_tRNS_SUPPORTED #define PNG_READ_zTXt_SUPPORTED #define PNG_SAVE_INT_32_SUPPORTED #define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED #define PNG_SEQUENTIAL_READ_SUPPORTED #define PNG_SETJMP_SUPPORTED #define PNG_SET_OPTION_SUPPORTED #define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED #define PNG_SET_USER_LIMITS_SUPPORTED #define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED #define PNG_SIMPLIFIED_READ_BGR_SUPPORTED #define PNG_SIMPLIFIED_READ_SUPPORTED #define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED #define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED #define PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED #define PNG_SIMPLIFIED_WRITE_SUPPORTED #define PNG_STDIO_SUPPORTED #define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED #define PNG_TEXT_SUPPORTED #define PNG_TIME_RFC1123_SUPPORTED #define PNG_UNKNOWN_CHUNKS_SUPPORTED #define PNG_USER_CHUNKS_SUPPORTED #define PNG_USER_LIMITS_SUPPORTED #define PNG_USER_MEM_SUPPORTED #define PNG_USER_TRANSFORM_INFO_SUPPORTED #define PNG_USER_TRANSFORM_PTR_SUPPORTED #define PNG_WARNINGS_SUPPORTED #define PNG_WRITE_16BIT_SUPPORTED #define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED #define PNG_WRITE_BGR_SUPPORTED #define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED #define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED #define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED #define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED #define PNG_WRITE_FILLER_SUPPORTED #define PNG_WRITE_FILTER_SUPPORTED #define PNG_WRITE_FLUSH_SUPPORTED #define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED #define PNG_WRITE_INTERLACING_SUPPORTED #define PNG_WRITE_INT_FUNCTIONS_SUPPORTED #define PNG_WRITE_INVERT_ALPHA_SUPPORTED #define PNG_WRITE_INVERT_SUPPORTED #define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED #define PNG_WRITE_PACKSWAP_SUPPORTED #define PNG_WRITE_PACK_SUPPORTED #define PNG_WRITE_SHIFT_SUPPORTED #define PNG_WRITE_SUPPORTED #define PNG_WRITE_SWAP_ALPHA_SUPPORTED #define PNG_WRITE_SWAP_SUPPORTED #define PNG_WRITE_TEXT_SUPPORTED #define PNG_WRITE_TRANSFORMS_SUPPORTED #define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED #define PNG_WRITE_USER_TRANSFORM_SUPPORTED #define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED #define PNG_WRITE_bKGD_SUPPORTED #define PNG_WRITE_cHRM_SUPPORTED #define PNG_WRITE_cICP_SUPPORTED #define PNG_WRITE_cLLI_SUPPORTED #define PNG_WRITE_eXIf_SUPPORTED #define PNG_WRITE_gAMA_SUPPORTED #define PNG_WRITE_hIST_SUPPORTED #define PNG_WRITE_iCCP_SUPPORTED #define PNG_WRITE_iTXt_SUPPORTED #define PNG_WRITE_mDCV_SUPPORTED #define PNG_WRITE_oFFs_SUPPORTED #define PNG_WRITE_pCAL_SUPPORTED #define PNG_WRITE_pHYs_SUPPORTED #define PNG_WRITE_sBIT_SUPPORTED #define PNG_WRITE_sCAL_SUPPORTED #define PNG_WRITE_sPLT_SUPPORTED #define PNG_WRITE_sRGB_SUPPORTED #define PNG_WRITE_tEXt_SUPPORTED #define PNG_WRITE_tIME_SUPPORTED #define PNG_WRITE_tRNS_SUPPORTED #define PNG_WRITE_zTXt_SUPPORTED #define PNG_bKGD_SUPPORTED #define PNG_cHRM_SUPPORTED #define PNG_cICP_SUPPORTED #define PNG_cLLI_SUPPORTED #define PNG_eXIf_SUPPORTED #define PNG_gAMA_SUPPORTED #define PNG_hIST_SUPPORTED #define PNG_iCCP_SUPPORTED #define PNG_iTXt_SUPPORTED #define PNG_mDCV_SUPPORTED #define PNG_oFFs_SUPPORTED #define PNG_pCAL_SUPPORTED #define PNG_pHYs_SUPPORTED #define PNG_sBIT_SUPPORTED #define PNG_sCAL_SUPPORTED #define PNG_sPLT_SUPPORTED #define PNG_sRGB_SUPPORTED #define PNG_tEXt_SUPPORTED #define PNG_tIME_SUPPORTED #define PNG_tRNS_SUPPORTED #define PNG_zTXt_SUPPORTED /* end of options */ /* settings */ #define PNG_API_RULE 0 #define PNG_DEFAULT_READ_MACROS 1 #define PNG_GAMMA_THRESHOLD_FIXED 5000 #define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE #define PNG_INFLATE_BUF_SIZE 1024 #define PNG_LINKAGE_API extern #define PNG_LINKAGE_CALLBACK extern #define PNG_LINKAGE_DATA extern #define PNG_LINKAGE_FUNCTION extern #define PNG_MAX_GAMMA_8 11 #define PNG_QUANTIZE_BLUE_BITS 5 #define PNG_QUANTIZE_GREEN_BITS 5 #define PNG_QUANTIZE_RED_BITS 5 #define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) #define PNG_TEXT_Z_DEFAULT_STRATEGY 0 #define PNG_USER_CHUNK_CACHE_MAX 1000 #define PNG_USER_CHUNK_MALLOC_MAX 8000000 #define PNG_USER_HEIGHT_MAX 1000000 #define PNG_USER_WIDTH_MAX 1000000 #define PNG_ZBUF_SIZE 8192 #define PNG_ZLIB_VERNUM 0 /* unknown */ #define PNG_Z_DEFAULT_COMPRESSION (-1) #define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 #define PNG_Z_DEFAULT_STRATEGY 1 #define PNG_sCAL_PRECISION 5 #define PNG_sRGB_PROFILE_CHECKS 2 /* end of settings */ #endif /* PNGLCONF_H */ ================================================ FILE: libpng/sbom_libpng.yml ================================================ name: libpng version: 1.6.58 cpe: - cpe:2.3:a:pnggroup:libpng:{}:*:*:*:*:*:*:* - cpe:2.3:a:libpng:libpng:{}:*:*:*:*:*:*:* supplier: 'Organization: pnggroup' description: Portable Network Graphics support, official PNG reference library url: https://github.com/pnggroup/libpng hash: 3061454d980de7d53608f594194cfac722721d2a cve-exclude-list: - cve: CVE-2026-33636 reason: Resolved in version 1.6.56 - cve: CVE-2026-33416 reason: Resolved in version 1.6.56 - cve: CVE-2026-3713 reason: Resolved in version 1.6.56 - cve: CVE-2026-34757 reason: Resolved in version 1.6.57 ================================================ FILE: libpng/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(libpng_test) ================================================ FILE: libpng/test_apps/main/CMakeLists.txt ================================================ idf_component_register( SRCS test_libpng.c test_main.c PRIV_REQUIRES unity WHOLE_ARCHIVE EMBED_FILES "in.png" "out.pgm") ================================================ FILE: libpng/test_apps/main/idf_component.yml ================================================ dependencies: espressif/libpng: version: "*" override_path: "../.." ================================================ FILE: libpng/test_apps/main/out.pgm ================================================ P5 522 52 255 ¶঒۶_YYYYbYY|YYYkYYYYYYYYY]YYiYYkiYYZZ_ZgZpZkjZ_ZgYYYYnYYƸYYYZZZZZZZZZZZZZZYYYYYrYYYYfZZZZzZZZbc_t__jZ|ZZzYYcsYYZYYYYuZZӾZZZZZZZZZZӾkkbYYYkkkٝ|bY_溅iYYfkkYYYbkkYYZkk|YYّrYY]knґnYY]kkiYYckYY_kks_YYjZZjzZZZ_ZZgZbmZzYYYYYYYYY]YYYYYYY{YYYYYYYY]YYYYYYYYYYrYYbYYbYYYYYYbZYYZYYYYYYgYYwYYYYYYZYYYYYYY]ǠYYgYYYYYYYYZZ_Zjۺྺ⾼ܼZZ֌ZZննZZp²ٷྺͲZZõո²ܶnnYYYjnntYYYYbsZYYYzYYYffYYYnn_YY]nnnYYiYYYYYYYY_r]YYYfYYYYYYbr]YYYkYYYYY_r_YYYZvYYYYcr]YYb|YY]YYYYnkYYYYZZZZZZgZZZZ~ZZpZZZZvpZezZZZZziZZZZpiZnZ_ZZZZZZZZbZZZZw_ZZZZbtZZZZZZZkiZZZZZ}ZZgZZZ_ZZpZZZZv߃ZZZZZmpZuZZbZZZvpZZZZiZZZZZ}ZZZZpZZZ_YYYYYYiYYYYYYYYZYYjYYYYYYYYYtYYYYYYY|YYYwYYYfYYYYYY~YYYYYbYYYrYY]ZZZZZZmZZZZZZZpZZk_ZZZZZZcZZkeZZZZZZpZZZZZZZ_ZZ_ZZZZZZZZZiZZZZZZZg|ZZZZZZZZZZZZZZZZZZZZZZZZcZZvZZZZZZZzZZk_ZZZZZZcgZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZcZZZZzZZZZZZZYYkuYYnYYrYY_kkjYYYYYYYgYYYYYsYYY{YY]YYikYYZYYYYY_YYZ_YYkZZZZZZZZ˨_ZZZZZZʹgZZmZZZZʚZZZZZZ˭bZbZZZZɭZZ_ZZtζvZZjZZj٭_ZZpZZZZ_ƞiZZZZZZʤ_ZZZZZZʹgZZmZZ}ӖZZguZp{ZZZZZZ_ZwZZZZZ_ƞiZZZZZZZeăZZZYYYYYYY]YYYYYYYY_YYtYYfYYZiYYnYY_YYYYYYYbYYY{YYY檆ZZsuZZpZZ_ZZbZZZbZZZZ_ZZZZ__ZZZ_ZZZZZmZZZZZZbZgbbZZpgZZbZZiZZZbZZeZeZZZZbZZpZZZbZZbbZZpuZZZZjcZZtYYYYnYYYYYYZYYbYY{~YYZYYYYYYYYYYYYYbYYfYYbYYYY]ZZZZpZZZZZkZZZZZZZZw~ZZZZZZZZZZZcZZvZZZZbZgZZZvZZkZZZZZZbwZnZZcZZmZZbZgZZpcZZZZZYYrYYYYYYYYYYYYYYYYYYYwYY~YYbYYYYbYYYYYjYYZYYsYYYYYbYYYYZZZZZZwZZZZcZZiZZZZZZgZZv_ZiZ_ZZܼZZZZbZZZZzZZZZZZcZZgZZkZZZZZZZZZZzZZZZnZZYYZ_YYYYYYYYYYYYYZYYYYYY]~YYbYYYYYYYwYYbYYYYknYYZYYYYZYYYrYYZZZZZZZZZZZZZZwZZZZZZZZZZzZZZZZZZZZZZZ_ZZZZZZ㭃iZZZZZZZZZZZZZZZZZZZb|ZkZZ~ZZkZZ㭃iZZZZZZZZZZZZZYYtYYYٚiYYYYYbYYYYYYYnYYYYv~YYYYYYYYYZYYgYYicYYcZbZZZZ~ZZZZZZZZZZZZZZbZZZkZZZZZZZZZZZZvZZZZZjsZZgZZZZZZZZZZ|ZZuZZZZZZgZZZZZ~ZZsZZbZcZZugZZZZZZZZZZZZZZpZZYYYYYҊYYYbYYYYYfYYkYYrYYbYYiYYYY]YYsYY]YYbYYZZ{kZ_iZZgZ_ZZ~ZZZZwZZZZZZZZZZZZj˗iZZZcZZnbZZktZZbZbbZgZZ~ZZ彊bZZZ~ZiZZmZZ~ZZbZZktZZkZ_wZZZZpkYYYYYYYYYYiYYuYYYYYYYYYYYYYYYYwYYYYYYYYnYYZZZZzZZZZZzeZZZZc}ZZ_ZjZZZZccZ}ZZZZZbZZZZZZ_ZeZZ_ZZeZZZZcZZZZZZZZZZZZZ_ZeZZz_ZiZZYYYbYYpnsYYYYY|vYYZYYYYYZjYYYYY]cYYYYkYYiYY~gYYYYY_YYYZZZZZZZnZZZZZZZbZcZZZZiZZZZbZcgZZͺZZZZZZZZZZZgZZZZZZZú|ZZZeZZZZZZZZZZZZZZZZYYYYYYYYY]YYvYY]YY]ZYYYY]YYYYtcYYYYYYY|bYYZfYYYYYYYY{ͤZYYYZYZYYYpZYYwZZZZZZZp|ZZ}ZZZbZZgZZZZeZZwZZZeZZZ_ZZt{ZZZZvZZZZZZ_ZZZZZzuZZZZZbZZgZZjZZZZZZvZZeZbZZ_ZZZZZZZZZYYYtjYYYcbYYYZYYYbkYYYYYYbYYYYYwYY~YY]nYYYYuYYwYY_kYYYYYYYYYbvYYYYYYYYYYYsYYY_YYYZzbYYYY_ZgZZ}ZZcZjjZZ_ZZgZbtZZZZZZZZZZbujZZZZZZZZZZ_ZZknZZb_ZZ|‡ZZbZZbZZ_ZZZZsZZeZngZZcZZgZbtZZZZZZκpZZZcjZ__ZjZZ}ZZ_ZZZZZZZZiZ_YYYYYnYYYYYYYYbYYYYYYYYYZYYYY~YYnYYcYYԀYYYYYYYZZYYzYYYYYYY]YYbYY|pYYYYYYYYYYY_YYYYYYYYbZZeZebZuZZZZZZZiwZZ_ZZZZZZ_ZZmZZZZZZZZwZZiZ|ZZZZbZZZZZZZbZZZZZZZZbZZZZZsZZZZZZ_eZZZZmZZZZZZZnwZZ_ZZZZZZ_ZZZZZZZZpZZZZvZZZZsZZZZZZ_eZZeZepZZZZwZYYZiYYYYfjYYYY]tpYYYYYsYYYY_]YYYYzYYZZYYYY~YYYY䴅snYYYZ]YYYYfZZZZZZmZZZj_ZiwZZZczZZZZsԪ{_ZmZZۖkZŹbZZZez_ZZZc_Zg_ZZZjZmZZgZZZk_ZiwZZZcnZZZZiZbZZZZZZ_ZZZjZmZZZZpZZ˽YYsYYYϾZZZZjZ|ZZZZZZssssssssssssssYYrrYYrZZeebZZZZggggggggggggZZZZZbwYYYYYYYYYYYYYYkYYYYZYYYZZZZe{ZZmmZwZZZZZZZZZZZZZjZ_ZZZZ昊YYYZYYYfgYYY{pZZpZZZZniZZZcZZZZzpZZZ_YYbcYYYYYYYYYwc_zZZZZZZZ__c_zҙfYYYYYrӵľ ================================================ FILE: libpng/test_apps/main/test_libpng.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "unity.h" #include "png.h" extern const uint8_t in_png_start[] asm("_binary_in_png_start"); extern const uint8_t in_png_end[] asm("_binary_in_png_end"); extern const uint8_t out_pgm_start[] asm("_binary_out_pgm_start"); extern const uint8_t out_pgm_end[] asm("_binary_out_pgm_end"); TEST_CASE("load a png image", "[libpng]") { png_image image; memset(&image, 0, (sizeof image)); image.version = PNG_IMAGE_VERSION; const uint8_t *buf = &in_png_start[0]; const size_t buf_len = in_png_end - in_png_start; const size_t expected_width = 522; const size_t expected_height = 52; TEST_ASSERT(png_image_begin_read_from_memory(&image, buf, buf_len)); image.format = PNG_FORMAT_GRAY; int stride = PNG_IMAGE_ROW_STRIDE(image); int buf_size = PNG_IMAGE_SIZE(image); TEST_ASSERT_EQUAL(expected_width, image.width); TEST_ASSERT_EQUAL(expected_height, image.height); png_bytep buffer = malloc(buf_size); TEST_ASSERT_NOT_NULL(buffer); TEST_ASSERT(png_image_finish_read(&image, NULL, buffer, stride, NULL)); FILE *expected = fmemopen((void *)out_pgm_start, out_pgm_end - out_pgm_start, "r"); TEST_ASSERT_NOT_NULL(expected); // skip the header fscanf(expected, "P5\n%*d %*d\n%*d\n"); // check the binary data for (int i = 0; i < buf_size; i++) { uint8_t expected_byte; TEST_ASSERT_EQUAL(1, fread(&expected_byte, 1, 1, expected)); TEST_ASSERT_EQUAL(expected_byte, buffer[i]); } fclose(expected); free(buffer); } ================================================ FILE: libpng/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running libpng component tests\n"); unity_run_menu(); } ================================================ FILE: libpng/test_apps/pytest_libpng.py ================================================ import pytest @pytest.mark.generic def test_libpng(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: libpng/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: libsodium/.build-test-rules.yml ================================================ libsodium/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: libsodium/CMakeLists.txt ================================================ set(SRC libsodium/src/libsodium) # Derived from libsodium/src/libsodium/Makefile.am # (ignoring the !MINIMAL set) set(srcs "${SRC}/crypto_aead/chacha20poly1305/aead_chacha20poly1305.c" "${SRC}/crypto_aead/xchacha20poly1305/aead_xchacha20poly1305.c" "${SRC}/crypto_aead/aegis256/aead_aegis256.c" "${SRC}/crypto_aead/aegis256/aegis256_soft.c" "${SRC}/crypto_aead/aegis128l/aead_aegis128l.c" "${SRC}/crypto_aead/aegis128l/aegis128l_soft.c" "${SRC}/crypto_auth/crypto_auth.c" "${SRC}/crypto_auth/hmacsha256/auth_hmacsha256.c" "${SRC}/crypto_auth/hmacsha512/auth_hmacsha512.c" "${SRC}/crypto_auth/hmacsha512256/auth_hmacsha512256.c" "${SRC}/crypto_box/crypto_box.c" "${SRC}/crypto_box/crypto_box_easy.c" "${SRC}/crypto_box/crypto_box_seal.c" "${SRC}/crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c" "${SRC}/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c" "${SRC}/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c" "${SRC}/crypto_core/ed25519/core_ed25519.c" "${SRC}/crypto_core/ed25519/core_ristretto255.c" "${SRC}/crypto_core/ed25519/ref10/ed25519_ref10.c" "${SRC}/crypto_core/hchacha20/core_hchacha20.c" "${SRC}/crypto_core/hsalsa20/core_hsalsa20.c" "${SRC}/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c" "${SRC}/crypto_core/keccak1600/keccak1600.c" "${SRC}/crypto_core/keccak1600/ref/keccak1600_ref.c" "${SRC}/crypto_core/salsa/ref/core_salsa_ref.c" "${SRC}/crypto_core/softaes/softaes.c" "${SRC}/crypto_generichash/blake2b/generichash_blake2.c" "${SRC}/crypto_generichash/blake2b/ref/blake2b-compress-avx2.c" "${SRC}/crypto_generichash/blake2b/ref/blake2b-compress-ref.c" "${SRC}/crypto_generichash/blake2b/ref/blake2b-compress-ssse3.c" "${SRC}/crypto_generichash/blake2b/ref/blake2b-ref.c" "${SRC}/crypto_generichash/blake2b/ref/generichash_blake2b.c" "${SRC}/crypto_generichash/crypto_generichash.c" "${SRC}/crypto_ipcrypt/crypto_ipcrypt.c" "${SRC}/crypto_ipcrypt/ipcrypt_soft.c" "${SRC}/crypto_hash/crypto_hash.c" "${SRC}/crypto_hash/sha256/hash_sha256.c" "${SRC}/crypto_hash/sha512/hash_sha512.c" "${SRC}/crypto_kdf/blake2b/kdf_blake2b.c" "${SRC}/crypto_kdf/crypto_kdf.c" "${SRC}/crypto_kdf/hkdf/kdf_hkdf_sha256.c" "${SRC}/crypto_kdf/hkdf/kdf_hkdf_sha512.c" "${SRC}/crypto_kx/crypto_kx.c" "${SRC}/crypto_onetimeauth/crypto_onetimeauth.c" "${SRC}/crypto_onetimeauth/poly1305/donna/poly1305_donna.c" "${SRC}/crypto_onetimeauth/poly1305/onetimeauth_poly1305.c" "${SRC}/crypto_pwhash/argon2/argon2-core.c" "${SRC}/crypto_pwhash/argon2/argon2-encoding.c" "${SRC}/crypto_pwhash/argon2/argon2-fill-block-avx2.c" "${SRC}/crypto_pwhash/argon2/argon2-fill-block-avx512f.c" "${SRC}/crypto_pwhash/argon2/argon2-fill-block-ref.c" "${SRC}/crypto_pwhash/argon2/argon2-fill-block-ssse3.c" "${SRC}/crypto_pwhash/argon2/argon2.c" "${SRC}/crypto_pwhash/argon2/blake2b-long.c" "${SRC}/crypto_pwhash/argon2/pwhash_argon2i.c" "${SRC}/crypto_pwhash/argon2/pwhash_argon2id.c" "${SRC}/crypto_pwhash/crypto_pwhash.c" "${SRC}/crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c" "${SRC}/crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c" "${SRC}/crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c" "${SRC}/crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c" "${SRC}/crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c" "${SRC}/crypto_scalarmult/crypto_scalarmult.c" "${SRC}/crypto_scalarmult/curve25519/ref10/x25519_ref10.c" "${SRC}/crypto_scalarmult/curve25519/sandy2x/consts.S" "${SRC}/crypto_scalarmult/curve25519/sandy2x/curve25519_sandy2x.c" "${SRC}/crypto_scalarmult/curve25519/sandy2x/fe51_invert.c" "${SRC}/crypto_scalarmult/curve25519/sandy2x/fe51_mul.S" "${SRC}/crypto_scalarmult/curve25519/sandy2x/fe51_nsquare.S" "${SRC}/crypto_scalarmult/curve25519/sandy2x/fe51_pack.S" "${SRC}/crypto_scalarmult/curve25519/sandy2x/fe_frombytes_sandy2x.c" "${SRC}/crypto_scalarmult/curve25519/sandy2x/ladder.S" "${SRC}/crypto_scalarmult/curve25519/sandy2x/sandy2x.S" "${SRC}/crypto_scalarmult/curve25519/scalarmult_curve25519.c" "${SRC}/crypto_scalarmult/ed25519/ref10/scalarmult_ed25519_ref10.c" "${SRC}/crypto_scalarmult/ristretto255/ref10/scalarmult_ristretto255_ref10.c" "${SRC}/crypto_secretbox/crypto_secretbox.c" "${SRC}/crypto_secretbox/crypto_secretbox_easy.c" "${SRC}/crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c" "${SRC}/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c" "${SRC}/crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c" "${SRC}/crypto_shorthash/crypto_shorthash.c" "${SRC}/crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c" "${SRC}/crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c" "${SRC}/crypto_shorthash/siphash24/shorthash_siphash24.c" "${SRC}/crypto_shorthash/siphash24/shorthash_siphashx24.c" "${SRC}/crypto_sign/crypto_sign.c" "${SRC}/crypto_sign/ed25519/ref10/keypair.c" "${SRC}/crypto_sign/ed25519/ref10/obsolete.c" "${SRC}/crypto_sign/ed25519/ref10/open.c" "${SRC}/crypto_sign/ed25519/ref10/sign.c" "${SRC}/crypto_sign/ed25519/sign_ed25519.c" "${SRC}/crypto_stream/chacha20/dolbeau/chacha20_dolbeau-avx2.c" "${SRC}/crypto_stream/chacha20/dolbeau/chacha20_dolbeau-ssse3.c" "${SRC}/crypto_stream/chacha20/ref/chacha20_ref.c" "${SRC}/crypto_stream/chacha20/stream_chacha20.c" "${SRC}/crypto_stream/crypto_stream.c" "${SRC}/crypto_stream/salsa20/ref/salsa20_ref.c" "${SRC}/crypto_stream/salsa20/stream_salsa20.c" "${SRC}/crypto_stream/salsa20/xmm6/salsa20_xmm6-asm.S" "${SRC}/crypto_stream/salsa20/xmm6/salsa20_xmm6.c" "${SRC}/crypto_stream/salsa20/xmm6int/salsa20_xmm6int-avx2.c" "${SRC}/crypto_stream/salsa20/xmm6int/salsa20_xmm6int-sse2.c" "${SRC}/crypto_stream/salsa2012/ref/stream_salsa2012_ref.c" "${SRC}/crypto_stream/salsa2012/stream_salsa2012.c" "${SRC}/crypto_stream/salsa208/ref/stream_salsa208_ref.c" "${SRC}/crypto_stream/salsa208/stream_salsa208.c" "${SRC}/crypto_stream/xchacha20/stream_xchacha20.c" "${SRC}/crypto_stream/xsalsa20/stream_xsalsa20.c" "${SRC}/crypto_verify/verify.c" "${SRC}/crypto_xof/shake128/xof_shake128.c" "${SRC}/crypto_xof/shake128/ref/shake128_ref.c" "${SRC}/crypto_xof/shake256/xof_shake256.c" "${SRC}/crypto_xof/shake256/ref/shake256_ref.c" "${SRC}/crypto_xof/turboshake128/xof_turboshake128.c" "${SRC}/crypto_xof/turboshake128/ref/turboshake128_ref.c" "${SRC}/crypto_xof/turboshake256/xof_turboshake256.c" "${SRC}/crypto_xof/turboshake256/ref/turboshake256_ref.c" "${SRC}/randombytes/randombytes.c" "${SRC}/sodium/codecs.c" "${SRC}/sodium/core.c" "${SRC}/sodium/runtime.c" "${SRC}/sodium/utils.c" "${SRC}/sodium/version.c" "port/randombytes_esp32.c") if(CONFIG_LIBSODIUM_USE_MBEDTLS_SHA) list(APPEND srcs "port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c" "port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c") set(include_dirs port_include ${SRC}/include) set(priv_include_dirs port port_include/sodium ${SRC}/include/sodium) else() list(APPEND srcs "${SRC}/crypto_hash/sha256/cp/hash_sha256_cp.c" "${SRC}/crypto_hash/sha512/cp/hash_sha512_cp.c") set(include_dirs ${SRC}/include port_include) set(priv_include_dirs ${SRC}/include/sodium port_include/sodium port) endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS "${priv_include_dirs}" REQUIRES mbedtls) target_compile_definitions(${COMPONENT_LIB} PRIVATE CONFIGURED NATIVE_LITTLE_ENDIAN HAVE_WEAK_SYMBOLS __STDC_LIMIT_MACROS __STDC_CONSTANT_MACROS ) # patch around warnings in third-party files set_source_files_properties( ${SRC}/crypto_pwhash/argon2/pwhash_argon2i.c ${SRC}/crypto_pwhash/argon2/pwhash_argon2id.c ${SRC}/crypto_pwhash/argon2/argon2-core.c ${SRC}/crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c PROPERTIES COMPILE_FLAGS -Wno-type-limits ) set_source_files_properties( ${SRC}/sodium/utils.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable ) set_source_files_properties( ${SRC}/crypto_pwhash/argon2/argon2-fill-block-ref.c PROPERTIES COMPILE_FLAGS -Wno-unknown-pragmas ) # Temporary suppress "fallthrough" warnings until they are fixed in libsodium repo set_source_files_properties( ${SRC}/crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c ${SRC}/crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c PROPERTIES COMPILE_FLAGS -Wno-implicit-fallthrough ) set_source_files_properties( ${SRC}/randombytes/randombytes.c PROPERTIES COMPILE_FLAGS -DRANDOMBYTES_DEFAULT_IMPLEMENTATION ) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function) if(CONFIG_LIBSODIUM_USE_MBEDTLS_SHA) target_compile_options(${COMPONENT_LIB} PRIVATE "$<$:SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/port_include/sodium/crypto_hash_sha256.h>" "$<$:SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/port_include/sodium/crypto_hash_sha512.h>") endif() if(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE) # some libsodium variables are only used for asserts target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable) endif() if(CONFIG_COMPILER_STATIC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # GCC's -fanalyzer causes the compiler to hang on libsodium's elliptic curve code # due to combinatorial state explosion in the interprocedural analysis target_compile_options(${COMPONENT_LIB} PRIVATE "-fno-analyzer") endif() ================================================ FILE: libsodium/Kconfig ================================================ menu "libsodium" config LIBSODIUM_USE_MBEDTLS_SHA bool "Use mbedTLS SHA256 & SHA512 implementations" default y help If this option is enabled, libsodium will use thin wrappers around mbedTLS for SHA256 & SHA512 operations. This saves some code size if mbedTLS is also used. endmenu # libsodium ================================================ FILE: libsodium/idf_component.yml ================================================ version: "1.0.21" description: libsodium port to ESP url: https://github.com/espressif/idf-extra-components/tree/master/libsodium dependencies: idf: ">=4.2" sbom: manifests: - path: sbom_libsodium.yml dest: libsodium ================================================ FILE: libsodium/port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c ================================================ /* * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include /* Keep forward-compatibility with Mbed TLS 3.x */ #if (MBEDTLS_VERSION_NUMBER < 0x03000000) #define MBEDTLS_2_X_COMPAT #else /* !(MBEDTLS_VERSION_NUMBER < 0x03000000) */ /* Macro wrapper for struct's private members */ #ifndef MBEDTLS_ALLOW_PRIVATE_ACCESS #define MBEDTLS_ALLOW_PRIVATE_ACCESS #endif /* MBEDTLS_ALLOW_PRIVATE_ACCESS */ #endif /* !(MBEDTLS_VERSION_NUMBER < 0x03000000) */ /* For MbedTLS 4.x support using PSA Crypto */ #if (MBEDTLS_VERSION_NUMBER >= 0x04000000) #define MBEDTLS_PSA_CRYPTO #endif #include "crypto_hash_sha256.h" #include int crypto_hash_sha256_init(crypto_hash_sha256_state *state) { #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; status = psa_crypto_init(); if (status != PSA_SUCCESS) { return -1; } state->_psa_op = psa_hash_operation_init(); status = psa_hash_setup(&state->_psa_op, PSA_ALG_SHA_256); if (status != PSA_SUCCESS) { return -1; } return 0; #else mbedtls_sha256_init(&state->ctx); #ifdef MBEDTLS_2_X_COMPAT int ret = mbedtls_sha256_starts_ret(&state->ctx, 0); #else int ret = mbedtls_sha256_starts(&state->ctx, 0); #endif /* MBEDTLS_2_X_COMPAT */ if (ret != 0) { return ret; } return 0; #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha256_update(crypto_hash_sha256_state *state, const unsigned char *in, unsigned long long inlen) { if (in == NULL && inlen > 0) { return -1; } #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; status = psa_hash_update(&state->_psa_op, in, inlen); if (status != PSA_SUCCESS) { psa_hash_abort(&state->_psa_op); return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT int ret = mbedtls_sha256_update_ret(&state->ctx, in, inlen); #else int ret = mbedtls_sha256_update(&state->ctx, in, inlen); #endif /* MBEDTLS_2_X_COMPAT */ if (ret != 0) { return ret; } return 0; #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha256_final(crypto_hash_sha256_state *state, unsigned char *out) { #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; size_t hash_len; status = psa_hash_finish(&state->_psa_op, out, crypto_hash_sha256_BYTES, &hash_len); if (status != PSA_SUCCESS || hash_len != crypto_hash_sha256_BYTES) { psa_hash_abort(&state->_psa_op); return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT return mbedtls_sha256_finish_ret(&state->ctx, out); #else return mbedtls_sha256_finish(&state->ctx, out); #endif /* MBEDTLS_2_X_COMPAT */ #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha256(unsigned char *out, const unsigned char *in, unsigned long long inlen) { if (in == NULL && inlen > 0) { return -1; } #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; size_t hash_len; status = psa_hash_compute(PSA_ALG_SHA_256, in, inlen, out, crypto_hash_sha256_BYTES, &hash_len); if (status != PSA_SUCCESS || hash_len != crypto_hash_sha256_BYTES) { return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT return mbedtls_sha256_ret(in, inlen, out, 0); #else return mbedtls_sha256(in, inlen, out, 0); #endif /* MBEDTLS_2_X_COMPAT */ #endif /* !MBEDTLS_PSA_CRYPTO */ } ================================================ FILE: libsodium/port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c ================================================ /* * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include /* Keep forward-compatibility with Mbed TLS 3.x */ #if (MBEDTLS_VERSION_NUMBER < 0x03000000) #define MBEDTLS_2_X_COMPAT #else /* !(MBEDTLS_VERSION_NUMBER < 0x03000000) */ /* Macro wrapper for struct's private members */ #ifndef MBEDTLS_ALLOW_PRIVATE_ACCESS #define MBEDTLS_ALLOW_PRIVATE_ACCESS #endif /* MBEDTLS_ALLOW_PRIVATE_ACCESS */ #endif /* !(MBEDTLS_VERSION_NUMBER < 0x03000000) */ /* For MbedTLS 4.x support using PSA crypto */ #if (MBEDTLS_VERSION_NUMBER >= 0x04000000) #define MBEDTLS_PSA_CRYPTO #endif #include "crypto_hash_sha512.h" #include int crypto_hash_sha512_init(crypto_hash_sha512_state *state) { #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; status = psa_crypto_init(); if (status != PSA_SUCCESS) { return -1; } state->_psa_op = psa_hash_operation_init(); status = psa_hash_setup(&state->_psa_op, PSA_ALG_SHA_512); if (status != PSA_SUCCESS) { return -1; } return 0; #else mbedtls_sha512_init(&state->ctx); #ifdef MBEDTLS_2_X_COMPAT int ret = mbedtls_sha512_starts_ret(&state->ctx, 0); #else int ret = mbedtls_sha512_starts(&state->ctx, 0); #endif /* MBEDTLS_2_X_COMPAT */ if (ret != 0) { return ret; } return 0; #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha512_update(crypto_hash_sha512_state *state, const unsigned char *in, unsigned long long inlen) { if (inlen > 0 && in == NULL) { return -1; } #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; status = psa_hash_update(&state->_psa_op, in, inlen); if (status != PSA_SUCCESS) { psa_hash_abort(&state->_psa_op); return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT int ret = mbedtls_sha512_update_ret(&state->ctx, in, inlen); #else int ret = mbedtls_sha512_update(&state->ctx, in, inlen); #endif /* MBEDTLS_2_X_COMPAT */ if (ret != 0) { return ret; } return 0; #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha512_final(crypto_hash_sha512_state *state, unsigned char *out) { #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; size_t hash_len; status = psa_hash_finish(&state->_psa_op, out, crypto_hash_sha512_BYTES, &hash_len); if (status != PSA_SUCCESS || hash_len != crypto_hash_sha512_BYTES) { psa_hash_abort(&state->_psa_op); return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT return mbedtls_sha512_finish_ret(&state->ctx, out); #else return mbedtls_sha512_finish(&state->ctx, out); #endif /* MBEDTLS_2_X_COMPAT */ #endif /* !MBEDTLS_PSA_CRYPTO */ } int crypto_hash_sha512(unsigned char *out, const unsigned char *in, unsigned long long inlen) { if (inlen > 0 && in == NULL) { return -1; } #ifdef MBEDTLS_PSA_CRYPTO psa_status_t status; size_t hash_len; status = psa_hash_compute(PSA_ALG_SHA_512, in, inlen, out, crypto_hash_sha512_BYTES, &hash_len); if (status != PSA_SUCCESS || hash_len != crypto_hash_sha512_BYTES) { return -1; } return 0; #else #ifdef MBEDTLS_2_X_COMPAT return mbedtls_sha512_ret(in, inlen, out, 0); #else return mbedtls_sha512(in, inlen, out, 0); #endif /* MBEDTLS_2_X_COMPAT */ #endif /* !MBEDTLS_PSA_CRYPTO */ } ================================================ FILE: libsodium/port/randombytes_esp32.c ================================================ /* * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #if __has_include("esp_random.h") #include "esp_random.h" #else #include "esp_system.h" #endif #include "randombytes_internal.h" static const char *randombytes_esp32xx_implementation_name(void) { return CONFIG_IDF_TARGET; } /* Plug the ESP32 hardware RNG into libsodium's custom RNG support, as per https://download.libsodium.org/doc/advanced/custom_rng.html Note that this RNG is selected by default (see randombytes_default.h), so there is no need to call randombytes_set_implementation(). */ const struct randombytes_implementation randombytes_esp32_implementation = { .implementation_name = randombytes_esp32xx_implementation_name, .random = esp_random, .stir = NULL, .uniform = NULL, .buf = esp_fill_random, .close = NULL, }; ================================================ FILE: libsodium/port/randombytes_internal.h ================================================ /* * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once # include "export.h" # include "randombytes.h" SODIUM_EXPORT extern const struct randombytes_implementation randombytes_esp32_implementation; /* Defining RANDOMBYTES_DEFAULT_IMPLEMENTATION here allows us to compile with the ESP32 hardware implementation as the default. No need to call randombytes_set_implementation(). Doing it in the header like this is easier than passing it via a -D argument to gcc. */ #undef RANDOMBYTES_DEFAULT_IMPLEMENTATION #define RANDOMBYTES_DEFAULT_IMPLEMENTATION &randombytes_esp32_implementation ================================================ FILE: libsodium/port_include/sodium/crypto_hash_sha256.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef crypto_hash_sha256_H #define crypto_hash_sha256_H #include #include /* For MbedTLS 4.x support using PSA Crypto */ #if (MBEDTLS_VERSION_NUMBER >= 0x04000000) #define MBEDTLS_PSA_CRYPTO #endif #ifdef MBEDTLS_PSA_CRYPTO #include "psa/crypto.h" #else #include "mbedtls/sha256.h" #endif #ifdef __cplusplus extern "C" { #endif typedef struct crypto_hash_sha256_state { #ifdef MBEDTLS_PSA_CRYPTO psa_hash_operation_t _psa_op; #else mbedtls_sha256_context ctx; #endif /* MBEDTLS_PSA_CRYPTO */ } crypto_hash_sha256_state; SODIUM_EXPORT size_t crypto_hash_sha256_statebytes(void); #define crypto_hash_sha256_BYTES 32U SODIUM_EXPORT size_t crypto_hash_sha256_bytes(void); SODIUM_EXPORT int crypto_hash_sha256(unsigned char *out, const unsigned char *in, unsigned long long inlen) __attribute__ ((nonnull(1))); SODIUM_EXPORT int crypto_hash_sha256_init(crypto_hash_sha256_state *state) __attribute__ ((nonnull)); SODIUM_EXPORT int crypto_hash_sha256_update(crypto_hash_sha256_state *state, const unsigned char *in, unsigned long long inlen) __attribute__ ((nonnull(1))); SODIUM_EXPORT int crypto_hash_sha256_final(crypto_hash_sha256_state *state, unsigned char *out) __attribute__ ((nonnull)); #ifdef __cplusplus } #endif #endif /* crypto_hash_sha256_H */ ================================================ FILE: libsodium/port_include/sodium/crypto_hash_sha512.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef crypto_hash_sha512_H #define crypto_hash_sha512_H #include #include /* For MbedTLS 4.x support using PSA Crypto */ #if (MBEDTLS_VERSION_NUMBER >= 0x04000000) #define MBEDTLS_PSA_CRYPTO #endif #ifdef MBEDTLS_PSA_CRYPTO #include "psa/crypto.h" #else #include "mbedtls/sha512.h" #endif #ifdef __cplusplus extern "C" { #endif typedef struct crypto_hash_sha512_state { #ifdef MBEDTLS_PSA_CRYPTO psa_hash_operation_t _psa_op; #else mbedtls_sha512_context ctx; #endif /* MBEDTLS_PSA_CRYPTO */ } crypto_hash_sha512_state; SODIUM_EXPORT size_t crypto_hash_sha512_statebytes(void); #define crypto_hash_sha512_BYTES 64U SODIUM_EXPORT size_t crypto_hash_sha512_bytes(void); SODIUM_EXPORT int crypto_hash_sha512(unsigned char *out, const unsigned char *in, unsigned long long inlen) __attribute__ ((nonnull(1))); SODIUM_EXPORT int crypto_hash_sha512_init(crypto_hash_sha512_state *state) __attribute__ ((nonnull)); SODIUM_EXPORT int crypto_hash_sha512_update(crypto_hash_sha512_state *state, const unsigned char *in, unsigned long long inlen) __attribute__ ((nonnull(1))); SODIUM_EXPORT int crypto_hash_sha512_final(crypto_hash_sha512_state *state, unsigned char *out) __attribute__ ((nonnull)); #ifdef __cplusplus } #endif #endif /* crypto_hash_sha512_H */ ================================================ FILE: libsodium/port_include/sodium/version.h ================================================ /* * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef sodium_version_H #define sodium_version_H #include /* IMPORTANT: As we don't use autotools, these version are not automatically updated if we change submodules. They need to be changed manually. */ #define SODIUM_VERSION_STRING "1.0.21" /* Note: these are not the same as the overall version, see configure.ac for the relevant macros */ #define SODIUM_LIBRARY_VERSION_MAJOR 26 #define SODIUM_LIBRARY_VERSION_MINOR 3 #ifdef __cplusplus extern "C" { #endif SODIUM_EXPORT const char *sodium_version_string(void); SODIUM_EXPORT int sodium_library_version_major(void); SODIUM_EXPORT int sodium_library_version_minor(void); SODIUM_EXPORT int sodium_library_minimal(void); #ifdef __cplusplus } #endif #endif ================================================ FILE: libsodium/port_include/sodium.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef port_sodium_wrapper_H #define port_sodium_wrapper_H /* Pre-include port-specific hash state headers via angle brackets. * Angle brackets search -I paths directly, so port_include/sodium/ * is found before the original. Once included, their guards are set, * so when the original sodium.h tries to include them via relative * double-quote paths, they are silently skipped. */ #include #include /* Delegate to the original sodium.h. #include_next skips port_include/ * and finds the next sodium.h in the search path. */ #include_next "sodium.h" #endif /* port_sodium_wrapper_H */ ================================================ FILE: libsodium/sbom_libsodium.yml ================================================ name: libsodium version: "1.0.21" cpe: cpe:2.3:a:jedisct1:libsodium:{}:*:*:*:*:*:*:* supplier: 'Person: Frank Denis (jedisct1)' description: A modern, portable, easy to use crypto library url: https://github.com/jedisct1/libsodium hash: d24faf56214469b354b01c8ba36257e04737101e cve-exclude-list: - cve: CVE-2025-69277 reason: Resolved in version 1.0.21 with commit f2da4cd8cb26 ================================================ FILE: libsodium/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(libsodium_test) ================================================ FILE: libsodium/test_apps/main/CMakeLists.txt ================================================ get_filename_component(LS_TESTDIR "${CMAKE_CURRENT_LIST_DIR}/../../libsodium/test/default" ABSOLUTE) set(TEST_CASES "aead_aegis128l;aead_aegis256;chacha20;aead_chacha20poly1305;box;box2;ed25519_convert;sign;hash") foreach(test_case ${TEST_CASES}) file(GLOB test_case_file "${LS_TESTDIR}/${test_case}.c") list(APPEND TEST_CASES_FILES ${test_case_file}) file(GLOB test_case_expected_output "${LS_TESTDIR}/${test_case}.exp") list(APPEND TEST_CASES_EXP_FILES ${test_case_expected_output}) endforeach() idf_component_register(SRCS "${TEST_CASES_FILES}" "test_sodium.c" "test_main.c" PRIV_INCLUDE_DIRS "." "${LS_TESTDIR}/../quirks" PRIV_REQUIRES unity EMBED_TXTFILES ${TEST_CASES_EXP_FILES} WHOLE_ARCHIVE) # The libsodium test suite is designed to be run each test case as an executable on a desktop computer and uses # filesystem to write & then compare contents of each file. # # For now, use their "BROWSER_TEST" mode with these hacks so that # multiple test cases can be combined into one ELF file. # # Run each test case from test_sodium.c as CASENAME_xmain(). foreach(test_case_file ${TEST_CASES_FILES}) get_filename_component(test_case ${test_case_file} NAME_WE) set_source_files_properties(${test_case_file} PROPERTIES COMPILE_FLAGS # This would generate 'warning "main" redefined' warnings at runtime, which are # silenced here. Only other solution involves patching libsodium's cmptest.h. "-Dxmain=${test_case}_xmain -Dmain=${test_case}_main -Wp,-w") endforeach() # this seems odd, but it prevents the libsodium test harness from # trying to write to a file! add_definitions(-DBROWSER_TESTS) ================================================ FILE: libsodium/test_apps/main/idf_component.yml ================================================ dependencies: espressif/libsodium: version: "*" override_path: "../.." ================================================ FILE: libsodium/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running libsodium component tests\n"); unity_run_menu(); } ================================================ FILE: libsodium/test_apps/main/test_sodium.c ================================================ /* * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "unity.h" #include "sodium/crypto_hash_sha256.h" #include "sodium/crypto_hash_sha512.h" #ifdef CONFIG_LIBSODIUM_USE_MBEDTLS_SHA /* * Added these static assert checks to verify that the port headers are being included * and the state structs are the expected size. If these fail, it likely means that the * port headers are not included correctly */ #ifdef MBEDTLS_PSA_CRYPTO _Static_assert(sizeof(crypto_hash_sha256_state) == sizeof(psa_hash_operation_t), "crypto_hash_sha256_state must wrap psa_hash_operation_t on PSA path"); _Static_assert(sizeof(crypto_hash_sha512_state) == sizeof(psa_hash_operation_t), "crypto_hash_sha512_state must wrap psa_hash_operation_t on PSA path"); #else _Static_assert(sizeof(crypto_hash_sha256_state) == sizeof(mbedtls_sha256_context), "crypto_hash_sha256_state must wrap mbedtls_sha256_context on MbedTLS path"); _Static_assert(sizeof(crypto_hash_sha512_state) == sizeof(mbedtls_sha512_context), "crypto_hash_sha512_state must wrap mbedtls_sha512_context on MbedTLS path"); #endif /* MBEDTLS_PSA_CRYPTO */ #endif /* CONFIG_LIBSODIUM_USE_MBEDTLS_SHA */ #define LIBSODIUM_TEST(name_) \ extern int name_ ## _xmain(void); \ extern const uint8_t name_ ## _exp_start[] asm("_binary_" #name_ "_exp_start"); \ extern const uint8_t name_ ## _exp_end[] asm("_binary_" #name_ "_exp_end"); \ TEST_CASE("" #name_ " test vectors", "[libsodium]") { \ printf("Running " #name_ "\n"); \ FILE* old_stdout = stdout; \ char* test_output; \ size_t test_output_size; \ FILE* test_output_stream = open_memstream(&test_output, &test_output_size); \ stdout = test_output_stream; \ TEST_ASSERT_EQUAL(0, name_ ## _xmain()); \ fclose(test_output_stream); \ stdout = old_stdout; \ const char *expected = (const char*) &name_ ## _exp_start[0]; \ TEST_ASSERT_EQUAL_STRING(expected, test_output); \ free(test_output); \ } LIBSODIUM_TEST(aead_aegis128l) LIBSODIUM_TEST(aead_aegis256) LIBSODIUM_TEST(aead_chacha20poly1305) LIBSODIUM_TEST(chacha20) LIBSODIUM_TEST(box) LIBSODIUM_TEST(box2) LIBSODIUM_TEST(ed25519_convert) LIBSODIUM_TEST(hash) LIBSODIUM_TEST(sign) TEST_CASE("sha256 sanity check", "[libsodium]") { const uint8_t expected[] = { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, }; uint8_t calculated[32]; crypto_hash_sha256_state state; const uint8_t *in = (const uint8_t *)"abc"; const size_t inlen = 3; // One-liner version crypto_hash_sha256(calculated, in, inlen); TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected)); TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha256_bytes()); TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes()); // Multi-line version crypto_hash_sha256_init(&state); crypto_hash_sha256_update(&state, in, inlen - 1); // split into two updates crypto_hash_sha256_update(&state, in + (inlen - 1), 1); crypto_hash_sha256_final(&state, calculated); TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes()); } TEST_CASE("sha512 sanity check", "[libsodium]") { const uint8_t expected[] = { 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f }; uint8_t calculated[64]; crypto_hash_sha512_state state; const uint8_t *in = (const uint8_t *)"abc"; const size_t inlen = 3; // One-liner version crypto_hash_sha512(calculated, in, inlen); TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected)); TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha512_bytes()); TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes()); // Multi-line version crypto_hash_sha512_init(&state); crypto_hash_sha512_update(&state, in, inlen - 1); // split into two updates crypto_hash_sha512_update(&state, in + (inlen - 1), 1); crypto_hash_sha512_final(&state, calculated); TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes()); } ================================================ FILE: libsodium/test_apps/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1500K, ================================================ FILE: libsodium/test_apps/pytest_libsodium.py ================================================ import pytest @pytest.mark.generic def test_libsodium(dut) -> None: dut.run_all_single_board_cases(timeout=120) ================================================ FILE: libsodium/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 CONFIG_ESP_TASK_WDT_INIT=n CONFIG_PARTITION_TABLE_CUSTOM=y ================================================ FILE: network_provisioning/CHANGELOG.md ================================================ # 1.2.4 (14-April-2026) - Fix incorrect fail reason reported in `NETWORK_PROV_WIFI_CRED_FAIL` event. # 1.2.3 (2-April-2026) - Fix possible NULL pointer dereference with malformed protobuf messages - Fix possible buffer overflow in Thread provisioning - Fix potential memory leaks # 1.2.2 (18-Dec-2025) - Fix connection attempts counter not being reset on state reset or new credentials - Reset `connection_attempts_completed` to 0 in `network_prov_mgr_reset_wifi_sm_state_on_failure()` and `network_prov_mgr_configure_wifi_sta()` to ensure full `wifi_conn_attempts` retries after reset or when applying new credentials. # 1.2.1 (15-Dec-2025) - Fix prov-ctrl reset handler to return success when device is already in provisioning mode - If firmware has already called `network_prov_mgr_reset_wifi_sm_state_on_failure()` or `network_prov_mgr_reset_thread_sm_state_on_failure()`, the device state is already reset to provisioning mode. The prov-ctrl handler now returns success in this case instead of an invalid state error, allowing phone apps to successfully reset even if firmware has already performed the reset operation. # 07-October-2025 - Use managed cJSON component for IDF v6.0 and above # 01-April-2025 - Extend provisioning check for `ESP_WIFI_REMOTE_ENABLED` as well along with existing `ESP_WIFI_ENABLED` - This enables provisioning for the devices not having native Wi-Fi (e.g., ESP32-P4) and using external/remote Wi-Fi solution such as esp-hosted for Wi-Fi connectivity. # 17-March-2025 - Update the network provisioning component to work with the protocomm component which fixes incorrect AES-GCM IV usage in security2 scheme. # 19-June-2024 - Change the proto files to make the network provisioning component stay backward compatible with the wifi_provisioing # 23-April-2024 - Add `wifi_prov` or `thread_prov` in provision capabilities in the network provisioning manager for the provisioner to distinguish Thread or Wi-Fi devices # 16-April-2024 - Move wifi_provisioning component from ESP-IDF at commit 5a40bb8746 and rename it to network_provisioning with the addition of Thread provisioning support. - Update esp_prov tool to support both Wi-Fi provisioning and Thread provisioning. - Create thread_prov and wifi_prov examples ================================================ FILE: network_provisioning/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() set(srcs "src/network_config.c" "src/network_scan.c" "src/network_ctrl.c" "src/manager.c" "src/handlers.c" "src/scheme_console.c" "proto-c/network_config.pb-c.c" "proto-c/network_scan.pb-c.c" "proto-c/network_ctrl.pb-c.c" "proto-c/network_constants.pb-c.c") if((CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_WIFI_REMOTE_ENABLED) AND CONFIG_ESP_WIFI_SOFTAP_SUPPORT) list(APPEND srcs "src/scheme_softap.c") endif() if(CONFIG_BT_ENABLED) if(CONFIG_BT_BLUEDROID_ENABLED OR CONFIG_BT_NIMBLE_ENABLED) list(APPEND srcs "src/scheme_ble.c") endif() endif() set(priv_requires protobuf-c bt esp_timer esp_wifi openthread) # For IDF < 6.0, use IDF's built-in json component; for IDF >= 6.0 use managed cJSON component if("${IDF_VERSION_MAJOR}" VERSION_LESS "6") list(APPEND priv_requires json) endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS include PRIV_INCLUDE_DIRS src proto-c REQUIRES lwip protocomm PRIV_REQUIRES ${priv_requires}) ================================================ FILE: network_provisioning/Kconfig ================================================ menu "Network Provisioning Manager" choice NETWORK_PROV_NETWORK_TYPE prompt "Network Type" default NETWORK_PROV_NETWORK_TYPE_WIFI if (ESP_WIFI_ENABLED || ESP_WIFI_REMOTE_ENABLED) default NETWORK_PROV_NETWORK_TYPE_THREAD if !ESP_WIFI_ENABLE && OPENTHREAD_ENABLED config NETWORK_PROV_NETWORK_TYPE_WIFI bool "Network Type - Wi-Fi" depends on ESP_WIFI_ENABLED || ESP_WIFI_REMOTE_ENABLED config NETWORK_PROV_NETWORK_TYPE_THREAD bool "Network Type - Thread" depends on OPENTHREAD_ENABLED endchoice config NETWORK_PROV_SCAN_MAX_ENTRIES int "Max Network Scan Result Entries" default 16 range 1 255 help This sets the maximum number of entries of network scan results that will be kept by the provisioning manager config NETWORK_PROV_AUTOSTOP_TIMEOUT int "Provisioning auto-stop timeout" default 30 range 5 600 help Time (in seconds) after which the network provisioning manager will auto-stop after connecting to a network successfully. config NETWORK_PROV_BLE_BONDING bool prompt "Enable BLE bonding" depends on BT_ENABLED help This option is applicable only when provisioning transport is BLE. Used to enable BLE bonding process where the information from the pairing process will be stored on the devices. config NETWORK_PROV_BLE_SEC_CONN bool prompt "Enable BLE Secure connection flag" depends on BT_NIMBLE_ENABLED default y help Used to enable Secure connection support when provisioning transport is BLE. config NETWORK_PROV_BLE_FORCE_ENCRYPTION bool prompt "Force Link Encryption during characteristic Read / Write" depends on BT_ENABLED help Used to enforce link encryption when attempting to read / write characteristic config NETWORK_PROV_BLE_NOTIFY bool prompt "Add support for Notification for provisioning BLE descriptors" depends on BT_ENABLED help Used to enable support Notification in BLE descriptors of prov* characteristics config NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV bool "Keep BT on after provisioning is done" depends on BT_ENABLED select ESP_PROTOCOMM_KEEP_BLE_ON_AFTER_BLE_STOP config NETWORK_PROV_DISCONNECT_AFTER_PROV bool "Terminate connection after provisioning is done" depends on NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV default y select ESP_PROTOCOMM_DISCONNECT_AFTER_BLE_STOP choice NETWORK_PROV_WIFI_STA_SCAN_METHOD bool "Wifi Provisioning Scan Method" depends on NETWORK_PROV_NETWORK_TYPE_WIFI default NETWORK_PROV_WIFI_STA_ALL_CHANNEL_SCAN config NETWORK_PROV_WIFI_STA_ALL_CHANNEL_SCAN bool "All Channel Scan" help Scan will end after scanning the entire channel. This option is useful in Mesh WiFi Systems. config NETWORK_PROV_WIFI_STA_FAST_SCAN bool "Fast Scan" help Scan will end after an AP matching with the SSID has been detected. endchoice endmenu ================================================ FILE: network_provisioning/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Piyush Shah Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: network_provisioning/README.md ================================================ # Network Provisioning component [![Component Registry](https://components.espressif.com/components/espressif/network_provisioning/badge.svg)](https://components.espressif.com/components/espressif/network_provisioning) The network provisioning component provides APIs that control the network provisioning service for receiving and configuring network credentials via secure [Protocol Communication](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/provisioning/protocomm.html) sessions. It currently supports both Wi-Fi and Thread network provisioning: - Provision Wi-Fi credentials over SoftAP or Bluetooth LE - Provision Thread credentials over Bluetooth LE ================================================ FILE: network_provisioning/examples/README.md ================================================ # Provisioning Application Examples This primarily consists of two examples `wifi_prov` and `thread_prov`. * wifi_prov Abstracts out most of the complexity of Wi-Fi provisioning and allows easy switching between the SoftAP (using HTTP) and BLE transports. It also demonstrates how applications can register and use additional custom data endpoints. * thread_prov Abstracts out most of the complexity of Thread provisioning over BLE transport. It also demonstrates how applications can register and use additional custom data endpoints. Provisioning applications are available for `Linux / Windows / macOS` platform as `esp_prov.py` [script](../tool/esp_prov/esp_prov.py) ================================================ FILE: network_provisioning/examples/thread_prov/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(thread_prov) ================================================ FILE: network_provisioning/examples/thread_prov/README.md ================================================ | Supported Targets | ESP32-C6 | ESP32-H2 | | ----------------- | -------- | -------- | # Network Provisioning Manager Example for Thread Provisioning (See the README.md file in the upper level 'examples' directory for more information about examples.) `thread_prov` example demonstrates the usage of `network_provisioning` manager component for building a Thread provisioning application. For this example, Bluetooth LE is chosen as the mode of transport, over which the provisioning related communication is to take place. NimBLE has been configured as the host. In the provisioning process the device is configured as a Thread FTD with specified dataset. Once configured, the device will retain the Thread configuration, until a flash erase is performed. Right after the provisioning is complete, Bluetooth LE is turned off and disabled to free the memory used by the Bluetooth LE stack. Though, that is specific to this example, and the user can choose to keep Bluetooth LE stack intact in their own application. `thread_prov` uses the following components : * `network_provisioning` : Provides provisioning manager, data structures and protocomm endpoint handlers for Thread configuration * `protocomm` : For protocol based communication and secure session establishment * `protobuf` : Google's protocol buffer library for serialization of protocomm data structures * `bt` : ESP-IDF's Bluetooth LE stack for transport of protobuf packets This example can be used, as it is, for adding a provisioning service to any application intended for IoT. > Note: If you use this example code in your own project, in Bluetooth LE mode, then remember to enable the BT stack and BTDM BLE control settings in your SDK configuration (e.g. by using the `sdkconfig.defaults` file from this project). ## Security Scheme The `protocomm` component is used for establishing secure communication channel at the time of provisioning. It supports two security schemes for establishing secure communication which are [Security 1 Scheme](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/provisioning/provisioning.html#security-1-scheme) and [Security 2 Scheme](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/provisioning/provisioning.html#security-2-scheme). The example uses `Security 2 Scheme` (latest) by default. ## How to use example ### Hardware Required Example should be able to run on any commonly available ESP32-H2/ESP32-C6 development board. ### Script Required Currently, provisioning script is available for `Linux / Windows / macOS` platforms. #### Platform : Linux / Windows / macOS To install the dependency packages needed, please refer to the ESP-IDF examples [README file](https://github.com/espressif/esp-idf/blob/master/examples/README.md#running-test-python-script-ttfw). `esp_prov` supports Bluetooth LE and SoftAP transport for Linux, MacOS and Windows platforms. For Bluetooth LE, however, if dependencies are not met, the script falls back to console mode and requires another application through which the communication can take place. The `esp_prov` console will guide you through the provisioning process of locating the correct Bluetooth LE GATT services and characteristics, the values to write, and input read values. ### Configure the project ``` idf.py menuconfig ``` ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (445) app: Starting provisioning I (1035) app: Provisioning started I (1045) network_prov_mgr: Provisioning started with service name : PROV_F72E6B ``` Make sure to note down the Bluetooth LE device name (starting with `PROV_`) displayed in the serial monitor log (eg. PROV_F72E6B). This will depend on the MAC ID and will be unique for every device. In a separate terminal run the `esp_prov.py` script under [directory](../../tool/esp_prov/) (make sure to replace `dataset_tlvs` with the dataset of the Thread network to which the device is supposed to connect to after provisioning). Assuming default example configuration, which uses the protocomm security version 2: ``` python esp_prov.py --transport ble --service_name PROV_F72E6B --sec_ver 2 --sec2_username threadprov --sec2_pwd abcd1234 --dataset_tlvs ``` For security scheme 1 with PoP-based (proof-of-possession) authentication, the following command can be used: ``` python esp_prov.py --transport ble --service_name PROV_F72E6B --sec_ver 1 --pop abcd1234 --dataset_tlvs ``` Above command will perform the provisioning steps, and the monitor log should display something like this : ``` I(125493) OPENTHREAD:[N] Mle-----------: Role disabled -> detached I (125503) OT_STATE: netif up I(125883) OPENTHREAD:[N] Mle-----------: Attach attempt 1, AnyPartition reattaching with Active Dataset I(126793) OPENTHREAD:[N] Mle-----------: RLOC16 fffe -> a40d I(126793) OPENTHREAD:[N] Mle-----------: Role detached -> child I (126813) OT_STATE: Set dns server address: FDE6:8626:404B:2::808:808 I (126823) network_prov_mgr: Thread attached I (126823) app: Provisioning successful I (126823) app: Hello World! I (127833) app: Hello World! I (128833) app: Hello World! . . . I (131883) network_prov_mgr: Provisioning stopped . . . I (52355) app: Hello World! I (53355) app: Hello World! I (54355) app: Hello World! I (55355) app: Hello World! ``` **Note:** For generating the credentials for security version 2 (`SRP6a` salt and verifier) for the device-side, the following example command can be used. The output can then directly be used in this example. The config option `CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE` should be enabled for the example and in `main/app_main.c`, the macro `EXAMPLE_PROV_SEC2_USERNAME` should be set to the same username used in the salt-verifier generation. ```log $ python esp_prov.py --transport ble --sec_ver 2 --sec2_gen_cred --sec2_username threadprov --sec2_pwd abcd1234 ==== Salt-verifier for security scheme 2 (SRP6a) ==== static const char sec2_salt[] = { 0x1f, 0xff, 0x29, 0xf5, 0xc7, 0x7e, 0x07, 0x48, 0x02, 0xe9, 0x93, 0x3e, 0xa3, 0xa2, 0x26, 0x73 }; static const char sec2_verifier[] = { 0xa7, 0x29, 0xe6, 0xa5, 0x4d, 0x20, 0x57, 0x71, 0x7c, 0x9d, 0x78, 0x2d, 0x0a, 0xb0, 0x9f, 0xec, 0x7e, 0x8b, 0xab, 0xf5, 0xe6, 0xc3, 0x36, 0x41, 0x93, 0xfd, 0xb9, 0x49, 0x67, 0xe7, 0x7f, 0x79, 0x66, 0x25, 0x2e, 0xac, 0x89, 0x19, 0xb2, 0xb3, 0x14, 0xb1, 0x16, 0xb0, 0xb0, 0xe4, 0x34, 0xd4, 0x99, 0x40, 0x85, 0xa4, 0x99, 0x2b, 0x84, 0x21, 0xa1, 0xfb, 0x15, 0x48, 0x04, 0x91, 0xf5, 0x74, . . . 0x80, 0x86, 0xf4, 0xd5, 0x08, 0xbc, 0xb0, 0xdd, 0x6b, 0x50, 0xfa, 0xdd, 0x16, 0x10, 0x23, 0x4b }; ``` ### QR Code Scanning Enabling `CONFIG_EXAMPLE_PROV_SHOW_QR` will display a QR code on the serial terminal, which can be scanned from the ESP Provisioning phone apps to start the Thread provisioning process. The ESP Provisioning phone apps will be released later. The monitor log should display something like this : ``` I (673) app: Provisioning started I (673) app: Scan this QR code from the provisioning application for Provisioning. I (693) QRCODE: Encoding below text with ECC LVL 0 & QR Code Version 10 I (774) QRCODE: {"ver":"v1","name":"PROV_F72E6B","username":"threadprov","pop":"abcd1234","transport":"ble","network":"thread"} > █▀▀▀▀▀█ ▄▄▄█ ██▄▀█▀▀▀▀ ▀▄ ▄█▀█▄▀ █▀▀▀▀▀█ █ ███ █ ▄▄██ ▀▄▄▄▀▀▀▄▀▄▀▀▄▄▄▄▄█▀▄ █ ███ █ █ ▀▀▀ █ ▄ █ █▄ ▀ ▄ ▄▀▄ █▄███▀ █ ▀▀▀ █ ▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▄▀ ▀ ▀▄▀ █ █▄█ █▄▀ ▀▀▀▀▀▀▀ ▀▀▀██▄▀██▄▀██▀▀▀ ▀▄▄█▄▀█ ▀▄▀▀▀█▄ █▄▀▄█ ▀▄ ██▄ ▀ ▀ ▀▄▄█ ▄█▄ ▀ ██ ▀█▀█▀█ █▀ ▀█▄▀█▄ ▄█▀▀▄▄▀█▄▀▀ ▀▄▄▄▄ ▀▀▄▄▀▄▀▀▀▀▄▀▄▄ ▄▄▄▄ ▀▀▄ ▀ █▄▄ ▀ ▀▀█▀▀█ ▄ ▀█▄█▄ ▀▀▀▀▄▀█ ▄█▄ ▄▀▀▄▄ ▀█▄█▀▀▀ ▄ ▀█▀██ █▄█▄▄▀▄▀▄▀ ▀ ▄▄█▄▀▄█▀▀▄ █ ▀██ ▀▀▄▄▄▄██▀▀ █▄ ▄█▄▄▄▀█▀▄▀█ █▄█▄▄█ ▀ ▀▀▄ ▄ ▀▄ ▄█ ▄ ▀█▀ ▄ ▀ ▀▄ ▀█▀▄▀▄██ ▄█▄█▀█▄ █▄▄ ▀▀ ▄▀ ▄ █▀ ▄ ▀██▄█▀ ▀ █▄ ▄▀▄█ ▀▄ ▄▄ ██▄▀▄█▀█▄▀▀ ▀ ▀ ▄█▄▀▄ ▀ █▄▄ ▄▄█▄█▄ ▀▀█ █▄█ ▄▄▀▀ ▀ ▄ ▄▀▄ ▀▄ █▀ ▀ ▀█ ▄ ▀▀▄ ▄ ▀█ █ ▄▀▀▀▀█▄▄ ▀ ▄█▄ ▀ ▄▄ ▄█▀▀ ▀▄▄█ ▄█▄ ▀ ▄ █ █▀█ ▀█▄ ██▀▀ ▄ ██▄██▄█▀ █▄▀█▄██▄ ▄ ▀ ▄ ▀ ▀▀ ▀ ▄ ▀█▀█▄ ██ █ ▄ █ █ ▄█ ▄▀█▀▀▀█ ▀▄█ █▀▀▀▀▀█ ▀▄ ▄██▀▀██▄▄ ▄ ▄█▀▄▀▄ ██ ▀ █ ▀ ▄ █ ███ █ █▀▀ ▄▄▀▄▀ ▄▀ ▄█▄▄█ ▀ ██ █▀▀▀▀███▄ █ ▀▀▀ █ ███ ▄▀ █▀▀█ █▄ ▄█ ▀▀ ▀ ▀██▀ ▀ ▄ ▀▀▀▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀▀ ▀▀ ▀▀ ▀ ▀ ▀ I (1134) app: If QR code is not visible, copy paste the below URL in a browser. https://espressif.github.io/esp-jumpstart/qrcode.html?data={"ver":"v1","name":"PROV_F72E6B","username":"threadprov","pop":"abcd1234","transport":"ble","network":"thread"} ``` ### Thread Scanning Provisioning manager also supports providing real-time Thread scan results (performed on the device) during provisioning. This allows the client side applications to choose the Thread network for which the device is to be configured. Various information about the visible Thread networks is available, like signal strength (RSSI) and link quality (LQI), etc. Also, the manager now provides capabilities information which can be used by client applications to determine availability of specific features (like `thread_scan`). When using the scan based provisioning, we don't need to specify the `--dataset_tlvs` fields explicitly: ``` python esp_prov.py --transport ble --service_name PROV_F72E6B --pop abcd1234 ``` See below the sample output from `esp_prov` tool on running above command: ``` Connecting... Connected Getting Services... Security scheme determined to be : 1 ==== Starting Session ==== ==== Session Established ==== ==== Scanning Thread Networks ==== ++++ Scan process executed in 6.247516632080078 sec ++++ Scan results : 12 ++++ Scan finished in 7.349079132080078 sec ==== Thread Scan results ==== S.N. PAN ID EXT PAN ID NAME EXT ADDR CHN RSSI LQI [ 1] 34121 9a6526ce2aaf4383 ST-34EW e2848e0e9e315357 11 -53 9 [ 2] 14311 7e010e5a22beb040 OpenThread-37e7 02a2ab187a4fb728 21 -47 10 [ 3] 14311 7e010e5a22beb040 OpenThread-37e7 22bfc4ba63cf3bb8 21 -44 9 [ 4] 4660 dead00beef00cafe OpenThread-13b4 1e4d2bb3a614163f 22 -40 10 [ 5] 48989 516f754f983e50c6 OpenThread-bf5d be64e2845dc1d21a 22 -46 9 [ 6] 4660 dead00beef00cafe OpenThread-79c0 3e19ed4f89be20ee 22 -53 10 [ 7] 31233 6c5105b3cb215393 qqqQQQQQQQQ 86ba2d8a2ded00d0 24 -47 8 [ 8] 32231 0458ef52172c21d6 OpenThread-7de7 deca5eddda6da2a7 25 -50 10 [ 9] 32231 0458ef52172c21d6 OpenThread-7de7 0e085d0f1d89db89 25 -45 10 [10] 10299 07f592f684bc4266 MyHome1926416771 b268d0525d81f5c4 25 -59 9 [11] 10299 07f592f684bc4266 MyHome1926416771 6ee0445b8d49d4b8 25 -56 9 [12] 51344 7899e5214acf64db OpenThread-c890 b22f21bc5ce8a058 26 -40 10 Select Network by number (0 to rescan) : 4 Enter Thread network key string : ==== Sending Thread Dataset to Target ==== ==== Thread Dataset sent successfully ==== ==== Applying Thread Config to Target ==== ==== Apply config sent successfully ==== ==== Thread connection state ==== ==== Thread state: Attaching ==== ==== Thread connection state ==== ==== Thread state: Attaching ==== ==== Thread connection state ==== ==== Thread state: Attached ==== ==== Provisioning was successful ==== ``` ### Interactive Provisioning `esp_prov` supports interactive provisioning. You can trigger the script with a simplified command and input the necessary details (`Proof-of-possession` for security scheme 1 and `SRP6a username`, `SRP6a password` for security scheme 2) as the provisioning process advances. The command `python esp_prov.py --transport ble --sec_ver 2` gives out the following sample output: ``` Discovering... ==== BLE Discovery results ==== S.N. Name Address . . . [35] PROV_F73E7E 60:55:F9:F7:3E:7E [36] 62-37-56-08-AA-64 62:37:56:08:AA:64 . . . Select device by number (0 to rescan) : 35 Connecting... Getting Services.. Security Scheme 2 - SRP6a Username required: threadprov Security Scheme 2 - SRP6a Password required: ==== Starting Session ==== ==== Session Established ==== ==== Scanning Thread Networks ==== ++++ Scan process executed in 6.247041702270508 sec ++++ Scan results : 14 ++++ Scan finished in 7.40191650390625 sec ==== Thread Scan results ==== S.N. PAN ID EXT PAN ID NAME EXT ADDR CHN RSSI LQI [ 1] 34121 9a6526ce2aaf4383 ST-34EW e2848e0e9e315357 11 -53 9 [ 2] 14311 7e010e5a22beb040 OpenThread-37e7 02a2ab187a4fb728 21 -45 9 [ 3] 14311 7e010e5a22beb040 OpenThread-37e7 22bfc4ba63cf3bb8 21 -40 10 [ 4] 4660 dead00beef00cafe OpenThread-79c0 3e19ed4f89be20ee 22 -59 10 [ 5] 4660 dead00beef00cafe OpenThread-13b4 1e4d2bb3a614163f 22 -43 9 [ 6] 48989 516f754f983e50c6 OpenThread-bf5d be64e2845dc1d21a 22 -47 10 [ 7] 23427 5b83dead5b83beef 5b83 8a1e30a60615c16c 24 -57 8 [ 8] 31233 6c5105b3cb215393 qqqQQQQQQQQ 86ba2d8a2ded00d0 24 -48 10 [ 9] 10299 07f592f684bc4266 MyHome1926416771 6ee0445b8d49d4b8 25 -55 9 [10] 32231 0458ef52172c21d6 OpenThread-7de7 deca5eddda6da2a7 25 -50 10 [11] 10299 07f592f684bc4266 MyHome1926416771 b268d0525d81f5c4 25 -55 9 [12] 32231 0458ef52172c21d6 OpenThread-7de7 0e085d0f1d89db89 25 -47 10 [13] 10299 07f592f684bc4266 MyHome1926416771 7e06f10b32fe678f 25 -54 10 [14] 51344 7899e5214acf64db OpenThread-c890 b22f21bc5ce8a058 26 -42 10 Select Network by number (0 to rescan) : 5 Enter Thread network key string : ==== Sending Thread Dataset to Target ==== ==== Thread Dataset sent successfully ==== ==== Applying Thread Config to Target ==== ==== Apply config sent successfully ==== ==== Thread connection state ==== ==== Thread state: Attaching ==== ==== Thread connection state ==== ==== Thread state: Attaching ==== ==== Thread connection state ==== ==== Thread state: Attached ==== ==== Provisioning was successful ==== ``` ### Sending Custom Data The provisioning manager allows applications to send some custom data during provisioning, which may be required for some other operations like connecting to some cloud service. This is achieved by creating and registering additional endpoints using the below APIs ``` network_prov_mgr_endpoint_create(); network_prov_mgr_endpoint_register(); ``` In this particular example, we have added an endpoint named "custom-data" which can be tested by passing the `--custom_data ` option to the esp\_prov tool. Following output is expected on success: ``` ==== Sending Custom data to esp32 ==== CustomData response: SUCCESS ``` ## Troubleshooting ### Provisioning failed It is possible that the Thread dataset provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason). Serial monitor log will display the failure along with disconnect reason : ``` E (367015) app: Provisioning failed! Reason : Thread network not found Please reset to factory and retry provisioning ``` Once dataset have been applied, even though wrong dataset were provided, the device will no longer go into provisioning mode on subsequent reboots until NVS is erased (see following section). ### Provisioning does not start If the serial monitor log shows the following : ``` I (465) app: Already provisioned, enabling netif and starting Thread ``` it means either the device has been provisioned earlier with or without success (e.g. scenario covered in above section), or that the Thread dataset was already set by some other application flashed previously onto your device. To fix this we simple need to erase the NVS partition from flash. First we need to find out its address and size. This can be seen from the monitor log on the top right after reboot. ``` I (47) boot: Partition Table: I (50) boot: ## Label Usage Type ST Offset Length I (58) boot: 0 nvs WiFi data 01 02 00009000 00006000 I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 I (73) boot: 2 factory factory app 00 00 00010000 00124f80 I (80) boot: End of partition table ``` Now erase NVS partition by running the following commands : ``` $IDF_PATH/components/esptool_py/esptool/esptool.py erase_region 0x9000 0x6000 ``` ### Bluetooth Pairing Request during provisioning ESP-IDF now has functionality to enforce link encryption requirement while performing GATT write on characteristics of provisioning service. This will however result in a pairing pop-up dialog, if link is not encrypted. This feature is disabled by default. In order to enable this feature, please set `CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION=y` in the sdkconfig or select the configuration using "idf.py menuconfig" . ``` Component Config --> Network Provisioning Manager --> Force Link Encryption during Characteristic Read/Write ``` Recompiling the application with above changes should suffice to enable this functionality. ================================================ FILE: network_provisioning/examples/thread_prov/main/CMakeLists.txt ================================================ idf_component_register(SRCS "app_main.c" INCLUDE_DIRS ".") ================================================ FILE: network_provisioning/examples/thread_prov/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice EXAMPLE_PROV_SECURITY_VERSION bool "Protocomm security version" default EXAMPLE_PROV_SECURITY_VERSION_2 help Network provisioning component offers 3 security versions. The example offers a choice between security version 1 and 2. config EXAMPLE_PROV_SECURITY_VERSION_1 bool "Security version 1" select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 config EXAMPLE_PROV_SECURITY_VERSION_2 bool "Security version 2" select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 endchoice choice EXAMPLE_PROV_MODE bool "Security version 2 mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 default EXAMPLE_PROV_SEC2_DEV_MODE config EXAMPLE_PROV_SEC2_DEV_MODE bool "Security version 2 development mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 help This enables the development mode for security version 2. Please note that this mode is NOT recommended for production purpose. config EXAMPLE_PROV_SEC2_PROD_MODE bool "Security version 2 production mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 help This enables the production mode for security version 2. endchoice config EXAMPLE_PROV_TRANSPORT int default 1 if EXAMPLE_PROV_TRANSPORT_BLE config EXAMPLE_RESET_PROVISIONED bool default n prompt "Reset provisioned status of the device" help This erases the NVS to reset provisioned status of the device on every reboot. Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. config EXAMPLE_RESET_PROV_MGR_ON_FAILURE bool default y prompt "Reset provisioned credentials and state machine after session failure" help Enable resetting provisioned credentials and state machine after session failure. This will restart the provisioning service after retries are exhausted. config EXAMPLE_PROV_MGR_MAX_RETRY_CNT int default 5 prompt "Max retries before resetting provisioning state machine" depends on EXAMPLE_RESET_PROV_MGR_ON_FAILURE help Set the Maximum retry to avoid reconnecting to an inexistent AP or if credentials are misconfigured. Provisioned credentials are erased and internal state machine is reset after this threshold is reached. config EXAMPLE_PROV_SHOW_QR bool "Show provisioning QR code" default y help Show the QR code for provisioning. config EXAMPLE_REPROVISIONING bool "Re-provisioning" help Enable re-provisioning - allow the device to provision for new credentials after previous successful provisioning. endmenu ================================================ FILE: network_provisioning/examples/thread_prov/main/app_main.c ================================================ /* Network Provisioning Manager Example for Thread network This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include #include #include #include #include "esp_openthread_cli.h" #include "esp_openthread_netif_glue.h" #include "esp_openthread_types.h" #include #include #include #include #include #include #include #include "openthread/cli.h" #include "openthread/instance.h" #include "openthread/logging.h" #include "openthread/tasklet.h" #include "openthread/thread.h" #include #include "qrcode.h" static const char *TAG = "app"; #if CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE #define EXAMPLE_PROV_SEC2_USERNAME "threadprov" #define EXAMPLE_PROV_SEC2_PWD "abcd1234" /* This salt,verifier has been generated for username = "threadprov" and password = "abcd1234" * IMPORTANT NOTE: For production cases, this must be unique to every device * and should come from device manufacturing partition.*/ static const char sec2_salt[] = { 0x1f, 0xff, 0x29, 0xf5, 0xc7, 0x7e, 0x07, 0x48, 0x02, 0xe9, 0x93, 0x3e, 0xa3, 0xa2, 0x26, 0x73 }; static const char sec2_verifier[] = { 0xa7, 0x29, 0xe6, 0xa5, 0x4d, 0x20, 0x57, 0x71, 0x7c, 0x9d, 0x78, 0x2d, 0x0a, 0xb0, 0x9f, 0xec, 0x7e, 0x8b, 0xab, 0xf5, 0xe6, 0xc3, 0x36, 0x41, 0x93, 0xfd, 0xb9, 0x49, 0x67, 0xe7, 0x7f, 0x79, 0x66, 0x25, 0x2e, 0xac, 0x89, 0x19, 0xb2, 0xb3, 0x14, 0xb1, 0x16, 0xb0, 0xb0, 0xe4, 0x34, 0xd4, 0x99, 0x40, 0x85, 0xa4, 0x99, 0x2b, 0x84, 0x21, 0xa1, 0xfb, 0x15, 0x48, 0x04, 0x91, 0xf5, 0x74, 0x95, 0x8a, 0x88, 0xd4, 0x4e, 0x25, 0xf6, 0xf3, 0x8e, 0x5c, 0xf9, 0x3c, 0xda, 0xbb, 0x4f, 0xa2, 0x47, 0xe1, 0x01, 0x8f, 0x1c, 0xf5, 0xe0, 0x34, 0x41, 0x0c, 0x88, 0x76, 0x46, 0xd0, 0x16, 0xd9, 0xfa, 0x57, 0x3d, 0x78, 0x46, 0xf1, 0xcb, 0xb1, 0x05, 0x16, 0xab, 0xf7, 0xbf, 0x9d, 0xeb, 0x05, 0x2e, 0xc1, 0xd5, 0xe1, 0xde, 0x92, 0xe6, 0x20, 0x5f, 0xe4, 0x27, 0xda, 0xe3, 0x59, 0x91, 0x27, 0x7b, 0x40, 0x83, 0x4c, 0xe8, 0xb5, 0xe0, 0x75, 0xe6, 0xbf, 0x26, 0xa9, 0x67, 0x06, 0xa3, 0x15, 0x2d, 0x20, 0x81, 0xd5, 0x2a, 0x2e, 0x30, 0x84, 0xdf, 0xa2, 0x82, 0x62, 0xc4, 0x47, 0x25, 0xb6, 0x93, 0x73, 0x87, 0x3c, 0xa7, 0x57, 0x2a, 0x47, 0x96, 0x1d, 0x89, 0xce, 0x49, 0xc6, 0x9d, 0x4f, 0x6b, 0x39, 0x38, 0x67, 0xbb, 0x85, 0x24, 0xdf, 0xcd, 0xf5, 0xf1, 0x9f, 0x0a, 0x9e, 0x1c, 0x31, 0xfa, 0xf1, 0x01, 0xa5, 0x30, 0xf0, 0xcb, 0x5e, 0x1c, 0xd6, 0xa0, 0x11, 0x3f, 0xf8, 0xdd, 0x07, 0x09, 0x53, 0x62, 0x9f, 0x76, 0x5c, 0x69, 0xd1, 0x5e, 0x5b, 0xc7, 0xb0, 0x0e, 0x53, 0xeb, 0x8c, 0x67, 0x88, 0xc7, 0x45, 0xc0, 0x26, 0xd9, 0xfa, 0xf8, 0x63, 0x0c, 0x64, 0xcb, 0x9e, 0xf4, 0x1b, 0xb3, 0xfd, 0x78, 0x0c, 0x47, 0x0f, 0x66, 0xf3, 0xf7, 0xcd, 0xe9, 0xc6, 0x36, 0xa5, 0x58, 0xe5, 0x9d, 0x31, 0x53, 0xb2, 0xe4, 0x8e, 0xdd, 0xd0, 0x8d, 0x13, 0xe8, 0xc6, 0x96, 0x60, 0x30, 0x50, 0xbc, 0xef, 0xce, 0xbc, 0x23, 0xe3, 0x60, 0x63, 0x54, 0x11, 0x24, 0xba, 0x68, 0x47, 0x6a, 0xb2, 0x5e, 0x70, 0xa3, 0xa6, 0xc3, 0xad, 0x58, 0xd1, 0x3b, 0xce, 0xce, 0x90, 0xe9, 0x90, 0x7e, 0x7a, 0xfb, 0x4f, 0x69, 0xa2, 0x81, 0xdf, 0x15, 0xec, 0xa7, 0x8f, 0xd6, 0x5a, 0xb8, 0x1f, 0x42, 0x18, 0x0e, 0x4f, 0x3e, 0x45, 0x2d, 0x08, 0xf2, 0xd6, 0x51, 0x90, 0xef, 0x64, 0x77, 0xee, 0xcc, 0x3c, 0xb4, 0xa6, 0x6f, 0x0b, 0x10, 0xb2, 0xce, 0x31, 0x19, 0x10, 0x8d, 0x75, 0x8f, 0xa8, 0xa2, 0x6e, 0x7a, 0x00, 0x92, 0x91, 0xe2, 0x16, 0xe3, 0x7a, 0xf9, 0x1d, 0x4e, 0x39, 0xe5, 0xd0, 0xd1, 0x7e, 0x80, 0x86, 0xf4, 0xd5, 0x08, 0xbc, 0xb0, 0xdd, 0x6b, 0x50, 0xfa, 0xdd, 0x16, 0x10, 0x23, 0x4b }; #endif static esp_err_t example_get_sec2_salt(const char **salt, uint16_t *salt_len) { #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE ESP_LOGI(TAG, "Development mode: using hard coded salt"); *salt = sec2_salt; *salt_len = sizeof(sec2_salt); return ESP_OK; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE ESP_LOGE(TAG, "Not implemented!"); return ESP_FAIL; #endif } static esp_err_t example_get_sec2_verifier(const char **verifier, uint16_t *verifier_len) { #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE ESP_LOGI(TAG, "Development mode: using hard coded verifier"); *verifier = sec2_verifier; *verifier_len = sizeof(sec2_verifier); return ESP_OK; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE /* This code needs to be updated with appropriate implementation to provide verifier */ ESP_LOGE(TAG, "Not implemented!"); return ESP_FAIL; #endif } #endif /* Signal Thread events on this event-group */ const int THREAD_ATTACHED_EVENT = BIT0; static EventGroupHandle_t thread_event_group; #define PROV_QR_VERSION "v1" #define PROV_TRANSPORT_BLE "ble" #define QRCODE_BASE_URL "https://espressif.github.io/esp-jumpstart/qrcode.html" /* Event handler for catching system events */ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == NETWORK_PROV_EVENT) { switch (event_id) { case NETWORK_PROV_START: ESP_LOGI(TAG, "Provisioning started"); break; case NETWORK_PROV_THREAD_DATASET_RECV: { // TODO Log thread dataset break; } case NETWORK_PROV_THREAD_DATASET_FAIL: { network_prov_thread_fail_reason_t *reason = (network_prov_thread_fail_reason_t *)event_data; ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s" "\n\tPlease reset to factory and retry provisioning", (*reason == NETWORK_PROV_THREAD_DATASET_INVALID) ? "Invalid Thread dataset" : "Thread network not found"); break; } case NETWORK_PROV_THREAD_DATASET_SUCCESS: ESP_LOGI(TAG, "Provisioning successful"); break; case NETWORK_PROV_END: /* De-initialize manager once provisioning is finished */ network_prov_mgr_deinit(); break; default: break; } } else if (event_base == OPENTHREAD_EVENT && event_id == OPENTHREAD_EVENT_ATTACHED) { xEventGroupSetBits(thread_event_group, THREAD_ATTACHED_EVENT); } else if (event_base == PROTOCOMM_SECURITY_SESSION_EVENT) { switch (event_id) { case PROTOCOMM_SECURITY_SESSION_SETUP_OK: ESP_LOGI(TAG, "Secured session established!"); break; case PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS: ESP_LOGE(TAG, "Received invalid security parameters for establishing secure session!"); break; case PROTOCOMM_SECURITY_SESSION_CREDENTIALS_MISMATCH: ESP_LOGE(TAG, "Received incorrect username and/or PoP for establishing secure session!"); break; default: break; } } } static void get_device_service_name(char *service_name, size_t max) { uint8_t ieee802154_mac[8]; const char *ssid_prefix = "PROV_"; esp_read_mac(ieee802154_mac, ESP_MAC_IEEE802154); snprintf(service_name, max, "%s%02X%02X%02X", ssid_prefix, ieee802154_mac[5], ieee802154_mac[6], ieee802154_mac[7]); } /* Handler for the optional provisioning endpoint registered by the application. * The data format can be chosen by applications. Here, we are using plain ascii text. * Applications can choose to use other formats like protobuf, JSON, XML, etc. */ esp_err_t custom_prov_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) { if (inbuf) { ESP_LOGI(TAG, "Received data: %.*s", inlen, (char *)inbuf); } char response[] = "SUCCESS"; *outbuf = (uint8_t *)strdup(response); if (*outbuf == NULL) { ESP_LOGE(TAG, "System out of memory"); return ESP_ERR_NO_MEM; } *outlen = strlen(response) + 1; /* +1 for NULL terminating byte */ return ESP_OK; } static void network_prov_print_qr(const char *name, const char *username, const char *pop, const char *transport) { if (!name || !transport) { ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing."); return; } char payload[150] = {0}; if (pop) { #if CONFIG_EXAMPLE_PROV_SECURITY_VERSION_1 snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"pop\":\"%s\",\"transport\":\"%s\"}", PROV_QR_VERSION, name, pop, transport); #elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"username\":\"%s\",\"pop\":\"%s\",\"transport\":\"%s\"}", PROV_QR_VERSION, name, username, pop, transport); #endif } else { snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"transport\":\"%s\"}", PROV_QR_VERSION, name, transport); } #ifdef CONFIG_EXAMPLE_PROV_SHOW_QR ESP_LOGI(TAG, "Scan this QR code from the provisioning application for Provisioning."); esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); esp_qrcode_generate(&cfg, payload); //TODO: Add the network protocol type to the QR code payload #endif /* CONFIG EXAMPLE_PROV_SHOW_QR */ ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\n%s?data=%s", QRCODE_BASE_URL, payload); } static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) { esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD(); esp_netif_t *netif = esp_netif_new(&cfg); assert(netif != NULL); ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config))); return netif; } static void ot_task_worker(void *aContext) { esp_openthread_platform_config_t config = { .radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(), .host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(), .port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(), }; // Initialize the OpenThread stack ESP_ERROR_CHECK(esp_openthread_init(&config)); #if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC // The OpenThread log level directly matches ESP log level (void)otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL); #endif #if CONFIG_OPENTHREAD_CLI // Initialize the OpenThread cli esp_openthread_cli_init(); #endif esp_netif_t *openthread_netif = init_openthread_netif(&config); // Initialize the esp_netif bindings esp_netif_set_default_netif(openthread_netif); // Run the main loop #if CONFIG_OPENTHREAD_CLI esp_openthread_cli_create_task(); #endif esp_openthread_launch_mainloop(); // Clean up esp_netif_destroy(openthread_netif); esp_openthread_netif_glue_deinit(); esp_vfs_eventfd_unregister(); vTaskDelete(NULL); } void app_main(void) { /* Initialize NVS partition */ esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { /* NVS partition was truncated * and needs to be erased */ ESP_ERROR_CHECK(nvs_flash_erase()); /* Retry nvs_flash_init */ ESP_ERROR_CHECK(nvs_flash_init()); } /* Initialize TCP/IP */ ESP_ERROR_CHECK(esp_netif_init()); /* Initialize the event loop */ ESP_ERROR_CHECK(esp_event_loop_create_default()); thread_event_group = xEventGroupCreate(); /* Register our event handler for OpenThread and Provisioning related events */ ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_TRANSPORT_BLE_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); #endif ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_SECURITY_SESSION_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); esp_vfs_eventfd_config_t eventfd_config = { .max_fds = 3, }; ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config)); xTaskCreate(ot_task_worker, "ot_task", 6144, xTaskGetCurrentTaskHandle(), 5, NULL); /* Configuration for the provisioning manager */ network_prov_mgr_config_t config = { /* Use network_prov_scheme_ble as the Provisioning Scheme */ .scheme = network_prov_scheme_ble, /* Any default scheme specific event handler that you would * like to choose. Since our example application requires * neither BT nor BLE, we can choose to release the associated * memory once provisioning is complete, or not needed * (in case when device is already provisioned). Choosing * appropriate scheme specific event handler allows the manager * to take care of this automatically. */ .scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE }; /* Initialize provisioning manager with the * configuration parameters set above */ ESP_ERROR_CHECK(network_prov_mgr_init(config)); bool provisioned = false; #ifdef CONFIG_EXAMPLE_RESET_PROVISIONED network_prov_mgr_reset_thread_provisioning(); #else /* Let's find out if the device is provisioned */ ESP_ERROR_CHECK(network_prov_mgr_is_thread_provisioned(&provisioned)); #endif /* If device is not yet provisioned start provisioning service */ if (!provisioned) { ESP_LOGI(TAG, "Starting provisioning"); /* What is the Device Service Name that we want * This translates to : * - device name when scheme is network_prov_scheme_ble */ char service_name[12]; get_device_service_name(service_name, sizeof(service_name)); #ifdef CONFIG_EXAMPLE_PROV_SECURITY_VERSION_1 /* What is the security level that we want (0, 1, 2): * - NETWORK_PROV_SECURITY_0 is simply plain text communication. * - NETWORK_PROV_SECURITY_1 is secure communication which consists of secure handshake * using X25519 key exchange and proof of possession (pop) and AES-CTR * for encryption/decryption of messages. * - NETWORK_PROV_SECURITY_2 SRP6a based authentication and key exchange * + AES-GCM encryption/decryption of messages */ network_prov_security_t security = NETWORK_PROV_SECURITY_1; /* Do we want a proof-of-possession (ignored if Security 0 is selected): * - this should be a string with length > 0 * - NULL if not used */ const char *pop = "abcd1234"; /* This is the structure for passing security parameters * for the protocomm security 1. */ network_prov_security1_params_t *sec_params = pop; const char *username = NULL; #elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 network_prov_security_t security = NETWORK_PROV_SECURITY_2; /* The username must be the same one, which has been used in the generation of salt and verifier */ #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE /* This pop field represents the password that will be used to generate salt and verifier. * The field is present here in order to generate the QR code containing password. * In production this password field shall not be stored on the device */ const char *username = EXAMPLE_PROV_SEC2_USERNAME; const char *pop = EXAMPLE_PROV_SEC2_PWD; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE /* The username and password shall not be embedded in the firmware, * they should be provided to the user by other means. * e.g. QR code sticker */ const char *username = NULL; const char *pop = NULL; #endif /* This is the structure for passing security parameters * for the protocomm security 2. * If dynamically allocated, sec2_params pointer and its content * must be valid till WIFI_PROV_END event is triggered. */ network_prov_security2_params_t sec2_params = {}; ESP_ERROR_CHECK(example_get_sec2_salt(&sec2_params.salt, &sec2_params.salt_len)); ESP_ERROR_CHECK(example_get_sec2_verifier(&sec2_params.verifier, &sec2_params.verifier_len)); network_prov_security2_params_t *sec_params = &sec2_params; #endif /* What is the service key (could be NULL) * This translates to : * - simply ignored when scheme is network_prov_scheme_ble */ const char *service_key = NULL; #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE /* This step is only useful when scheme is network_prov_scheme_ble. This will * set a custom 128 bit UUID which will be included in the BLE advertisement * and will correspond to the primary GATT service that provides provisioning * endpoints as GATT characteristics. Each GATT characteristic will be * formed using the primary service UUID as base, with different auto assigned * 12th and 13th bytes (assume counting starts from 0th byte). The client side * applications must identify the endpoints by reading the User Characteristic * Description descriptor (0x2901) for each characteristic, which contains the * endpoint name of the characteristic */ uint8_t custom_service_uuid[] = { /* LSB <--------------------------------------- * ---------------------------------------> MSB */ 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf, 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02, }; /* If your build fails with linker errors at this point, then you may have * forgotten to enable the BT stack or BTDM BLE settings in the SDK (e.g. see * the sdkconfig.defaults in the example project) */ network_prov_scheme_ble_set_service_uuid(custom_service_uuid); #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ /* An optional endpoint that applications can create if they expect to * get some additional custom data during provisioning workflow. * The endpoint name can be anything of your choice. * This call must be made before starting the provisioning. */ network_prov_mgr_endpoint_create("custom-data"); /* Do not stop and de-init provisioning even after success, * so that we can restart it later. */ #ifdef CONFIG_EXAMPLE_REPROVISIONING network_prov_mgr_disable_auto_stop(1000); #endif /* Start provisioning service */ ESP_ERROR_CHECK(network_prov_mgr_start_provisioning(security, (const void *) sec_params, service_name, service_key)); /* The handler for the optional endpoint created above. * This call must be made after starting the provisioning, and only if the endpoint * has already been created above. */ network_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL); /* Uncomment the following to wait for the provisioning to finish and then release * the resources of the manager. Since in this case de-initialization is triggered * by the default event loop handler, we don't need to call the following */ // network_prov_mgr_wait(); // network_prov_mgr_deinit(); /* Print QR code for provisioning */ network_prov_print_qr(service_name, username, pop, PROV_TRANSPORT_BLE); } else { ESP_LOGI(TAG, "Already provisioned, enabling netif and starting Thread"); /* We don't need the manager as device is already provisioned, * so let's release it's resources */ network_prov_mgr_deinit(); otInstance *instance = esp_openthread_get_instance(); (void)otIp6SetEnabled(instance, true); (void)otThreadSetEnabled(instance, true); } /* Wait for Thread connection */ xEventGroupWaitBits(thread_event_group, THREAD_ATTACHED_EVENT, true, true, portMAX_DELAY); /* Start main application now */ #if CONFIG_EXAMPLE_REPROVISIONING while (1) { for (int i = 0; i < 10; i++) { ESP_LOGI(TAG, "Hello World!"); vTaskDelay(1000 / portTICK_PERIOD_MS); } /* Resetting provisioning state machine to enable re-provisioning */ network_prov_mgr_reset_thread_sm_state_for_reprovision(); /* Wait for thread connection */ xEventGroupWaitBits(thread_event_group, THREAD_ATTACHED_EVENT, true, true, portMAX_DELAY); } #else while (1) { ESP_LOGI(TAG, "Hello World!"); vTaskDelay(1000 / portTICK_PERIOD_MS); } #endif } ================================================ FILE: network_provisioning/examples/thread_prov/main/esp_ot_config.h ================================================ /* Network Provisioning Manager Example for Thread network * * This example code is in the Public Domain (or CC0 licensed, at your option.) * * Unless required by applicable law or agreed to in writing, this * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. */ #pragma once #include "esp_openthread_types.h" #if SOC_IEEE802154_SUPPORTED #define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ { \ .radio_mode = RADIO_MODE_NATIVE, \ } #else #define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ { \ .radio_mode = RADIO_MODE_UART_RCP, \ .radio_uart_config = { \ .port = 1, \ .uart_config = \ { \ .baud_rate = 115200, \ .data_bits = UART_DATA_8_BITS, \ .parity = UART_PARITY_DISABLE, \ .stop_bits = UART_STOP_BITS_1, \ .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ .rx_flow_ctrl_thresh = 0, \ .source_clk = UART_SCLK_DEFAULT, \ }, \ .rx_pin = 4, \ .tx_pin = 5, \ }, \ } #endif #define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \ { \ .host_connection_mode = HOST_CONNECTION_MODE_CLI_UART, \ .host_uart_config = { \ .port = 0, \ .uart_config = \ { \ .baud_rate = 115200, \ .data_bits = UART_DATA_8_BITS, \ .parity = UART_PARITY_DISABLE, \ .stop_bits = UART_STOP_BITS_1, \ .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ .rx_flow_ctrl_thresh = 0, \ .source_clk = UART_SCLK_DEFAULT, \ }, \ .rx_pin = UART_PIN_NO_CHANGE, \ .tx_pin = UART_PIN_NO_CHANGE, \ }, \ } #define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \ { \ .storage_partition_name = "nvs", \ .netif_queue_size = 10, \ .task_queue_size = 10, \ } ================================================ FILE: network_provisioning/examples/thread_prov/main/idf_component.yml ================================================ version: "1.0.0" dependencies: espressif/qrcode: version: "^0.1.0" espressif/network_provisioning: version: "^1.0.0" override_path: '../../../' ================================================ FILE: network_provisioning/examples/thread_prov/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 0x180000, ================================================ FILE: network_provisioning/examples/thread_prov/sdkconfig.defaults ================================================ # Override some defaults so BT stack is enabled CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y ## For Bluedroid as binary is larger than default size CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" # mbedTLS CONFIG_MBEDTLS_CMAC_C=y CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y CONFIG_MBEDTLS_ECJPAKE_C=y # OpenThread CONFIG_OPENTHREAD_ENABLED=y CONFIG_OPENTHREAD_BORDER_ROUTER=n CONFIG_OPENTHREAD_DNS64_CLIENT=y # Network type CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD=y # LwIP CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 CONFIG_LWIP_IPV6_NUM_ADDRESSES=8 CONFIG_LWIP_MULTICAST_PING=y CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y # IEEE 802.15.4 CONFIG_IEEE802154_ENABLED=y ================================================ FILE: network_provisioning/examples/thread_prov/sdkconfig.defaults.esp32 ================================================ # ESP32 specific default configurations CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n CONFIG_BTDM_CTRL_MODE_BTDM=n ================================================ FILE: network_provisioning/examples/wifi_prov/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(wifi_prov) ================================================ FILE: network_provisioning/examples/wifi_prov/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | # Wi-Fi Provisioning Example (See the README.md file in the upper level 'examples' directory for more information about examples.) `wifi_prov` example demonstrates the usage of `network_provisioning` manager component for building a Wi-Fi provisioning application. For this example, Bluetooth LE is chosen as the default mode of transport, over which the provisioning related communication is to take place. NimBLE has been configured as the default host, but you can also switch to Bluedroid using menuconfig -> Components -> Bluetooth -> Bluetooth Host. > Note: Since ESP32-S2 does not support Bluetooth LE, the SoftAP will be the default mode of transport in that case. Even for ESP32, you can change to SoftAP transport from menuconfig. In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. Right after provisioning is complete, Bluetooth LE is turned off and disabled to free the memory used by the Bluetooth LE stack. Though, that is specific to this example, and the user can choose to keep Bluetooth LE stack intact in their own application. `wifi_prov` uses the following components : * `network_provisioning` : Provides provisioning manager, data structures and protocomm endpoint handlers for Wi-Fi configuration * `protocomm` : For protocol based communication and secure session establishment * `protobuf` : Google's protocol buffer library for serialization of protocomm data structures * `bt` : ESP-IDF's Bluetooth LE stack for transport of protobuf packets This example can be used, as it is, for adding a provisioning service to any application intended for IoT. > Note: If you use this example code in your own project, in Bluetooth LE mode, then remember to enable the BT stack and BTDM BLE control settings in your SDK configuration (e.g. by using the `sdkconfig.defaults` file from this project). ## How to use example ### Hardware Required Example should be able to run on any commonly available ESP32/ESP32-C2/ESP32-C3/ESP32-C6/ESP32-S2/ESP32-S3 development board. ### Script Required Currently, Provisioning script is available for `Linux / Windows / macOS` platforms. #### Platform : Linux / Windows / macOS To install the dependency packages needed, please refer to the ESP-IDF examples [README file](https://github.com/espressif/esp-idf/blob/master/examples/README.md#running-test-python-script-ttfw). `esp_prov` supports Bluetooth LE and SoftAP transport for Linux, MacOS and Windows platforms. For Bluetooth LE, however, if dependencies are not met, the script falls back to console mode and requires another application through which the communication can take place. The `esp_prov` console will guide you through the provisioning process of locating the correct Bluetooth LE GATT services and characteristics, the values to write, and input read values. ### Configure the project ``` idf.py menuconfig ``` * Set the Bluetooth LE/Soft AP transport under "Example Configuration" options. ESP32-S2 will have only SoftAP option (SoftAP option cannot be used if IPv4 is disabled in lwIP) ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (445) app: Starting provisioning I (1035) app: Provisioning started I (1045) network_prov_mgr: Provisioning started with service name : PROV_01B1E8 ``` Make sure to note down the Bluetooth LE device name (starting with `PROV_`) displayed in the serial monitor log (eg. PROV_01B1E8). This will depend on the MAC ID and will be unique for every device. In a separate terminal run the `esp_prov.py` script under [directory](../../tool/esp_prov) (make sure to replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, which uses the protocomm security version 2 with username and password authentication: ``` python esp_prov.py --transport ble --service_name PROV_01B1E8 --sec_ver 2 --sec2_username wifiprov --sec2_pwd abcd1234 --ssid myssid --passphrase mypassword ``` For security scheme 1 with PoP-based (proof-of-possession) authentication, the following command can be used: ``` python esp_prov.py --transport ble --service_name PROV_01B1E8 --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword ``` Above command will perform the provisioning steps, and the monitor log should display something like this : ``` I (39725) app: Received Wi-Fi credentials SSID : myssid Password : mypassword . . . I (45335) esp_netif_handlers: sta ip: 192.168.43.243, mask: 255.255.255.0, gw: 192.168.43.1 I (45345) app: Provisioning successful I (45345) app: Connected with IP Address:192.168.43.243 I (46355) app: Hello World! I (47355) app: Hello World! I (48355) app: Hello World! I (49355) app: Hello World! . . . I (52315) network_prov_mgr: Provisioning stopped . . . I (52355) app: Hello World! I (53355) app: Hello World! I (54355) app: Hello World! I (55355) app: Hello World! ``` **Note:** For generating the credentials for security version 2 (`SRP6a` salt and verifier) for the device-side, the following example command can be used. The output can then directly be used in this example. The config option `CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE` should be enabled for the example and in `main/app_main.c`, the macro `EXAMPLE_PROV_SEC2_USERNAME` should be set to the same username used in the salt-verifier generation. ```log $ python esp_prov.py --transport softap --sec_ver 2 --sec2_gen_cred --sec2_username wifiprov --sec2_pwd abcd1234 ==== Salt-verifier for security scheme 2 (SRP6a) ==== static const char sec2_salt[] = { 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 }; static const char sec2_verifier[] = { 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, . . . 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba }; ``` ### QR Code Scanning Enabling `CONFIG_EXAMPLE_PROV_SHOW_QR` will display a QR code on the serial terminal, which can be scanned from the ESP Provisioning phone apps to start the Wi-Fi provisioning process. The monitor log should display something like this : ``` I (690) app: Provisioning started I (690) app: Scan this QR code from the provisioning application for Provisioning. I (700) QRCODE: Encoding below text with ECC LVL 0 & QR Code Version 10 I (710) QRCODE: {"ver":"v1","name":"PROV_01B1E8","username":"wifiprov","pop":"abcd1234","transport":"ble","network":"wifi"} █▀▀▀▀▀█ ▄▀▀████▄██▀▀▀▀ ▀▄██▄ ▄▄▀ █▀▀▀▀▀█ █ ███ █ ▄▀ ▀▀▀▄ █▀▀▀█▀▄▀█▄█▄ ▄▀▀ █ ███ █ █ ▀▀▀ █ ▄█▄▀▄ █▄▀█ ▄ █▀▀ ▀▄███▀▀ █ ▀▀▀ █ ▀▀▀▀▀▀▀ ▀▄▀ █ ▀▄▀ ▀ ▀▄▀ █ █ █ █▄▀ ▀▀▀▀▀▀▀ ▀▀█▀▀ ▀█▀ ██▀▀ ▀▄▀ ▄▄ ▀█ ▀█▄██▄ █▄▀▄█ █▄ ▀ █▀▀██▄█▀▀ ▄█ ▀ ▀██ ▀ █▀ ▄ █▀▄▀█▄▀█ ▀▄ ▀▄ ▀█ ██ ▀ ▄█▄ ▀▀▄█ ▄▀▀▀▀█▀▄▄ ▄█▄ ▀ ▄ █▄▄█ ▄▀▀▀ █▀▀ ▄ ██▄█▄▄▀ ██▄▀█▄ █▄▄ ▀▄▄ ▀██▀█▄▀ █▄▀█▄█▄ ██ █▄ ▀▄▀█▄▀▀ █▄█▄▀▄▄▀▀▄ ▄▄▀▀▄ ▀▄▄▀▀ ▀█▀▀ █▄▄█ ▀▄▄▀▄█ ██ ▄▄█ ▀▄ █▀█▄▀▀ █▀ ▄▄▀█▀ ▄ ▀▀▀▄▀█ ▀█▀██▀▄▄▄▄███▄ █▄▀▄▀▀▀▀▄█ ▄▀ █▀▀█ ▀██ ▄ ██ █▄ ▄▀▀█ ▀▄ ▀▄█▀▀ ▀ ▀ ▄▄▀▀ ▀ ▀▄▄█▄▀█ ▀ ▀▀ ▄▄█▄█▄ ▀▀█ ▀ █▀▀█▀▄▀ ▄▄▄ ▄█▄ ▀▄ █▄ ▀ ▀█▀█ ▀ █ ▄ ▀█▄ █▀▀█▄▄▀█ ▄ ▀ ▄▄▄▄▀ ▄█▄▄▀▀▄ ▄▀█▄ ▄▄▄ ▀▀▄ █ ▄ █▀▀▄█▀▄█▀▀ ▄▀ █▄██▄ ▀ ▀▄▀█ ▄█▄ ▄▄▀ ▄ ▀ ▀ ▀ ▀▀█▄██▀█▀ █ █ ▄ █ ▄█▄▄ █▀▀▀█▀▀▀█ █▀▀▀▀▀█ ▀▀█▄██▀▀ █▄▄ ▀▄█▀ ▀▄ ▄██ ▀ █ ▀▀ █ ███ █ █▀▀ ▄ ▀█▀ ▄▀▀█▀▄ █ ▀▄██ █▀▀▀▀█▀█▄ █ ▀▀▀ █ █▄ ▄ ▄█▀▀▄ ▀▄ ▄█ ▀▀ ▀ ███▀ ▀▄ ▄ ▀▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀ ▀▀ ▀ ▀▀ ▀▀ I (1000) app: If QR code is not visible, copy paste the below URL in a browser. https://espressif.github.io/esp-jumpstart/qrcode.html?data={"ver":"v1","name":"PROV_01B1E8","username":"wifiprov","pop":"abcd1234","transport":"ble","network":"wifi"} ``` ### Wi-Fi Scanning Provisioning manager also supports providing real-time Wi-Fi scan results (performed on the device) during provisioning. This allows the client side applications to choose the AP for which the device Wi-Fi station is to be configured. Various information about the visible APs is available, like signal strength (RSSI) and security type, etc. Also, the manager now provides capabilities information which can be used by client applications to determine the security type and availability of specific features (like `wifi_scan`). When using the scan based provisioning, we don't need to specify the `--ssid` and `--passphrase` fields explicitly: ``` python esp_prov.py --transport ble --service_name PROV_01B1E8 --pop abcd1234 ``` See below the sample output from `esp_prov` tool on running above command: ``` Connecting... Connected Getting Services... Security scheme determined to be : 1 ==== Starting Session ==== ==== Session Established ==== ==== Scanning Wi-Fi APs ==== ++++ Scan process executed in 1.9967520237 sec ++++ Scan results : 5 ++++ Scan finished in 2.7374596596 sec ==== Wi-Fi Scan results ==== S.N. SSID BSSID CHN RSSI AUTH [ 1] MyHomeWiFiAP 788a20841996 1 -45 WPA2_PSK [ 2] MobileHotspot 7a8a20841996 11 -46 WPA2_PSK [ 3] MyHomeWiFiAP 788a208daa26 11 -54 WPA2_PSK [ 4] NeighborsWiFiAP 8a8a20841996 6 -61 WPA2_PSK [ 5] InsecureWiFiAP dca4caf1227c 7 -74 Open Select AP by number (0 to rescan) : 1 Enter passphrase for MyHomeWiFiAP : ==== Sending Wi-Fi Credentials to Target ==== ==== Wi-Fi Credentials sent successfully ==== ==== Applying Wi-Fi Config to Target ==== ==== Apply config sent successfully ==== ==== Wi-Fi connection state ==== ==== WiFi state: Connected ==== ==== Provisioning was successful ==== ``` ### Interactive Provisioning `esp_prov` supports interactive provisioning. You can trigger the script with a simplified command and input the necessary details (`Proof-of-possession` for security scheme 1 and `SRP6a username`, `SRP6a password` for security scheme 2) as the provisioning process advances. The command `python esp_prov.py --transport ble --sec_ver 1` gives out the following sample output: ``` Discovering... ==== BLE Discovery results ==== S.N. Name Address [ 1] PROV_4C33E8 01:02:03:04:05:06 [ 1] BT_DEVICE_SBC 0A:0B:0C:0D:0E:0F Select device by number (0 to rescan) : 1 Connecting... Getting Services... Proof of Possession required: ==== Starting Session ==== ==== Session Established ==== ==== Scanning Wi-Fi APs ==== ++++ Scan process executed in 3.8695244789123535 sec ++++ Scan results : 2 ++++ Scan finished in 4.4132080078125 sec ==== Wi-Fi Scan results ==== S.N. SSID BSSID CHN RSSI AUTH [ 1] MyHomeWiFiAP 788a20841996 1 -45 WPA2_PSK [ 2] MobileHotspot 7a8a20841996 11 -46 WPA2_PSK Select AP by number (0 to rescan) : 1 Enter passphrase for myssid : ==== Sending Wi-Fi Credentials to Target ==== ==== Wi-Fi Credentials sent successfully ==== ==== Applying Wi-Fi Config to Target ==== ==== Apply config sent successfully ==== ==== Wi-Fi connection state ==== ==== WiFi state: Connected ==== ==== Provisioning was successful ==== ``` ### Sending Custom Data The provisioning manager allows applications to send some custom data during provisioning, which may be required for some other operations like connecting to some cloud service. This is achieved by creating and registering additional endpoints using the below APIs ``` network_prov_mgr_endpoint_create(); network_prov_mgr_endpoint_register(); ``` In this particular example, we have added an endpoint named "custom-data" which can be tested by passing the `--custom_data ` option to the esp\_prov tool. Following output is expected on success: ``` ==== Sending Custom data to esp32 ==== CustomData response: SUCCESS ``` ## Troubleshooting ### Provisioning failed It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason). Serial monitor log will display the failure along with disconnect reason : ``` E (367015) app: Provisioning failed! Reason : Wi-Fi AP password incorrect Please reset to factory and retry provisioning ``` Once credentials have been applied, even though wrong credentials were provided, the device will no longer go into provisioning mode on subsequent reboots until NVS is erased (see following section). ### Provisioning does not start If the serial monitor log shows the following : ``` I (465) app: Already provisioned, starting Wi-Fi STA ``` it means either the device has been provisioned earlier with or without success (e.g. scenario covered in above section), or that the Wi-Fi credentials were already set by some other application flashed previously onto your device. On setting the log level to DEBUG this is clearly evident : ``` D (455) network_prov_mgr: Found Wi-Fi SSID : myssid D (465) network_prov_mgr: Found Wi-Fi Password : m********d I (465) app: Already provisioned, starting Wi-Fi STA ``` To fix this we simple need to erase the NVS partition from flash. First we need to find out its address and size. This can be seen from the monitor log on the top right after reboot. ``` I (47) boot: Partition Table: I (50) boot: ## Label Usage Type ST Offset Length I (58) boot: 0 nvs WiFi data 01 02 00009000 00006000 I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 I (73) boot: 2 factory factory app 00 00 00010000 00124f80 I (80) boot: End of partition table ``` Now erase NVS partition by running the following commands : ``` $IDF_PATH/components/esptool_py/esptool/esptool.py erase_region 0x9000 0x6000 ``` ### Bluetooth Pairing Request during provisioning ESP-IDF now has functionality to enforce link encryption requirement while performing GATT write on characteristics of provisioning service. This will however result in a pairing pop-up dialog, if link is not encrypted. This feature is disabled by default. In order to enable this feature, please set `CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION=y` in the sdkconfig or select the configuration using "idf.py menuconfig" . ``` Component Config --> Wi-Fi Provisioning Manager --> Force Link Encryption during Characteristic Read/Write ``` Recompiling the application with above changes should suffice to enable this functionality. ### Unsupported platform If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this : ``` ==== Esp_Prov Version: v1.0 ==== BLE client is running in console mode This could be due to your platform not being supported or dependencies not being met Please ensure all pre-requisites are met to run the full fledged client BLECLI >> Please connect to BLE device `PROV_01B1E8` manually using your tool of choice BLECLI >> Was the device connected successfully? [y/n] y BLECLI >> List available attributes of the connected device BLECLI >> Is the service UUID '0000ffff-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y BLECLI >> Is the characteristic UUID '0000ff53-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y BLECLI >> Is the characteristic UUID '0000ff51-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y BLECLI >> Is the characteristic UUID '0000ff52-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y ==== Verifying protocol version ==== BLECLI >> Write following data to characteristic with UUID '0000ff53-0000-1000-8000-00805f9b34fb' : >> 56302e31 BLECLI >> Enter data read from characteristic (in hex) : << 53554343455353 ==== Verified protocol version successfully ==== ==== Starting Session ==== BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : >> 10015a25a201220a20ae6d9d5d1029f8c366892252d2d5a0ffa7ce1ee5829312545dd5f2aba057294d BLECLI >> Enter data read from characteristic (in hex) : << 10015a390801aa0134122048008bfc365fad4753dc75912e0c764d60749cb26dd609595b6fbc72e12614031a1089733af233c7448e7d7fb7963682c6d8 BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : >> 10015a270802b2012212204051088dc294fe4621fac934a8ea22e948fcc3e8ac458aac088ce705c65dbfb9 BLECLI >> Enter data read from characteristic (in hex) : << 10015a270803ba01221a20c8d38059d5206a3d92642973ac6ba8ac2f6ecf2b7a3632964eb35a0f20133adb ==== Session Established ==== ==== Sending Wifi credential to esp32 ==== BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : >> 98471ac4019a46765c28d87df8c8ae71c1ae6cfe0bc9c615bc6d2c BLECLI >> Enter data read from characteristic (in hex) : << 3271f39a ==== Wifi Credentials sent successfully ==== ==== Applying config to esp32 ==== BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : >> 5355 BLECLI >> Enter data read from characteristic (in hex) : << 1664db24 ==== Apply config sent successfully ==== ==== Wifi connection state ==== BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : >> 290d BLECLI >> Enter data read from characteristic (in hex) : << 505f72a9f8521025c1964d7789c4d7edc56aedebd144e1b667bc7c0975757b80cc091aa9f3e95b06eaefbc30290fa1 ++++ WiFi state: connected ++++ ==== Provisioning was successful ==== ``` The write data is to be copied from the console output ```>>``` to the platform specific application and the data read from the application is to be pasted at the user input prompt ```<<``` of the console, in the format (hex) indicated in above sample log. ================================================ FILE: network_provisioning/examples/wifi_prov/main/CMakeLists.txt ================================================ idf_component_register(SRCS "app_main.c" INCLUDE_DIRS ".") ================================================ FILE: network_provisioning/examples/wifi_prov/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice EXAMPLE_PROV_TRANSPORT bool "Provisioning Transport" default EXAMPLE_PROV_TRANSPORT_SOFTAP if IDF_TARGET_ESP32S2 default EXAMPLE_PROV_TRANSPORT_BLE help Wi-Fi provisioning component offers both, SoftAP and BLE transports. Choose any one. config EXAMPLE_PROV_TRANSPORT_BLE bool "BLE" select BT_ENABLED depends on !IDF_TARGET_ESP32S2 config EXAMPLE_PROV_TRANSPORT_SOFTAP bool "Soft AP" select LWIP_IPV4 endchoice choice EXAMPLE_PROV_SECURITY_VERSION bool "Protocomm security version" default EXAMPLE_PROV_SECURITY_VERSION_2 help Wi-Fi provisioning component offers 3 security versions. The example offers a choice between security version 1 and 2. config EXAMPLE_PROV_SECURITY_VERSION_1 bool "Security version 1" select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 config EXAMPLE_PROV_SECURITY_VERSION_2 bool "Security version 2" select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 endchoice choice EXAMPLE_PROV_MODE bool "Security version 2 mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 default EXAMPLE_PROV_SEC2_DEV_MODE config EXAMPLE_PROV_SEC2_DEV_MODE bool "Security version 2 development mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 help This enables the development mode for security version 2. Please note that this mode is NOT recommended for production purpose. config EXAMPLE_PROV_SEC2_PROD_MODE bool "Security version 2 production mode" depends on EXAMPLE_PROV_SECURITY_VERSION_2 help This enables the production mode for security version 2. endchoice config EXAMPLE_PROV_TRANSPORT int default 1 if EXAMPLE_PROV_TRANSPORT_BLE default 2 if EXAMPLE_PROV_TRANSPORT_SOFTAP config EXAMPLE_PROV_ENABLE_APP_CALLBACK bool "Enable provisioning manager app callback" default n help This is for advanced use-cases like modifying Wi-Fi configuration parameters. This executes a blocking app callback when any provisioning event is triggered. config EXAMPLE_RESET_PROVISIONED bool default n prompt "Reset provisioned status of the device" help This erases the NVS to reset provisioned status of the device on every reboot. Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. config EXAMPLE_RESET_PROV_MGR_ON_FAILURE bool default y prompt "Reset provisioned credentials and state machine after session failure" help Enable resetting provisioned credentials and state machine after session failure. This will restart the provisioning service after retries are exhausted. config EXAMPLE_PROV_MGR_CONNECTION_CNT int default 5 prompt "Max connection attempts before resetting provisioning state machine" depends on EXAMPLE_RESET_PROV_MGR_ON_FAILURE help Set the total number of connection attempts to avoid reconnecting to an inexistent AP or if credentials are misconfigured. Provisioned credentials are erased and internal state machine is reset after this threshold is reached. config EXAMPLE_PROV_SHOW_QR bool "Show provisioning QR code" default y help Show the QR code for provisioning. config EXAMPLE_PROV_USING_BLUEDROID bool depends on (BT_BLUEDROID_ENABLED && (IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S3)) select BT_BLE_42_FEATURES_SUPPORTED default y help This enables BLE 4.2 features for Bluedroid. config EXAMPLE_REPROVISIONING bool "Re-provisioning" help Enable re-provisioning - allow the device to provision for new credentials after previous successful provisioning. endmenu ================================================ FILE: network_provisioning/examples/wifi_prov/main/app_main.c ================================================ /* Wi-Fi Provisioning Manager Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE #include #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP #include #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ #include "qrcode.h" static const char *TAG = "app"; #if CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE #define EXAMPLE_PROV_SEC2_USERNAME "wifiprov" #define EXAMPLE_PROV_SEC2_PWD "abcd1234" /* This salt,verifier has been generated for username = "wifiprov" and password = "abcd1234" * IMPORTANT NOTE: For production cases, this must be unique to every device * and should come from device manufacturing partition.*/ static const char sec2_salt[] = { 0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4 }; static const char sec2_verifier[] = { 0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43, 0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24, 0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11, 0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89, 0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e, 0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc, 0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4, 0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e, 0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e, 0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9, 0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76, 0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17, 0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23, 0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96, 0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc, 0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2, 0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64, 0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d, 0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84, 0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb, 0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0, 0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8, 0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1, 0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba }; #endif static esp_err_t example_get_sec2_salt(const char **salt, uint16_t *salt_len) { #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE ESP_LOGI(TAG, "Development mode: using hard coded salt"); *salt = sec2_salt; *salt_len = sizeof(sec2_salt); return ESP_OK; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE ESP_LOGE(TAG, "Not implemented!"); return ESP_FAIL; #endif } static esp_err_t example_get_sec2_verifier(const char **verifier, uint16_t *verifier_len) { #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE ESP_LOGI(TAG, "Development mode: using hard coded verifier"); *verifier = sec2_verifier; *verifier_len = sizeof(sec2_verifier); return ESP_OK; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE /* This code needs to be updated with appropriate implementation to provide verifier */ ESP_LOGE(TAG, "Not implemented!"); return ESP_FAIL; #endif } #endif /* Signal Wi-Fi events on this event-group */ const int WIFI_CONNECTED_EVENT = BIT0; static EventGroupHandle_t wifi_event_group; #define PROV_QR_VERSION "v1" #define PROV_TRANSPORT_SOFTAP "softap" #define PROV_TRANSPORT_BLE "ble" #define QRCODE_BASE_URL "https://espressif.github.io/esp-jumpstart/qrcode.html" /* Event handler for catching system events */ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == NETWORK_PROV_EVENT) { switch (event_id) { case NETWORK_PROV_START: ESP_LOGI(TAG, "Provisioning started"); break; case NETWORK_PROV_WIFI_CRED_RECV: { wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data; ESP_LOGI(TAG, "Received Wi-Fi credentials" "\n\tSSID : %s\n\tPassword : %s", (const char *) wifi_sta_cfg->ssid, (const char *) wifi_sta_cfg->password); break; } case NETWORK_PROV_WIFI_CRED_FAIL: { network_prov_wifi_sta_fail_reason_t *reason = (network_prov_wifi_sta_fail_reason_t *)event_data; ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s" "\n\tPlease reset to factory and retry provisioning", (*reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) ? "Wi-Fi station authentication failed" : "Wi-Fi access-point not found"); #ifdef CONFIG_EXAMPLE_RESET_PROV_MGR_ON_FAILURE /* Reset the state machine on provisioning failure. * This is enabled by the CONFIG_EXAMPLE_RESET_PROV_MGR_ON_FAILURE configuration. * It allows the provisioning manager to retry the provisioning process * based on the number of attempts specified in wifi_conn_attempts. After attempting * the maximum number of retries, the provisioning manager will reset the state machine * and the provisioning process will be terminated. */ network_prov_mgr_reset_wifi_sm_state_on_failure(); #endif break; } case NETWORK_PROV_WIFI_CRED_SUCCESS: ESP_LOGI(TAG, "Provisioning successful"); break; case NETWORK_PROV_END: /* De-initialize manager once provisioning is finished */ esp_err_t err = network_prov_mgr_deinit(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to de-initialize provisioning manager: %s", esp_err_to_name(err)); } break; default: break; } } else if (event_base == WIFI_EVENT) { switch (event_id) { case WIFI_EVENT_STA_START: esp_wifi_connect(); break; case WIFI_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "Disconnected. Connecting to the AP again..."); esp_wifi_connect(); break; #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP case WIFI_EVENT_AP_STACONNECTED: ESP_LOGI(TAG, "SoftAP transport: Connected!"); break; case WIFI_EVENT_AP_STADISCONNECTED: ESP_LOGI(TAG, "SoftAP transport: Disconnected!"); break; #endif default: break; } } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; ESP_LOGI(TAG, "Connected with IP Address:" IPSTR, IP2STR(&event->ip_info.ip)); /* Signal main application to continue execution */ xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_EVENT); #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE } else if (event_base == PROTOCOMM_TRANSPORT_BLE_EVENT) { switch (event_id) { case PROTOCOMM_TRANSPORT_BLE_CONNECTED: ESP_LOGI(TAG, "BLE transport: Connected!"); break; case PROTOCOMM_TRANSPORT_BLE_DISCONNECTED: ESP_LOGI(TAG, "BLE transport: Disconnected!"); break; default: break; } #endif } else if (event_base == PROTOCOMM_SECURITY_SESSION_EVENT) { switch (event_id) { case PROTOCOMM_SECURITY_SESSION_SETUP_OK: ESP_LOGI(TAG, "Secured session established!"); break; case PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS: ESP_LOGE(TAG, "Received invalid security parameters for establishing secure session!"); break; case PROTOCOMM_SECURITY_SESSION_CREDENTIALS_MISMATCH: ESP_LOGE(TAG, "Received incorrect username and/or PoP for establishing secure session!"); break; default: break; } } } static void wifi_init_sta(void) { /* Start Wi-Fi in station mode */ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); } static void get_device_service_name(char *service_name, size_t max) { uint8_t eth_mac[6]; const char *ssid_prefix = "PROV_"; esp_wifi_get_mac(WIFI_IF_STA, eth_mac); snprintf(service_name, max, "%s%02X%02X%02X", ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]); } /* Handler for the optional provisioning endpoint registered by the application. * The data format can be chosen by applications. Here, we are using plain ascii text. * Applications can choose to use other formats like protobuf, JSON, XML, etc. * Note that memory for the response buffer must be allocated using heap as this buffer * gets freed by the protocomm layer once it has been sent by the transport layer. */ esp_err_t custom_prov_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) { if (inbuf) { ESP_LOGI(TAG, "Received data: %.*s", inlen, (char *)inbuf); } char response[] = "SUCCESS"; *outbuf = (uint8_t *)strdup(response); if (*outbuf == NULL) { ESP_LOGE(TAG, "System out of memory"); return ESP_ERR_NO_MEM; } *outlen = strlen(response) + 1; /* +1 for NULL terminating byte */ return ESP_OK; } static void wifi_prov_print_qr(const char *name, const char *username, const char *pop, const char *transport) { if (!name || !transport) { ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing."); return; } char payload[150] = {0}; if (pop) { #if CONFIG_EXAMPLE_PROV_SECURITY_VERSION_1 snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"pop\":\"%s\",\"transport\":\"%s\"}", PROV_QR_VERSION, name, pop, transport); #elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"username\":\"%s\",\"pop\":\"%s\",\"transport\":\"%s\"}", PROV_QR_VERSION, name, username, pop, transport); #endif } else { snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \ ",\"transport\":\"%s\",\"network\":\"wifi\"}", PROV_QR_VERSION, name, transport); } //TODO: Add the network protocol type to the QR code payload #ifdef CONFIG_EXAMPLE_PROV_SHOW_QR ESP_LOGI(TAG, "Scan this QR code from the provisioning application for Provisioning."); esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); esp_qrcode_generate(&cfg, payload); #endif /* CONFIG_EXAMPLE_PROV_SHOW_QR */ ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\n%s?data=%s", QRCODE_BASE_URL, payload); } #ifdef CONFIG_EXAMPLE_PROV_ENABLE_APP_CALLBACK void wifi_prov_app_callback(void *user_data, wifi_prov_cb_event_t event, void *event_data) { /** * This is blocking callback, any configurations that needs to be set when a particular * provisioning event is triggered can be set here. */ switch (event) { case WIFI_PROV_SET_STA_CONFIG: { /** * Wi-Fi configurations can be set here before the Wi-Fi is enabled in * STA mode. */ wifi_config_t *wifi_config = (wifi_config_t *)event_data; (void) wifi_config; break; } default: break; } } const wifi_prov_event_handler_t wifi_prov_event_handler = { .event_cb = wifi_prov_app_callback, .user_data = NULL, }; #endif /* EXAMPLE_PROV_ENABLE_APP_CALLBACK */ void app_main(void) { /* Initialize NVS partition */ esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { /* NVS partition was truncated * and needs to be erased */ ESP_ERROR_CHECK(nvs_flash_erase()); /* Retry nvs_flash_init */ ESP_ERROR_CHECK(nvs_flash_init()); } /* Initialize TCP/IP */ ESP_ERROR_CHECK(esp_netif_init()); /* Initialize the event loop */ ESP_ERROR_CHECK(esp_event_loop_create_default()); wifi_event_group = xEventGroupCreate(); /* Register our event handler for Wi-Fi, IP and Provisioning related events */ ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_TRANSPORT_BLE_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); #endif ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_SECURITY_SESSION_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); /* Initialize Wi-Fi including netif with default config */ esp_netif_create_default_wifi_sta(); #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP esp_netif_create_default_wifi_ap(); #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); /* Configuration for the provisioning manager */ network_prov_mgr_config_t config = { #ifdef CONFIG_EXAMPLE_RESET_PROV_MGR_ON_FAILURE .network_prov_wifi_conn_cfg = { .wifi_conn_attempts = CONFIG_EXAMPLE_PROV_MGR_CONNECTION_CNT, }, #endif /* What is the Provisioning Scheme that we want ? * network_prov_scheme_softap or network_prov_scheme_ble */ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE .scheme = network_prov_scheme_ble, #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP .scheme = network_prov_scheme_softap, #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ #ifdef CONFIG_EXAMPLE_PROV_ENABLE_APP_CALLBACK .app_event_handler = wifi_prov_event_handler, #endif /* EXAMPLE_PROV_ENABLE_APP_CALLBACK */ /* Any default scheme specific event handler that you would * like to choose. Since our example application requires * neither BT nor BLE, we can choose to release the associated * memory once provisioning is complete, or not needed * (in case when device is already provisioned). Choosing * appropriate scheme specific event handler allows the manager * to take care of this automatically. This can be set to * NETWORK_PROV_EVENT_HANDLER_NONE when using network_prov_scheme_softap*/ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE .scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP .scheme_event_handler = NETWORK_PROV_EVENT_HANDLER_NONE #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ }; /* Initialize provisioning manager with the * configuration parameters set above */ ESP_ERROR_CHECK(network_prov_mgr_init(config)); bool provisioned = false; #ifdef CONFIG_EXAMPLE_RESET_PROVISIONED network_prov_mgr_reset_wifi_provisioning(); #else /* Let's find out if the device is provisioned */ ESP_ERROR_CHECK(network_prov_mgr_is_wifi_provisioned(&provisioned)); #endif /* If device is not yet provisioned start provisioning service */ if (!provisioned) { ESP_LOGI(TAG, "Starting provisioning"); /* What is the Device Service Name that we want * This translates to : * - Wi-Fi SSID when scheme is network_prov_scheme_softap * - device name when scheme is network_prov_scheme_ble */ char service_name[12]; get_device_service_name(service_name, sizeof(service_name)); #ifdef CONFIG_EXAMPLE_PROV_SECURITY_VERSION_1 /* What is the security level that we want (0, 1, 2): * - NETWORK_PROV_SECURITY_0 is simply plain text communication. * - NETWORK_PROV_SECURITY_1 is secure communication which consists of secure handshake * using X25519 key exchange and proof of possession (pop) and AES-CTR * for encryption/decryption of messages. * - NETWORK_PROV_SECURITY_2 SRP6a based authentication and key exchange * + AES-GCM encryption/decryption of messages */ network_prov_security_t security = NETWORK_PROV_SECURITY_1; /* Do we want a proof-of-possession (ignored if Security 0 is selected): * - this should be a string with length > 0 * - NULL if not used */ const char *pop = "abcd1234"; /* This is the structure for passing security parameters * for the protocomm security 1. */ network_prov_security1_params_t *sec_params = pop; const char *username = NULL; #elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2 network_prov_security_t security = NETWORK_PROV_SECURITY_2; /* The username must be the same one, which has been used in the generation of salt and verifier */ #if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE /* This pop field represents the password that will be used to generate salt and verifier. * The field is present here in order to generate the QR code containing password. * In production this password field shall not be stored on the device */ const char *username = EXAMPLE_PROV_SEC2_USERNAME; const char *pop = EXAMPLE_PROV_SEC2_PWD; #elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE /* The username and password shall not be embedded in the firmware, * they should be provided to the user by other means. * e.g. QR code sticker */ const char *username = NULL; const char *pop = NULL; #endif /* This is the structure for passing security parameters * for the protocomm security 2. * If dynamically allocated, sec2_params pointer and its content * must be valid till NETWORK_PROV_END event is triggered. */ network_prov_security2_params_t sec2_params = {}; ESP_ERROR_CHECK(example_get_sec2_salt(&sec2_params.salt, &sec2_params.salt_len)); ESP_ERROR_CHECK(example_get_sec2_verifier(&sec2_params.verifier, &sec2_params.verifier_len)); network_prov_security2_params_t *sec_params = &sec2_params; #endif /* What is the service key (could be NULL) * This translates to : * - Wi-Fi password when scheme is network_prov_scheme_softap * (Minimum expected length: 8, maximum 64 for WPA2-PSK) * - simply ignored when scheme is network_prov_scheme_ble */ const char *service_key = NULL; #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE /* This step is only useful when scheme is network_prov_scheme_ble. This will * set a custom 128 bit UUID which will be included in the BLE advertisement * and will correspond to the primary GATT service that provides provisioning * endpoints as GATT characteristics. Each GATT characteristic will be * formed using the primary service UUID as base, with different auto assigned * 12th and 13th bytes (assume counting starts from 0th byte). The client side * applications must identify the endpoints by reading the User Characteristic * Description descriptor (0x2901) for each characteristic, which contains the * endpoint name of the characteristic */ uint8_t custom_service_uuid[] = { /* LSB <--------------------------------------- * ---------------------------------------> MSB */ 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf, 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02, }; /* If your build fails with linker errors at this point, then you may have * forgotten to enable the BT stack or BTDM BLE settings in the SDK (e.g. see * the sdkconfig.defaults in the example project) */ network_prov_scheme_ble_set_service_uuid(custom_service_uuid); #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ /* An optional endpoint that applications can create if they expect to * get some additional custom data during provisioning workflow. * The endpoint name can be anything of your choice. * This call must be made before starting the provisioning. */ network_prov_mgr_endpoint_create("custom-data"); /* Do not stop and de-init provisioning even after success, * so that we can restart it later. */ #ifdef CONFIG_EXAMPLE_REPROVISIONING network_prov_mgr_disable_auto_stop(1000); #endif /* Start provisioning service */ ESP_ERROR_CHECK(network_prov_mgr_start_provisioning(security, (const void *) sec_params, service_name, service_key)); /* The handler for the optional endpoint created above. * This call must be made after starting the provisioning, and only if the endpoint * has already been created above. */ network_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL); /* Uncomment the following to wait for the provisioning to finish and then release * the resources of the manager. Since in this case de-initialization is triggered * by the default event loop handler, we don't need to call the following */ // network_prov_mgr_wait(); // network_prov_mgr_deinit(); /* Print QR code for provisioning */ #ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE wifi_prov_print_qr(service_name, username, pop, PROV_TRANSPORT_BLE); #else /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ wifi_prov_print_qr(service_name, username, pop, PROV_TRANSPORT_SOFTAP); #endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ } else { ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA"); /* We don't need the manager as device is already provisioned, * so let's release it's resources */ ESP_ERROR_CHECK(network_prov_mgr_deinit()); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); /* Start Wi-Fi station */ wifi_init_sta(); } /* Wait for Wi-Fi connection */ xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, true, true, portMAX_DELAY); /* Start main application now */ #if CONFIG_EXAMPLE_REPROVISIONING while (1) { for (int i = 0; i < 10; i++) { ESP_LOGI(TAG, "Hello World!"); vTaskDelay(1000 / portTICK_PERIOD_MS); } /* Resetting provisioning state machine to enable re-provisioning */ network_prov_mgr_reset_wifi_sm_state_for_reprovision(); /* Wait for Wi-Fi connection */ xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, true, true, portMAX_DELAY); } #else while (1) { ESP_LOGI(TAG, "Hello World!"); vTaskDelay(1000 / portTICK_PERIOD_MS); } #endif } ================================================ FILE: network_provisioning/examples/wifi_prov/main/idf_component.yml ================================================ version: "1.0.0" dependencies: espressif/qrcode: version: "^0.1.0" espressif/network_provisioning: version: "*" override_path: '../../../' ================================================ FILE: network_provisioning/examples/wifi_prov/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 0x180000, ================================================ FILE: network_provisioning/examples/wifi_prov/sdkconfig.defaults ================================================ # Override some defaults so BT stack is enabled CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y ## For Bluedroid as binary is larger than default size CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" ================================================ FILE: network_provisioning/examples/wifi_prov/sdkconfig.defaults.esp32 ================================================ # ESP32 specific default configurations CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n CONFIG_BTDM_CTRL_MODE_BTDM=n ================================================ FILE: network_provisioning/idf_component.yml ================================================ version: "1.2.4" description: Network provisioning component for Wi-Fi or Thread devices url: https://github.com/espressif/idf-extra-components/tree/master/network_provisioning dependencies: idf: ">=5.1" espressif/cjson: version: "^1.7.19" override_path: "../cjson" rules: - if: "idf_version >= 6.0" ================================================ FILE: network_provisioning/include/network_provisioning/manager.h ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_event.h" #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #include "esp_wifi_types.h" #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #include "openthread/dataset.h" #endif #include "network_provisioning/network_config.h" #ifdef __cplusplus extern "C" { #endif ESP_EVENT_DECLARE_BASE(NETWORK_PROV_EVENT); /** * @brief Events generated by manager * * These events are generated in order of declaration and, for the * stretch of time between initialization and de-initialization of * the manager, each event is signaled only once */ typedef enum { /** * Emitted when the manager is initialized */ NETWORK_PROV_INIT, /** * Indicates that provisioning has started */ NETWORK_PROV_START, #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Emitted before accepting the wifi credentials to * set the wifi configurations according to requirement. * NOTE - In this case event_data shall be populated with a pointer to `wifi_config_t`. */ NETWORK_PROV_SET_WIFI_STA_CONFIG, /** * Emitted when Wi-Fi AP credentials are received via `protocomm` * endpoint `network_config`. The event data in this case is a pointer * to the corresponding `wifi_sta_config_t` structure */ NETWORK_PROV_WIFI_CRED_RECV, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Emitted when Thread Dataset is received via `protocomm` endpoint * `network_config`, The event data in this case is a pointer to the * corresponding `otOperationalDatasetTlvs` structure */ NETWORK_PROV_THREAD_DATASET_RECV, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Emitted when device fails to connect to the AP of which the * credentials were received earlier on event `NETWORK_PROV_WIFI_CRED_RECV`. * The event data in this case is a pointer to the disconnection * reason code with type `network_prov_wifi_sta_fail_reason_t` */ NETWORK_PROV_WIFI_CRED_FAIL, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Emitted when device fails to connect to the Thread network of which * dataset was received earlier on event `NETWORK_PROv_THREAD_DATASET_RECV`. * The event data in this case is a pointer to the disconnection * reason code with type `network_prov_thread_fail_reason_t` */ NETWORK_PROV_THREAD_DATASET_FAIL, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Emitted when device successfully connects to the AP of which the * credentials were received earlier on event `NETWORK_PROV_WIFI_CRED_RECV` */ NETWORK_PROV_WIFI_CRED_SUCCESS, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Emitted when device successfully connects to the Thread etwork of * which the dataset was received earlier on event * `NETWORK_PROV_THREAD_DATASET_RECV` */ NETWORK_PROV_THREAD_DATASET_SUCCESS, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Signals that provisioning service has stopped */ NETWORK_PROV_END, /** * Signals that manager has been de-initialized */ NETWORK_PROV_DEINIT, } network_prov_cb_event_t; typedef void (*network_prov_cb_func_t)(void *user_data, network_prov_cb_event_t event, void *event_data); /** * @brief Event handler that is used by the manager while * provisioning service is active */ typedef struct { /** * Callback function to be executed on provisioning events */ network_prov_cb_func_t event_cb; /** * User context data to pass as parameter to callback function */ void *user_data; } network_prov_event_handler_t; /** * @brief Structure holding the configuration related to Wi-Fi provisioning */ typedef struct { /** * Maximum number of allowed connection attempts for Wi-Fi. If value 0 * same as legacy behavior of infinite connection attempts. */ uint32_t wifi_conn_attempts; } network_prov_wifi_conn_cfg_t; /** * @brief Event handler can be set to none if not used */ #define NETWORK_PROV_EVENT_HANDLER_NONE { \ .event_cb = NULL, \ .user_data = NULL \ } /** * @brief Structure for specifying the provisioning scheme to be * followed by the manager * * @note Ready to use schemes are available: * - network_prov_scheme_ble : for provisioning over BLE transport + GATT server * - network_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server * - network_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) */ typedef struct network_prov_scheme { /** * Function which is to be called by the manager when it is to * start the provisioning service associated with a protocomm instance * and a scheme specific configuration */ esp_err_t (*prov_start) (protocomm_t *pc, void *config); /** * Function which is to be called by the manager to stop the * provisioning service previously associated with a protocomm instance */ esp_err_t (*prov_stop) (protocomm_t *pc); /** * Function which is to be called by the manager to generate * a new configuration for the provisioning service, that is * to be passed to prov_start() */ void *(*new_config) (void); /** * Function which is to be called by the manager to delete a * configuration generated using new_config() */ void (*delete_config) (void *config); /** * Function which is to be called by the manager to set the * service name and key values in the configuration structure */ esp_err_t (*set_config_service) (void *config, const char *service_name, const char *service_key); /** * Function which is to be called by the manager to set a protocomm endpoint * with an identifying name and UUID in the configuration structure */ esp_err_t (*set_config_endpoint) (void *config, const char *endpoint_name, uint16_t uuid); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Sets mode of operation of Wi-Fi during provisioning * This is set to : * - WIFI_MODE_APSTA for SoftAP transport * - WIFI_MODE_STA for BLE transport */ wifi_mode_t wifi_mode; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI } network_prov_scheme_t; /** * @brief Structure for specifying the manager configuration */ typedef struct { /** * Provisioning scheme to use. Following schemes are already available: * - network_prov_scheme_ble : for provisioning over BLE transport + GATT server * - network_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server + mDNS (optional) * - network_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) */ network_prov_scheme_t scheme; /** * Event handler required by the scheme for incorporating scheme specific * behavior while provisioning manager is running. Various options may be * provided by the scheme for setting this field. Use NETWORK_PROV_EVENT_HANDLER_NONE * when not used. When using scheme network_prov_scheme_ble, the following * options are available: * - NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM * - NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE * - NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT */ network_prov_event_handler_t scheme_event_handler; /** * Event handler that can be set for the purpose of incorporating application * specific behavior. Use NETWORK_PROV_EVENT_HANDLER_NONE when not used. */ network_prov_event_handler_t app_event_handler; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * This config holds the Wi-Fi provisioning related configurations. */ network_prov_wifi_conn_cfg_t network_prov_wifi_conn_cfg; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI } network_prov_mgr_config_t; /** * @brief Security modes supported by the Provisioning Manager. * * These are same as the security modes provided by protocomm */ typedef enum network_prov_security { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 /** * No security (plain-text communication) */ NETWORK_PROV_SECURITY_0 = 0, #endif #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 /** * This secure communication mode consists of * X25519 key exchange * + proof of possession (pop) based authentication * + AES-CTR encryption */ NETWORK_PROV_SECURITY_1 = 1, #endif #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 /** * This secure communication mode consists of * SRP6a based authentication and key exchange * + AES-GCM encryption/decryption */ NETWORK_PROV_SECURITY_2 = 2 #endif #if !CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 && !CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 && !CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 #error "All of the protocomm security versions are disabled. Make sure to enable at least one security version." #endif } network_prov_security_t; /** * @brief Security 1 params structure * This needs to be passed when using NETWORK_PROV_SECURITY_1 */ typedef const char network_prov_security1_params_t; /** * @brief Security 2 params structure * This needs to be passed when using NETWORK_PROV_SECURITY_2 */ typedef protocomm_security2_params_t network_prov_security2_params_t; /** * @brief Initialize provisioning manager instance * * Configures the manager and allocates internal resources * * Configuration specifies the provisioning scheme (transport) * and event handlers * * Event NETWORK_PROV_INIT is emitted right after initialization * is complete * * @param[in] config Configuration structure * * @return * - ESP_OK : Success * - ESP_FAIL : Fail */ esp_err_t network_prov_mgr_init(network_prov_mgr_config_t config); /** * @brief Stop provisioning (if running) and release * resource used by the manager * * Event NETWORK_PROV_DEINIT is emitted right after de-initialization * is finished * * If provisioning service is still active when this API is called, * it first stops the service, hence emitting NETWORK_PROV_END, and * then performs the de-initialization * * @return * - ESP_OK : Success * - ESP_FAIL : Failed to post event NETWORK_PROV_DEINIT or NETWORK_PROV_END * - ESP_ERR_NO_MEM : Out of memory (as may be returned by esp_event_post) */ esp_err_t network_prov_mgr_deinit(void); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * @brief Checks if device is provisioned * * This checks if Wi-Fi credentials are present on the NVS * * The Wi-Fi credentials are assumed to be kept in the same * NVS namespace as used by esp_wifi component * * If one were to call esp_wifi_set_config() directly instead * of going through the provisioning process, this function will * still yield true (i.e. device will be found to be provisioned) * * @note Calling network_prov_mgr_start_provisioning() automatically * resets the provision state, irrespective of what the * state was prior to making the call. * * @param[out] provisioned True if provisioned, else false * * @return * - ESP_OK : Retrieved provision state successfully * - ESP_FAIL : Wi-Fi not initialized * - ESP_ERR_INVALID_ARG : Null argument supplied */ esp_err_t network_prov_mgr_is_wifi_provisioned(bool *provisioned); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Checks if device is provisioned * * This checks if there is active dataset for Thread device * * @note Calling network_prov_mgr_start_provisioning() automatically * resets the provision state, irrespective of what the * state was prior to making the call. * * @param[out] provisioned True if provisioned, else false * * @return * - ESP_OK : Retrieved provision state successfully * - ESP_FAIL : Not initialized * - ESP_ERR_INVALID_ARG : Null argument supplied */ esp_err_t network_prov_mgr_is_thread_provisioned(bool *provisioned); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Checks whether the provisioning state machine is idle * * @return True if state machine is idle, else false */ bool network_prov_mgr_is_sm_idle(void); /** * @brief Start provisioning service * * This starts the provisioning service according to the scheme * configured at the time of initialization. For scheme : * - network_prov_scheme_ble : This starts protocomm_ble, which internally initializes * BLE transport and starts GATT server for handling * provisioning requests * - network_prov_scheme_softap : This activates SoftAP mode of Wi-Fi and starts * protocomm_httpd, which internally starts an HTTP * server for handling provisioning requests (If mDNS is * active it also starts advertising service with type * _esp_wifi_prov._tcp) * * Event NETWORK_PROV_START is emitted right after provisioning starts without failure * * @note This API will start provisioning service even if device is found to be * already provisioned, i.e. network_prov_mgr_is_wifi_provisioned() yields true * * @param[in] security Specify which protocomm security scheme to use : * - NETWORK_PROV_SECURITY_0 : For no security * - NETWORK_PROV_SECURITY_1 : x25519 secure handshake for session * establishment followed by AES-CTR encryption of provisioning messages * - NETWORK_PROV_SECURITY_2: SRP6a based authentication and key exchange * followed by AES-GCM encryption/decryption of provisioning messages * @param[in] network_prov_sec_params * Pointer to security params (NULL if not needed). * This is not needed for protocomm security 0 * This pointer should hold the struct of type * network_prov_security1_params_t for protocomm security 1 * and network_prov_security2_params_t for protocomm security 2 respectively. * This pointer and its contents should be valid till the provisioning service is * running and has not been stopped or de-inited. * @param[in] service_name Unique name of the service. This translates to: * - Wi-Fi SSID when provisioning mode is softAP * - Device name when provisioning mode is BLE * @param[in] service_key Key required by client to access the service (NULL if not needed). * This translates to: * - Wi-Fi password when provisioning mode is softAP * - ignored when provisioning mode is BLE * * @return * - ESP_OK : Provisioning started successfully * - ESP_FAIL : Failed to start provisioning service * - ESP_ERR_INVALID_STATE : Provisioning manager not initialized or already started */ esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, const void *network_prov_sec_params, const char *service_name, const char *service_key); /** * @brief Stop provisioning service * * If provisioning service is active, this API will initiate a process to stop * the service and return. Once the service actually stops, the event NETWORK_PROV_END * will be emitted. * * If network_prov_mgr_deinit() is called without calling this API first, it will * automatically stop the provisioning service and emit the NETWORK_PROV_END, followed * by NETWORK_PROV_DEINIT, before returning. * * This API will generally be used along with network_prov_mgr_disable_auto_stop() * in the scenario when the main application has registered its own endpoints, * and wishes that the provisioning service is stopped only when some protocomm * command from the client side application is received. * * Calling this API inside an endpoint handler, with sufficient cleanup_delay, * will allow the response / acknowledgment to be sent successfully before the * underlying protocomm service is stopped. * * Cleaup_delay is set when calling network_prov_mgr_disable_auto_stop(). * If not specified, it defaults to 1000ms. * * For straightforward cases, using this API is usually not necessary as * provisioning is stopped automatically once NETWORK_PROV_CRED_SUCCESS is emitted. * Stopping is delayed (maximum 30 seconds) thus allowing the client side * application to query for network state, i.e. after receiving the first query * and sending `network state connected` response the service is stopped immediately. */ void network_prov_mgr_stop_provisioning(void); /** * @brief Wait for provisioning service to finish * * Calling this API will block until provisioning service is stopped * i.e. till event NETWORK_PROV_END is emitted. * * This will not block if provisioning is not started or not initialized. */ void network_prov_mgr_wait(void); /** * @brief Disable auto stopping of provisioning service upon completion * * By default, once provisioning is complete, the provisioning service is automatically * stopped, and all endpoints (along with those registered by main application) are * deactivated. * * This API is useful in the case when main application wishes to close provisioning service * only after it receives some protocomm command from the client side app. For example, after * connecting to network, the device may want to connect to the cloud, and only once that is * successfully, the device is said to be fully configured. But, then it is upto the main * application to explicitly call network_prov_mgr_stop_provisioning() later when the device is * fully configured and the provisioning service is no longer required. * * @note This must be called before executing network_prov_mgr_start_provisioning() * * @param[in] cleanup_delay Sets the delay after which the actual cleanup of transport related * resources is done after a call to network_prov_mgr_stop_provisioning() * returns. Minimum allowed value is 100ms. If not specified, this will * default to 1000ms. * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_STATE : Manager not initialized or * provisioning service already started */ esp_err_t network_prov_mgr_disable_auto_stop(uint32_t cleanup_delay); /** * @brief Set application version and capabilities in the JSON data returned by * proto-ver endpoint * * This function can be called multiple times, to specify information about the various * application specific services running on the device, identified by unique labels. * * The provisioning service itself registers an entry in the JSON data, by the label "prov", * containing only provisioning service version and capabilities. Application services should * use a label other than "prov" so as not to overwrite this. * * @note This must be called before executing network_prov_mgr_start_provisioning() * * @param[in] label String indicating the application name. * * @param[in] version String indicating the application version. * There is no constraint on format. * * @param[in] capabilities Array of strings with capabilities. * These could be used by the client side app to know * the application registered endpoint capabilities * * @param[in] total_capabilities Size of capabilities array * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_STATE : Manager not initialized or * provisioning service already started * - ESP_ERR_NO_MEM : Failed to allocate memory for version string * - ESP_ERR_INVALID_ARG : Null argument */ esp_err_t network_prov_mgr_set_app_info(const char *label, const char *version, const char **capabilities, size_t total_capabilities); /** * @brief Create an additional endpoint and allocate internal resources for it * * This API is to be called by the application if it wants to create an additional * endpoint. All additional endpoints will be assigned UUIDs starting from 0xFF54 * and so on in the order of execution. * * protocomm handler for the created endpoint is to be registered later using * network_prov_mgr_endpoint_register() after provisioning has started. * * @note This API can only be called BEFORE provisioning is started * * @note Additional endpoints can be used for configuring client provided * parameters other than Wi-Fi credentials or Thread dataset, that are necessary * for the main application and hence must be set prior to starting the application * * @note After session establishment, the additional endpoints must be targeted * first by the client side application before sending Wi-Fi/Thread configuration, * because once Wi-Fi/Thread configuration finishes the provisioning service is * stopped and hence all endpoints are unregistered * * @param[in] ep_name unique name of the endpoint * * @return * - ESP_OK : Success * - ESP_FAIL : Failure */ esp_err_t network_prov_mgr_endpoint_create(const char *ep_name); /** * @brief Register a handler for the previously created endpoint * * This API can be called by the application to register a protocomm handler * to any endpoint that was created using network_prov_mgr_endpoint_create(). * * @note This API can only be called AFTER provisioning has started * * @note Additional endpoints can be used for configuring client provided * parameters other than Wi-Fi credentials or Thread dataset, that are necessary * for the main application and hence must be set prior to starting the application * * @note After session establishment, the additional endpoints must be targeted * first by the client side application before sending Wi-Fi/Thread configuration, * because once Wi-Fi/Thread configuration finishes the provisioning service is * stopped and hence all endpoints are unregistered * * @param[in] ep_name Name of the endpoint * @param[in] handler Endpoint handler function * @param[in] user_ctx User data * * @return * - ESP_OK : Success * - ESP_FAIL : Failure */ esp_err_t network_prov_mgr_endpoint_register(const char *ep_name, protocomm_req_handler_t handler, void *user_ctx); /** * @brief Unregister the handler for an endpoint * * This API can be called if the application wants to selectively * unregister the handler of an endpoint while the provisioning * is still in progress. * * All the endpoint handlers are unregistered automatically when * the provisioning stops. * * @param[in] ep_name Name of the endpoint */ void network_prov_mgr_endpoint_unregister(const char *ep_name); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * @brief Get state of Wi-Fi Station during provisioning * * @param[out] state Pointer to network_prov_wifi_sta_state_t * variable to be filled * * @return * - ESP_OK : Successfully retrieved Wi-Fi state * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_get_wifi_state(network_prov_wifi_sta_state_t *state); /** * @brief Get reason code in case of Wi-Fi station * disconnection during provisioning * * @param[out] reason Pointer to network_prov_wifi_sta_fail_reason_t * variable to be filled * * @return * - ESP_OK : Successfully retrieved Wi-Fi disconnect reason * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_get_wifi_disconnect_reason(network_prov_wifi_sta_fail_reason_t *reason); /** * @brief Runs Wi-Fi as Station with the supplied configuration * * Configures the Wi-Fi station mode to connect to the AP with * SSID and password specified in config structure and sets * Wi-Fi to run as station. * * This is automatically called by provisioning service upon * receiving new credentials. * * If credentials are to be supplied to the manager via a * different mode other than through protocomm, then this * API needs to be called. * * Event NETWORK_PROV_CRED_RECV is emitted after credentials have * been applied and Wi-Fi station started * * @param[in] wifi_cfg Pointer to Wi-Fi configuration structure * * @return * - ESP_OK : Wi-Fi configured and started successfully * - ESP_FAIL : Failed to set configuration */ esp_err_t network_prov_mgr_configure_wifi_sta(wifi_config_t *wifi_cfg); /** * @brief Reset Wi-Fi provisioning config * * Calling this API will restore WiFi stack persistent settings to default values. * * @return * - ESP_OK : Reset provisioning config successfully * - ESP_FAIL : Failed to reset provisioning config */ esp_err_t network_prov_mgr_reset_wifi_provisioning(void); /** * @brief Reset internal state machine and clear provisioned credentials. * * This API should be used to restart provisioning ONLY in the case * of provisioning failures without rebooting the device. * * @return * - ESP_OK : Reset provisioning state machine successfully * - ESP_FAIL : Failed to reset provisioning state machine * - ESP_ERR_INVALID_STATE : Manager not initialized */ esp_err_t network_prov_mgr_reset_wifi_sm_state_on_failure(void); /** * @brief Reset internal state machine and clear provisioned credentials. * * This API can be used to restart provisioning ONLY in case the device is * to be provisioned again for new credentials after a previous successful * provisioning without rebooting the device. * * @note This API can be used only if provisioning auto-stop has been * disabled using network_prov_mgr_disable_auto_stop() * * @return * - ESP_OK : Reset provisioning state machine successfully * - ESP_FAIL : Failed to reset provisioning state machine * - ESP_ERR_INVALID_STATE : Manager not initialized */ esp_err_t network_prov_mgr_reset_wifi_sm_state_for_reprovision(void); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Reset Thread provisioning config * * Calling this API will restore Thread stack persistent settings to default values. * * @return * - ESP_OK : Reset provisioning config successfully * - ESP_FAIL : Failed to reset provisioning config */ esp_err_t network_prov_mgr_reset_thread_provisioning(void); /** * @brief Get state of Thread during provisioning * * @param[out] state Pointer to network_prov_thread_state_t * variable to be filled * * @return * - ESP_OK : Successfully retrieved Thread state * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_get_thread_state(network_prov_thread_state_t *state); /** * @brief Get reason code in case of thread detached during provisioning * * @param[out] reason Pointer to network_prov_thread_fail_reason_t * variable to be filled * * @return * - ESP_OK : Successfully retrieved thread detached reason * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_get_thread_detached_reason(network_prov_thread_fail_reason_t *reason); /** * @brief Runs Thread with the supplied configuration * * Configures the Thread Dataset so that the device can be attached * to a specific Thread network * * This is automatically called by provisioning service upon * receiving new Thread dataset. * * If the dataset is to be supplied to the manager via a * different mode other than through protocomm, then this * API needs to be called. * * Event THREAD_PROV_CRED_RECV is emitted after credentials have * been applied and Thread started * * @param[in] thread_dataset Pointer to Dataset Tlvs structure * * @return * - ESP_OK : Thread dataset configured and started successfully * - ESP_FAIL : Failed to set configuration */ esp_err_t network_prov_mgr_configure_thread_dataset(otOperationalDatasetTlvs *thread_dataset); /** * @brief Reset internal state machine and clear provisioned credentials. * * This API should be used to restart provisioning ONLY in the case * of provisioning failures without rebooting the device. * * @return * - ESP_OK : Reset provisioning state machine successfully * - ESP_FAIL : Failed to reset provisioning state machine * - ESP_ERR_INVALID_STATE : Manager not initialized */ esp_err_t network_prov_mgr_reset_thread_sm_state_on_failure(void); /** * @brief Reset internal state machine and clear provisioned credentials. * * This API can be used to restart provisioning ONLY in case the device is * to be provisioned again for new credentials after a previous successful * provisioning without rebooting the device. * * @note This API can be used only if provisioning auto-stop has been * disabled using network_prov_mgr_disable_auto_stop() * * @return * - ESP_OK : Reset provisioning state machine successfully * - ESP_FAIL : Failed to reset provisioning state machine * - ESP_ERR_INVALID_STATE : Manager not initialized */ esp_err_t network_prov_mgr_reset_thread_sm_state_for_reprovision(void); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #ifdef __cplusplus } #endif ================================================ FILE: network_provisioning/include/network_provisioning/network_config.h ================================================ /* * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef _NETWORK_PROV_CONFIG_H_ #define _NETWORK_PROV_CONFIG_H_ #include "esp_netif_ip_addr.h" #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * @brief WiFi STA status for conveying back to the provisioning master */ typedef enum { NETWORK_PROV_WIFI_STA_CONNECTING, NETWORK_PROV_WIFI_STA_CONNECTED, NETWORK_PROV_WIFI_STA_DISCONNECTED, NETWORK_PROV_WIFI_STA_CONN_ATTEMPT_FAILED } network_prov_wifi_sta_state_t; /** * @brief WiFi STA connection fail reason */ typedef enum { NETWORK_PROV_WIFI_STA_AUTH_ERROR, NETWORK_PROV_WIFI_STA_AP_NOT_FOUND } network_prov_wifi_sta_fail_reason_t; /** * @brief WiFi STA connected status information */ typedef struct { /** * IP Address received by station */ char ip_addr[IP4ADDR_STRLEN_MAX]; char bssid[6]; /*!< BSSID of the AP to which connection was estalished */ char ssid[33]; /*!< SSID of the to which connection was estalished */ uint8_t channel; /*!< Channel of the AP */ uint8_t auth_mode; /*!< Authorization mode of the AP */ } network_prov_wifi_sta_conn_info_t; /** * @brief WiFi STA connecting status information */ typedef struct { uint32_t attempts_remaining; /*!< Number of Wi-Fi connection attempts remaining */ } network_prov_wifi_sta_connecting_info_t; /** * @brief WiFi status data to be sent in response to `get_status` request from master */ typedef struct { network_prov_wifi_sta_state_t wifi_state; /*!< WiFi state of the station */ union { /** * Reason for disconnection (valid only when `wifi_state` is `WIFI_STATION_DISCONNECTED`) */ network_prov_wifi_sta_fail_reason_t fail_reason; /** * Connection information (valid only when `wifi_state` is `WIFI_STATION_CONNECTED`) */ network_prov_wifi_sta_conn_info_t conn_info; /** * Connecting information (valid only when `wifi_state` is `WIFI_STATION_CONNECTING`) */ network_prov_wifi_sta_connecting_info_t connecting_info; }; } network_prov_config_get_wifi_data_t; /** * @brief WiFi config data received by slave during `set_config` request from master */ typedef struct { char ssid[33]; /*!< SSID of the AP to which the slave is to be connected */ char password[64]; /*!< Password of the AP */ char bssid[6]; /*!< BSSID of the AP */ uint8_t channel; /*!< Channel of the AP */ } network_prov_config_set_wifi_data_t; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Thread status for conveying back to the provisioning master */ typedef enum { NETWORK_PROV_THREAD_ATTACHING, NETWORK_PROV_THREAD_ATTACHED, NETWORK_PROV_THREAD_DETACHED, } network_prov_thread_state_t; /** * @brief Thread attaching fail reason */ typedef enum { NETWORK_PROV_THREAD_DATASET_INVALID, NETWORK_PROV_THREAD_NETWORK_NOT_FOUND, } network_prov_thread_fail_reason_t; /** * @brief Thread attached status information */ typedef struct { uint32_t pan_id; /*!< PAN ID */ uint16_t channel; /*!< Channel */ char name[17]; /*!< Network Name */ uint8_t ext_pan_id[8]; /*!< Extended PAN ID */ } network_prov_thread_conn_info_t; /** * @brief Thread status data to be sent in response to `get_status` request from master */ typedef struct { network_prov_thread_state_t thread_state; union { /** * Reason for disconnection (valid only when `thread_state` is `THREAD_DETACHED`) */ network_prov_thread_fail_reason_t fail_reason; /** * Connection information (valid only when `thread_state` is `THREAD_ATTACHED`) */ network_prov_thread_conn_info_t conn_info; }; } network_prov_config_get_thread_data_t; /** * @brief Thread config data received by slave during `set_config` request from master */ typedef struct { uint8_t dataset[254]; uint8_t length; } network_prov_config_set_thread_data_t; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Type of context data passed to each get/set/apply handler * function set in `network_prov_config_handlers` structure. * * This is passed as an opaque pointer, thereby allowing it be defined * later in application code as per requirements. */ typedef struct network_prov_ctx network_prov_ctx_t; /** * @brief Internal handlers for receiving and responding to protocomm * requests from master * * This is to be passed as priv_data for protocomm request handler * (refer to `network_prov_config_data_handler()`) when calling `protocomm_add_endpoint()`. */ typedef struct network_prov_config_handlers { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Handler function called when connection status * of the slave (in WiFi station mode) is requested */ esp_err_t (*wifi_get_status_handler)(network_prov_config_get_wifi_data_t *resp_data, network_prov_ctx_t **ctx); /** * Handler function called when WiFi connection configuration * (eg. AP SSID, password, etc.) of the slave (in WiFi station mode) * is to be set to user provided values */ esp_err_t (*wifi_set_config_handler)(const network_prov_config_set_wifi_data_t *req_data, network_prov_ctx_t **ctx); /** * Handler function for applying the configuration that was set in * `set_config_handler`. After applying the station may get connected to * the AP or may fail to connect. The slave must be ready to convey the * updated connection status information when `get_status_handler` is * invoked again by the master. */ esp_err_t (*wifi_apply_config_handler)(network_prov_ctx_t **ctx); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t (*thread_get_status_handler)(network_prov_config_get_thread_data_t *resp_data, network_prov_ctx_t **ctx); esp_err_t (*thread_set_config_handler)(const network_prov_config_set_thread_data_t *req_data, network_prov_ctx_t **ctx); esp_err_t (*thread_apply_config_handler)(network_prov_ctx_t **ctx); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Context pointer to be passed to above handler functions upon invocation */ network_prov_ctx_t *ctx; } network_prov_config_handlers_t; /** * @brief Handler for receiving and responding to requests from master * * This is to be registered as the `network_config` endpoint handler * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` */ esp_err_t network_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data); #ifdef __cplusplus } #endif #endif ================================================ FILE: network_provisioning/include/network_provisioning/network_scan.h ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef _PROV_NETWORK_SCAN_H_ #define _PROV_NETWORK_SCAN_H_ #ifdef __cplusplus extern "C" { #endif #include #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #include #define WIFI_SSID_LEN sizeof(((wifi_ap_record_t *)0)->ssid) #define WIFI_BSSID_LEN sizeof(((wifi_ap_record_t *)0)->bssid) #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #include #include #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Type of context data passed to each get/set/apply handler * function set in `network_prov_scan_handlers` structure. * * This is passed as an opaque pointer, thereby allowing it be defined * later in application code as per requirements. */ typedef struct network_prov_scan_ctx network_prov_scan_ctx_t; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * @brief Structure of entries in the scan results list */ typedef struct { /** * SSID of Wi-Fi AP */ char ssid[WIFI_SSID_LEN]; /** * BSSID of Wi-Fi AP */ char bssid[WIFI_BSSID_LEN]; /** * Wi-Fi channel number */ uint8_t channel; /** * Signal strength */ int rssi; /** * Wi-Fi security mode */ uint8_t auth; } network_prov_scan_wifi_result_t; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD typedef struct { uint16_t pan_id; uint8_t ext_pan_id[OT_EXT_ADDRESS_SIZE]; char network_name[OT_NETWORK_NAME_MAX_SIZE + 1]; uint16_t channel; uint8_t ext_addr[OT_EXT_ADDRESS_SIZE]; int8_t rssi; uint8_t lqi; } network_prov_scan_thread_result_t; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Internal handlers for receiving and responding to protocomm * requests from client * * This is to be passed as priv_data for protocomm request handler * (refer to `network_prov_scan_handler()`) when calling `protocomm_add_endpoint()`. */ typedef struct network_prov_scan_handlers { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Handler function called when scan start command is received * with various scan parameters : * * blocking (input) - If true, the function should return only * when the scanning is finished * * passive (input) - If true, scan is to be started in passive * mode (this may be slower) instead of active mode * * group_channels (input) - This specifies whether to scan * all channels in one go (when zero) or perform scanning of * channels in groups, with 120ms delay between scanning of * consecutive groups, and the value of this parameter sets the * number of channels in each group. This is useful when transport * mode is SoftAP, where scanning all channels in one go may not * give the Wi-Fi driver enough time to send out beacons, and * hence may cause disconnection with any connected stations. * When scanning in groups, the manager will wait for at least * 120ms after completing scan on a group of channels, and thus * allow the driver to send out the beacons. For example, given * that the total number of Wi-Fi channels is 14, then setting * group_channels to 4, will create 5 groups, with each group * having 3 channels, except the last one which will have * 14 % 3 = 2 channels. So, when scan is started, the first 3 * channels will be scanned, followed by a 120ms delay, and then * the next 3 channels, and so on, until all the 14 channels have * been scanned. One may need to adjust this parameter as having * only few channels in a group may slow down the overall scan * time, while having too many may again cause disconnection. * Usually a value of 4 should work for most cases. Note that * for any other mode of transport, e.g. BLE, this can be safely * set to 0, and hence achieve the fastest overall scanning time. * * period_ms (input) - Scan parameter specifying how long to * wait on each channel (in milli-seconds) */ esp_err_t (*wifi_scan_start)(bool blocking, bool passive, uint8_t group_channels, uint32_t period_ms, network_prov_scan_ctx_t **ctx); /** * Handler function called when scan status is requested. Status * is given the parameters : * * scan_finished (output) - When scan has finished this returns true * * result_count (output) - This gives the total number of results * obtained till now. If scan is yet happening this number will * keep on updating */ esp_err_t (*wifi_scan_status)(bool *scan_finished, uint16_t *result_count, network_prov_scan_ctx_t **ctx); /** * Handler function called when scan result is requested. Parameters : * * scan_result - For fetching scan results. This can be called even * if scan is still on going * * start_index (input) - Starting index from where to fetch the * entries from the results list * * count (input) - Number of entries to fetch from the starting index * * entries (output) - List of entries returned. Each entry consists * of ssid, channel and rssi information */ esp_err_t (*wifi_scan_result)(uint16_t result_index, network_prov_scan_wifi_result_t *result, network_prov_scan_ctx_t **ctx); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t (*thread_scan_start)(bool blocking, uint32_t channel_mask, network_prov_scan_ctx_t **ctx); esp_err_t (*thread_scan_status)(bool *scan_finished, uint16_t *result_count, network_prov_scan_ctx_t **ctx); esp_err_t (*thread_scan_result)(uint16_t result_index, network_prov_scan_thread_result_t *result, network_prov_scan_ctx_t **ctx); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * Context pointer to be passed to above handler functions upon invocation */ network_prov_scan_ctx_t *ctx; } network_prov_scan_handlers_t; /** * @brief Handler for sending on demand Wi-Fi scan results * * This is to be registered as the `prov-scan` endpoint handler * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` */ esp_err_t network_prov_scan_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data); #ifdef __cplusplus } #endif #endif ================================================ FILE: network_provisioning/include/network_provisioning/scheme_ble.h ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "network_provisioning/manager.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Scheme that can be used by manager for provisioning * over BLE transport with GATT server */ extern const network_prov_scheme_t network_prov_scheme_ble; /* This scheme specific event handler is to be used when application * doesn't require BT and BLE after provisioning has finished */ #define NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM { \ .event_cb = network_prov_scheme_ble_event_cb_free_btdm, \ .user_data = NULL \ } /* This scheme specific event handler is to be used when application * doesn't require BLE to be active after provisioning has finished */ #define NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE { \ .event_cb = network_prov_scheme_ble_event_cb_free_ble, \ .user_data = NULL \ } /* This scheme specific event handler is to be used when application * doesn't require BT to be active after provisioning has finished */ #define NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT { \ .event_cb = network_prov_scheme_ble_event_cb_free_bt, \ .user_data = NULL \ } void network_prov_scheme_ble_event_cb_free_btdm(void *user_data, network_prov_cb_event_t event, void *event_data); void network_prov_scheme_ble_event_cb_free_ble (void *user_data, network_prov_cb_event_t event, void *event_data); void network_prov_scheme_ble_event_cb_free_bt (void *user_data, network_prov_cb_event_t event, void *event_data); /** * @brief Set the 128 bit GATT service UUID used for provisioning * * This API is used to override the default 128 bit provisioning * service UUID, which is 0000ffff-0000-1000-8000-00805f9b34fb. * * This must be called before starting provisioning, i.e. before * making a call to network_prov_mgr_start_provisioning(), otherwise * the default UUID will be used. * * @note The data being pointed to by the argument must be valid * at least till provisioning is started. Upon start, the * manager will store an internal copy of this UUID, and * this data can be freed or invalidated afterwards. * * @param[in] uuid128 A custom 128 bit UUID * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_ARG : Null argument */ esp_err_t network_prov_scheme_ble_set_service_uuid(uint8_t *uuid128); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) /** * @brief Keep the BLE on after provisioning * * This API is used to specify whether the BLE should remain on * after the provisioning process has stopped. * * This must be called before starting provisioning, i.e. before * making a call to network_prov_mgr_start_provisioning(), otherwise * the default behavior will be used. * * @note The value being pointed to by the argument must be valid * at least until provisioning is started. Upon start, the * manager will store an internal copy of this value, and * this data can be freed or invalidated afterwards. * * @param[in] is_on_after_ble_stop A boolean indicating if BLE should remain on after provisioning * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_ARG : Null argument */ esp_err_t network_prov_mgr_keep_ble_on(uint8_t is_on_after_ble_stop); /** * @brief Set Bluetooth Random address * * This must be called before starting provisioning, i.e. before * making a call to network_prov_mgr_start_provisioning(). * * This API can be used in cases where a new identity address is to be used during * provisioning. This will result in this device being treated as a new device by remote * devices. * * @note This API will change the existing BD address for the device. The address once * set will remain unchanged until BLE stack tear down happens when * network_prov_mgr_deinit is invoked. * * This API is only to be called to set random address. Re-invoking this API * after provisioning is started will have no effect. * * @param[in] rand_addr The static random address to be set of length 6 bytes. * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_ARG : Null argument */ esp_err_t network_prov_scheme_ble_set_random_addr(const uint8_t *rand_addr); #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) /** * @brief Set manufacturer specific data in scan response * * This must be called before starting provisioning, i.e. before * making a call to network_prov_mgr_start_provisioning(). * * @note It is important to understand that length of custom manufacturer * data should be within limits. The manufacturer data goes into scan * response along with BLE device name. By default, BLE device name * length is of 11 Bytes, however it can vary as per application use * case. So, one has to honour the scan response data size limits i.e. * (mfg_data_len + 2) < 31 - (device_name_length + 2 ). If the * mfg_data length exceeds this limit, the length will be truncated. * * @param[in] mfg_data Custom manufacturer data * @param[in] mfg_data_len Manufacturer data length * * @return * - ESP_OK : Success * - ESP_ERR_INVALID_ARG : Null argument */ esp_err_t network_prov_scheme_ble_set_mfg_data(uint8_t *mfg_data, ssize_t mfg_data_len); #ifdef __cplusplus } #endif ================================================ FILE: network_provisioning/include/network_provisioning/scheme_console.h ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "network_provisioning/manager.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Scheme that can be used by manager for provisioning * over console (Serial UART) */ extern const network_prov_scheme_t network_prov_scheme_console; #ifdef __cplusplus } #endif ================================================ FILE: network_provisioning/include/network_provisioning/scheme_softap.h ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "network_provisioning/manager.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Scheme that can be used by manager for provisioning * over SoftAP transport with HTTP server */ extern const network_prov_scheme_t network_prov_scheme_softap; /** * * @brief Provide HTTPD Server handle externally. * * Useful in cases wherein applications need the webserver for some * different operations, and do not want the wifi provisioning component * to start/stop a new instance. * * @note This API should be called before network_prov_mgr_start_provisioning() * * @param[in] handle Handle to HTTPD server instance */ void network_prov_scheme_softap_set_httpd_handle(void *handle); #ifdef __cplusplus } #endif ================================================ FILE: network_provisioning/proto/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) set(PROTO_COMPILER "protoc") set(PROTO_C_COMPILER "protoc-c") set(C_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../proto-c") set(PY_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../python") set(PROTOCOMM_INCL_PATH "${IDF_PATH}/component/protocomm/proto") set(PROTO_SRCS "network_constants.proto" "network_config.proto" "network_scan.proto") add_custom_target(c_proto COMMAND ${PROTO_C_COMPILER} --c_out=${C_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_SRCS} VERBATIM WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) add_custom_target(python_proto COMMAND ${PROTO_COMPILER} --python_out=${PY_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_SRCS} VERBATIM WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) add_custom_target(proto ALL DEPENDS c_proto python_proto VERBATIM WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) ================================================ FILE: network_provisioning/proto/README.md ================================================ # Protobuf files for defining Network provisioning packet structures `network_provisioning` uses Google Protobuf for language, transport and architecture agnostic protocol communication. These proto files define the protocomm packet structure, separated across multiple files: * network_contants.proto - Defines the various enums for indicating state of network (connected / disconnect / connecting), disconnect reasons, auth modes, etc. * network_config.proto - Defines network configuration structures and commands for setting Wi-Fi credentials (SSID, passphrase, BSSID) or Thread dataset, applying credentials and getting connection state. * network_scan.proto - Defines network scan commands and result structures * network_ctrl.proto - Defines network control commands(reset and re-provision) and result structures Note : These proto files are not automatically compiled during the build process. # Compilation Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under `components/network_provisioning/proto-c` and `components/network_provisioning/python` directories, and thus running cmake / make (and installing the Protobuf compilers) is optional. If using `cmake` follow the below steps. If using `make`, jump to Step 2 directly. ## Step 1 (Only for cmake) When using cmake, first create a build directory and call cmake from inside: ``` mkdir build cd build cmake .. ``` ## Step 2 Simply run `make` to generate the respective C and Python files. The newly created files will overwrite those under `components/network_provisioning/proto-c` and `components/network_provisioning/python` ================================================ FILE: network_provisioning/proto/makefile ================================================ all: c_proto python_proto c_proto: *.proto @protoc-c --c_out=../proto-c/ -I . -I ${IDF_PATH}/components/protocomm/proto/ *.proto python_proto: *.proto @protoc --python_out=../python/ -I . -I ${IDF_PATH}/components/protocomm/proto/ *.proto ================================================ FILE: network_provisioning/proto/network_config.proto ================================================ syntax = "proto3"; import "constants.proto"; import "network_constants.proto"; message CmdGetWifiStatus { } message RespGetWifiStatus { Status status = 1; WifiStationState wifi_sta_state = 2; oneof state { WifiConnectFailedReason wifi_fail_reason = 10; WifiConnectedState wifi_connected = 11; WifiAttemptFailed attempt_failed = 12; } } message CmdGetThreadStatus { } message RespGetThreadStatus { Status status = 1; ThreadNetworkState thread_state = 2; oneof state { ThreadAttachFailedReason thread_fail_reason = 10; ThreadAttachState thread_attached = 11; } } message CmdSetWifiConfig { bytes ssid = 1; bytes passphrase = 2; bytes bssid = 3; int32 channel = 4; } message CmdSetThreadConfig { bytes dataset = 1; } message RespSetWifiConfig { Status status = 1; } message RespSetThreadConfig { Status status = 1; } message CmdApplyWifiConfig { } message CmdApplyThreadConfig { } message RespApplyWifiConfig { Status status = 1; } message RespApplyThreadConfig { Status status = 1; } enum NetworkConfigMsgType { TypeCmdGetWifiStatus = 0; TypeRespGetWifiStatus = 1; TypeCmdSetWifiConfig = 2; TypeRespSetWifiConfig = 3; TypeCmdApplyWifiConfig = 4; TypeRespApplyWifiConfig = 5; TypeCmdGetThreadStatus = 6; TypeRespGetThreadStatus = 7; TypeCmdSetThreadConfig = 8; TypeRespSetThreadConfig = 9; TypeCmdApplyThreadConfig = 10; TypeRespApplyThreadConfig = 11; } message NetworkConfigPayload { NetworkConfigMsgType msg = 1; oneof payload { CmdGetWifiStatus cmd_get_wifi_status = 10; RespGetWifiStatus resp_get_wifi_status = 11; CmdSetWifiConfig cmd_set_wifi_config = 12; RespSetWifiConfig resp_set_wifi_config = 13; CmdApplyWifiConfig cmd_apply_wifi_config = 14; RespApplyWifiConfig resp_apply_wifi_config = 15; CmdGetThreadStatus cmd_get_thread_status = 16; RespGetThreadStatus resp_get_thread_status = 17; CmdSetThreadConfig cmd_set_thread_config = 18; RespSetThreadConfig resp_set_thread_config = 19; CmdApplyThreadConfig cmd_apply_thread_config = 20; RespApplyThreadConfig resp_apply_thread_config = 21; } } ================================================ FILE: network_provisioning/proto/network_constants.proto ================================================ syntax = "proto3"; enum WifiStationState { Connected = 0; Connecting = 1; Disconnected = 2; ConnectionFailed = 3; } enum WifiConnectFailedReason { AuthError = 0; WifiNetworkNotFound = 1; } enum WifiAuthMode { Open = 0; WEP = 1; WPA_PSK = 2; WPA2_PSK = 3; WPA_WPA2_PSK = 4; WPA2_ENTERPRISE = 5; WPA3_PSK = 6; WPA2_WPA3_PSK = 7; } message WifiConnectedState { string ip4_addr = 1; WifiAuthMode auth_mode = 2; bytes ssid = 3; bytes bssid = 4; int32 channel = 5; } message WifiAttemptFailed { uint32 attempts_remaining = 1; } enum ThreadNetworkState { Attached = 0; Attaching = 1; Dettached = 2; AttachingFailed = 3; } enum ThreadAttachFailedReason { DatasetInvalid = 0; ThreadNetworkNotFound = 1; } message ThreadAttachState { uint32 pan_id = 1; bytes ext_pan_id = 2; uint32 channel = 3; string name = 4; } ================================================ FILE: network_provisioning/proto/network_ctrl.proto ================================================ syntax = "proto3"; import "constants.proto"; message CmdCtrlWifiReset { } message RespCtrlWifiReset { } message CmdCtrlWifiReprov { } message RespCtrlWifiReprov{ } message CmdCtrlThreadReset { } message RespCtrlThreadReset { } message CmdCtrlThreadReprov { } message RespCtrlThreadReprov{ } enum NetworkCtrlMsgType { TypeCtrlReserved = 0; TypeCmdCtrlWifiReset = 1; TypeRespCtrlWifiReset = 2; TypeCmdCtrlWifiReprov = 3; TypeRespCtrlWifiReprov = 4; TypeCmdCtrlThreadReset = 5; TypeRespCtrlThreadReset = 6; TypeCmdCtrlThreadReprov = 7; TypeRespCtrlThreadReprov = 8; } message NetworkCtrlPayload { NetworkCtrlMsgType msg = 1; Status status = 2; oneof payload { CmdCtrlWifiReset cmd_ctrl_wifi_reset = 11; RespCtrlWifiReset resp_ctrl_wifi_reset = 12; CmdCtrlWifiReprov cmd_ctrl_wifi_reprov = 13; RespCtrlWifiReprov resp_ctrl_wifi_reprov = 14; CmdCtrlThreadReset cmd_ctrl_thread_reset = 15; RespCtrlThreadReset resp_ctrl_thread_reset = 16; CmdCtrlThreadReprov cmd_ctrl_thread_reprov = 17; RespCtrlThreadReprov resp_ctrl_thread_reprov = 18; } } ================================================ FILE: network_provisioning/proto/network_scan.proto ================================================ syntax = "proto3"; import "constants.proto"; import "network_constants.proto"; message CmdScanWifiStart { bool blocking = 1; bool passive = 2; uint32 group_channels = 3; uint32 period_ms = 4; } message CmdScanThreadStart { bool blocking = 1; uint32 channel_mask = 2; } message RespScanWifiStart { } message RespScanThreadStart { } message CmdScanWifiStatus { } message CmdScanThreadStatus { } message RespScanWifiStatus { bool scan_finished = 1; uint32 result_count = 2; } message RespScanThreadStatus { bool scan_finished = 1; uint32 result_count = 2; } message CmdScanWifiResult { uint32 start_index = 1; uint32 count = 2; } message CmdScanThreadResult { uint32 start_index = 1; uint32 count = 2; } message WiFiScanResult { bytes ssid = 1; uint32 channel = 2; int32 rssi = 3; bytes bssid = 4; WifiAuthMode auth = 5; } message ThreadScanResult { uint32 pan_id = 1; uint32 channel = 2; int32 rssi = 3; uint32 lqi = 4; bytes ext_addr = 5; string network_name = 6; bytes ext_pan_id = 7; } message RespScanWifiResult { repeated WiFiScanResult entries = 1; } message RespScanThreadResult { repeated ThreadScanResult entries = 1; } enum NetworkScanMsgType { TypeCmdScanWifiStart = 0; TypeRespScanWifiStart = 1; TypeCmdScanWifiStatus = 2; TypeRespScanWifiStatus = 3; TypeCmdScanWifiResult = 4; TypeRespScanWifiResult = 5; TypeCmdScanThreadStart = 6; TypeRespScanThreadStart = 7; TypeCmdScanThreadStatus = 8; TypeRespScanThreadStatus = 9; TypeCmdScanThreadResult = 10; TypeRespScanThreadResult = 11; } message NetworkScanPayload { NetworkScanMsgType msg = 1; Status status = 2; oneof payload { CmdScanWifiStart cmd_scan_wifi_start = 10; RespScanWifiStart resp_scan_wifi_start = 11; CmdScanWifiStatus cmd_scan_wifi_status = 12; RespScanWifiStatus resp_scan_wifi_status = 13; CmdScanWifiResult cmd_scan_wifi_result = 14; RespScanWifiResult resp_scan_wifi_result = 15; CmdScanThreadStart cmd_scan_thread_start = 16; RespScanThreadStart resp_scan_thread_start = 17; CmdScanThreadStatus cmd_scan_thread_status = 18; RespScanThreadStatus resp_scan_thread_status = 19; CmdScanThreadResult cmd_scan_thread_result = 20; RespScanThreadResult resp_scan_thread_result = 21; } } ================================================ FILE: network_provisioning/proto-c/network_config.pb-c.c ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_config.proto */ /* Do not generate deprecated warnings for self */ #ifndef PROTOBUF_C__NO_DEPRECATED #define PROTOBUF_C__NO_DEPRECATED #endif #include "network_config.pb-c.h" void cmd_get_wifi_status__init (CmdGetWifiStatus *message) { static const CmdGetWifiStatus init_value = CMD_GET_WIFI_STATUS__INIT; *message = init_value; } size_t cmd_get_wifi_status__get_packed_size (const CmdGetWifiStatus *message) { assert(message->base.descriptor == &cmd_get_wifi_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_get_wifi_status__pack (const CmdGetWifiStatus *message, uint8_t *out) { assert(message->base.descriptor == &cmd_get_wifi_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_get_wifi_status__pack_to_buffer (const CmdGetWifiStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_get_wifi_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdGetWifiStatus * cmd_get_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdGetWifiStatus *) protobuf_c_message_unpack (&cmd_get_wifi_status__descriptor, allocator, len, data); } void cmd_get_wifi_status__free_unpacked (CmdGetWifiStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_get_wifi_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_get_wifi_status__init (RespGetWifiStatus *message) { static const RespGetWifiStatus init_value = RESP_GET_WIFI_STATUS__INIT; *message = init_value; } size_t resp_get_wifi_status__get_packed_size (const RespGetWifiStatus *message) { assert(message->base.descriptor == &resp_get_wifi_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_get_wifi_status__pack (const RespGetWifiStatus *message, uint8_t *out) { assert(message->base.descriptor == &resp_get_wifi_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_get_wifi_status__pack_to_buffer (const RespGetWifiStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_get_wifi_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespGetWifiStatus * resp_get_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespGetWifiStatus *) protobuf_c_message_unpack (&resp_get_wifi_status__descriptor, allocator, len, data); } void resp_get_wifi_status__free_unpacked (RespGetWifiStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_get_wifi_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_get_thread_status__init (CmdGetThreadStatus *message) { static const CmdGetThreadStatus init_value = CMD_GET_THREAD_STATUS__INIT; *message = init_value; } size_t cmd_get_thread_status__get_packed_size (const CmdGetThreadStatus *message) { assert(message->base.descriptor == &cmd_get_thread_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_get_thread_status__pack (const CmdGetThreadStatus *message, uint8_t *out) { assert(message->base.descriptor == &cmd_get_thread_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_get_thread_status__pack_to_buffer (const CmdGetThreadStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_get_thread_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdGetThreadStatus * cmd_get_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdGetThreadStatus *) protobuf_c_message_unpack (&cmd_get_thread_status__descriptor, allocator, len, data); } void cmd_get_thread_status__free_unpacked (CmdGetThreadStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_get_thread_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_get_thread_status__init (RespGetThreadStatus *message) { static const RespGetThreadStatus init_value = RESP_GET_THREAD_STATUS__INIT; *message = init_value; } size_t resp_get_thread_status__get_packed_size (const RespGetThreadStatus *message) { assert(message->base.descriptor == &resp_get_thread_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_get_thread_status__pack (const RespGetThreadStatus *message, uint8_t *out) { assert(message->base.descriptor == &resp_get_thread_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_get_thread_status__pack_to_buffer (const RespGetThreadStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_get_thread_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespGetThreadStatus * resp_get_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespGetThreadStatus *) protobuf_c_message_unpack (&resp_get_thread_status__descriptor, allocator, len, data); } void resp_get_thread_status__free_unpacked (RespGetThreadStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_get_thread_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_set_wifi_config__init (CmdSetWifiConfig *message) { static const CmdSetWifiConfig init_value = CMD_SET_WIFI_CONFIG__INIT; *message = init_value; } size_t cmd_set_wifi_config__get_packed_size (const CmdSetWifiConfig *message) { assert(message->base.descriptor == &cmd_set_wifi_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_set_wifi_config__pack (const CmdSetWifiConfig *message, uint8_t *out) { assert(message->base.descriptor == &cmd_set_wifi_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_set_wifi_config__pack_to_buffer (const CmdSetWifiConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_set_wifi_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdSetWifiConfig * cmd_set_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdSetWifiConfig *) protobuf_c_message_unpack (&cmd_set_wifi_config__descriptor, allocator, len, data); } void cmd_set_wifi_config__free_unpacked (CmdSetWifiConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_set_wifi_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_set_thread_config__init (CmdSetThreadConfig *message) { static const CmdSetThreadConfig init_value = CMD_SET_THREAD_CONFIG__INIT; *message = init_value; } size_t cmd_set_thread_config__get_packed_size (const CmdSetThreadConfig *message) { assert(message->base.descriptor == &cmd_set_thread_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_set_thread_config__pack (const CmdSetThreadConfig *message, uint8_t *out) { assert(message->base.descriptor == &cmd_set_thread_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_set_thread_config__pack_to_buffer (const CmdSetThreadConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_set_thread_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdSetThreadConfig * cmd_set_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdSetThreadConfig *) protobuf_c_message_unpack (&cmd_set_thread_config__descriptor, allocator, len, data); } void cmd_set_thread_config__free_unpacked (CmdSetThreadConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_set_thread_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_set_wifi_config__init (RespSetWifiConfig *message) { static const RespSetWifiConfig init_value = RESP_SET_WIFI_CONFIG__INIT; *message = init_value; } size_t resp_set_wifi_config__get_packed_size (const RespSetWifiConfig *message) { assert(message->base.descriptor == &resp_set_wifi_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_set_wifi_config__pack (const RespSetWifiConfig *message, uint8_t *out) { assert(message->base.descriptor == &resp_set_wifi_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_set_wifi_config__pack_to_buffer (const RespSetWifiConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_set_wifi_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespSetWifiConfig * resp_set_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespSetWifiConfig *) protobuf_c_message_unpack (&resp_set_wifi_config__descriptor, allocator, len, data); } void resp_set_wifi_config__free_unpacked (RespSetWifiConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_set_wifi_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_set_thread_config__init (RespSetThreadConfig *message) { static const RespSetThreadConfig init_value = RESP_SET_THREAD_CONFIG__INIT; *message = init_value; } size_t resp_set_thread_config__get_packed_size (const RespSetThreadConfig *message) { assert(message->base.descriptor == &resp_set_thread_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_set_thread_config__pack (const RespSetThreadConfig *message, uint8_t *out) { assert(message->base.descriptor == &resp_set_thread_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_set_thread_config__pack_to_buffer (const RespSetThreadConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_set_thread_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespSetThreadConfig * resp_set_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespSetThreadConfig *) protobuf_c_message_unpack (&resp_set_thread_config__descriptor, allocator, len, data); } void resp_set_thread_config__free_unpacked (RespSetThreadConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_set_thread_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_apply_wifi_config__init (CmdApplyWifiConfig *message) { static const CmdApplyWifiConfig init_value = CMD_APPLY_WIFI_CONFIG__INIT; *message = init_value; } size_t cmd_apply_wifi_config__get_packed_size (const CmdApplyWifiConfig *message) { assert(message->base.descriptor == &cmd_apply_wifi_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_apply_wifi_config__pack (const CmdApplyWifiConfig *message, uint8_t *out) { assert(message->base.descriptor == &cmd_apply_wifi_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_apply_wifi_config__pack_to_buffer (const CmdApplyWifiConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_apply_wifi_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdApplyWifiConfig * cmd_apply_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdApplyWifiConfig *) protobuf_c_message_unpack (&cmd_apply_wifi_config__descriptor, allocator, len, data); } void cmd_apply_wifi_config__free_unpacked (CmdApplyWifiConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_apply_wifi_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_apply_thread_config__init (CmdApplyThreadConfig *message) { static const CmdApplyThreadConfig init_value = CMD_APPLY_THREAD_CONFIG__INIT; *message = init_value; } size_t cmd_apply_thread_config__get_packed_size (const CmdApplyThreadConfig *message) { assert(message->base.descriptor == &cmd_apply_thread_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_apply_thread_config__pack (const CmdApplyThreadConfig *message, uint8_t *out) { assert(message->base.descriptor == &cmd_apply_thread_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_apply_thread_config__pack_to_buffer (const CmdApplyThreadConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_apply_thread_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdApplyThreadConfig * cmd_apply_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdApplyThreadConfig *) protobuf_c_message_unpack (&cmd_apply_thread_config__descriptor, allocator, len, data); } void cmd_apply_thread_config__free_unpacked (CmdApplyThreadConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_apply_thread_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_apply_wifi_config__init (RespApplyWifiConfig *message) { static const RespApplyWifiConfig init_value = RESP_APPLY_WIFI_CONFIG__INIT; *message = init_value; } size_t resp_apply_wifi_config__get_packed_size (const RespApplyWifiConfig *message) { assert(message->base.descriptor == &resp_apply_wifi_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_apply_wifi_config__pack (const RespApplyWifiConfig *message, uint8_t *out) { assert(message->base.descriptor == &resp_apply_wifi_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_apply_wifi_config__pack_to_buffer (const RespApplyWifiConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_apply_wifi_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespApplyWifiConfig * resp_apply_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespApplyWifiConfig *) protobuf_c_message_unpack (&resp_apply_wifi_config__descriptor, allocator, len, data); } void resp_apply_wifi_config__free_unpacked (RespApplyWifiConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_apply_wifi_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_apply_thread_config__init (RespApplyThreadConfig *message) { static const RespApplyThreadConfig init_value = RESP_APPLY_THREAD_CONFIG__INIT; *message = init_value; } size_t resp_apply_thread_config__get_packed_size (const RespApplyThreadConfig *message) { assert(message->base.descriptor == &resp_apply_thread_config__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_apply_thread_config__pack (const RespApplyThreadConfig *message, uint8_t *out) { assert(message->base.descriptor == &resp_apply_thread_config__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_apply_thread_config__pack_to_buffer (const RespApplyThreadConfig *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_apply_thread_config__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespApplyThreadConfig * resp_apply_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespApplyThreadConfig *) protobuf_c_message_unpack (&resp_apply_thread_config__descriptor, allocator, len, data); } void resp_apply_thread_config__free_unpacked (RespApplyThreadConfig *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_apply_thread_config__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void network_config_payload__init (NetworkConfigPayload *message) { static const NetworkConfigPayload init_value = NETWORK_CONFIG_PAYLOAD__INIT; *message = init_value; } size_t network_config_payload__get_packed_size (const NetworkConfigPayload *message) { assert(message->base.descriptor == &network_config_payload__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t network_config_payload__pack (const NetworkConfigPayload *message, uint8_t *out) { assert(message->base.descriptor == &network_config_payload__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t network_config_payload__pack_to_buffer (const NetworkConfigPayload *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &network_config_payload__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } NetworkConfigPayload * network_config_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (NetworkConfigPayload *) protobuf_c_message_unpack (&network_config_payload__descriptor, allocator, len, data); } void network_config_payload__free_unpacked (NetworkConfigPayload *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &network_config_payload__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } #define cmd_get_wifi_status__field_descriptors NULL #define cmd_get_wifi_status__field_indices_by_name NULL #define cmd_get_wifi_status__number_ranges NULL const ProtobufCMessageDescriptor cmd_get_wifi_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdGetWifiStatus", "CmdGetWifiStatus", "CmdGetWifiStatus", "", sizeof(CmdGetWifiStatus), 0, cmd_get_wifi_status__field_descriptors, cmd_get_wifi_status__field_indices_by_name, 0, cmd_get_wifi_status__number_ranges, (ProtobufCMessageInit) cmd_get_wifi_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_get_wifi_status__field_descriptors[5] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespGetWifiStatus, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "wifi_sta_state", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespGetWifiStatus, wifi_sta_state), &wifi_station_state__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "wifi_fail_reason", 10, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, offsetof(RespGetWifiStatus, state_case), offsetof(RespGetWifiStatus, wifi_fail_reason), &wifi_connect_failed_reason__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "wifi_connected", 11, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(RespGetWifiStatus, state_case), offsetof(RespGetWifiStatus, wifi_connected), &wifi_connected_state__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "attempt_failed", 12, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(RespGetWifiStatus, state_case), offsetof(RespGetWifiStatus, attempt_failed), &wifi_attempt_failed__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_get_wifi_status__field_indices_by_name[] = { 4, /* field[4] = attempt_failed */ 0, /* field[0] = status */ 3, /* field[3] = wifi_connected */ 2, /* field[2] = wifi_fail_reason */ 1, /* field[1] = wifi_sta_state */ }; static const ProtobufCIntRange resp_get_wifi_status__number_ranges[2 + 1] = { { 1, 0 }, { 10, 2 }, { 0, 5 } }; const ProtobufCMessageDescriptor resp_get_wifi_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespGetWifiStatus", "RespGetWifiStatus", "RespGetWifiStatus", "", sizeof(RespGetWifiStatus), 5, resp_get_wifi_status__field_descriptors, resp_get_wifi_status__field_indices_by_name, 2, resp_get_wifi_status__number_ranges, (ProtobufCMessageInit) resp_get_wifi_status__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_get_thread_status__field_descriptors NULL #define cmd_get_thread_status__field_indices_by_name NULL #define cmd_get_thread_status__number_ranges NULL const ProtobufCMessageDescriptor cmd_get_thread_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdGetThreadStatus", "CmdGetThreadStatus", "CmdGetThreadStatus", "", sizeof(CmdGetThreadStatus), 0, cmd_get_thread_status__field_descriptors, cmd_get_thread_status__field_indices_by_name, 0, cmd_get_thread_status__number_ranges, (ProtobufCMessageInit) cmd_get_thread_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_get_thread_status__field_descriptors[4] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespGetThreadStatus, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "thread_state", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespGetThreadStatus, thread_state), &thread_network_state__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "thread_fail_reason", 10, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, offsetof(RespGetThreadStatus, state_case), offsetof(RespGetThreadStatus, thread_fail_reason), &thread_attach_failed_reason__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "thread_attached", 11, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(RespGetThreadStatus, state_case), offsetof(RespGetThreadStatus, thread_attached), &thread_attach_state__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_get_thread_status__field_indices_by_name[] = { 0, /* field[0] = status */ 3, /* field[3] = thread_attached */ 2, /* field[2] = thread_fail_reason */ 1, /* field[1] = thread_state */ }; static const ProtobufCIntRange resp_get_thread_status__number_ranges[2 + 1] = { { 1, 0 }, { 10, 2 }, { 0, 4 } }; const ProtobufCMessageDescriptor resp_get_thread_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespGetThreadStatus", "RespGetThreadStatus", "RespGetThreadStatus", "", sizeof(RespGetThreadStatus), 4, resp_get_thread_status__field_descriptors, resp_get_thread_status__field_indices_by_name, 2, resp_get_thread_status__number_ranges, (ProtobufCMessageInit) resp_get_thread_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor cmd_set_wifi_config__field_descriptors[4] = { { "ssid", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(CmdSetWifiConfig, ssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "passphrase", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(CmdSetWifiConfig, passphrase), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "bssid", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(CmdSetWifiConfig, bssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_INT32, 0, /* quantifier_offset */ offsetof(CmdSetWifiConfig, channel), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_set_wifi_config__field_indices_by_name[] = { 2, /* field[2] = bssid */ 3, /* field[3] = channel */ 1, /* field[1] = passphrase */ 0, /* field[0] = ssid */ }; static const ProtobufCIntRange cmd_set_wifi_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 4 } }; const ProtobufCMessageDescriptor cmd_set_wifi_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdSetWifiConfig", "CmdSetWifiConfig", "CmdSetWifiConfig", "", sizeof(CmdSetWifiConfig), 4, cmd_set_wifi_config__field_descriptors, cmd_set_wifi_config__field_indices_by_name, 1, cmd_set_wifi_config__number_ranges, (ProtobufCMessageInit) cmd_set_wifi_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor cmd_set_thread_config__field_descriptors[1] = { { "dataset", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(CmdSetThreadConfig, dataset), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_set_thread_config__field_indices_by_name[] = { 0, /* field[0] = dataset */ }; static const ProtobufCIntRange cmd_set_thread_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor cmd_set_thread_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdSetThreadConfig", "CmdSetThreadConfig", "CmdSetThreadConfig", "", sizeof(CmdSetThreadConfig), 1, cmd_set_thread_config__field_descriptors, cmd_set_thread_config__field_indices_by_name, 1, cmd_set_thread_config__number_ranges, (ProtobufCMessageInit) cmd_set_thread_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_set_wifi_config__field_descriptors[1] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespSetWifiConfig, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_set_wifi_config__field_indices_by_name[] = { 0, /* field[0] = status */ }; static const ProtobufCIntRange resp_set_wifi_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_set_wifi_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespSetWifiConfig", "RespSetWifiConfig", "RespSetWifiConfig", "", sizeof(RespSetWifiConfig), 1, resp_set_wifi_config__field_descriptors, resp_set_wifi_config__field_indices_by_name, 1, resp_set_wifi_config__number_ranges, (ProtobufCMessageInit) resp_set_wifi_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_set_thread_config__field_descriptors[1] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespSetThreadConfig, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_set_thread_config__field_indices_by_name[] = { 0, /* field[0] = status */ }; static const ProtobufCIntRange resp_set_thread_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_set_thread_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespSetThreadConfig", "RespSetThreadConfig", "RespSetThreadConfig", "", sizeof(RespSetThreadConfig), 1, resp_set_thread_config__field_descriptors, resp_set_thread_config__field_indices_by_name, 1, resp_set_thread_config__number_ranges, (ProtobufCMessageInit) resp_set_thread_config__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_apply_wifi_config__field_descriptors NULL #define cmd_apply_wifi_config__field_indices_by_name NULL #define cmd_apply_wifi_config__number_ranges NULL const ProtobufCMessageDescriptor cmd_apply_wifi_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdApplyWifiConfig", "CmdApplyWifiConfig", "CmdApplyWifiConfig", "", sizeof(CmdApplyWifiConfig), 0, cmd_apply_wifi_config__field_descriptors, cmd_apply_wifi_config__field_indices_by_name, 0, cmd_apply_wifi_config__number_ranges, (ProtobufCMessageInit) cmd_apply_wifi_config__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_apply_thread_config__field_descriptors NULL #define cmd_apply_thread_config__field_indices_by_name NULL #define cmd_apply_thread_config__number_ranges NULL const ProtobufCMessageDescriptor cmd_apply_thread_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdApplyThreadConfig", "CmdApplyThreadConfig", "CmdApplyThreadConfig", "", sizeof(CmdApplyThreadConfig), 0, cmd_apply_thread_config__field_descriptors, cmd_apply_thread_config__field_indices_by_name, 0, cmd_apply_thread_config__number_ranges, (ProtobufCMessageInit) cmd_apply_thread_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_apply_wifi_config__field_descriptors[1] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespApplyWifiConfig, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_apply_wifi_config__field_indices_by_name[] = { 0, /* field[0] = status */ }; static const ProtobufCIntRange resp_apply_wifi_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_apply_wifi_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespApplyWifiConfig", "RespApplyWifiConfig", "RespApplyWifiConfig", "", sizeof(RespApplyWifiConfig), 1, resp_apply_wifi_config__field_descriptors, resp_apply_wifi_config__field_indices_by_name, 1, resp_apply_wifi_config__number_ranges, (ProtobufCMessageInit) resp_apply_wifi_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_apply_thread_config__field_descriptors[1] = { { "status", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(RespApplyThreadConfig, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_apply_thread_config__field_indices_by_name[] = { 0, /* field[0] = status */ }; static const ProtobufCIntRange resp_apply_thread_config__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_apply_thread_config__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespApplyThreadConfig", "RespApplyThreadConfig", "RespApplyThreadConfig", "", sizeof(RespApplyThreadConfig), 1, resp_apply_thread_config__field_descriptors, resp_apply_thread_config__field_indices_by_name, 1, resp_apply_thread_config__number_ranges, (ProtobufCMessageInit) resp_apply_thread_config__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor network_config_payload__field_descriptors[13] = { { "msg", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(NetworkConfigPayload, msg), &network_config_msg_type__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_get_wifi_status", 10, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_get_wifi_status), &cmd_get_wifi_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_get_wifi_status", 11, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_get_wifi_status), &resp_get_wifi_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_set_wifi_config", 12, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_set_wifi_config), &cmd_set_wifi_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_set_wifi_config", 13, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_set_wifi_config), &resp_set_wifi_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_apply_wifi_config", 14, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_apply_wifi_config), &cmd_apply_wifi_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_apply_wifi_config", 15, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_apply_wifi_config), &resp_apply_wifi_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_get_thread_status", 16, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_get_thread_status), &cmd_get_thread_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_get_thread_status", 17, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_get_thread_status), &resp_get_thread_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_set_thread_config", 18, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_set_thread_config), &cmd_set_thread_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_set_thread_config", 19, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_set_thread_config), &resp_set_thread_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_apply_thread_config", 20, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, cmd_apply_thread_config), &cmd_apply_thread_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_apply_thread_config", 21, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkConfigPayload, payload_case), offsetof(NetworkConfigPayload, resp_apply_thread_config), &resp_apply_thread_config__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned network_config_payload__field_indices_by_name[] = { 11, /* field[11] = cmd_apply_thread_config */ 5, /* field[5] = cmd_apply_wifi_config */ 7, /* field[7] = cmd_get_thread_status */ 1, /* field[1] = cmd_get_wifi_status */ 9, /* field[9] = cmd_set_thread_config */ 3, /* field[3] = cmd_set_wifi_config */ 0, /* field[0] = msg */ 12, /* field[12] = resp_apply_thread_config */ 6, /* field[6] = resp_apply_wifi_config */ 8, /* field[8] = resp_get_thread_status */ 2, /* field[2] = resp_get_wifi_status */ 10, /* field[10] = resp_set_thread_config */ 4, /* field[4] = resp_set_wifi_config */ }; static const ProtobufCIntRange network_config_payload__number_ranges[2 + 1] = { { 1, 0 }, { 10, 1 }, { 0, 13 } }; const ProtobufCMessageDescriptor network_config_payload__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "NetworkConfigPayload", "NetworkConfigPayload", "NetworkConfigPayload", "", sizeof(NetworkConfigPayload), 13, network_config_payload__field_descriptors, network_config_payload__field_indices_by_name, 2, network_config_payload__number_ranges, (ProtobufCMessageInit) network_config_payload__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCEnumValue network_config_msg_type__enum_values_by_number[12] = { { "TypeCmdGetWifiStatus", "NETWORK_CONFIG_MSG_TYPE__TypeCmdGetWifiStatus", 0 }, { "TypeRespGetWifiStatus", "NETWORK_CONFIG_MSG_TYPE__TypeRespGetWifiStatus", 1 }, { "TypeCmdSetWifiConfig", "NETWORK_CONFIG_MSG_TYPE__TypeCmdSetWifiConfig", 2 }, { "TypeRespSetWifiConfig", "NETWORK_CONFIG_MSG_TYPE__TypeRespSetWifiConfig", 3 }, { "TypeCmdApplyWifiConfig", "NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyWifiConfig", 4 }, { "TypeRespApplyWifiConfig", "NETWORK_CONFIG_MSG_TYPE__TypeRespApplyWifiConfig", 5 }, { "TypeCmdGetThreadStatus", "NETWORK_CONFIG_MSG_TYPE__TypeCmdGetThreadStatus", 6 }, { "TypeRespGetThreadStatus", "NETWORK_CONFIG_MSG_TYPE__TypeRespGetThreadStatus", 7 }, { "TypeCmdSetThreadConfig", "NETWORK_CONFIG_MSG_TYPE__TypeCmdSetThreadConfig", 8 }, { "TypeRespSetThreadConfig", "NETWORK_CONFIG_MSG_TYPE__TypeRespSetThreadConfig", 9 }, { "TypeCmdApplyThreadConfig", "NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyThreadConfig", 10 }, { "TypeRespApplyThreadConfig", "NETWORK_CONFIG_MSG_TYPE__TypeRespApplyThreadConfig", 11 }, }; static const ProtobufCIntRange network_config_msg_type__value_ranges[] = { {0, 0},{0, 12} }; static const ProtobufCEnumValueIndex network_config_msg_type__enum_values_by_name[12] = { { "TypeCmdApplyThreadConfig", 10 }, { "TypeCmdApplyWifiConfig", 4 }, { "TypeCmdGetThreadStatus", 6 }, { "TypeCmdGetWifiStatus", 0 }, { "TypeCmdSetThreadConfig", 8 }, { "TypeCmdSetWifiConfig", 2 }, { "TypeRespApplyThreadConfig", 11 }, { "TypeRespApplyWifiConfig", 5 }, { "TypeRespGetThreadStatus", 7 }, { "TypeRespGetWifiStatus", 1 }, { "TypeRespSetThreadConfig", 9 }, { "TypeRespSetWifiConfig", 3 }, }; const ProtobufCEnumDescriptor network_config_msg_type__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "NetworkConfigMsgType", "NetworkConfigMsgType", "NetworkConfigMsgType", "", 12, network_config_msg_type__enum_values_by_number, 12, network_config_msg_type__enum_values_by_name, 1, network_config_msg_type__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; ================================================ FILE: network_provisioning/proto-c/network_config.pb-c.h ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_config.proto */ #ifndef PROTOBUF_C_network_5fconfig_2eproto__INCLUDED #define PROTOBUF_C_network_5fconfig_2eproto__INCLUDED #include PROTOBUF_C__BEGIN_DECLS #if PROTOBUF_C_VERSION_NUMBER < 1003000 # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. #elif 1004000 < PROTOBUF_C_MIN_COMPILER_VERSION # error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. #endif #include "constants.pb-c.h" #include "network_constants.pb-c.h" typedef struct CmdGetWifiStatus CmdGetWifiStatus; typedef struct RespGetWifiStatus RespGetWifiStatus; typedef struct CmdGetThreadStatus CmdGetThreadStatus; typedef struct RespGetThreadStatus RespGetThreadStatus; typedef struct CmdSetWifiConfig CmdSetWifiConfig; typedef struct CmdSetThreadConfig CmdSetThreadConfig; typedef struct RespSetWifiConfig RespSetWifiConfig; typedef struct RespSetThreadConfig RespSetThreadConfig; typedef struct CmdApplyWifiConfig CmdApplyWifiConfig; typedef struct CmdApplyThreadConfig CmdApplyThreadConfig; typedef struct RespApplyWifiConfig RespApplyWifiConfig; typedef struct RespApplyThreadConfig RespApplyThreadConfig; typedef struct NetworkConfigPayload NetworkConfigPayload; /* --- enums --- */ typedef enum _NetworkConfigMsgType { NETWORK_CONFIG_MSG_TYPE__TypeCmdGetWifiStatus = 0, NETWORK_CONFIG_MSG_TYPE__TypeRespGetWifiStatus = 1, NETWORK_CONFIG_MSG_TYPE__TypeCmdSetWifiConfig = 2, NETWORK_CONFIG_MSG_TYPE__TypeRespSetWifiConfig = 3, NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyWifiConfig = 4, NETWORK_CONFIG_MSG_TYPE__TypeRespApplyWifiConfig = 5, NETWORK_CONFIG_MSG_TYPE__TypeCmdGetThreadStatus = 6, NETWORK_CONFIG_MSG_TYPE__TypeRespGetThreadStatus = 7, NETWORK_CONFIG_MSG_TYPE__TypeCmdSetThreadConfig = 8, NETWORK_CONFIG_MSG_TYPE__TypeRespSetThreadConfig = 9, NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyThreadConfig = 10, NETWORK_CONFIG_MSG_TYPE__TypeRespApplyThreadConfig = 11 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_CONFIG_MSG_TYPE) } NetworkConfigMsgType; /* --- messages --- */ struct CmdGetWifiStatus { ProtobufCMessage base; }; #define CMD_GET_WIFI_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_get_wifi_status__descriptor) \ } typedef enum { RESP_GET_WIFI_STATUS__STATE__NOT_SET = 0, RESP_GET_WIFI_STATUS__STATE_WIFI_FAIL_REASON = 10, RESP_GET_WIFI_STATUS__STATE_WIFI_CONNECTED = 11, RESP_GET_WIFI_STATUS__STATE_ATTEMPT_FAILED = 12 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(RESP_GET_WIFI_STATUS__STATE__CASE) } RespGetWifiStatus__StateCase; struct RespGetWifiStatus { ProtobufCMessage base; Status status; WifiStationState wifi_sta_state; RespGetWifiStatus__StateCase state_case; union { WifiConnectFailedReason wifi_fail_reason; WifiConnectedState *wifi_connected; WifiAttemptFailed *attempt_failed; }; }; #define RESP_GET_WIFI_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_get_wifi_status__descriptor) \ , STATUS__Success, WIFI_STATION_STATE__Connected, RESP_GET_WIFI_STATUS__STATE__NOT_SET, {0} } struct CmdGetThreadStatus { ProtobufCMessage base; }; #define CMD_GET_THREAD_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_get_thread_status__descriptor) \ } typedef enum { RESP_GET_THREAD_STATUS__STATE__NOT_SET = 0, RESP_GET_THREAD_STATUS__STATE_THREAD_FAIL_REASON = 10, RESP_GET_THREAD_STATUS__STATE_THREAD_ATTACHED = 11 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(RESP_GET_THREAD_STATUS__STATE__CASE) } RespGetThreadStatus__StateCase; struct RespGetThreadStatus { ProtobufCMessage base; Status status; ThreadNetworkState thread_state; RespGetThreadStatus__StateCase state_case; union { ThreadAttachFailedReason thread_fail_reason; ThreadAttachState *thread_attached; }; }; #define RESP_GET_THREAD_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_get_thread_status__descriptor) \ , STATUS__Success, THREAD_NETWORK_STATE__Attached, RESP_GET_THREAD_STATUS__STATE__NOT_SET, {0} } struct CmdSetWifiConfig { ProtobufCMessage base; ProtobufCBinaryData ssid; ProtobufCBinaryData passphrase; ProtobufCBinaryData bssid; int32_t channel; }; #define CMD_SET_WIFI_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_set_wifi_config__descriptor) \ , {0,NULL}, {0,NULL}, {0,NULL}, 0 } struct CmdSetThreadConfig { ProtobufCMessage base; ProtobufCBinaryData dataset; }; #define CMD_SET_THREAD_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_set_thread_config__descriptor) \ , {0,NULL} } struct RespSetWifiConfig { ProtobufCMessage base; Status status; }; #define RESP_SET_WIFI_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_set_wifi_config__descriptor) \ , STATUS__Success } struct RespSetThreadConfig { ProtobufCMessage base; Status status; }; #define RESP_SET_THREAD_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_set_thread_config__descriptor) \ , STATUS__Success } struct CmdApplyWifiConfig { ProtobufCMessage base; }; #define CMD_APPLY_WIFI_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_apply_wifi_config__descriptor) \ } struct CmdApplyThreadConfig { ProtobufCMessage base; }; #define CMD_APPLY_THREAD_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_apply_thread_config__descriptor) \ } struct RespApplyWifiConfig { ProtobufCMessage base; Status status; }; #define RESP_APPLY_WIFI_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_apply_wifi_config__descriptor) \ , STATUS__Success } struct RespApplyThreadConfig { ProtobufCMessage base; Status status; }; #define RESP_APPLY_THREAD_CONFIG__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_apply_thread_config__descriptor) \ , STATUS__Success } typedef enum { NETWORK_CONFIG_PAYLOAD__PAYLOAD__NOT_SET = 0, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_GET_WIFI_STATUS = 10, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_WIFI_STATUS = 11, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_WIFI_CONFIG = 12, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_WIFI_CONFIG = 13, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_APPLY_WIFI_CONFIG = 14, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_WIFI_CONFIG = 15, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_GET_THREAD_STATUS = 16, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_THREAD_STATUS = 17, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_THREAD_CONFIG = 18, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_THREAD_CONFIG = 19, NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_APPLY_THREAD_CONFIG = 20, NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_THREAD_CONFIG = 21 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_CONFIG_PAYLOAD__PAYLOAD__CASE) } NetworkConfigPayload__PayloadCase; struct NetworkConfigPayload { ProtobufCMessage base; NetworkConfigMsgType msg; NetworkConfigPayload__PayloadCase payload_case; union { CmdGetWifiStatus *cmd_get_wifi_status; RespGetWifiStatus *resp_get_wifi_status; CmdSetWifiConfig *cmd_set_wifi_config; RespSetWifiConfig *resp_set_wifi_config; CmdApplyWifiConfig *cmd_apply_wifi_config; RespApplyWifiConfig *resp_apply_wifi_config; CmdGetThreadStatus *cmd_get_thread_status; RespGetThreadStatus *resp_get_thread_status; CmdSetThreadConfig *cmd_set_thread_config; RespSetThreadConfig *resp_set_thread_config; CmdApplyThreadConfig *cmd_apply_thread_config; RespApplyThreadConfig *resp_apply_thread_config; }; }; #define NETWORK_CONFIG_PAYLOAD__INIT \ { PROTOBUF_C_MESSAGE_INIT (&network_config_payload__descriptor) \ , NETWORK_CONFIG_MSG_TYPE__TypeCmdGetWifiStatus, NETWORK_CONFIG_PAYLOAD__PAYLOAD__NOT_SET, {0} } /* CmdGetWifiStatus methods */ void cmd_get_wifi_status__init (CmdGetWifiStatus *message); size_t cmd_get_wifi_status__get_packed_size (const CmdGetWifiStatus *message); size_t cmd_get_wifi_status__pack (const CmdGetWifiStatus *message, uint8_t *out); size_t cmd_get_wifi_status__pack_to_buffer (const CmdGetWifiStatus *message, ProtobufCBuffer *buffer); CmdGetWifiStatus * cmd_get_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_get_wifi_status__free_unpacked (CmdGetWifiStatus *message, ProtobufCAllocator *allocator); /* RespGetWifiStatus methods */ void resp_get_wifi_status__init (RespGetWifiStatus *message); size_t resp_get_wifi_status__get_packed_size (const RespGetWifiStatus *message); size_t resp_get_wifi_status__pack (const RespGetWifiStatus *message, uint8_t *out); size_t resp_get_wifi_status__pack_to_buffer (const RespGetWifiStatus *message, ProtobufCBuffer *buffer); RespGetWifiStatus * resp_get_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_get_wifi_status__free_unpacked (RespGetWifiStatus *message, ProtobufCAllocator *allocator); /* CmdGetThreadStatus methods */ void cmd_get_thread_status__init (CmdGetThreadStatus *message); size_t cmd_get_thread_status__get_packed_size (const CmdGetThreadStatus *message); size_t cmd_get_thread_status__pack (const CmdGetThreadStatus *message, uint8_t *out); size_t cmd_get_thread_status__pack_to_buffer (const CmdGetThreadStatus *message, ProtobufCBuffer *buffer); CmdGetThreadStatus * cmd_get_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_get_thread_status__free_unpacked (CmdGetThreadStatus *message, ProtobufCAllocator *allocator); /* RespGetThreadStatus methods */ void resp_get_thread_status__init (RespGetThreadStatus *message); size_t resp_get_thread_status__get_packed_size (const RespGetThreadStatus *message); size_t resp_get_thread_status__pack (const RespGetThreadStatus *message, uint8_t *out); size_t resp_get_thread_status__pack_to_buffer (const RespGetThreadStatus *message, ProtobufCBuffer *buffer); RespGetThreadStatus * resp_get_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_get_thread_status__free_unpacked (RespGetThreadStatus *message, ProtobufCAllocator *allocator); /* CmdSetWifiConfig methods */ void cmd_set_wifi_config__init (CmdSetWifiConfig *message); size_t cmd_set_wifi_config__get_packed_size (const CmdSetWifiConfig *message); size_t cmd_set_wifi_config__pack (const CmdSetWifiConfig *message, uint8_t *out); size_t cmd_set_wifi_config__pack_to_buffer (const CmdSetWifiConfig *message, ProtobufCBuffer *buffer); CmdSetWifiConfig * cmd_set_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_set_wifi_config__free_unpacked (CmdSetWifiConfig *message, ProtobufCAllocator *allocator); /* CmdSetThreadConfig methods */ void cmd_set_thread_config__init (CmdSetThreadConfig *message); size_t cmd_set_thread_config__get_packed_size (const CmdSetThreadConfig *message); size_t cmd_set_thread_config__pack (const CmdSetThreadConfig *message, uint8_t *out); size_t cmd_set_thread_config__pack_to_buffer (const CmdSetThreadConfig *message, ProtobufCBuffer *buffer); CmdSetThreadConfig * cmd_set_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_set_thread_config__free_unpacked (CmdSetThreadConfig *message, ProtobufCAllocator *allocator); /* RespSetWifiConfig methods */ void resp_set_wifi_config__init (RespSetWifiConfig *message); size_t resp_set_wifi_config__get_packed_size (const RespSetWifiConfig *message); size_t resp_set_wifi_config__pack (const RespSetWifiConfig *message, uint8_t *out); size_t resp_set_wifi_config__pack_to_buffer (const RespSetWifiConfig *message, ProtobufCBuffer *buffer); RespSetWifiConfig * resp_set_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_set_wifi_config__free_unpacked (RespSetWifiConfig *message, ProtobufCAllocator *allocator); /* RespSetThreadConfig methods */ void resp_set_thread_config__init (RespSetThreadConfig *message); size_t resp_set_thread_config__get_packed_size (const RespSetThreadConfig *message); size_t resp_set_thread_config__pack (const RespSetThreadConfig *message, uint8_t *out); size_t resp_set_thread_config__pack_to_buffer (const RespSetThreadConfig *message, ProtobufCBuffer *buffer); RespSetThreadConfig * resp_set_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_set_thread_config__free_unpacked (RespSetThreadConfig *message, ProtobufCAllocator *allocator); /* CmdApplyWifiConfig methods */ void cmd_apply_wifi_config__init (CmdApplyWifiConfig *message); size_t cmd_apply_wifi_config__get_packed_size (const CmdApplyWifiConfig *message); size_t cmd_apply_wifi_config__pack (const CmdApplyWifiConfig *message, uint8_t *out); size_t cmd_apply_wifi_config__pack_to_buffer (const CmdApplyWifiConfig *message, ProtobufCBuffer *buffer); CmdApplyWifiConfig * cmd_apply_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_apply_wifi_config__free_unpacked (CmdApplyWifiConfig *message, ProtobufCAllocator *allocator); /* CmdApplyThreadConfig methods */ void cmd_apply_thread_config__init (CmdApplyThreadConfig *message); size_t cmd_apply_thread_config__get_packed_size (const CmdApplyThreadConfig *message); size_t cmd_apply_thread_config__pack (const CmdApplyThreadConfig *message, uint8_t *out); size_t cmd_apply_thread_config__pack_to_buffer (const CmdApplyThreadConfig *message, ProtobufCBuffer *buffer); CmdApplyThreadConfig * cmd_apply_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_apply_thread_config__free_unpacked (CmdApplyThreadConfig *message, ProtobufCAllocator *allocator); /* RespApplyWifiConfig methods */ void resp_apply_wifi_config__init (RespApplyWifiConfig *message); size_t resp_apply_wifi_config__get_packed_size (const RespApplyWifiConfig *message); size_t resp_apply_wifi_config__pack (const RespApplyWifiConfig *message, uint8_t *out); size_t resp_apply_wifi_config__pack_to_buffer (const RespApplyWifiConfig *message, ProtobufCBuffer *buffer); RespApplyWifiConfig * resp_apply_wifi_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_apply_wifi_config__free_unpacked (RespApplyWifiConfig *message, ProtobufCAllocator *allocator); /* RespApplyThreadConfig methods */ void resp_apply_thread_config__init (RespApplyThreadConfig *message); size_t resp_apply_thread_config__get_packed_size (const RespApplyThreadConfig *message); size_t resp_apply_thread_config__pack (const RespApplyThreadConfig *message, uint8_t *out); size_t resp_apply_thread_config__pack_to_buffer (const RespApplyThreadConfig *message, ProtobufCBuffer *buffer); RespApplyThreadConfig * resp_apply_thread_config__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_apply_thread_config__free_unpacked (RespApplyThreadConfig *message, ProtobufCAllocator *allocator); /* NetworkConfigPayload methods */ void network_config_payload__init (NetworkConfigPayload *message); size_t network_config_payload__get_packed_size (const NetworkConfigPayload *message); size_t network_config_payload__pack (const NetworkConfigPayload *message, uint8_t *out); size_t network_config_payload__pack_to_buffer (const NetworkConfigPayload *message, ProtobufCBuffer *buffer); NetworkConfigPayload * network_config_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void network_config_payload__free_unpacked (NetworkConfigPayload *message, ProtobufCAllocator *allocator); /* --- per-message closures --- */ typedef void (*CmdGetWifiStatus_Closure) (const CmdGetWifiStatus *message, void *closure_data); typedef void (*RespGetWifiStatus_Closure) (const RespGetWifiStatus *message, void *closure_data); typedef void (*CmdGetThreadStatus_Closure) (const CmdGetThreadStatus *message, void *closure_data); typedef void (*RespGetThreadStatus_Closure) (const RespGetThreadStatus *message, void *closure_data); typedef void (*CmdSetWifiConfig_Closure) (const CmdSetWifiConfig *message, void *closure_data); typedef void (*CmdSetThreadConfig_Closure) (const CmdSetThreadConfig *message, void *closure_data); typedef void (*RespSetWifiConfig_Closure) (const RespSetWifiConfig *message, void *closure_data); typedef void (*RespSetThreadConfig_Closure) (const RespSetThreadConfig *message, void *closure_data); typedef void (*CmdApplyWifiConfig_Closure) (const CmdApplyWifiConfig *message, void *closure_data); typedef void (*CmdApplyThreadConfig_Closure) (const CmdApplyThreadConfig *message, void *closure_data); typedef void (*RespApplyWifiConfig_Closure) (const RespApplyWifiConfig *message, void *closure_data); typedef void (*RespApplyThreadConfig_Closure) (const RespApplyThreadConfig *message, void *closure_data); typedef void (*NetworkConfigPayload_Closure) (const NetworkConfigPayload *message, void *closure_data); /* --- services --- */ /* --- descriptors --- */ extern const ProtobufCEnumDescriptor network_config_msg_type__descriptor; extern const ProtobufCMessageDescriptor cmd_get_wifi_status__descriptor; extern const ProtobufCMessageDescriptor resp_get_wifi_status__descriptor; extern const ProtobufCMessageDescriptor cmd_get_thread_status__descriptor; extern const ProtobufCMessageDescriptor resp_get_thread_status__descriptor; extern const ProtobufCMessageDescriptor cmd_set_wifi_config__descriptor; extern const ProtobufCMessageDescriptor cmd_set_thread_config__descriptor; extern const ProtobufCMessageDescriptor resp_set_wifi_config__descriptor; extern const ProtobufCMessageDescriptor resp_set_thread_config__descriptor; extern const ProtobufCMessageDescriptor cmd_apply_wifi_config__descriptor; extern const ProtobufCMessageDescriptor cmd_apply_thread_config__descriptor; extern const ProtobufCMessageDescriptor resp_apply_wifi_config__descriptor; extern const ProtobufCMessageDescriptor resp_apply_thread_config__descriptor; extern const ProtobufCMessageDescriptor network_config_payload__descriptor; PROTOBUF_C__END_DECLS #endif /* PROTOBUF_C_network_5fconfig_2eproto__INCLUDED */ ================================================ FILE: network_provisioning/proto-c/network_constants.pb-c.c ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_constants.proto */ /* Do not generate deprecated warnings for self */ #ifndef PROTOBUF_C__NO_DEPRECATED #define PROTOBUF_C__NO_DEPRECATED #endif #include "network_constants.pb-c.h" void wifi_connected_state__init (WifiConnectedState *message) { static const WifiConnectedState init_value = WIFI_CONNECTED_STATE__INIT; *message = init_value; } size_t wifi_connected_state__get_packed_size (const WifiConnectedState *message) { assert(message->base.descriptor == &wifi_connected_state__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t wifi_connected_state__pack (const WifiConnectedState *message, uint8_t *out) { assert(message->base.descriptor == &wifi_connected_state__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t wifi_connected_state__pack_to_buffer (const WifiConnectedState *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &wifi_connected_state__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } WifiConnectedState * wifi_connected_state__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (WifiConnectedState *) protobuf_c_message_unpack (&wifi_connected_state__descriptor, allocator, len, data); } void wifi_connected_state__free_unpacked (WifiConnectedState *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &wifi_connected_state__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void wifi_attempt_failed__init (WifiAttemptFailed *message) { static const WifiAttemptFailed init_value = WIFI_ATTEMPT_FAILED__INIT; *message = init_value; } size_t wifi_attempt_failed__get_packed_size (const WifiAttemptFailed *message) { assert(message->base.descriptor == &wifi_attempt_failed__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t wifi_attempt_failed__pack (const WifiAttemptFailed *message, uint8_t *out) { assert(message->base.descriptor == &wifi_attempt_failed__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t wifi_attempt_failed__pack_to_buffer (const WifiAttemptFailed *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &wifi_attempt_failed__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } WifiAttemptFailed * wifi_attempt_failed__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (WifiAttemptFailed *) protobuf_c_message_unpack (&wifi_attempt_failed__descriptor, allocator, len, data); } void wifi_attempt_failed__free_unpacked (WifiAttemptFailed *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &wifi_attempt_failed__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void thread_attach_state__init (ThreadAttachState *message) { static const ThreadAttachState init_value = THREAD_ATTACH_STATE__INIT; *message = init_value; } size_t thread_attach_state__get_packed_size (const ThreadAttachState *message) { assert(message->base.descriptor == &thread_attach_state__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t thread_attach_state__pack (const ThreadAttachState *message, uint8_t *out) { assert(message->base.descriptor == &thread_attach_state__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t thread_attach_state__pack_to_buffer (const ThreadAttachState *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &thread_attach_state__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } ThreadAttachState * thread_attach_state__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (ThreadAttachState *) protobuf_c_message_unpack (&thread_attach_state__descriptor, allocator, len, data); } void thread_attach_state__free_unpacked (ThreadAttachState *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &thread_attach_state__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } static const ProtobufCFieldDescriptor wifi_connected_state__field_descriptors[5] = { { "ip4_addr", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ offsetof(WifiConnectedState, ip4_addr), NULL, &protobuf_c_empty_string, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "auth_mode", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(WifiConnectedState, auth_mode), &wifi_auth_mode__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "ssid", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(WifiConnectedState, ssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "bssid", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(WifiConnectedState, bssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel", 5, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_INT32, 0, /* quantifier_offset */ offsetof(WifiConnectedState, channel), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned wifi_connected_state__field_indices_by_name[] = { 1, /* field[1] = auth_mode */ 3, /* field[3] = bssid */ 4, /* field[4] = channel */ 0, /* field[0] = ip4_addr */ 2, /* field[2] = ssid */ }; static const ProtobufCIntRange wifi_connected_state__number_ranges[1 + 1] = { { 1, 0 }, { 0, 5 } }; const ProtobufCMessageDescriptor wifi_connected_state__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "WifiConnectedState", "WifiConnectedState", "WifiConnectedState", "", sizeof(WifiConnectedState), 5, wifi_connected_state__field_descriptors, wifi_connected_state__field_indices_by_name, 1, wifi_connected_state__number_ranges, (ProtobufCMessageInit) wifi_connected_state__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor wifi_attempt_failed__field_descriptors[1] = { { "attempts_remaining", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(WifiAttemptFailed, attempts_remaining), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned wifi_attempt_failed__field_indices_by_name[] = { 0, /* field[0] = attempts_remaining */ }; static const ProtobufCIntRange wifi_attempt_failed__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor wifi_attempt_failed__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "WifiAttemptFailed", "WifiAttemptFailed", "WifiAttemptFailed", "", sizeof(WifiAttemptFailed), 1, wifi_attempt_failed__field_descriptors, wifi_attempt_failed__field_indices_by_name, 1, wifi_attempt_failed__number_ranges, (ProtobufCMessageInit) wifi_attempt_failed__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor thread_attach_state__field_descriptors[4] = { { "pan_id", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(ThreadAttachState, pan_id), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "ext_pan_id", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(ThreadAttachState, ext_pan_id), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(ThreadAttachState, channel), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "name", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ offsetof(ThreadAttachState, name), NULL, &protobuf_c_empty_string, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned thread_attach_state__field_indices_by_name[] = { 2, /* field[2] = channel */ 1, /* field[1] = ext_pan_id */ 3, /* field[3] = name */ 0, /* field[0] = pan_id */ }; static const ProtobufCIntRange thread_attach_state__number_ranges[1 + 1] = { { 1, 0 }, { 0, 4 } }; const ProtobufCMessageDescriptor thread_attach_state__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "ThreadAttachState", "ThreadAttachState", "ThreadAttachState", "", sizeof(ThreadAttachState), 4, thread_attach_state__field_descriptors, thread_attach_state__field_indices_by_name, 1, thread_attach_state__number_ranges, (ProtobufCMessageInit) thread_attach_state__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCEnumValue wifi_station_state__enum_values_by_number[4] = { { "Connected", "WIFI_STATION_STATE__Connected", 0 }, { "Connecting", "WIFI_STATION_STATE__Connecting", 1 }, { "Disconnected", "WIFI_STATION_STATE__Disconnected", 2 }, { "ConnectionFailed", "WIFI_STATION_STATE__ConnectionFailed", 3 }, }; static const ProtobufCIntRange wifi_station_state__value_ranges[] = { {0, 0},{0, 4} }; static const ProtobufCEnumValueIndex wifi_station_state__enum_values_by_name[4] = { { "Connected", 0 }, { "Connecting", 1 }, { "ConnectionFailed", 3 }, { "Disconnected", 2 }, }; const ProtobufCEnumDescriptor wifi_station_state__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "WifiStationState", "WifiStationState", "WifiStationState", "", 4, wifi_station_state__enum_values_by_number, 4, wifi_station_state__enum_values_by_name, 1, wifi_station_state__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; static const ProtobufCEnumValue wifi_connect_failed_reason__enum_values_by_number[2] = { { "AuthError", "WIFI_CONNECT_FAILED_REASON__AuthError", 0 }, { "WifiNetworkNotFound", "WIFI_CONNECT_FAILED_REASON__WifiNetworkNotFound", 1 }, }; static const ProtobufCIntRange wifi_connect_failed_reason__value_ranges[] = { {0, 0},{0, 2} }; static const ProtobufCEnumValueIndex wifi_connect_failed_reason__enum_values_by_name[2] = { { "AuthError", 0 }, { "WifiNetworkNotFound", 1 }, }; const ProtobufCEnumDescriptor wifi_connect_failed_reason__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "WifiConnectFailedReason", "WifiConnectFailedReason", "WifiConnectFailedReason", "", 2, wifi_connect_failed_reason__enum_values_by_number, 2, wifi_connect_failed_reason__enum_values_by_name, 1, wifi_connect_failed_reason__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; static const ProtobufCEnumValue wifi_auth_mode__enum_values_by_number[8] = { { "Open", "WIFI_AUTH_MODE__Open", 0 }, { "WEP", "WIFI_AUTH_MODE__WEP", 1 }, { "WPA_PSK", "WIFI_AUTH_MODE__WPA_PSK", 2 }, { "WPA2_PSK", "WIFI_AUTH_MODE__WPA2_PSK", 3 }, { "WPA_WPA2_PSK", "WIFI_AUTH_MODE__WPA_WPA2_PSK", 4 }, { "WPA2_ENTERPRISE", "WIFI_AUTH_MODE__WPA2_ENTERPRISE", 5 }, { "WPA3_PSK", "WIFI_AUTH_MODE__WPA3_PSK", 6 }, { "WPA2_WPA3_PSK", "WIFI_AUTH_MODE__WPA2_WPA3_PSK", 7 }, }; static const ProtobufCIntRange wifi_auth_mode__value_ranges[] = { {0, 0},{0, 8} }; static const ProtobufCEnumValueIndex wifi_auth_mode__enum_values_by_name[8] = { { "Open", 0 }, { "WEP", 1 }, { "WPA2_ENTERPRISE", 5 }, { "WPA2_PSK", 3 }, { "WPA2_WPA3_PSK", 7 }, { "WPA3_PSK", 6 }, { "WPA_PSK", 2 }, { "WPA_WPA2_PSK", 4 }, }; const ProtobufCEnumDescriptor wifi_auth_mode__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "WifiAuthMode", "WifiAuthMode", "WifiAuthMode", "", 8, wifi_auth_mode__enum_values_by_number, 8, wifi_auth_mode__enum_values_by_name, 1, wifi_auth_mode__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; static const ProtobufCEnumValue thread_network_state__enum_values_by_number[4] = { { "Attached", "THREAD_NETWORK_STATE__Attached", 0 }, { "Attaching", "THREAD_NETWORK_STATE__Attaching", 1 }, { "Dettached", "THREAD_NETWORK_STATE__Dettached", 2 }, { "AttachingFailed", "THREAD_NETWORK_STATE__AttachingFailed", 3 }, }; static const ProtobufCIntRange thread_network_state__value_ranges[] = { {0, 0},{0, 4} }; static const ProtobufCEnumValueIndex thread_network_state__enum_values_by_name[4] = { { "Attached", 0 }, { "Attaching", 1 }, { "AttachingFailed", 3 }, { "Dettached", 2 }, }; const ProtobufCEnumDescriptor thread_network_state__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "ThreadNetworkState", "ThreadNetworkState", "ThreadNetworkState", "", 4, thread_network_state__enum_values_by_number, 4, thread_network_state__enum_values_by_name, 1, thread_network_state__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; static const ProtobufCEnumValue thread_attach_failed_reason__enum_values_by_number[2] = { { "DatasetInvalid", "THREAD_ATTACH_FAILED_REASON__DatasetInvalid", 0 }, { "ThreadNetworkNotFound", "THREAD_ATTACH_FAILED_REASON__ThreadNetworkNotFound", 1 }, }; static const ProtobufCIntRange thread_attach_failed_reason__value_ranges[] = { {0, 0},{0, 2} }; static const ProtobufCEnumValueIndex thread_attach_failed_reason__enum_values_by_name[2] = { { "DatasetInvalid", 0 }, { "ThreadNetworkNotFound", 1 }, }; const ProtobufCEnumDescriptor thread_attach_failed_reason__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "ThreadAttachFailedReason", "ThreadAttachFailedReason", "ThreadAttachFailedReason", "", 2, thread_attach_failed_reason__enum_values_by_number, 2, thread_attach_failed_reason__enum_values_by_name, 1, thread_attach_failed_reason__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; ================================================ FILE: network_provisioning/proto-c/network_constants.pb-c.h ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_constants.proto */ #ifndef PROTOBUF_C_network_5fconstants_2eproto__INCLUDED #define PROTOBUF_C_network_5fconstants_2eproto__INCLUDED #include PROTOBUF_C__BEGIN_DECLS #if PROTOBUF_C_VERSION_NUMBER < 1003000 # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. #elif 1004000 < PROTOBUF_C_MIN_COMPILER_VERSION # error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. #endif typedef struct WifiConnectedState WifiConnectedState; typedef struct WifiAttemptFailed WifiAttemptFailed; typedef struct ThreadAttachState ThreadAttachState; /* --- enums --- */ typedef enum _WifiStationState { WIFI_STATION_STATE__Connected = 0, WIFI_STATION_STATE__Connecting = 1, WIFI_STATION_STATE__Disconnected = 2, WIFI_STATION_STATE__ConnectionFailed = 3 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_STATION_STATE) } WifiStationState; typedef enum _WifiConnectFailedReason { WIFI_CONNECT_FAILED_REASON__AuthError = 0, WIFI_CONNECT_FAILED_REASON__WifiNetworkNotFound = 1 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_CONNECT_FAILED_REASON) } WifiConnectFailedReason; typedef enum _WifiAuthMode { WIFI_AUTH_MODE__Open = 0, WIFI_AUTH_MODE__WEP = 1, WIFI_AUTH_MODE__WPA_PSK = 2, WIFI_AUTH_MODE__WPA2_PSK = 3, WIFI_AUTH_MODE__WPA_WPA2_PSK = 4, WIFI_AUTH_MODE__WPA2_ENTERPRISE = 5, WIFI_AUTH_MODE__WPA3_PSK = 6, WIFI_AUTH_MODE__WPA2_WPA3_PSK = 7 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_AUTH_MODE) } WifiAuthMode; typedef enum _ThreadNetworkState { THREAD_NETWORK_STATE__Attached = 0, THREAD_NETWORK_STATE__Attaching = 1, THREAD_NETWORK_STATE__Dettached = 2, THREAD_NETWORK_STATE__AttachingFailed = 3 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(THREAD_NETWORK_STATE) } ThreadNetworkState; typedef enum _ThreadAttachFailedReason { THREAD_ATTACH_FAILED_REASON__DatasetInvalid = 0, THREAD_ATTACH_FAILED_REASON__ThreadNetworkNotFound = 1 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(THREAD_ATTACH_FAILED_REASON) } ThreadAttachFailedReason; /* --- messages --- */ struct WifiConnectedState { ProtobufCMessage base; char *ip4_addr; WifiAuthMode auth_mode; ProtobufCBinaryData ssid; ProtobufCBinaryData bssid; int32_t channel; }; #define WIFI_CONNECTED_STATE__INIT \ { PROTOBUF_C_MESSAGE_INIT (&wifi_connected_state__descriptor) \ , (char *)protobuf_c_empty_string, WIFI_AUTH_MODE__Open, {0,NULL}, {0,NULL}, 0 } struct WifiAttemptFailed { ProtobufCMessage base; uint32_t attempts_remaining; }; #define WIFI_ATTEMPT_FAILED__INIT \ { PROTOBUF_C_MESSAGE_INIT (&wifi_attempt_failed__descriptor) \ , 0 } struct ThreadAttachState { ProtobufCMessage base; uint32_t pan_id; ProtobufCBinaryData ext_pan_id; uint32_t channel; char *name; }; #define THREAD_ATTACH_STATE__INIT \ { PROTOBUF_C_MESSAGE_INIT (&thread_attach_state__descriptor) \ , 0, {0,NULL}, 0, (char *)protobuf_c_empty_string } /* WifiConnectedState methods */ void wifi_connected_state__init (WifiConnectedState *message); size_t wifi_connected_state__get_packed_size (const WifiConnectedState *message); size_t wifi_connected_state__pack (const WifiConnectedState *message, uint8_t *out); size_t wifi_connected_state__pack_to_buffer (const WifiConnectedState *message, ProtobufCBuffer *buffer); WifiConnectedState * wifi_connected_state__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void wifi_connected_state__free_unpacked (WifiConnectedState *message, ProtobufCAllocator *allocator); /* WifiAttemptFailed methods */ void wifi_attempt_failed__init (WifiAttemptFailed *message); size_t wifi_attempt_failed__get_packed_size (const WifiAttemptFailed *message); size_t wifi_attempt_failed__pack (const WifiAttemptFailed *message, uint8_t *out); size_t wifi_attempt_failed__pack_to_buffer (const WifiAttemptFailed *message, ProtobufCBuffer *buffer); WifiAttemptFailed * wifi_attempt_failed__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void wifi_attempt_failed__free_unpacked (WifiAttemptFailed *message, ProtobufCAllocator *allocator); /* ThreadAttachState methods */ void thread_attach_state__init (ThreadAttachState *message); size_t thread_attach_state__get_packed_size (const ThreadAttachState *message); size_t thread_attach_state__pack (const ThreadAttachState *message, uint8_t *out); size_t thread_attach_state__pack_to_buffer (const ThreadAttachState *message, ProtobufCBuffer *buffer); ThreadAttachState * thread_attach_state__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void thread_attach_state__free_unpacked (ThreadAttachState *message, ProtobufCAllocator *allocator); /* --- per-message closures --- */ typedef void (*WifiConnectedState_Closure) (const WifiConnectedState *message, void *closure_data); typedef void (*WifiAttemptFailed_Closure) (const WifiAttemptFailed *message, void *closure_data); typedef void (*ThreadAttachState_Closure) (const ThreadAttachState *message, void *closure_data); /* --- services --- */ /* --- descriptors --- */ extern const ProtobufCEnumDescriptor wifi_station_state__descriptor; extern const ProtobufCEnumDescriptor wifi_connect_failed_reason__descriptor; extern const ProtobufCEnumDescriptor wifi_auth_mode__descriptor; extern const ProtobufCEnumDescriptor thread_network_state__descriptor; extern const ProtobufCEnumDescriptor thread_attach_failed_reason__descriptor; extern const ProtobufCMessageDescriptor wifi_connected_state__descriptor; extern const ProtobufCMessageDescriptor wifi_attempt_failed__descriptor; extern const ProtobufCMessageDescriptor thread_attach_state__descriptor; PROTOBUF_C__END_DECLS #endif /* PROTOBUF_C_network_5fconstants_2eproto__INCLUDED */ ================================================ FILE: network_provisioning/proto-c/network_ctrl.pb-c.c ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_ctrl.proto */ /* Do not generate deprecated warnings for self */ #ifndef PROTOBUF_C__NO_DEPRECATED #define PROTOBUF_C__NO_DEPRECATED #endif #include "network_ctrl.pb-c.h" void cmd_ctrl_wifi_reset__init (CmdCtrlWifiReset *message) { static const CmdCtrlWifiReset init_value = CMD_CTRL_WIFI_RESET__INIT; *message = init_value; } size_t cmd_ctrl_wifi_reset__get_packed_size (const CmdCtrlWifiReset *message) { assert(message->base.descriptor == &cmd_ctrl_wifi_reset__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_ctrl_wifi_reset__pack (const CmdCtrlWifiReset *message, uint8_t *out) { assert(message->base.descriptor == &cmd_ctrl_wifi_reset__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_ctrl_wifi_reset__pack_to_buffer (const CmdCtrlWifiReset *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_ctrl_wifi_reset__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdCtrlWifiReset * cmd_ctrl_wifi_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdCtrlWifiReset *) protobuf_c_message_unpack (&cmd_ctrl_wifi_reset__descriptor, allocator, len, data); } void cmd_ctrl_wifi_reset__free_unpacked (CmdCtrlWifiReset *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_ctrl_wifi_reset__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_ctrl_wifi_reset__init (RespCtrlWifiReset *message) { static const RespCtrlWifiReset init_value = RESP_CTRL_WIFI_RESET__INIT; *message = init_value; } size_t resp_ctrl_wifi_reset__get_packed_size (const RespCtrlWifiReset *message) { assert(message->base.descriptor == &resp_ctrl_wifi_reset__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_ctrl_wifi_reset__pack (const RespCtrlWifiReset *message, uint8_t *out) { assert(message->base.descriptor == &resp_ctrl_wifi_reset__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_ctrl_wifi_reset__pack_to_buffer (const RespCtrlWifiReset *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_ctrl_wifi_reset__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespCtrlWifiReset * resp_ctrl_wifi_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespCtrlWifiReset *) protobuf_c_message_unpack (&resp_ctrl_wifi_reset__descriptor, allocator, len, data); } void resp_ctrl_wifi_reset__free_unpacked (RespCtrlWifiReset *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_ctrl_wifi_reset__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_ctrl_wifi_reprov__init (CmdCtrlWifiReprov *message) { static const CmdCtrlWifiReprov init_value = CMD_CTRL_WIFI_REPROV__INIT; *message = init_value; } size_t cmd_ctrl_wifi_reprov__get_packed_size (const CmdCtrlWifiReprov *message) { assert(message->base.descriptor == &cmd_ctrl_wifi_reprov__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_ctrl_wifi_reprov__pack (const CmdCtrlWifiReprov *message, uint8_t *out) { assert(message->base.descriptor == &cmd_ctrl_wifi_reprov__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_ctrl_wifi_reprov__pack_to_buffer (const CmdCtrlWifiReprov *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_ctrl_wifi_reprov__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdCtrlWifiReprov * cmd_ctrl_wifi_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdCtrlWifiReprov *) protobuf_c_message_unpack (&cmd_ctrl_wifi_reprov__descriptor, allocator, len, data); } void cmd_ctrl_wifi_reprov__free_unpacked (CmdCtrlWifiReprov *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_ctrl_wifi_reprov__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_ctrl_wifi_reprov__init (RespCtrlWifiReprov *message) { static const RespCtrlWifiReprov init_value = RESP_CTRL_WIFI_REPROV__INIT; *message = init_value; } size_t resp_ctrl_wifi_reprov__get_packed_size (const RespCtrlWifiReprov *message) { assert(message->base.descriptor == &resp_ctrl_wifi_reprov__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_ctrl_wifi_reprov__pack (const RespCtrlWifiReprov *message, uint8_t *out) { assert(message->base.descriptor == &resp_ctrl_wifi_reprov__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_ctrl_wifi_reprov__pack_to_buffer (const RespCtrlWifiReprov *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_ctrl_wifi_reprov__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespCtrlWifiReprov * resp_ctrl_wifi_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespCtrlWifiReprov *) protobuf_c_message_unpack (&resp_ctrl_wifi_reprov__descriptor, allocator, len, data); } void resp_ctrl_wifi_reprov__free_unpacked (RespCtrlWifiReprov *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_ctrl_wifi_reprov__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_ctrl_thread_reset__init (CmdCtrlThreadReset *message) { static const CmdCtrlThreadReset init_value = CMD_CTRL_THREAD_RESET__INIT; *message = init_value; } size_t cmd_ctrl_thread_reset__get_packed_size (const CmdCtrlThreadReset *message) { assert(message->base.descriptor == &cmd_ctrl_thread_reset__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_ctrl_thread_reset__pack (const CmdCtrlThreadReset *message, uint8_t *out) { assert(message->base.descriptor == &cmd_ctrl_thread_reset__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_ctrl_thread_reset__pack_to_buffer (const CmdCtrlThreadReset *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_ctrl_thread_reset__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdCtrlThreadReset * cmd_ctrl_thread_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdCtrlThreadReset *) protobuf_c_message_unpack (&cmd_ctrl_thread_reset__descriptor, allocator, len, data); } void cmd_ctrl_thread_reset__free_unpacked (CmdCtrlThreadReset *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_ctrl_thread_reset__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_ctrl_thread_reset__init (RespCtrlThreadReset *message) { static const RespCtrlThreadReset init_value = RESP_CTRL_THREAD_RESET__INIT; *message = init_value; } size_t resp_ctrl_thread_reset__get_packed_size (const RespCtrlThreadReset *message) { assert(message->base.descriptor == &resp_ctrl_thread_reset__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_ctrl_thread_reset__pack (const RespCtrlThreadReset *message, uint8_t *out) { assert(message->base.descriptor == &resp_ctrl_thread_reset__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_ctrl_thread_reset__pack_to_buffer (const RespCtrlThreadReset *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_ctrl_thread_reset__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespCtrlThreadReset * resp_ctrl_thread_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespCtrlThreadReset *) protobuf_c_message_unpack (&resp_ctrl_thread_reset__descriptor, allocator, len, data); } void resp_ctrl_thread_reset__free_unpacked (RespCtrlThreadReset *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_ctrl_thread_reset__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_ctrl_thread_reprov__init (CmdCtrlThreadReprov *message) { static const CmdCtrlThreadReprov init_value = CMD_CTRL_THREAD_REPROV__INIT; *message = init_value; } size_t cmd_ctrl_thread_reprov__get_packed_size (const CmdCtrlThreadReprov *message) { assert(message->base.descriptor == &cmd_ctrl_thread_reprov__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_ctrl_thread_reprov__pack (const CmdCtrlThreadReprov *message, uint8_t *out) { assert(message->base.descriptor == &cmd_ctrl_thread_reprov__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_ctrl_thread_reprov__pack_to_buffer (const CmdCtrlThreadReprov *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_ctrl_thread_reprov__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdCtrlThreadReprov * cmd_ctrl_thread_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdCtrlThreadReprov *) protobuf_c_message_unpack (&cmd_ctrl_thread_reprov__descriptor, allocator, len, data); } void cmd_ctrl_thread_reprov__free_unpacked (CmdCtrlThreadReprov *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_ctrl_thread_reprov__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_ctrl_thread_reprov__init (RespCtrlThreadReprov *message) { static const RespCtrlThreadReprov init_value = RESP_CTRL_THREAD_REPROV__INIT; *message = init_value; } size_t resp_ctrl_thread_reprov__get_packed_size (const RespCtrlThreadReprov *message) { assert(message->base.descriptor == &resp_ctrl_thread_reprov__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_ctrl_thread_reprov__pack (const RespCtrlThreadReprov *message, uint8_t *out) { assert(message->base.descriptor == &resp_ctrl_thread_reprov__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_ctrl_thread_reprov__pack_to_buffer (const RespCtrlThreadReprov *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_ctrl_thread_reprov__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespCtrlThreadReprov * resp_ctrl_thread_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespCtrlThreadReprov *) protobuf_c_message_unpack (&resp_ctrl_thread_reprov__descriptor, allocator, len, data); } void resp_ctrl_thread_reprov__free_unpacked (RespCtrlThreadReprov *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_ctrl_thread_reprov__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void network_ctrl_payload__init (NetworkCtrlPayload *message) { static const NetworkCtrlPayload init_value = NETWORK_CTRL_PAYLOAD__INIT; *message = init_value; } size_t network_ctrl_payload__get_packed_size (const NetworkCtrlPayload *message) { assert(message->base.descriptor == &network_ctrl_payload__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t network_ctrl_payload__pack (const NetworkCtrlPayload *message, uint8_t *out) { assert(message->base.descriptor == &network_ctrl_payload__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t network_ctrl_payload__pack_to_buffer (const NetworkCtrlPayload *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &network_ctrl_payload__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } NetworkCtrlPayload * network_ctrl_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (NetworkCtrlPayload *) protobuf_c_message_unpack (&network_ctrl_payload__descriptor, allocator, len, data); } void network_ctrl_payload__free_unpacked (NetworkCtrlPayload *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &network_ctrl_payload__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } #define cmd_ctrl_wifi_reset__field_descriptors NULL #define cmd_ctrl_wifi_reset__field_indices_by_name NULL #define cmd_ctrl_wifi_reset__number_ranges NULL const ProtobufCMessageDescriptor cmd_ctrl_wifi_reset__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdCtrlWifiReset", "CmdCtrlWifiReset", "CmdCtrlWifiReset", "", sizeof(CmdCtrlWifiReset), 0, cmd_ctrl_wifi_reset__field_descriptors, cmd_ctrl_wifi_reset__field_indices_by_name, 0, cmd_ctrl_wifi_reset__number_ranges, (ProtobufCMessageInit) cmd_ctrl_wifi_reset__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_ctrl_wifi_reset__field_descriptors NULL #define resp_ctrl_wifi_reset__field_indices_by_name NULL #define resp_ctrl_wifi_reset__number_ranges NULL const ProtobufCMessageDescriptor resp_ctrl_wifi_reset__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespCtrlWifiReset", "RespCtrlWifiReset", "RespCtrlWifiReset", "", sizeof(RespCtrlWifiReset), 0, resp_ctrl_wifi_reset__field_descriptors, resp_ctrl_wifi_reset__field_indices_by_name, 0, resp_ctrl_wifi_reset__number_ranges, (ProtobufCMessageInit) resp_ctrl_wifi_reset__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_ctrl_wifi_reprov__field_descriptors NULL #define cmd_ctrl_wifi_reprov__field_indices_by_name NULL #define cmd_ctrl_wifi_reprov__number_ranges NULL const ProtobufCMessageDescriptor cmd_ctrl_wifi_reprov__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdCtrlWifiReprov", "CmdCtrlWifiReprov", "CmdCtrlWifiReprov", "", sizeof(CmdCtrlWifiReprov), 0, cmd_ctrl_wifi_reprov__field_descriptors, cmd_ctrl_wifi_reprov__field_indices_by_name, 0, cmd_ctrl_wifi_reprov__number_ranges, (ProtobufCMessageInit) cmd_ctrl_wifi_reprov__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_ctrl_wifi_reprov__field_descriptors NULL #define resp_ctrl_wifi_reprov__field_indices_by_name NULL #define resp_ctrl_wifi_reprov__number_ranges NULL const ProtobufCMessageDescriptor resp_ctrl_wifi_reprov__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespCtrlWifiReprov", "RespCtrlWifiReprov", "RespCtrlWifiReprov", "", sizeof(RespCtrlWifiReprov), 0, resp_ctrl_wifi_reprov__field_descriptors, resp_ctrl_wifi_reprov__field_indices_by_name, 0, resp_ctrl_wifi_reprov__number_ranges, (ProtobufCMessageInit) resp_ctrl_wifi_reprov__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_ctrl_thread_reset__field_descriptors NULL #define cmd_ctrl_thread_reset__field_indices_by_name NULL #define cmd_ctrl_thread_reset__number_ranges NULL const ProtobufCMessageDescriptor cmd_ctrl_thread_reset__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdCtrlThreadReset", "CmdCtrlThreadReset", "CmdCtrlThreadReset", "", sizeof(CmdCtrlThreadReset), 0, cmd_ctrl_thread_reset__field_descriptors, cmd_ctrl_thread_reset__field_indices_by_name, 0, cmd_ctrl_thread_reset__number_ranges, (ProtobufCMessageInit) cmd_ctrl_thread_reset__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_ctrl_thread_reset__field_descriptors NULL #define resp_ctrl_thread_reset__field_indices_by_name NULL #define resp_ctrl_thread_reset__number_ranges NULL const ProtobufCMessageDescriptor resp_ctrl_thread_reset__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespCtrlThreadReset", "RespCtrlThreadReset", "RespCtrlThreadReset", "", sizeof(RespCtrlThreadReset), 0, resp_ctrl_thread_reset__field_descriptors, resp_ctrl_thread_reset__field_indices_by_name, 0, resp_ctrl_thread_reset__number_ranges, (ProtobufCMessageInit) resp_ctrl_thread_reset__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_ctrl_thread_reprov__field_descriptors NULL #define cmd_ctrl_thread_reprov__field_indices_by_name NULL #define cmd_ctrl_thread_reprov__number_ranges NULL const ProtobufCMessageDescriptor cmd_ctrl_thread_reprov__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdCtrlThreadReprov", "CmdCtrlThreadReprov", "CmdCtrlThreadReprov", "", sizeof(CmdCtrlThreadReprov), 0, cmd_ctrl_thread_reprov__field_descriptors, cmd_ctrl_thread_reprov__field_indices_by_name, 0, cmd_ctrl_thread_reprov__number_ranges, (ProtobufCMessageInit) cmd_ctrl_thread_reprov__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_ctrl_thread_reprov__field_descriptors NULL #define resp_ctrl_thread_reprov__field_indices_by_name NULL #define resp_ctrl_thread_reprov__number_ranges NULL const ProtobufCMessageDescriptor resp_ctrl_thread_reprov__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespCtrlThreadReprov", "RespCtrlThreadReprov", "RespCtrlThreadReprov", "", sizeof(RespCtrlThreadReprov), 0, resp_ctrl_thread_reprov__field_descriptors, resp_ctrl_thread_reprov__field_indices_by_name, 0, resp_ctrl_thread_reprov__number_ranges, (ProtobufCMessageInit) resp_ctrl_thread_reprov__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor network_ctrl_payload__field_descriptors[10] = { { "msg", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(NetworkCtrlPayload, msg), &network_ctrl_msg_type__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "status", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(NetworkCtrlPayload, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_ctrl_wifi_reset", 11, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, cmd_ctrl_wifi_reset), &cmd_ctrl_wifi_reset__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_ctrl_wifi_reset", 12, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, resp_ctrl_wifi_reset), &resp_ctrl_wifi_reset__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_ctrl_wifi_reprov", 13, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, cmd_ctrl_wifi_reprov), &cmd_ctrl_wifi_reprov__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_ctrl_wifi_reprov", 14, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, resp_ctrl_wifi_reprov), &resp_ctrl_wifi_reprov__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_ctrl_thread_reset", 15, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, cmd_ctrl_thread_reset), &cmd_ctrl_thread_reset__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_ctrl_thread_reset", 16, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, resp_ctrl_thread_reset), &resp_ctrl_thread_reset__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_ctrl_thread_reprov", 17, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, cmd_ctrl_thread_reprov), &cmd_ctrl_thread_reprov__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_ctrl_thread_reprov", 18, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkCtrlPayload, payload_case), offsetof(NetworkCtrlPayload, resp_ctrl_thread_reprov), &resp_ctrl_thread_reprov__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned network_ctrl_payload__field_indices_by_name[] = { 8, /* field[8] = cmd_ctrl_thread_reprov */ 6, /* field[6] = cmd_ctrl_thread_reset */ 4, /* field[4] = cmd_ctrl_wifi_reprov */ 2, /* field[2] = cmd_ctrl_wifi_reset */ 0, /* field[0] = msg */ 9, /* field[9] = resp_ctrl_thread_reprov */ 7, /* field[7] = resp_ctrl_thread_reset */ 5, /* field[5] = resp_ctrl_wifi_reprov */ 3, /* field[3] = resp_ctrl_wifi_reset */ 1, /* field[1] = status */ }; static const ProtobufCIntRange network_ctrl_payload__number_ranges[2 + 1] = { { 1, 0 }, { 11, 2 }, { 0, 10 } }; const ProtobufCMessageDescriptor network_ctrl_payload__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "NetworkCtrlPayload", "NetworkCtrlPayload", "NetworkCtrlPayload", "", sizeof(NetworkCtrlPayload), 10, network_ctrl_payload__field_descriptors, network_ctrl_payload__field_indices_by_name, 2, network_ctrl_payload__number_ranges, (ProtobufCMessageInit) network_ctrl_payload__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCEnumValue network_ctrl_msg_type__enum_values_by_number[9] = { { "TypeCtrlReserved", "NETWORK_CTRL_MSG_TYPE__TypeCtrlReserved", 0 }, { "TypeCmdCtrlWifiReset", "NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReset", 1 }, { "TypeRespCtrlWifiReset", "NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReset", 2 }, { "TypeCmdCtrlWifiReprov", "NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReprov", 3 }, { "TypeRespCtrlWifiReprov", "NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReprov", 4 }, { "TypeCmdCtrlThreadReset", "NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReset", 5 }, { "TypeRespCtrlThreadReset", "NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReset", 6 }, { "TypeCmdCtrlThreadReprov", "NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReprov", 7 }, { "TypeRespCtrlThreadReprov", "NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReprov", 8 }, }; static const ProtobufCIntRange network_ctrl_msg_type__value_ranges[] = { {0, 0},{0, 9} }; static const ProtobufCEnumValueIndex network_ctrl_msg_type__enum_values_by_name[9] = { { "TypeCmdCtrlThreadReprov", 7 }, { "TypeCmdCtrlThreadReset", 5 }, { "TypeCmdCtrlWifiReprov", 3 }, { "TypeCmdCtrlWifiReset", 1 }, { "TypeCtrlReserved", 0 }, { "TypeRespCtrlThreadReprov", 8 }, { "TypeRespCtrlThreadReset", 6 }, { "TypeRespCtrlWifiReprov", 4 }, { "TypeRespCtrlWifiReset", 2 }, }; const ProtobufCEnumDescriptor network_ctrl_msg_type__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "NetworkCtrlMsgType", "NetworkCtrlMsgType", "NetworkCtrlMsgType", "", 9, network_ctrl_msg_type__enum_values_by_number, 9, network_ctrl_msg_type__enum_values_by_name, 1, network_ctrl_msg_type__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; ================================================ FILE: network_provisioning/proto-c/network_ctrl.pb-c.h ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_ctrl.proto */ #ifndef PROTOBUF_C_network_5fctrl_2eproto__INCLUDED #define PROTOBUF_C_network_5fctrl_2eproto__INCLUDED #include PROTOBUF_C__BEGIN_DECLS #if PROTOBUF_C_VERSION_NUMBER < 1003000 # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. #elif 1004000 < PROTOBUF_C_MIN_COMPILER_VERSION # error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. #endif #include "constants.pb-c.h" typedef struct CmdCtrlWifiReset CmdCtrlWifiReset; typedef struct RespCtrlWifiReset RespCtrlWifiReset; typedef struct CmdCtrlWifiReprov CmdCtrlWifiReprov; typedef struct RespCtrlWifiReprov RespCtrlWifiReprov; typedef struct CmdCtrlThreadReset CmdCtrlThreadReset; typedef struct RespCtrlThreadReset RespCtrlThreadReset; typedef struct CmdCtrlThreadReprov CmdCtrlThreadReprov; typedef struct RespCtrlThreadReprov RespCtrlThreadReprov; typedef struct NetworkCtrlPayload NetworkCtrlPayload; /* --- enums --- */ typedef enum _NetworkCtrlMsgType { NETWORK_CTRL_MSG_TYPE__TypeCtrlReserved = 0, NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReset = 1, NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReset = 2, NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReprov = 3, NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReprov = 4, NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReset = 5, NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReset = 6, NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReprov = 7, NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReprov = 8 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_CTRL_MSG_TYPE) } NetworkCtrlMsgType; /* --- messages --- */ struct CmdCtrlWifiReset { ProtobufCMessage base; }; #define CMD_CTRL_WIFI_RESET__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_ctrl_wifi_reset__descriptor) \ } struct RespCtrlWifiReset { ProtobufCMessage base; }; #define RESP_CTRL_WIFI_RESET__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_ctrl_wifi_reset__descriptor) \ } struct CmdCtrlWifiReprov { ProtobufCMessage base; }; #define CMD_CTRL_WIFI_REPROV__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_ctrl_wifi_reprov__descriptor) \ } struct RespCtrlWifiReprov { ProtobufCMessage base; }; #define RESP_CTRL_WIFI_REPROV__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_ctrl_wifi_reprov__descriptor) \ } struct CmdCtrlThreadReset { ProtobufCMessage base; }; #define CMD_CTRL_THREAD_RESET__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_ctrl_thread_reset__descriptor) \ } struct RespCtrlThreadReset { ProtobufCMessage base; }; #define RESP_CTRL_THREAD_RESET__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_ctrl_thread_reset__descriptor) \ } struct CmdCtrlThreadReprov { ProtobufCMessage base; }; #define CMD_CTRL_THREAD_REPROV__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_ctrl_thread_reprov__descriptor) \ } struct RespCtrlThreadReprov { ProtobufCMessage base; }; #define RESP_CTRL_THREAD_REPROV__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_ctrl_thread_reprov__descriptor) \ } typedef enum { NETWORK_CTRL_PAYLOAD__PAYLOAD__NOT_SET = 0, NETWORK_CTRL_PAYLOAD__PAYLOAD_CMD_CTRL_WIFI_RESET = 11, NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_WIFI_RESET = 12, NETWORK_CTRL_PAYLOAD__PAYLOAD_CMD_CTRL_WIFI_REPROV = 13, NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_WIFI_REPROV = 14, NETWORK_CTRL_PAYLOAD__PAYLOAD_CMD_CTRL_THREAD_RESET = 15, NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_THREAD_RESET = 16, NETWORK_CTRL_PAYLOAD__PAYLOAD_CMD_CTRL_THREAD_REPROV = 17, NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_THREAD_REPROV = 18 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_CTRL_PAYLOAD__PAYLOAD__CASE) } NetworkCtrlPayload__PayloadCase; struct NetworkCtrlPayload { ProtobufCMessage base; NetworkCtrlMsgType msg; Status status; NetworkCtrlPayload__PayloadCase payload_case; union { CmdCtrlWifiReset *cmd_ctrl_wifi_reset; RespCtrlWifiReset *resp_ctrl_wifi_reset; CmdCtrlWifiReprov *cmd_ctrl_wifi_reprov; RespCtrlWifiReprov *resp_ctrl_wifi_reprov; CmdCtrlThreadReset *cmd_ctrl_thread_reset; RespCtrlThreadReset *resp_ctrl_thread_reset; CmdCtrlThreadReprov *cmd_ctrl_thread_reprov; RespCtrlThreadReprov *resp_ctrl_thread_reprov; }; }; #define NETWORK_CTRL_PAYLOAD__INIT \ { PROTOBUF_C_MESSAGE_INIT (&network_ctrl_payload__descriptor) \ , NETWORK_CTRL_MSG_TYPE__TypeCtrlReserved, STATUS__Success, NETWORK_CTRL_PAYLOAD__PAYLOAD__NOT_SET, {0} } /* CmdCtrlWifiReset methods */ void cmd_ctrl_wifi_reset__init (CmdCtrlWifiReset *message); size_t cmd_ctrl_wifi_reset__get_packed_size (const CmdCtrlWifiReset *message); size_t cmd_ctrl_wifi_reset__pack (const CmdCtrlWifiReset *message, uint8_t *out); size_t cmd_ctrl_wifi_reset__pack_to_buffer (const CmdCtrlWifiReset *message, ProtobufCBuffer *buffer); CmdCtrlWifiReset * cmd_ctrl_wifi_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_ctrl_wifi_reset__free_unpacked (CmdCtrlWifiReset *message, ProtobufCAllocator *allocator); /* RespCtrlWifiReset methods */ void resp_ctrl_wifi_reset__init (RespCtrlWifiReset *message); size_t resp_ctrl_wifi_reset__get_packed_size (const RespCtrlWifiReset *message); size_t resp_ctrl_wifi_reset__pack (const RespCtrlWifiReset *message, uint8_t *out); size_t resp_ctrl_wifi_reset__pack_to_buffer (const RespCtrlWifiReset *message, ProtobufCBuffer *buffer); RespCtrlWifiReset * resp_ctrl_wifi_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_ctrl_wifi_reset__free_unpacked (RespCtrlWifiReset *message, ProtobufCAllocator *allocator); /* CmdCtrlWifiReprov methods */ void cmd_ctrl_wifi_reprov__init (CmdCtrlWifiReprov *message); size_t cmd_ctrl_wifi_reprov__get_packed_size (const CmdCtrlWifiReprov *message); size_t cmd_ctrl_wifi_reprov__pack (const CmdCtrlWifiReprov *message, uint8_t *out); size_t cmd_ctrl_wifi_reprov__pack_to_buffer (const CmdCtrlWifiReprov *message, ProtobufCBuffer *buffer); CmdCtrlWifiReprov * cmd_ctrl_wifi_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_ctrl_wifi_reprov__free_unpacked (CmdCtrlWifiReprov *message, ProtobufCAllocator *allocator); /* RespCtrlWifiReprov methods */ void resp_ctrl_wifi_reprov__init (RespCtrlWifiReprov *message); size_t resp_ctrl_wifi_reprov__get_packed_size (const RespCtrlWifiReprov *message); size_t resp_ctrl_wifi_reprov__pack (const RespCtrlWifiReprov *message, uint8_t *out); size_t resp_ctrl_wifi_reprov__pack_to_buffer (const RespCtrlWifiReprov *message, ProtobufCBuffer *buffer); RespCtrlWifiReprov * resp_ctrl_wifi_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_ctrl_wifi_reprov__free_unpacked (RespCtrlWifiReprov *message, ProtobufCAllocator *allocator); /* CmdCtrlThreadReset methods */ void cmd_ctrl_thread_reset__init (CmdCtrlThreadReset *message); size_t cmd_ctrl_thread_reset__get_packed_size (const CmdCtrlThreadReset *message); size_t cmd_ctrl_thread_reset__pack (const CmdCtrlThreadReset *message, uint8_t *out); size_t cmd_ctrl_thread_reset__pack_to_buffer (const CmdCtrlThreadReset *message, ProtobufCBuffer *buffer); CmdCtrlThreadReset * cmd_ctrl_thread_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_ctrl_thread_reset__free_unpacked (CmdCtrlThreadReset *message, ProtobufCAllocator *allocator); /* RespCtrlThreadReset methods */ void resp_ctrl_thread_reset__init (RespCtrlThreadReset *message); size_t resp_ctrl_thread_reset__get_packed_size (const RespCtrlThreadReset *message); size_t resp_ctrl_thread_reset__pack (const RespCtrlThreadReset *message, uint8_t *out); size_t resp_ctrl_thread_reset__pack_to_buffer (const RespCtrlThreadReset *message, ProtobufCBuffer *buffer); RespCtrlThreadReset * resp_ctrl_thread_reset__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_ctrl_thread_reset__free_unpacked (RespCtrlThreadReset *message, ProtobufCAllocator *allocator); /* CmdCtrlThreadReprov methods */ void cmd_ctrl_thread_reprov__init (CmdCtrlThreadReprov *message); size_t cmd_ctrl_thread_reprov__get_packed_size (const CmdCtrlThreadReprov *message); size_t cmd_ctrl_thread_reprov__pack (const CmdCtrlThreadReprov *message, uint8_t *out); size_t cmd_ctrl_thread_reprov__pack_to_buffer (const CmdCtrlThreadReprov *message, ProtobufCBuffer *buffer); CmdCtrlThreadReprov * cmd_ctrl_thread_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_ctrl_thread_reprov__free_unpacked (CmdCtrlThreadReprov *message, ProtobufCAllocator *allocator); /* RespCtrlThreadReprov methods */ void resp_ctrl_thread_reprov__init (RespCtrlThreadReprov *message); size_t resp_ctrl_thread_reprov__get_packed_size (const RespCtrlThreadReprov *message); size_t resp_ctrl_thread_reprov__pack (const RespCtrlThreadReprov *message, uint8_t *out); size_t resp_ctrl_thread_reprov__pack_to_buffer (const RespCtrlThreadReprov *message, ProtobufCBuffer *buffer); RespCtrlThreadReprov * resp_ctrl_thread_reprov__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_ctrl_thread_reprov__free_unpacked (RespCtrlThreadReprov *message, ProtobufCAllocator *allocator); /* NetworkCtrlPayload methods */ void network_ctrl_payload__init (NetworkCtrlPayload *message); size_t network_ctrl_payload__get_packed_size (const NetworkCtrlPayload *message); size_t network_ctrl_payload__pack (const NetworkCtrlPayload *message, uint8_t *out); size_t network_ctrl_payload__pack_to_buffer (const NetworkCtrlPayload *message, ProtobufCBuffer *buffer); NetworkCtrlPayload * network_ctrl_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void network_ctrl_payload__free_unpacked (NetworkCtrlPayload *message, ProtobufCAllocator *allocator); /* --- per-message closures --- */ typedef void (*CmdCtrlWifiReset_Closure) (const CmdCtrlWifiReset *message, void *closure_data); typedef void (*RespCtrlWifiReset_Closure) (const RespCtrlWifiReset *message, void *closure_data); typedef void (*CmdCtrlWifiReprov_Closure) (const CmdCtrlWifiReprov *message, void *closure_data); typedef void (*RespCtrlWifiReprov_Closure) (const RespCtrlWifiReprov *message, void *closure_data); typedef void (*CmdCtrlThreadReset_Closure) (const CmdCtrlThreadReset *message, void *closure_data); typedef void (*RespCtrlThreadReset_Closure) (const RespCtrlThreadReset *message, void *closure_data); typedef void (*CmdCtrlThreadReprov_Closure) (const CmdCtrlThreadReprov *message, void *closure_data); typedef void (*RespCtrlThreadReprov_Closure) (const RespCtrlThreadReprov *message, void *closure_data); typedef void (*NetworkCtrlPayload_Closure) (const NetworkCtrlPayload *message, void *closure_data); /* --- services --- */ /* --- descriptors --- */ extern const ProtobufCEnumDescriptor network_ctrl_msg_type__descriptor; extern const ProtobufCMessageDescriptor cmd_ctrl_wifi_reset__descriptor; extern const ProtobufCMessageDescriptor resp_ctrl_wifi_reset__descriptor; extern const ProtobufCMessageDescriptor cmd_ctrl_wifi_reprov__descriptor; extern const ProtobufCMessageDescriptor resp_ctrl_wifi_reprov__descriptor; extern const ProtobufCMessageDescriptor cmd_ctrl_thread_reset__descriptor; extern const ProtobufCMessageDescriptor resp_ctrl_thread_reset__descriptor; extern const ProtobufCMessageDescriptor cmd_ctrl_thread_reprov__descriptor; extern const ProtobufCMessageDescriptor resp_ctrl_thread_reprov__descriptor; extern const ProtobufCMessageDescriptor network_ctrl_payload__descriptor; PROTOBUF_C__END_DECLS #endif /* PROTOBUF_C_network_5fctrl_2eproto__INCLUDED */ ================================================ FILE: network_provisioning/proto-c/network_scan.pb-c.c ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_scan.proto */ /* Do not generate deprecated warnings for self */ #ifndef PROTOBUF_C__NO_DEPRECATED #define PROTOBUF_C__NO_DEPRECATED #endif #include "network_scan.pb-c.h" void cmd_scan_wifi_start__init (CmdScanWifiStart *message) { static const CmdScanWifiStart init_value = CMD_SCAN_WIFI_START__INIT; *message = init_value; } size_t cmd_scan_wifi_start__get_packed_size (const CmdScanWifiStart *message) { assert(message->base.descriptor == &cmd_scan_wifi_start__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_wifi_start__pack (const CmdScanWifiStart *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_wifi_start__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_wifi_start__pack_to_buffer (const CmdScanWifiStart *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_wifi_start__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanWifiStart * cmd_scan_wifi_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanWifiStart *) protobuf_c_message_unpack (&cmd_scan_wifi_start__descriptor, allocator, len, data); } void cmd_scan_wifi_start__free_unpacked (CmdScanWifiStart *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_wifi_start__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_scan_thread_start__init (CmdScanThreadStart *message) { static const CmdScanThreadStart init_value = CMD_SCAN_THREAD_START__INIT; *message = init_value; } size_t cmd_scan_thread_start__get_packed_size (const CmdScanThreadStart *message) { assert(message->base.descriptor == &cmd_scan_thread_start__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_thread_start__pack (const CmdScanThreadStart *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_thread_start__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_thread_start__pack_to_buffer (const CmdScanThreadStart *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_thread_start__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanThreadStart * cmd_scan_thread_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanThreadStart *) protobuf_c_message_unpack (&cmd_scan_thread_start__descriptor, allocator, len, data); } void cmd_scan_thread_start__free_unpacked (CmdScanThreadStart *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_thread_start__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_wifi_start__init (RespScanWifiStart *message) { static const RespScanWifiStart init_value = RESP_SCAN_WIFI_START__INIT; *message = init_value; } size_t resp_scan_wifi_start__get_packed_size (const RespScanWifiStart *message) { assert(message->base.descriptor == &resp_scan_wifi_start__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_wifi_start__pack (const RespScanWifiStart *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_wifi_start__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_wifi_start__pack_to_buffer (const RespScanWifiStart *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_wifi_start__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanWifiStart * resp_scan_wifi_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanWifiStart *) protobuf_c_message_unpack (&resp_scan_wifi_start__descriptor, allocator, len, data); } void resp_scan_wifi_start__free_unpacked (RespScanWifiStart *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_wifi_start__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_thread_start__init (RespScanThreadStart *message) { static const RespScanThreadStart init_value = RESP_SCAN_THREAD_START__INIT; *message = init_value; } size_t resp_scan_thread_start__get_packed_size (const RespScanThreadStart *message) { assert(message->base.descriptor == &resp_scan_thread_start__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_thread_start__pack (const RespScanThreadStart *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_thread_start__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_thread_start__pack_to_buffer (const RespScanThreadStart *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_thread_start__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanThreadStart * resp_scan_thread_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanThreadStart *) protobuf_c_message_unpack (&resp_scan_thread_start__descriptor, allocator, len, data); } void resp_scan_thread_start__free_unpacked (RespScanThreadStart *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_thread_start__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_scan_wifi_status__init (CmdScanWifiStatus *message) { static const CmdScanWifiStatus init_value = CMD_SCAN_WIFI_STATUS__INIT; *message = init_value; } size_t cmd_scan_wifi_status__get_packed_size (const CmdScanWifiStatus *message) { assert(message->base.descriptor == &cmd_scan_wifi_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_wifi_status__pack (const CmdScanWifiStatus *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_wifi_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_wifi_status__pack_to_buffer (const CmdScanWifiStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_wifi_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanWifiStatus * cmd_scan_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanWifiStatus *) protobuf_c_message_unpack (&cmd_scan_wifi_status__descriptor, allocator, len, data); } void cmd_scan_wifi_status__free_unpacked (CmdScanWifiStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_wifi_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_scan_thread_status__init (CmdScanThreadStatus *message) { static const CmdScanThreadStatus init_value = CMD_SCAN_THREAD_STATUS__INIT; *message = init_value; } size_t cmd_scan_thread_status__get_packed_size (const CmdScanThreadStatus *message) { assert(message->base.descriptor == &cmd_scan_thread_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_thread_status__pack (const CmdScanThreadStatus *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_thread_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_thread_status__pack_to_buffer (const CmdScanThreadStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_thread_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanThreadStatus * cmd_scan_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanThreadStatus *) protobuf_c_message_unpack (&cmd_scan_thread_status__descriptor, allocator, len, data); } void cmd_scan_thread_status__free_unpacked (CmdScanThreadStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_thread_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_wifi_status__init (RespScanWifiStatus *message) { static const RespScanWifiStatus init_value = RESP_SCAN_WIFI_STATUS__INIT; *message = init_value; } size_t resp_scan_wifi_status__get_packed_size (const RespScanWifiStatus *message) { assert(message->base.descriptor == &resp_scan_wifi_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_wifi_status__pack (const RespScanWifiStatus *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_wifi_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_wifi_status__pack_to_buffer (const RespScanWifiStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_wifi_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanWifiStatus * resp_scan_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanWifiStatus *) protobuf_c_message_unpack (&resp_scan_wifi_status__descriptor, allocator, len, data); } void resp_scan_wifi_status__free_unpacked (RespScanWifiStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_wifi_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_thread_status__init (RespScanThreadStatus *message) { static const RespScanThreadStatus init_value = RESP_SCAN_THREAD_STATUS__INIT; *message = init_value; } size_t resp_scan_thread_status__get_packed_size (const RespScanThreadStatus *message) { assert(message->base.descriptor == &resp_scan_thread_status__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_thread_status__pack (const RespScanThreadStatus *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_thread_status__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_thread_status__pack_to_buffer (const RespScanThreadStatus *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_thread_status__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanThreadStatus * resp_scan_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanThreadStatus *) protobuf_c_message_unpack (&resp_scan_thread_status__descriptor, allocator, len, data); } void resp_scan_thread_status__free_unpacked (RespScanThreadStatus *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_thread_status__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_scan_wifi_result__init (CmdScanWifiResult *message) { static const CmdScanWifiResult init_value = CMD_SCAN_WIFI_RESULT__INIT; *message = init_value; } size_t cmd_scan_wifi_result__get_packed_size (const CmdScanWifiResult *message) { assert(message->base.descriptor == &cmd_scan_wifi_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_wifi_result__pack (const CmdScanWifiResult *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_wifi_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_wifi_result__pack_to_buffer (const CmdScanWifiResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_wifi_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanWifiResult * cmd_scan_wifi_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanWifiResult *) protobuf_c_message_unpack (&cmd_scan_wifi_result__descriptor, allocator, len, data); } void cmd_scan_wifi_result__free_unpacked (CmdScanWifiResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_wifi_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void cmd_scan_thread_result__init (CmdScanThreadResult *message) { static const CmdScanThreadResult init_value = CMD_SCAN_THREAD_RESULT__INIT; *message = init_value; } size_t cmd_scan_thread_result__get_packed_size (const CmdScanThreadResult *message) { assert(message->base.descriptor == &cmd_scan_thread_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t cmd_scan_thread_result__pack (const CmdScanThreadResult *message, uint8_t *out) { assert(message->base.descriptor == &cmd_scan_thread_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t cmd_scan_thread_result__pack_to_buffer (const CmdScanThreadResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &cmd_scan_thread_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } CmdScanThreadResult * cmd_scan_thread_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (CmdScanThreadResult *) protobuf_c_message_unpack (&cmd_scan_thread_result__descriptor, allocator, len, data); } void cmd_scan_thread_result__free_unpacked (CmdScanThreadResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &cmd_scan_thread_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void wi_fi_scan_result__init (WiFiScanResult *message) { static const WiFiScanResult init_value = WI_FI_SCAN_RESULT__INIT; *message = init_value; } size_t wi_fi_scan_result__get_packed_size (const WiFiScanResult *message) { assert(message->base.descriptor == &wi_fi_scan_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t wi_fi_scan_result__pack (const WiFiScanResult *message, uint8_t *out) { assert(message->base.descriptor == &wi_fi_scan_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t wi_fi_scan_result__pack_to_buffer (const WiFiScanResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &wi_fi_scan_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } WiFiScanResult * wi_fi_scan_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (WiFiScanResult *) protobuf_c_message_unpack (&wi_fi_scan_result__descriptor, allocator, len, data); } void wi_fi_scan_result__free_unpacked (WiFiScanResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &wi_fi_scan_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void thread_scan_result__init (ThreadScanResult *message) { static const ThreadScanResult init_value = THREAD_SCAN_RESULT__INIT; *message = init_value; } size_t thread_scan_result__get_packed_size (const ThreadScanResult *message) { assert(message->base.descriptor == &thread_scan_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t thread_scan_result__pack (const ThreadScanResult *message, uint8_t *out) { assert(message->base.descriptor == &thread_scan_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t thread_scan_result__pack_to_buffer (const ThreadScanResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &thread_scan_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } ThreadScanResult * thread_scan_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (ThreadScanResult *) protobuf_c_message_unpack (&thread_scan_result__descriptor, allocator, len, data); } void thread_scan_result__free_unpacked (ThreadScanResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &thread_scan_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_wifi_result__init (RespScanWifiResult *message) { static const RespScanWifiResult init_value = RESP_SCAN_WIFI_RESULT__INIT; *message = init_value; } size_t resp_scan_wifi_result__get_packed_size (const RespScanWifiResult *message) { assert(message->base.descriptor == &resp_scan_wifi_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_wifi_result__pack (const RespScanWifiResult *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_wifi_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_wifi_result__pack_to_buffer (const RespScanWifiResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_wifi_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanWifiResult * resp_scan_wifi_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanWifiResult *) protobuf_c_message_unpack (&resp_scan_wifi_result__descriptor, allocator, len, data); } void resp_scan_wifi_result__free_unpacked (RespScanWifiResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_wifi_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void resp_scan_thread_result__init (RespScanThreadResult *message) { static const RespScanThreadResult init_value = RESP_SCAN_THREAD_RESULT__INIT; *message = init_value; } size_t resp_scan_thread_result__get_packed_size (const RespScanThreadResult *message) { assert(message->base.descriptor == &resp_scan_thread_result__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t resp_scan_thread_result__pack (const RespScanThreadResult *message, uint8_t *out) { assert(message->base.descriptor == &resp_scan_thread_result__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t resp_scan_thread_result__pack_to_buffer (const RespScanThreadResult *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &resp_scan_thread_result__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } RespScanThreadResult * resp_scan_thread_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (RespScanThreadResult *) protobuf_c_message_unpack (&resp_scan_thread_result__descriptor, allocator, len, data); } void resp_scan_thread_result__free_unpacked (RespScanThreadResult *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &resp_scan_thread_result__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void network_scan_payload__init (NetworkScanPayload *message) { static const NetworkScanPayload init_value = NETWORK_SCAN_PAYLOAD__INIT; *message = init_value; } size_t network_scan_payload__get_packed_size (const NetworkScanPayload *message) { assert(message->base.descriptor == &network_scan_payload__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } size_t network_scan_payload__pack (const NetworkScanPayload *message, uint8_t *out) { assert(message->base.descriptor == &network_scan_payload__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } size_t network_scan_payload__pack_to_buffer (const NetworkScanPayload *message, ProtobufCBuffer *buffer) { assert(message->base.descriptor == &network_scan_payload__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } NetworkScanPayload * network_scan_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { return (NetworkScanPayload *) protobuf_c_message_unpack (&network_scan_payload__descriptor, allocator, len, data); } void network_scan_payload__free_unpacked (NetworkScanPayload *message, ProtobufCAllocator *allocator) { if(!message) return; assert(message->base.descriptor == &network_scan_payload__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } static const ProtobufCFieldDescriptor cmd_scan_wifi_start__field_descriptors[4] = { { "blocking", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ offsetof(CmdScanWifiStart, blocking), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "passive", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ offsetof(CmdScanWifiStart, passive), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "group_channels", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanWifiStart, group_channels), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "period_ms", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanWifiStart, period_ms), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_scan_wifi_start__field_indices_by_name[] = { 0, /* field[0] = blocking */ 2, /* field[2] = group_channels */ 1, /* field[1] = passive */ 3, /* field[3] = period_ms */ }; static const ProtobufCIntRange cmd_scan_wifi_start__number_ranges[1 + 1] = { { 1, 0 }, { 0, 4 } }; const ProtobufCMessageDescriptor cmd_scan_wifi_start__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanWifiStart", "CmdScanWifiStart", "CmdScanWifiStart", "", sizeof(CmdScanWifiStart), 4, cmd_scan_wifi_start__field_descriptors, cmd_scan_wifi_start__field_indices_by_name, 1, cmd_scan_wifi_start__number_ranges, (ProtobufCMessageInit) cmd_scan_wifi_start__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor cmd_scan_thread_start__field_descriptors[2] = { { "blocking", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ offsetof(CmdScanThreadStart, blocking), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel_mask", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanThreadStart, channel_mask), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_scan_thread_start__field_indices_by_name[] = { 0, /* field[0] = blocking */ 1, /* field[1] = channel_mask */ }; static const ProtobufCIntRange cmd_scan_thread_start__number_ranges[1 + 1] = { { 1, 0 }, { 0, 2 } }; const ProtobufCMessageDescriptor cmd_scan_thread_start__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanThreadStart", "CmdScanThreadStart", "CmdScanThreadStart", "", sizeof(CmdScanThreadStart), 2, cmd_scan_thread_start__field_descriptors, cmd_scan_thread_start__field_indices_by_name, 1, cmd_scan_thread_start__number_ranges, (ProtobufCMessageInit) cmd_scan_thread_start__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_scan_wifi_start__field_descriptors NULL #define resp_scan_wifi_start__field_indices_by_name NULL #define resp_scan_wifi_start__number_ranges NULL const ProtobufCMessageDescriptor resp_scan_wifi_start__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanWifiStart", "RespScanWifiStart", "RespScanWifiStart", "", sizeof(RespScanWifiStart), 0, resp_scan_wifi_start__field_descriptors, resp_scan_wifi_start__field_indices_by_name, 0, resp_scan_wifi_start__number_ranges, (ProtobufCMessageInit) resp_scan_wifi_start__init, NULL,NULL,NULL /* reserved[123] */ }; #define resp_scan_thread_start__field_descriptors NULL #define resp_scan_thread_start__field_indices_by_name NULL #define resp_scan_thread_start__number_ranges NULL const ProtobufCMessageDescriptor resp_scan_thread_start__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanThreadStart", "RespScanThreadStart", "RespScanThreadStart", "", sizeof(RespScanThreadStart), 0, resp_scan_thread_start__field_descriptors, resp_scan_thread_start__field_indices_by_name, 0, resp_scan_thread_start__number_ranges, (ProtobufCMessageInit) resp_scan_thread_start__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_scan_wifi_status__field_descriptors NULL #define cmd_scan_wifi_status__field_indices_by_name NULL #define cmd_scan_wifi_status__number_ranges NULL const ProtobufCMessageDescriptor cmd_scan_wifi_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanWifiStatus", "CmdScanWifiStatus", "CmdScanWifiStatus", "", sizeof(CmdScanWifiStatus), 0, cmd_scan_wifi_status__field_descriptors, cmd_scan_wifi_status__field_indices_by_name, 0, cmd_scan_wifi_status__number_ranges, (ProtobufCMessageInit) cmd_scan_wifi_status__init, NULL,NULL,NULL /* reserved[123] */ }; #define cmd_scan_thread_status__field_descriptors NULL #define cmd_scan_thread_status__field_indices_by_name NULL #define cmd_scan_thread_status__number_ranges NULL const ProtobufCMessageDescriptor cmd_scan_thread_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanThreadStatus", "CmdScanThreadStatus", "CmdScanThreadStatus", "", sizeof(CmdScanThreadStatus), 0, cmd_scan_thread_status__field_descriptors, cmd_scan_thread_status__field_indices_by_name, 0, cmd_scan_thread_status__number_ranges, (ProtobufCMessageInit) cmd_scan_thread_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_scan_wifi_status__field_descriptors[2] = { { "scan_finished", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ offsetof(RespScanWifiStatus, scan_finished), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "result_count", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(RespScanWifiStatus, result_count), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_scan_wifi_status__field_indices_by_name[] = { 1, /* field[1] = result_count */ 0, /* field[0] = scan_finished */ }; static const ProtobufCIntRange resp_scan_wifi_status__number_ranges[1 + 1] = { { 1, 0 }, { 0, 2 } }; const ProtobufCMessageDescriptor resp_scan_wifi_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanWifiStatus", "RespScanWifiStatus", "RespScanWifiStatus", "", sizeof(RespScanWifiStatus), 2, resp_scan_wifi_status__field_descriptors, resp_scan_wifi_status__field_indices_by_name, 1, resp_scan_wifi_status__number_ranges, (ProtobufCMessageInit) resp_scan_wifi_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_scan_thread_status__field_descriptors[2] = { { "scan_finished", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ offsetof(RespScanThreadStatus, scan_finished), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "result_count", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(RespScanThreadStatus, result_count), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_scan_thread_status__field_indices_by_name[] = { 1, /* field[1] = result_count */ 0, /* field[0] = scan_finished */ }; static const ProtobufCIntRange resp_scan_thread_status__number_ranges[1 + 1] = { { 1, 0 }, { 0, 2 } }; const ProtobufCMessageDescriptor resp_scan_thread_status__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanThreadStatus", "RespScanThreadStatus", "RespScanThreadStatus", "", sizeof(RespScanThreadStatus), 2, resp_scan_thread_status__field_descriptors, resp_scan_thread_status__field_indices_by_name, 1, resp_scan_thread_status__number_ranges, (ProtobufCMessageInit) resp_scan_thread_status__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor cmd_scan_wifi_result__field_descriptors[2] = { { "start_index", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanWifiResult, start_index), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "count", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanWifiResult, count), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_scan_wifi_result__field_indices_by_name[] = { 1, /* field[1] = count */ 0, /* field[0] = start_index */ }; static const ProtobufCIntRange cmd_scan_wifi_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 2 } }; const ProtobufCMessageDescriptor cmd_scan_wifi_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanWifiResult", "CmdScanWifiResult", "CmdScanWifiResult", "", sizeof(CmdScanWifiResult), 2, cmd_scan_wifi_result__field_descriptors, cmd_scan_wifi_result__field_indices_by_name, 1, cmd_scan_wifi_result__number_ranges, (ProtobufCMessageInit) cmd_scan_wifi_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor cmd_scan_thread_result__field_descriptors[2] = { { "start_index", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanThreadResult, start_index), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "count", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(CmdScanThreadResult, count), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned cmd_scan_thread_result__field_indices_by_name[] = { 1, /* field[1] = count */ 0, /* field[0] = start_index */ }; static const ProtobufCIntRange cmd_scan_thread_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 2 } }; const ProtobufCMessageDescriptor cmd_scan_thread_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "CmdScanThreadResult", "CmdScanThreadResult", "CmdScanThreadResult", "", sizeof(CmdScanThreadResult), 2, cmd_scan_thread_result__field_descriptors, cmd_scan_thread_result__field_indices_by_name, 1, cmd_scan_thread_result__number_ranges, (ProtobufCMessageInit) cmd_scan_thread_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor wi_fi_scan_result__field_descriptors[5] = { { "ssid", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(WiFiScanResult, ssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(WiFiScanResult, channel), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "rssi", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_INT32, 0, /* quantifier_offset */ offsetof(WiFiScanResult, rssi), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "bssid", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(WiFiScanResult, bssid), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "auth", 5, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(WiFiScanResult, auth), &wifi_auth_mode__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned wi_fi_scan_result__field_indices_by_name[] = { 4, /* field[4] = auth */ 3, /* field[3] = bssid */ 1, /* field[1] = channel */ 2, /* field[2] = rssi */ 0, /* field[0] = ssid */ }; static const ProtobufCIntRange wi_fi_scan_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 5 } }; const ProtobufCMessageDescriptor wi_fi_scan_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "WiFiScanResult", "WiFiScanResult", "WiFiScanResult", "", sizeof(WiFiScanResult), 5, wi_fi_scan_result__field_descriptors, wi_fi_scan_result__field_indices_by_name, 1, wi_fi_scan_result__number_ranges, (ProtobufCMessageInit) wi_fi_scan_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor thread_scan_result__field_descriptors[7] = { { "pan_id", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(ThreadScanResult, pan_id), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "channel", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(ThreadScanResult, channel), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "rssi", 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_INT32, 0, /* quantifier_offset */ offsetof(ThreadScanResult, rssi), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "lqi", 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_UINT32, 0, /* quantifier_offset */ offsetof(ThreadScanResult, lqi), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "ext_addr", 5, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(ThreadScanResult, ext_addr), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "network_name", 6, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ offsetof(ThreadScanResult, network_name), NULL, &protobuf_c_empty_string, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "ext_pan_id", 7, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_BYTES, 0, /* quantifier_offset */ offsetof(ThreadScanResult, ext_pan_id), NULL, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned thread_scan_result__field_indices_by_name[] = { 1, /* field[1] = channel */ 4, /* field[4] = ext_addr */ 6, /* field[6] = ext_pan_id */ 3, /* field[3] = lqi */ 5, /* field[5] = network_name */ 0, /* field[0] = pan_id */ 2, /* field[2] = rssi */ }; static const ProtobufCIntRange thread_scan_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 7 } }; const ProtobufCMessageDescriptor thread_scan_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "ThreadScanResult", "ThreadScanResult", "ThreadScanResult", "", sizeof(ThreadScanResult), 7, thread_scan_result__field_descriptors, thread_scan_result__field_indices_by_name, 1, thread_scan_result__number_ranges, (ProtobufCMessageInit) thread_scan_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_scan_wifi_result__field_descriptors[1] = { { "entries", 1, PROTOBUF_C_LABEL_REPEATED, PROTOBUF_C_TYPE_MESSAGE, offsetof(RespScanWifiResult, n_entries), offsetof(RespScanWifiResult, entries), &wi_fi_scan_result__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_scan_wifi_result__field_indices_by_name[] = { 0, /* field[0] = entries */ }; static const ProtobufCIntRange resp_scan_wifi_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_scan_wifi_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanWifiResult", "RespScanWifiResult", "RespScanWifiResult", "", sizeof(RespScanWifiResult), 1, resp_scan_wifi_result__field_descriptors, resp_scan_wifi_result__field_indices_by_name, 1, resp_scan_wifi_result__number_ranges, (ProtobufCMessageInit) resp_scan_wifi_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor resp_scan_thread_result__field_descriptors[1] = { { "entries", 1, PROTOBUF_C_LABEL_REPEATED, PROTOBUF_C_TYPE_MESSAGE, offsetof(RespScanThreadResult, n_entries), offsetof(RespScanThreadResult, entries), &thread_scan_result__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned resp_scan_thread_result__field_indices_by_name[] = { 0, /* field[0] = entries */ }; static const ProtobufCIntRange resp_scan_thread_result__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; const ProtobufCMessageDescriptor resp_scan_thread_result__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "RespScanThreadResult", "RespScanThreadResult", "RespScanThreadResult", "", sizeof(RespScanThreadResult), 1, resp_scan_thread_result__field_descriptors, resp_scan_thread_result__field_indices_by_name, 1, resp_scan_thread_result__number_ranges, (ProtobufCMessageInit) resp_scan_thread_result__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCFieldDescriptor network_scan_payload__field_descriptors[14] = { { "msg", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(NetworkScanPayload, msg), &network_scan_msg_type__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "status", 2, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_ENUM, 0, /* quantifier_offset */ offsetof(NetworkScanPayload, status), &status__descriptor, NULL, 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_wifi_start", 10, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_wifi_start), &cmd_scan_wifi_start__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_wifi_start", 11, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_wifi_start), &resp_scan_wifi_start__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_wifi_status", 12, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_wifi_status), &cmd_scan_wifi_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_wifi_status", 13, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_wifi_status), &resp_scan_wifi_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_wifi_result", 14, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_wifi_result), &cmd_scan_wifi_result__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_wifi_result", 15, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_wifi_result), &resp_scan_wifi_result__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_thread_start", 16, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_thread_start), &cmd_scan_thread_start__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_thread_start", 17, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_thread_start), &resp_scan_thread_start__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_thread_status", 18, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_thread_status), &cmd_scan_thread_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_thread_status", 19, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_thread_status), &resp_scan_thread_status__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "cmd_scan_thread_result", 20, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, cmd_scan_thread_result), &cmd_scan_thread_result__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, { "resp_scan_thread_result", 21, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, offsetof(NetworkScanPayload, payload_case), offsetof(NetworkScanPayload, resp_scan_thread_result), &resp_scan_thread_result__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, }; static const unsigned network_scan_payload__field_indices_by_name[] = { 12, /* field[12] = cmd_scan_thread_result */ 8, /* field[8] = cmd_scan_thread_start */ 10, /* field[10] = cmd_scan_thread_status */ 6, /* field[6] = cmd_scan_wifi_result */ 2, /* field[2] = cmd_scan_wifi_start */ 4, /* field[4] = cmd_scan_wifi_status */ 0, /* field[0] = msg */ 13, /* field[13] = resp_scan_thread_result */ 9, /* field[9] = resp_scan_thread_start */ 11, /* field[11] = resp_scan_thread_status */ 7, /* field[7] = resp_scan_wifi_result */ 3, /* field[3] = resp_scan_wifi_start */ 5, /* field[5] = resp_scan_wifi_status */ 1, /* field[1] = status */ }; static const ProtobufCIntRange network_scan_payload__number_ranges[2 + 1] = { { 1, 0 }, { 10, 2 }, { 0, 14 } }; const ProtobufCMessageDescriptor network_scan_payload__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, "NetworkScanPayload", "NetworkScanPayload", "NetworkScanPayload", "", sizeof(NetworkScanPayload), 14, network_scan_payload__field_descriptors, network_scan_payload__field_indices_by_name, 2, network_scan_payload__number_ranges, (ProtobufCMessageInit) network_scan_payload__init, NULL,NULL,NULL /* reserved[123] */ }; static const ProtobufCEnumValue network_scan_msg_type__enum_values_by_number[12] = { { "TypeCmdScanWifiStart", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStart", 0 }, { "TypeRespScanWifiStart", "NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStart", 1 }, { "TypeCmdScanWifiStatus", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStatus", 2 }, { "TypeRespScanWifiStatus", "NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStatus", 3 }, { "TypeCmdScanWifiResult", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiResult", 4 }, { "TypeRespScanWifiResult", "NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiResult", 5 }, { "TypeCmdScanThreadStart", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStart", 6 }, { "TypeRespScanThreadStart", "NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStart", 7 }, { "TypeCmdScanThreadStatus", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStatus", 8 }, { "TypeRespScanThreadStatus", "NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStatus", 9 }, { "TypeCmdScanThreadResult", "NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadResult", 10 }, { "TypeRespScanThreadResult", "NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadResult", 11 }, }; static const ProtobufCIntRange network_scan_msg_type__value_ranges[] = { {0, 0},{0, 12} }; static const ProtobufCEnumValueIndex network_scan_msg_type__enum_values_by_name[12] = { { "TypeCmdScanThreadResult", 10 }, { "TypeCmdScanThreadStart", 6 }, { "TypeCmdScanThreadStatus", 8 }, { "TypeCmdScanWifiResult", 4 }, { "TypeCmdScanWifiStart", 0 }, { "TypeCmdScanWifiStatus", 2 }, { "TypeRespScanThreadResult", 11 }, { "TypeRespScanThreadStart", 7 }, { "TypeRespScanThreadStatus", 9 }, { "TypeRespScanWifiResult", 5 }, { "TypeRespScanWifiStart", 1 }, { "TypeRespScanWifiStatus", 3 }, }; const ProtobufCEnumDescriptor network_scan_msg_type__descriptor = { PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, "NetworkScanMsgType", "NetworkScanMsgType", "NetworkScanMsgType", "", 12, network_scan_msg_type__enum_values_by_number, 12, network_scan_msg_type__enum_values_by_name, 1, network_scan_msg_type__value_ranges, NULL,NULL,NULL,NULL /* reserved[1234] */ }; ================================================ FILE: network_provisioning/proto-c/network_scan.pb-c.h ================================================ /* Generated by the protocol buffer compiler. DO NOT EDIT! */ /* Generated from: network_scan.proto */ #ifndef PROTOBUF_C_network_5fscan_2eproto__INCLUDED #define PROTOBUF_C_network_5fscan_2eproto__INCLUDED #include PROTOBUF_C__BEGIN_DECLS #if PROTOBUF_C_VERSION_NUMBER < 1003000 # error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. #elif 1004000 < PROTOBUF_C_MIN_COMPILER_VERSION # error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. #endif #include "constants.pb-c.h" #include "network_constants.pb-c.h" typedef struct CmdScanWifiStart CmdScanWifiStart; typedef struct CmdScanThreadStart CmdScanThreadStart; typedef struct RespScanWifiStart RespScanWifiStart; typedef struct RespScanThreadStart RespScanThreadStart; typedef struct CmdScanWifiStatus CmdScanWifiStatus; typedef struct CmdScanThreadStatus CmdScanThreadStatus; typedef struct RespScanWifiStatus RespScanWifiStatus; typedef struct RespScanThreadStatus RespScanThreadStatus; typedef struct CmdScanWifiResult CmdScanWifiResult; typedef struct CmdScanThreadResult CmdScanThreadResult; typedef struct WiFiScanResult WiFiScanResult; typedef struct ThreadScanResult ThreadScanResult; typedef struct RespScanWifiResult RespScanWifiResult; typedef struct RespScanThreadResult RespScanThreadResult; typedef struct NetworkScanPayload NetworkScanPayload; /* --- enums --- */ typedef enum _NetworkScanMsgType { NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStart = 0, NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStart = 1, NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStatus = 2, NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStatus = 3, NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiResult = 4, NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiResult = 5, NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStart = 6, NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStart = 7, NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStatus = 8, NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStatus = 9, NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadResult = 10, NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadResult = 11 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_SCAN_MSG_TYPE) } NetworkScanMsgType; /* --- messages --- */ struct CmdScanWifiStart { ProtobufCMessage base; protobuf_c_boolean blocking; protobuf_c_boolean passive; uint32_t group_channels; uint32_t period_ms; }; #define CMD_SCAN_WIFI_START__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_wifi_start__descriptor) \ , 0, 0, 0, 0 } struct CmdScanThreadStart { ProtobufCMessage base; protobuf_c_boolean blocking; uint32_t channel_mask; }; #define CMD_SCAN_THREAD_START__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_thread_start__descriptor) \ , 0, 0 } struct RespScanWifiStart { ProtobufCMessage base; }; #define RESP_SCAN_WIFI_START__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_wifi_start__descriptor) \ } struct RespScanThreadStart { ProtobufCMessage base; }; #define RESP_SCAN_THREAD_START__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_thread_start__descriptor) \ } struct CmdScanWifiStatus { ProtobufCMessage base; }; #define CMD_SCAN_WIFI_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_wifi_status__descriptor) \ } struct CmdScanThreadStatus { ProtobufCMessage base; }; #define CMD_SCAN_THREAD_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_thread_status__descriptor) \ } struct RespScanWifiStatus { ProtobufCMessage base; protobuf_c_boolean scan_finished; uint32_t result_count; }; #define RESP_SCAN_WIFI_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_wifi_status__descriptor) \ , 0, 0 } struct RespScanThreadStatus { ProtobufCMessage base; protobuf_c_boolean scan_finished; uint32_t result_count; }; #define RESP_SCAN_THREAD_STATUS__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_thread_status__descriptor) \ , 0, 0 } struct CmdScanWifiResult { ProtobufCMessage base; uint32_t start_index; uint32_t count; }; #define CMD_SCAN_WIFI_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_wifi_result__descriptor) \ , 0, 0 } struct CmdScanThreadResult { ProtobufCMessage base; uint32_t start_index; uint32_t count; }; #define CMD_SCAN_THREAD_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_thread_result__descriptor) \ , 0, 0 } struct WiFiScanResult { ProtobufCMessage base; ProtobufCBinaryData ssid; uint32_t channel; int32_t rssi; ProtobufCBinaryData bssid; WifiAuthMode auth; }; #define WI_FI_SCAN_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&wi_fi_scan_result__descriptor) \ , {0,NULL}, 0, 0, {0,NULL}, WIFI_AUTH_MODE__Open } struct ThreadScanResult { ProtobufCMessage base; uint32_t pan_id; uint32_t channel; int32_t rssi; uint32_t lqi; ProtobufCBinaryData ext_addr; char *network_name; ProtobufCBinaryData ext_pan_id; }; #define THREAD_SCAN_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&thread_scan_result__descriptor) \ , 0, 0, 0, 0, {0,NULL}, (char *)protobuf_c_empty_string, {0,NULL} } struct RespScanWifiResult { ProtobufCMessage base; size_t n_entries; WiFiScanResult **entries; }; #define RESP_SCAN_WIFI_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_wifi_result__descriptor) \ , 0,NULL } struct RespScanThreadResult { ProtobufCMessage base; size_t n_entries; ThreadScanResult **entries; }; #define RESP_SCAN_THREAD_RESULT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&resp_scan_thread_result__descriptor) \ , 0,NULL } typedef enum { NETWORK_SCAN_PAYLOAD__PAYLOAD__NOT_SET = 0, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_WIFI_START = 10, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_START = 11, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_WIFI_STATUS = 12, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_STATUS = 13, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_WIFI_RESULT = 14, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_RESULT = 15, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_THREAD_START = 16, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_START = 17, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_THREAD_STATUS = 18, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_STATUS = 19, NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_THREAD_RESULT = 20, NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_RESULT = 21 PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(NETWORK_SCAN_PAYLOAD__PAYLOAD__CASE) } NetworkScanPayload__PayloadCase; struct NetworkScanPayload { ProtobufCMessage base; NetworkScanMsgType msg; Status status; NetworkScanPayload__PayloadCase payload_case; union { CmdScanWifiStart *cmd_scan_wifi_start; RespScanWifiStart *resp_scan_wifi_start; CmdScanWifiStatus *cmd_scan_wifi_status; RespScanWifiStatus *resp_scan_wifi_status; CmdScanWifiResult *cmd_scan_wifi_result; RespScanWifiResult *resp_scan_wifi_result; CmdScanThreadStart *cmd_scan_thread_start; RespScanThreadStart *resp_scan_thread_start; CmdScanThreadStatus *cmd_scan_thread_status; RespScanThreadStatus *resp_scan_thread_status; CmdScanThreadResult *cmd_scan_thread_result; RespScanThreadResult *resp_scan_thread_result; }; }; #define NETWORK_SCAN_PAYLOAD__INIT \ { PROTOBUF_C_MESSAGE_INIT (&network_scan_payload__descriptor) \ , NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStart, STATUS__Success, NETWORK_SCAN_PAYLOAD__PAYLOAD__NOT_SET, {0} } /* CmdScanWifiStart methods */ void cmd_scan_wifi_start__init (CmdScanWifiStart *message); size_t cmd_scan_wifi_start__get_packed_size (const CmdScanWifiStart *message); size_t cmd_scan_wifi_start__pack (const CmdScanWifiStart *message, uint8_t *out); size_t cmd_scan_wifi_start__pack_to_buffer (const CmdScanWifiStart *message, ProtobufCBuffer *buffer); CmdScanWifiStart * cmd_scan_wifi_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_wifi_start__free_unpacked (CmdScanWifiStart *message, ProtobufCAllocator *allocator); /* CmdScanThreadStart methods */ void cmd_scan_thread_start__init (CmdScanThreadStart *message); size_t cmd_scan_thread_start__get_packed_size (const CmdScanThreadStart *message); size_t cmd_scan_thread_start__pack (const CmdScanThreadStart *message, uint8_t *out); size_t cmd_scan_thread_start__pack_to_buffer (const CmdScanThreadStart *message, ProtobufCBuffer *buffer); CmdScanThreadStart * cmd_scan_thread_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_thread_start__free_unpacked (CmdScanThreadStart *message, ProtobufCAllocator *allocator); /* RespScanWifiStart methods */ void resp_scan_wifi_start__init (RespScanWifiStart *message); size_t resp_scan_wifi_start__get_packed_size (const RespScanWifiStart *message); size_t resp_scan_wifi_start__pack (const RespScanWifiStart *message, uint8_t *out); size_t resp_scan_wifi_start__pack_to_buffer (const RespScanWifiStart *message, ProtobufCBuffer *buffer); RespScanWifiStart * resp_scan_wifi_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_wifi_start__free_unpacked (RespScanWifiStart *message, ProtobufCAllocator *allocator); /* RespScanThreadStart methods */ void resp_scan_thread_start__init (RespScanThreadStart *message); size_t resp_scan_thread_start__get_packed_size (const RespScanThreadStart *message); size_t resp_scan_thread_start__pack (const RespScanThreadStart *message, uint8_t *out); size_t resp_scan_thread_start__pack_to_buffer (const RespScanThreadStart *message, ProtobufCBuffer *buffer); RespScanThreadStart * resp_scan_thread_start__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_thread_start__free_unpacked (RespScanThreadStart *message, ProtobufCAllocator *allocator); /* CmdScanWifiStatus methods */ void cmd_scan_wifi_status__init (CmdScanWifiStatus *message); size_t cmd_scan_wifi_status__get_packed_size (const CmdScanWifiStatus *message); size_t cmd_scan_wifi_status__pack (const CmdScanWifiStatus *message, uint8_t *out); size_t cmd_scan_wifi_status__pack_to_buffer (const CmdScanWifiStatus *message, ProtobufCBuffer *buffer); CmdScanWifiStatus * cmd_scan_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_wifi_status__free_unpacked (CmdScanWifiStatus *message, ProtobufCAllocator *allocator); /* CmdScanThreadStatus methods */ void cmd_scan_thread_status__init (CmdScanThreadStatus *message); size_t cmd_scan_thread_status__get_packed_size (const CmdScanThreadStatus *message); size_t cmd_scan_thread_status__pack (const CmdScanThreadStatus *message, uint8_t *out); size_t cmd_scan_thread_status__pack_to_buffer (const CmdScanThreadStatus *message, ProtobufCBuffer *buffer); CmdScanThreadStatus * cmd_scan_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_thread_status__free_unpacked (CmdScanThreadStatus *message, ProtobufCAllocator *allocator); /* RespScanWifiStatus methods */ void resp_scan_wifi_status__init (RespScanWifiStatus *message); size_t resp_scan_wifi_status__get_packed_size (const RespScanWifiStatus *message); size_t resp_scan_wifi_status__pack (const RespScanWifiStatus *message, uint8_t *out); size_t resp_scan_wifi_status__pack_to_buffer (const RespScanWifiStatus *message, ProtobufCBuffer *buffer); RespScanWifiStatus * resp_scan_wifi_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_wifi_status__free_unpacked (RespScanWifiStatus *message, ProtobufCAllocator *allocator); /* RespScanThreadStatus methods */ void resp_scan_thread_status__init (RespScanThreadStatus *message); size_t resp_scan_thread_status__get_packed_size (const RespScanThreadStatus *message); size_t resp_scan_thread_status__pack (const RespScanThreadStatus *message, uint8_t *out); size_t resp_scan_thread_status__pack_to_buffer (const RespScanThreadStatus *message, ProtobufCBuffer *buffer); RespScanThreadStatus * resp_scan_thread_status__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_thread_status__free_unpacked (RespScanThreadStatus *message, ProtobufCAllocator *allocator); /* CmdScanWifiResult methods */ void cmd_scan_wifi_result__init (CmdScanWifiResult *message); size_t cmd_scan_wifi_result__get_packed_size (const CmdScanWifiResult *message); size_t cmd_scan_wifi_result__pack (const CmdScanWifiResult *message, uint8_t *out); size_t cmd_scan_wifi_result__pack_to_buffer (const CmdScanWifiResult *message, ProtobufCBuffer *buffer); CmdScanWifiResult * cmd_scan_wifi_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_wifi_result__free_unpacked (CmdScanWifiResult *message, ProtobufCAllocator *allocator); /* CmdScanThreadResult methods */ void cmd_scan_thread_result__init (CmdScanThreadResult *message); size_t cmd_scan_thread_result__get_packed_size (const CmdScanThreadResult *message); size_t cmd_scan_thread_result__pack (const CmdScanThreadResult *message, uint8_t *out); size_t cmd_scan_thread_result__pack_to_buffer (const CmdScanThreadResult *message, ProtobufCBuffer *buffer); CmdScanThreadResult * cmd_scan_thread_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void cmd_scan_thread_result__free_unpacked (CmdScanThreadResult *message, ProtobufCAllocator *allocator); /* WiFiScanResult methods */ void wi_fi_scan_result__init (WiFiScanResult *message); size_t wi_fi_scan_result__get_packed_size (const WiFiScanResult *message); size_t wi_fi_scan_result__pack (const WiFiScanResult *message, uint8_t *out); size_t wi_fi_scan_result__pack_to_buffer (const WiFiScanResult *message, ProtobufCBuffer *buffer); WiFiScanResult * wi_fi_scan_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void wi_fi_scan_result__free_unpacked (WiFiScanResult *message, ProtobufCAllocator *allocator); /* ThreadScanResult methods */ void thread_scan_result__init (ThreadScanResult *message); size_t thread_scan_result__get_packed_size (const ThreadScanResult *message); size_t thread_scan_result__pack (const ThreadScanResult *message, uint8_t *out); size_t thread_scan_result__pack_to_buffer (const ThreadScanResult *message, ProtobufCBuffer *buffer); ThreadScanResult * thread_scan_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void thread_scan_result__free_unpacked (ThreadScanResult *message, ProtobufCAllocator *allocator); /* RespScanWifiResult methods */ void resp_scan_wifi_result__init (RespScanWifiResult *message); size_t resp_scan_wifi_result__get_packed_size (const RespScanWifiResult *message); size_t resp_scan_wifi_result__pack (const RespScanWifiResult *message, uint8_t *out); size_t resp_scan_wifi_result__pack_to_buffer (const RespScanWifiResult *message, ProtobufCBuffer *buffer); RespScanWifiResult * resp_scan_wifi_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_wifi_result__free_unpacked (RespScanWifiResult *message, ProtobufCAllocator *allocator); /* RespScanThreadResult methods */ void resp_scan_thread_result__init (RespScanThreadResult *message); size_t resp_scan_thread_result__get_packed_size (const RespScanThreadResult *message); size_t resp_scan_thread_result__pack (const RespScanThreadResult *message, uint8_t *out); size_t resp_scan_thread_result__pack_to_buffer (const RespScanThreadResult *message, ProtobufCBuffer *buffer); RespScanThreadResult * resp_scan_thread_result__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void resp_scan_thread_result__free_unpacked (RespScanThreadResult *message, ProtobufCAllocator *allocator); /* NetworkScanPayload methods */ void network_scan_payload__init (NetworkScanPayload *message); size_t network_scan_payload__get_packed_size (const NetworkScanPayload *message); size_t network_scan_payload__pack (const NetworkScanPayload *message, uint8_t *out); size_t network_scan_payload__pack_to_buffer (const NetworkScanPayload *message, ProtobufCBuffer *buffer); NetworkScanPayload * network_scan_payload__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); void network_scan_payload__free_unpacked (NetworkScanPayload *message, ProtobufCAllocator *allocator); /* --- per-message closures --- */ typedef void (*CmdScanWifiStart_Closure) (const CmdScanWifiStart *message, void *closure_data); typedef void (*CmdScanThreadStart_Closure) (const CmdScanThreadStart *message, void *closure_data); typedef void (*RespScanWifiStart_Closure) (const RespScanWifiStart *message, void *closure_data); typedef void (*RespScanThreadStart_Closure) (const RespScanThreadStart *message, void *closure_data); typedef void (*CmdScanWifiStatus_Closure) (const CmdScanWifiStatus *message, void *closure_data); typedef void (*CmdScanThreadStatus_Closure) (const CmdScanThreadStatus *message, void *closure_data); typedef void (*RespScanWifiStatus_Closure) (const RespScanWifiStatus *message, void *closure_data); typedef void (*RespScanThreadStatus_Closure) (const RespScanThreadStatus *message, void *closure_data); typedef void (*CmdScanWifiResult_Closure) (const CmdScanWifiResult *message, void *closure_data); typedef void (*CmdScanThreadResult_Closure) (const CmdScanThreadResult *message, void *closure_data); typedef void (*WiFiScanResult_Closure) (const WiFiScanResult *message, void *closure_data); typedef void (*ThreadScanResult_Closure) (const ThreadScanResult *message, void *closure_data); typedef void (*RespScanWifiResult_Closure) (const RespScanWifiResult *message, void *closure_data); typedef void (*RespScanThreadResult_Closure) (const RespScanThreadResult *message, void *closure_data); typedef void (*NetworkScanPayload_Closure) (const NetworkScanPayload *message, void *closure_data); /* --- services --- */ /* --- descriptors --- */ extern const ProtobufCEnumDescriptor network_scan_msg_type__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_wifi_start__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_thread_start__descriptor; extern const ProtobufCMessageDescriptor resp_scan_wifi_start__descriptor; extern const ProtobufCMessageDescriptor resp_scan_thread_start__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_wifi_status__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_thread_status__descriptor; extern const ProtobufCMessageDescriptor resp_scan_wifi_status__descriptor; extern const ProtobufCMessageDescriptor resp_scan_thread_status__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_wifi_result__descriptor; extern const ProtobufCMessageDescriptor cmd_scan_thread_result__descriptor; extern const ProtobufCMessageDescriptor wi_fi_scan_result__descriptor; extern const ProtobufCMessageDescriptor thread_scan_result__descriptor; extern const ProtobufCMessageDescriptor resp_scan_wifi_result__descriptor; extern const ProtobufCMessageDescriptor resp_scan_thread_result__descriptor; extern const ProtobufCMessageDescriptor network_scan_payload__descriptor; PROTOBUF_C__END_DECLS #endif /* PROTOBUF_C_network_5fscan_2eproto__INCLUDED */ ================================================ FILE: network_provisioning/python/network_config_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: network_config.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() import constants_pb2 as constants__pb2 import network_constants_pb2 as network__constants__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14network_config.proto\x1a\x0f\x63onstants.proto\x1a\x17network_constants.proto\"\x12\n\x10\x43mdGetWifiStatus\"\xf3\x01\n\x11RespGetWifiStatus\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12)\n\x0ewifi_sta_state\x18\x02 \x01(\x0e\x32\x11.WifiStationState\x12\x34\n\x10wifi_fail_reason\x18\n \x01(\x0e\x32\x18.WifiConnectFailedReasonH\x00\x12-\n\x0ewifi_connected\x18\x0b \x01(\x0b\x32\x13.WifiConnectedStateH\x00\x12,\n\x0e\x61ttempt_failed\x18\x0c \x01(\x0b\x32\x12.WifiAttemptFailedH\x00\x42\x07\n\x05state\"\x14\n\x12\x43mdGetThreadStatus\"\xca\x01\n\x13RespGetThreadStatus\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12)\n\x0cthread_state\x18\x02 \x01(\x0e\x32\x13.ThreadNetworkState\x12\x37\n\x12thread_fail_reason\x18\n \x01(\x0e\x32\x19.ThreadAttachFailedReasonH\x00\x12-\n\x0fthread_attached\x18\x0b \x01(\x0b\x32\x12.ThreadAttachStateH\x00\x42\x07\n\x05state\"T\n\x10\x43mdSetWifiConfig\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x12\n\npassphrase\x18\x02 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x03 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x04 \x01(\x05\"%\n\x12\x43mdSetThreadConfig\x12\x0f\n\x07\x64\x61taset\x18\x01 \x01(\x0c\",\n\x11RespSetWifiConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\".\n\x13RespSetThreadConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\x14\n\x12\x43mdApplyWifiConfig\"\x16\n\x14\x43mdApplyThreadConfig\".\n\x13RespApplyWifiConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"0\n\x15RespApplyThreadConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\xd1\x05\n\x14NetworkConfigPayload\x12\"\n\x03msg\x18\x01 \x01(\x0e\x32\x15.NetworkConfigMsgType\x12\x30\n\x13\x63md_get_wifi_status\x18\n \x01(\x0b\x32\x11.CmdGetWifiStatusH\x00\x12\x32\n\x14resp_get_wifi_status\x18\x0b \x01(\x0b\x32\x12.RespGetWifiStatusH\x00\x12\x30\n\x13\x63md_set_wifi_config\x18\x0c \x01(\x0b\x32\x11.CmdSetWifiConfigH\x00\x12\x32\n\x14resp_set_wifi_config\x18\r \x01(\x0b\x32\x12.RespSetWifiConfigH\x00\x12\x34\n\x15\x63md_apply_wifi_config\x18\x0e \x01(\x0b\x32\x13.CmdApplyWifiConfigH\x00\x12\x36\n\x16resp_apply_wifi_config\x18\x0f \x01(\x0b\x32\x14.RespApplyWifiConfigH\x00\x12\x34\n\x15\x63md_get_thread_status\x18\x10 \x01(\x0b\x32\x13.CmdGetThreadStatusH\x00\x12\x36\n\x16resp_get_thread_status\x18\x11 \x01(\x0b\x32\x14.RespGetThreadStatusH\x00\x12\x34\n\x15\x63md_set_thread_config\x18\x12 \x01(\x0b\x32\x13.CmdSetThreadConfigH\x00\x12\x36\n\x16resp_set_thread_config\x18\x13 \x01(\x0b\x32\x14.RespSetThreadConfigH\x00\x12\x38\n\x17\x63md_apply_thread_config\x18\x14 \x01(\x0b\x32\x15.CmdApplyThreadConfigH\x00\x12:\n\x18resp_apply_thread_config\x18\x15 \x01(\x0b\x32\x16.RespApplyThreadConfigH\x00\x42\t\n\x07payload*\xe8\x02\n\x14NetworkConfigMsgType\x12\x18\n\x14TypeCmdGetWifiStatus\x10\x00\x12\x19\n\x15TypeRespGetWifiStatus\x10\x01\x12\x18\n\x14TypeCmdSetWifiConfig\x10\x02\x12\x19\n\x15TypeRespSetWifiConfig\x10\x03\x12\x1a\n\x16TypeCmdApplyWifiConfig\x10\x04\x12\x1b\n\x17TypeRespApplyWifiConfig\x10\x05\x12\x1a\n\x16TypeCmdGetThreadStatus\x10\x06\x12\x1b\n\x17TypeRespGetThreadStatus\x10\x07\x12\x1a\n\x16TypeCmdSetThreadConfig\x10\x08\x12\x1b\n\x17TypeRespSetThreadConfig\x10\t\x12\x1c\n\x18TypeCmdApplyThreadConfig\x10\n\x12\x1d\n\x19TypeRespApplyThreadConfig\x10\x0b\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'network_config_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _NETWORKCONFIGMSGTYPE._serialized_start=1647 _NETWORKCONFIGMSGTYPE._serialized_end=2007 _CMDGETWIFISTATUS._serialized_start=66 _CMDGETWIFISTATUS._serialized_end=84 _RESPGETWIFISTATUS._serialized_start=87 _RESPGETWIFISTATUS._serialized_end=330 _CMDGETTHREADSTATUS._serialized_start=332 _CMDGETTHREADSTATUS._serialized_end=352 _RESPGETTHREADSTATUS._serialized_start=355 _RESPGETTHREADSTATUS._serialized_end=557 _CMDSETWIFICONFIG._serialized_start=559 _CMDSETWIFICONFIG._serialized_end=643 _CMDSETTHREADCONFIG._serialized_start=645 _CMDSETTHREADCONFIG._serialized_end=682 _RESPSETWIFICONFIG._serialized_start=684 _RESPSETWIFICONFIG._serialized_end=728 _RESPSETTHREADCONFIG._serialized_start=730 _RESPSETTHREADCONFIG._serialized_end=776 _CMDAPPLYWIFICONFIG._serialized_start=778 _CMDAPPLYWIFICONFIG._serialized_end=798 _CMDAPPLYTHREADCONFIG._serialized_start=800 _CMDAPPLYTHREADCONFIG._serialized_end=822 _RESPAPPLYWIFICONFIG._serialized_start=824 _RESPAPPLYWIFICONFIG._serialized_end=870 _RESPAPPLYTHREADCONFIG._serialized_start=872 _RESPAPPLYTHREADCONFIG._serialized_end=920 _NETWORKCONFIGPAYLOAD._serialized_start=923 _NETWORKCONFIGPAYLOAD._serialized_end=1644 # @@protoc_insertion_point(module_scope) ================================================ FILE: network_provisioning/python/network_constants_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: network_constants.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17network_constants.proto\"v\n\x12WifiConnectedState\x12\x10\n\x08ip4_addr\x18\x01 \x01(\t\x12 \n\tauth_mode\x18\x02 \x01(\x0e\x32\r.WifiAuthMode\x12\x0c\n\x04ssid\x18\x03 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x05 \x01(\x05\"/\n\x11WifiAttemptFailed\x12\x1a\n\x12\x61ttempts_remaining\x18\x01 \x01(\r\"V\n\x11ThreadAttachState\x12\x0e\n\x06pan_id\x18\x01 \x01(\r\x12\x12\n\next_pan_id\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12\x0c\n\x04name\x18\x04 \x01(\t*Y\n\x10WifiStationState\x12\r\n\tConnected\x10\x00\x12\x0e\n\nConnecting\x10\x01\x12\x10\n\x0c\x44isconnected\x10\x02\x12\x14\n\x10\x43onnectionFailed\x10\x03*A\n\x17WifiConnectFailedReason\x12\r\n\tAuthError\x10\x00\x12\x17\n\x13WifiNetworkNotFound\x10\x01*\x84\x01\n\x0cWifiAuthMode\x12\x08\n\x04Open\x10\x00\x12\x07\n\x03WEP\x10\x01\x12\x0b\n\x07WPA_PSK\x10\x02\x12\x0c\n\x08WPA2_PSK\x10\x03\x12\x10\n\x0cWPA_WPA2_PSK\x10\x04\x12\x13\n\x0fWPA2_ENTERPRISE\x10\x05\x12\x0c\n\x08WPA3_PSK\x10\x06\x12\x11\n\rWPA2_WPA3_PSK\x10\x07*U\n\x12ThreadNetworkState\x12\x0c\n\x08\x41ttached\x10\x00\x12\r\n\tAttaching\x10\x01\x12\r\n\tDettached\x10\x02\x12\x13\n\x0f\x41ttachingFailed\x10\x03*I\n\x18ThreadAttachFailedReason\x12\x12\n\x0e\x44\x61tasetInvalid\x10\x00\x12\x19\n\x15ThreadNetworkNotFound\x10\x01\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'network_constants_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _WIFISTATIONSTATE._serialized_start=284 _WIFISTATIONSTATE._serialized_end=373 _WIFICONNECTFAILEDREASON._serialized_start=375 _WIFICONNECTFAILEDREASON._serialized_end=440 _WIFIAUTHMODE._serialized_start=443 _WIFIAUTHMODE._serialized_end=575 _THREADNETWORKSTATE._serialized_start=577 _THREADNETWORKSTATE._serialized_end=662 _THREADATTACHFAILEDREASON._serialized_start=664 _THREADATTACHFAILEDREASON._serialized_end=737 _WIFICONNECTEDSTATE._serialized_start=27 _WIFICONNECTEDSTATE._serialized_end=145 _WIFIATTEMPTFAILED._serialized_start=147 _WIFIATTEMPTFAILED._serialized_end=194 _THREADATTACHSTATE._serialized_start=196 _THREADATTACHSTATE._serialized_end=282 # @@protoc_insertion_point(module_scope) ================================================ FILE: network_provisioning/python/network_ctrl_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: network_ctrl.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() import constants_pb2 as constants__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12network_ctrl.proto\x1a\x0f\x63onstants.proto\"\x12\n\x10\x43mdCtrlWifiReset\"\x13\n\x11RespCtrlWifiReset\"\x13\n\x11\x43mdCtrlWifiReprov\"\x14\n\x12RespCtrlWifiReprov\"\x14\n\x12\x43mdCtrlThreadReset\"\x15\n\x13RespCtrlThreadReset\"\x15\n\x13\x43mdCtrlThreadReprov\"\x16\n\x14RespCtrlThreadReprov\"\x8a\x04\n\x12NetworkCtrlPayload\x12 \n\x03msg\x18\x01 \x01(\x0e\x32\x13.NetworkCtrlMsgType\x12\x17\n\x06status\x18\x02 \x01(\x0e\x32\x07.Status\x12\x30\n\x13\x63md_ctrl_wifi_reset\x18\x0b \x01(\x0b\x32\x11.CmdCtrlWifiResetH\x00\x12\x32\n\x14resp_ctrl_wifi_reset\x18\x0c \x01(\x0b\x32\x12.RespCtrlWifiResetH\x00\x12\x32\n\x14\x63md_ctrl_wifi_reprov\x18\r \x01(\x0b\x32\x12.CmdCtrlWifiReprovH\x00\x12\x34\n\x15resp_ctrl_wifi_reprov\x18\x0e \x01(\x0b\x32\x13.RespCtrlWifiReprovH\x00\x12\x34\n\x15\x63md_ctrl_thread_reset\x18\x0f \x01(\x0b\x32\x13.CmdCtrlThreadResetH\x00\x12\x36\n\x16resp_ctrl_thread_reset\x18\x10 \x01(\x0b\x32\x14.RespCtrlThreadResetH\x00\x12\x36\n\x16\x63md_ctrl_thread_reprov\x18\x11 \x01(\x0b\x32\x14.CmdCtrlThreadReprovH\x00\x12\x38\n\x17resp_ctrl_thread_reprov\x18\x12 \x01(\x0b\x32\x15.RespCtrlThreadReprovH\x00\x42\t\n\x07payload*\x8a\x02\n\x12NetworkCtrlMsgType\x12\x14\n\x10TypeCtrlReserved\x10\x00\x12\x18\n\x14TypeCmdCtrlWifiReset\x10\x01\x12\x19\n\x15TypeRespCtrlWifiReset\x10\x02\x12\x19\n\x15TypeCmdCtrlWifiReprov\x10\x03\x12\x1a\n\x16TypeRespCtrlWifiReprov\x10\x04\x12\x1a\n\x16TypeCmdCtrlThreadReset\x10\x05\x12\x1b\n\x17TypeRespCtrlThreadReset\x10\x06\x12\x1b\n\x17TypeCmdCtrlThreadReprov\x10\x07\x12\x1c\n\x18TypeRespCtrlThreadReprov\x10\x08\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'network_ctrl_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _NETWORKCTRLMSGTYPE._serialized_start=741 _NETWORKCTRLMSGTYPE._serialized_end=1007 _CMDCTRLWIFIRESET._serialized_start=39 _CMDCTRLWIFIRESET._serialized_end=57 _RESPCTRLWIFIRESET._serialized_start=59 _RESPCTRLWIFIRESET._serialized_end=78 _CMDCTRLWIFIREPROV._serialized_start=80 _CMDCTRLWIFIREPROV._serialized_end=99 _RESPCTRLWIFIREPROV._serialized_start=101 _RESPCTRLWIFIREPROV._serialized_end=121 _CMDCTRLTHREADRESET._serialized_start=123 _CMDCTRLTHREADRESET._serialized_end=143 _RESPCTRLTHREADRESET._serialized_start=145 _RESPCTRLTHREADRESET._serialized_end=166 _CMDCTRLTHREADREPROV._serialized_start=168 _CMDCTRLTHREADREPROV._serialized_end=189 _RESPCTRLTHREADREPROV._serialized_start=191 _RESPCTRLTHREADREPROV._serialized_end=213 _NETWORKCTRLPAYLOAD._serialized_start=216 _NETWORKCTRLPAYLOAD._serialized_end=738 # @@protoc_insertion_point(module_scope) ================================================ FILE: network_provisioning/python/network_scan_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: network_scan.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() import constants_pb2 as constants__pb2 import network_constants_pb2 as network__constants__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12network_scan.proto\x1a\x0f\x63onstants.proto\x1a\x17network_constants.proto\"`\n\x10\x43mdScanWifiStart\x12\x10\n\x08\x62locking\x18\x01 \x01(\x08\x12\x0f\n\x07passive\x18\x02 \x01(\x08\x12\x16\n\x0egroup_channels\x18\x03 \x01(\r\x12\x11\n\tperiod_ms\x18\x04 \x01(\r\"<\n\x12\x43mdScanThreadStart\x12\x10\n\x08\x62locking\x18\x01 \x01(\x08\x12\x14\n\x0c\x63hannel_mask\x18\x02 \x01(\r\"\x13\n\x11RespScanWifiStart\"\x15\n\x13RespScanThreadStart\"\x13\n\x11\x43mdScanWifiStatus\"\x15\n\x13\x43mdScanThreadStatus\"A\n\x12RespScanWifiStatus\x12\x15\n\rscan_finished\x18\x01 \x01(\x08\x12\x14\n\x0cresult_count\x18\x02 \x01(\r\"C\n\x14RespScanThreadStatus\x12\x15\n\rscan_finished\x18\x01 \x01(\x08\x12\x14\n\x0cresult_count\x18\x02 \x01(\r\"7\n\x11\x43mdScanWifiResult\x12\x13\n\x0bstart_index\x18\x01 \x01(\r\x12\r\n\x05\x63ount\x18\x02 \x01(\r\"9\n\x13\x43mdScanThreadResult\x12\x13\n\x0bstart_index\x18\x01 \x01(\r\x12\r\n\x05\x63ount\x18\x02 \x01(\r\"i\n\x0eWiFiScanResult\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\r\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x1b\n\x04\x61uth\x18\x05 \x01(\x0e\x32\r.WifiAuthMode\"\x8a\x01\n\x10ThreadScanResult\x12\x0e\n\x06pan_id\x18\x01 \x01(\r\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\r\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\x12\x0b\n\x03lqi\x18\x04 \x01(\r\x12\x10\n\x08\x65xt_addr\x18\x05 \x01(\x0c\x12\x14\n\x0cnetwork_name\x18\x06 \x01(\t\x12\x12\n\next_pan_id\x18\x07 \x01(\x0c\"6\n\x12RespScanWifiResult\x12 \n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x0f.WiFiScanResult\":\n\x14RespScanThreadResult\x12\"\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x11.ThreadScanResult\"\xe6\x05\n\x12NetworkScanPayload\x12 \n\x03msg\x18\x01 \x01(\x0e\x32\x13.NetworkScanMsgType\x12\x17\n\x06status\x18\x02 \x01(\x0e\x32\x07.Status\x12\x30\n\x13\x63md_scan_wifi_start\x18\n \x01(\x0b\x32\x11.CmdScanWifiStartH\x00\x12\x32\n\x14resp_scan_wifi_start\x18\x0b \x01(\x0b\x32\x12.RespScanWifiStartH\x00\x12\x32\n\x14\x63md_scan_wifi_status\x18\x0c \x01(\x0b\x32\x12.CmdScanWifiStatusH\x00\x12\x34\n\x15resp_scan_wifi_status\x18\r \x01(\x0b\x32\x13.RespScanWifiStatusH\x00\x12\x32\n\x14\x63md_scan_wifi_result\x18\x0e \x01(\x0b\x32\x12.CmdScanWifiResultH\x00\x12\x34\n\x15resp_scan_wifi_result\x18\x0f \x01(\x0b\x32\x13.RespScanWifiResultH\x00\x12\x34\n\x15\x63md_scan_thread_start\x18\x10 \x01(\x0b\x32\x13.CmdScanThreadStartH\x00\x12\x36\n\x16resp_scan_thread_start\x18\x11 \x01(\x0b\x32\x14.RespScanThreadStartH\x00\x12\x36\n\x16\x63md_scan_thread_status\x18\x12 \x01(\x0b\x32\x14.CmdScanThreadStatusH\x00\x12\x38\n\x17resp_scan_thread_status\x18\x13 \x01(\x0b\x32\x15.RespScanThreadStatusH\x00\x12\x36\n\x16\x63md_scan_thread_result\x18\x14 \x01(\x0b\x32\x14.CmdScanThreadResultH\x00\x12\x38\n\x17resp_scan_thread_result\x18\x15 \x01(\x0b\x32\x15.RespScanThreadResultH\x00\x42\t\n\x07payload*\xe6\x02\n\x12NetworkScanMsgType\x12\x18\n\x14TypeCmdScanWifiStart\x10\x00\x12\x19\n\x15TypeRespScanWifiStart\x10\x01\x12\x19\n\x15TypeCmdScanWifiStatus\x10\x02\x12\x1a\n\x16TypeRespScanWifiStatus\x10\x03\x12\x19\n\x15TypeCmdScanWifiResult\x10\x04\x12\x1a\n\x16TypeRespScanWifiResult\x10\x05\x12\x1a\n\x16TypeCmdScanThreadStart\x10\x06\x12\x1b\n\x17TypeRespScanThreadStart\x10\x07\x12\x1b\n\x17TypeCmdScanThreadStatus\x10\x08\x12\x1c\n\x18TypeRespScanThreadStatus\x10\t\x12\x1b\n\x17TypeCmdScanThreadResult\x10\n\x12\x1c\n\x18TypeRespScanThreadResult\x10\x0b\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'network_scan_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _NETWORKSCANMSGTYPE._serialized_start=1674 _NETWORKSCANMSGTYPE._serialized_end=2032 _CMDSCANWIFISTART._serialized_start=64 _CMDSCANWIFISTART._serialized_end=160 _CMDSCANTHREADSTART._serialized_start=162 _CMDSCANTHREADSTART._serialized_end=222 _RESPSCANWIFISTART._serialized_start=224 _RESPSCANWIFISTART._serialized_end=243 _RESPSCANTHREADSTART._serialized_start=245 _RESPSCANTHREADSTART._serialized_end=266 _CMDSCANWIFISTATUS._serialized_start=268 _CMDSCANWIFISTATUS._serialized_end=287 _CMDSCANTHREADSTATUS._serialized_start=289 _CMDSCANTHREADSTATUS._serialized_end=310 _RESPSCANWIFISTATUS._serialized_start=312 _RESPSCANWIFISTATUS._serialized_end=377 _RESPSCANTHREADSTATUS._serialized_start=379 _RESPSCANTHREADSTATUS._serialized_end=446 _CMDSCANWIFIRESULT._serialized_start=448 _CMDSCANWIFIRESULT._serialized_end=503 _CMDSCANTHREADRESULT._serialized_start=505 _CMDSCANTHREADRESULT._serialized_end=562 _WIFISCANRESULT._serialized_start=564 _WIFISCANRESULT._serialized_end=669 _THREADSCANRESULT._serialized_start=672 _THREADSCANRESULT._serialized_end=810 _RESPSCANWIFIRESULT._serialized_start=812 _RESPSCANWIFIRESULT._serialized_end=866 _RESPSCANTHREADRESULT._serialized_start=868 _RESPSCANTHREADRESULT._serialized_end=926 _NETWORKSCANPAYLOAD._serialized_start=929 _NETWORKSCANPAYLOAD._serialized_end=1671 # @@protoc_insertion_point(module_scope) ================================================ FILE: network_provisioning/src/handlers.c ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #include #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #include #endif #include #include "network_provisioning/network_config.h" #include "network_provisioning/network_scan.h" #include "network_ctrl.h" #include "network_provisioning/manager.h" #include "network_provisioning_priv.h" #if defined(CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI) || defined(CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD) static const char *TAG = "network_prov_handlers"; /* Provide definition of network_prov_ctx_t */ struct network_prov_ctx { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI wifi_config_t wifi_cfg; #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD otOperationalDatasetTlvs thread_dataset; #endif }; static void free_network_prov_ctx(network_prov_ctx_t **ctx) { free(*ctx); *ctx = NULL; } #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI static wifi_config_t *get_wifi_config(network_prov_ctx_t **ctx) { return (*ctx ? & (*ctx)->wifi_cfg : NULL); } static wifi_config_t *new_wifi_config(network_prov_ctx_t **ctx) { free(*ctx); (*ctx) = (network_prov_ctx_t *) calloc(1, sizeof(network_prov_ctx_t)); return get_wifi_config(ctx); } static esp_err_t wifi_get_status_handler(network_prov_config_get_wifi_data_t *resp_data, network_prov_ctx_t **ctx) { /* Initialize to zero */ memset(resp_data, 0, sizeof(network_prov_config_get_wifi_data_t)); if (network_prov_mgr_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { ESP_LOGW(TAG, "Network provisioning manager for Wi-Fi not running"); return ESP_ERR_INVALID_STATE; } if (resp_data->wifi_state == NETWORK_PROV_WIFI_STA_CONNECTED) { ESP_LOGD(TAG, "Got state : connected"); /* IP Addr assigned to STA */ esp_netif_ip_info_t ip_info; esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); /* AP information to which STA is connected */ wifi_ap_record_t ap_info; esp_wifi_sta_get_ap_info(&ap_info); memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); resp_data->conn_info.channel = ap_info.primary; resp_data->conn_info.auth_mode = ap_info.authmode; /* Tell manager to stop provisioning service */ network_prov_mgr_done(); } else if (resp_data->wifi_state == NETWORK_PROV_WIFI_STA_DISCONNECTED) { ESP_LOGD(TAG, "Got state : disconnected"); /* If disconnected, convey reason */ network_prov_mgr_get_wifi_disconnect_reason(&resp_data->fail_reason); } else { if (network_prov_mgr_get_wifi_remaining_conn_attempts(&resp_data->connecting_info.attempts_remaining) != ESP_OK) { ESP_LOGW(TAG, "network provisioning manager not running"); return ESP_ERR_INVALID_STATE; } ESP_LOGD(TAG, "Got state : connecting"); } return ESP_OK; } static esp_err_t wifi_set_config_handler(const network_prov_config_set_wifi_data_t *req_data, network_prov_ctx_t **ctx) { wifi_config_t *wifi_cfg = get_wifi_config(ctx); if (wifi_cfg) { free_network_prov_ctx(ctx); } wifi_cfg = new_wifi_config(ctx); if (!wifi_cfg) { ESP_LOGE(TAG, "Unable to allocate Wi-Fi config"); return ESP_ERR_NO_MEM; } ESP_LOGD(TAG, "Wi-Fi Credentials Received"); /* Using memcpy allows the max SSID length to be 32 bytes (as per 802.11 standard). * But this doesn't guarantee that the saved SSID will be null terminated, because * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character). * Although, this is not a matter for concern because esp_wifi library reads the SSID * upto 32 bytes in absence of null termination */ const size_t ssid_len = strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)); /* Ensure SSID less than 32 bytes is null terminated */ memset(wifi_cfg->sta.ssid, 0, sizeof(wifi_cfg->sta.ssid)); memcpy(wifi_cfg->sta.ssid, req_data->ssid, ssid_len); /* Using strlcpy allows both max passphrase length (63 bytes) and ensures null termination * because size of wifi_cfg->sta.password is 64 bytes (1 extra byte for null character) */ strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); #ifdef CONFIG_NETWORK_PROV_WIFI_STA_ALL_CHANNEL_SCAN wifi_cfg->sta.scan_method = WIFI_ALL_CHANNEL_SCAN; #else /* CONFIG_NETWORK_PROV_WIFI_STA_FAST_SCAN */ wifi_cfg->sta.scan_method = WIFI_FAST_SCAN; #endif return ESP_OK; } static esp_err_t wifi_apply_config_handler(network_prov_ctx_t **ctx) { wifi_config_t *wifi_cfg = get_wifi_config(ctx); if (!wifi_cfg) { ESP_LOGE(TAG, "Wi-Fi config not set"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = network_prov_mgr_configure_wifi_sta(wifi_cfg); if (ret == ESP_OK) { ESP_LOGD(TAG, "Wi-Fi Credentials Applied"); } else { ESP_LOGE(TAG, "Failed to apply Wi-Fi Credentials"); } free_network_prov_ctx(ctx); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD static otOperationalDatasetTlvs *get_thread_dataset(network_prov_ctx_t **ctx) { return (*ctx ? & (*ctx)->thread_dataset : NULL); } static otOperationalDatasetTlvs *new_thread_dataset(network_prov_ctx_t **ctx) { free(*ctx); (*ctx) = (network_prov_ctx_t *) calloc(1, sizeof(network_prov_ctx_t)); return get_thread_dataset(ctx); } static esp_err_t thread_get_status_handler(network_prov_config_get_thread_data_t *resp_data, network_prov_ctx_t **ctx) { /* Initialize to zero */ memset(resp_data, 0, sizeof(network_prov_config_get_thread_data_t)); if (network_prov_mgr_get_thread_state(&resp_data->thread_state) != ESP_OK) { ESP_LOGW(TAG, "Network provisioning manager not running"); return ESP_ERR_INVALID_STATE; } if (resp_data->thread_state == NETWORK_PROV_THREAD_ATTACHED) { ESP_LOGD(TAG, "Got state : attached"); otOperationalDataset dataset; if (otDatasetGetActive(esp_openthread_get_instance(), &dataset) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to get Thread dataset"); return ESP_FAIL; } resp_data->conn_info.channel = dataset.mChannel; memcpy(resp_data->conn_info.ext_pan_id, dataset.mExtendedPanId.m8, sizeof(dataset.mExtendedPanId.m8)); strncpy(resp_data->conn_info.name, dataset.mNetworkName.m8, strnlen(dataset.mNetworkName.m8, sizeof(dataset.mNetworkName.m8))); resp_data->conn_info.pan_id = dataset.mPanId; /* Tell manager to stop provisioning service */ network_prov_mgr_done(); } else if (resp_data->thread_state == NETWORK_PROV_THREAD_DETACHED) { ESP_LOGD(TAG, "Got state : disconnected"); /* If disconnected, convey reason */ network_prov_mgr_get_thread_detached_reason(&resp_data->fail_reason); } else { ESP_LOGD(TAG, "Got state : attaching"); } return ESP_OK; } static esp_err_t thread_set_config_handler(const network_prov_config_set_thread_data_t *req_data, network_prov_ctx_t **ctx) { otOperationalDatasetTlvs *thread_dataset = get_thread_dataset(ctx); if (thread_dataset) { free_network_prov_ctx(ctx); } thread_dataset = new_thread_dataset(ctx); if (!thread_dataset) { ESP_LOGE(TAG, "Unable to allocate Thread dataset"); return ESP_ERR_NO_MEM; } ESP_LOGD(TAG, "Thread Dataset Received"); thread_dataset->mLength = req_data->length; memcpy(thread_dataset->mTlvs, req_data->dataset, thread_dataset->mLength); return ESP_OK; } static esp_err_t thread_apply_config_handler(network_prov_ctx_t **ctx) { otOperationalDatasetTlvs *thread_dataset = get_thread_dataset(ctx); if (!thread_dataset) { ESP_LOGE(TAG, "Thread Dataset not set"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = network_prov_mgr_configure_thread_dataset(thread_dataset); if (ret == ESP_OK) { ESP_LOGD(TAG, "Thread Dataset Applied"); } else { ESP_LOGE(TAG, "Failed to apply Thread Dataset"); } free_network_prov_ctx(ctx); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t get_network_prov_handlers(network_prov_config_handlers_t *ptr) { if (!ptr) { return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI ptr->wifi_get_status_handler = wifi_get_status_handler; ptr->wifi_set_config_handler = wifi_set_config_handler; ptr->wifi_apply_config_handler = wifi_apply_config_handler; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ptr->thread_get_status_handler = thread_get_status_handler; ptr->thread_set_config_handler = thread_set_config_handler; ptr->thread_apply_config_handler = thread_apply_config_handler; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ptr->ctx = NULL; return ESP_OK; } /*************************************************************************/ #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI static esp_err_t wifi_scan_start(bool blocking, bool passive, uint8_t group_channels, uint32_t period_ms, network_prov_scan_ctx_t **ctx) { return network_prov_mgr_wifi_scan_start(blocking, passive, group_channels, period_ms); } static esp_err_t wifi_scan_status(bool *scan_finished, uint16_t *result_count, network_prov_scan_ctx_t **ctx) { *scan_finished = network_prov_mgr_wifi_scan_finished(); *result_count = network_prov_mgr_wifi_scan_result_count(); return ESP_OK; } static esp_err_t wifi_scan_result(uint16_t result_index, network_prov_scan_wifi_result_t *result, network_prov_scan_ctx_t **ctx) { const wifi_ap_record_t *record = network_prov_mgr_wifi_scan_result(result_index); if (!record) { return ESP_FAIL; } /* Compile time check ensures memory safety in case SSID length in * record / result structure definition changes in future */ _Static_assert(sizeof(result->ssid) == sizeof(record->ssid), "source and destination should be of same size"); memcpy(result->ssid, record->ssid, sizeof(record->ssid)); memcpy(result->bssid, record->bssid, sizeof(record->bssid)); result->channel = record->primary; result->rssi = record->rssi; result->auth = record->authmode; return ESP_OK; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD static esp_err_t thread_scan_start(bool blocking, uint32_t channel_mask, network_prov_scan_ctx_t **ctx) { return network_prov_mgr_thread_scan_start(blocking, channel_mask); } static esp_err_t thread_scan_status(bool *scan_finished, uint16_t *result_count, network_prov_scan_ctx_t **ctx) { *scan_finished = network_prov_mgr_thread_scan_finished(); *result_count = network_prov_mgr_thread_scan_result_count(); return ESP_OK; } static esp_err_t thread_scan_result(uint16_t result_index, network_prov_scan_thread_result_t *result, network_prov_scan_ctx_t **ctx) { const otActiveScanResult *record = network_prov_mgr_thread_scan_result(result_index); if (!record) { return ESP_FAIL; } result->channel = record->mChannel; result->pan_id = record->mPanId; result->rssi = record->mRssi; result->lqi = record->mLqi; memcpy(result->ext_addr, record->mExtAddress.m8, sizeof(record->mExtAddress.m8)); if (record->mDiscover) { memcpy(result->ext_pan_id, record->mExtendedPanId.m8, sizeof(result->ext_pan_id)); memcpy(result->network_name, record->mNetworkName.m8, sizeof(record->mNetworkName.m8)); } else { memset(result->ext_pan_id, 0, sizeof(result->ext_pan_id)); memset(result->network_name, 0, sizeof(result->network_name)); } return ESP_OK; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t get_network_scan_handlers(network_prov_scan_handlers_t *ptr) { if (!ptr) { return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI ptr->wifi_scan_start = wifi_scan_start; ptr->wifi_scan_status = wifi_scan_status; ptr->wifi_scan_result = wifi_scan_result; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ptr->thread_scan_start = thread_scan_start; ptr->thread_scan_status = thread_scan_status; ptr->thread_scan_result = thread_scan_result; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ptr->ctx = NULL; return ESP_OK; } /*************************************************************************/ #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI static esp_err_t wifi_ctrl_reset(void) { return network_prov_mgr_reset_wifi_sm_state_on_failure(); } static esp_err_t wifi_ctrl_reprov(void) { return network_prov_mgr_reset_wifi_sm_state_for_reprovision(); } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD static esp_err_t thread_ctrl_reset(void) { return network_prov_mgr_reset_thread_sm_state_on_failure(); } static esp_err_t thread_ctrl_reprov(void) { return network_prov_mgr_reset_thread_sm_state_for_reprovision(); } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t get_network_ctrl_handlers(network_ctrl_handlers_t *ptr) { if (!ptr) { return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI ptr->wifi_ctrl_reset = wifi_ctrl_reset; ptr->wifi_ctrl_reprov = wifi_ctrl_reprov; #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ptr->thread_ctrl_reset = thread_ctrl_reset; ptr->thread_ctrl_reprov = thread_ctrl_reprov; #endif return ESP_OK; } ================================================ FILE: network_provisioning/src/manager.c ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "network_provisioning_priv.h" #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #include #include #include #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #define NETWORK_PROV_MGR_VERSION "netprov-v1.2" #define WIFI_PROV_STORAGE_BIT BIT0 #define WIFI_PROV_SETTING_BIT BIT1 #define MAX_SCAN_RESULTS CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES #define ACQUIRE_LOCK(mux) assert(xSemaphoreTake(mux, portMAX_DELAY) == pdTRUE) #define RELEASE_LOCK(mux) assert(xSemaphoreGive(mux) == pdTRUE) static const char *TAG = "network_prov_mgr"; ESP_EVENT_DEFINE_BASE(NETWORK_PROV_EVENT); ESP_EVENT_DEFINE_BASE(NETWORK_PROV_MGR_PVT_EVENT); typedef enum { NETWORK_PROV_STATE_IDLE, NETWORK_PROV_STATE_STARTING, NETWORK_PROV_STATE_STARTED, #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI NETWORK_PROV_STATE_CRED_RECV, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD NETWORK_PROV_STATE_DATASET_RECV, #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD NETWORK_PROV_STATE_FAIL, NETWORK_PROV_STATE_SUCCESS, NETWORK_PROV_STATE_STOPPING } network_prov_mgr_state_t; typedef enum { NETWORK_PROV_MGR_STOP, } network_prov_mgr_pvt_event_t; /** * @brief Structure for storing capabilities supported by * the provisioning service */ struct network_prov_capabilities { /* Security 0 is used */ bool no_sec; /* Proof of Possession is not required for establishing session */ bool no_pop; /* Provisioning doesn't stop on it's own after receiving Wi-Fi credentials or * Thread dataset instead application has to explicitly call * network_prov_mgr_stop_provisioning() */ bool no_auto_stop; }; /** * @brief Structure for storing miscellaneous information about * provisioning service that will be conveyed to clients */ struct network_prov_info { const char *version; struct network_prov_capabilities capabilities; }; /** * @brief Context data for provisioning manager */ struct network_prov_mgr_ctx { /* Provisioning manager configuration */ network_prov_mgr_config_t mgr_config; /* State of the provisioning service */ network_prov_mgr_state_t prov_state; /* Provisioning scheme configuration */ void *prov_scheme_config; /* Protocomm handle */ protocomm_t *pc; /* Type of security to use with protocomm */ int security; /* Pointer to security params */ const void *protocomm_sec_params; /* Handle for Provisioning Auto Stop timer */ esp_timer_handle_t autostop_timer; /* Handle for delayed cleanup timer */ esp_timer_handle_t cleanup_delay_timer; /* Protocomm handlers for network configuration endpoint */ network_prov_config_handlers_t *network_prov_handlers; /* Protocomm handlers for network scan endpoint */ network_prov_scan_handlers_t *network_scan_handlers; /* Protocomm handlers for network ctrl endpoint */ network_ctrl_handlers_t *network_ctrl_handlers; /* Count of used endpoint UUIDs */ unsigned int endpoint_uuid_used; /* Provisioning service information */ struct network_prov_info mgr_info; /* Application related information in JSON format */ cJSON *app_info_json; /* Delay after which resources will be cleaned up asynchronously * upon execution of network_prov_mgr_stop_provisioning() */ uint32_t cleanup_delay; /* Scan status */ bool scanning; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /* Handle for delayed wifi connection timer */ esp_timer_handle_t wifi_connect_timer; /* State of Wi-Fi Station */ network_prov_wifi_sta_state_t wifi_state; /* Code for Wi-Fi station disconnection (if disconnected) */ network_prov_wifi_sta_fail_reason_t wifi_disconnect_reason; /* Wi-Fi scan parameters and state variables */ uint8_t channels_per_group; uint16_t curr_channel; uint16_t ap_list_len[14]; // 14 entries corresponding to each channel wifi_ap_record_t *ap_list[14]; wifi_ap_record_t *ap_list_sorted[MAX_SCAN_RESULTS]; wifi_scan_config_t scan_cfg; /* Total number of attempts done for connecting to Wi-Fi */ uint32_t connection_attempts_completed; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /* Handle for Thread attaching timeout timer */ esp_timer_handle_t thread_timeout_timer; /* State of Thread */ network_prov_thread_state_t thread_state; /* Code for Thread detached (if detached) */ network_prov_thread_fail_reason_t thread_detached_reason; /* Thread scan parameters and state variables */ uint16_t scan_result_count; otActiveScanResult *scan_result[MAX_SCAN_RESULTS]; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD }; /* Mutex to lock/unlock access to provisioning singleton * context data. This is allocated only once on first init * and never deleted as network_prov_mgr is a singleton */ static SemaphoreHandle_t prov_ctx_lock = NULL; /* Pointer to provisioning context data */ static struct network_prov_mgr_ctx *prov_ctx; /* This executes registered app_event_callback for a particular event * * NOTE : By the time this function returns, it is possible that * the manager got de-initialized due to a call to network_prov_mgr_deinit() * either inside the event callbacks or from another thread. Therefore * post execution of execute_event_cb(), the validity of prov_ctx must * always be checked. A cleaner way, to avoid this pitfall safely, would * be to limit the usage of this function to only public APIs, and that * too at the very end, just before returning. * * NOTE: This function should be called only after ensuring that the * context is valid and the control mutex is locked. */ static void execute_event_cb(network_prov_cb_event_t event_id, void *event_data, size_t event_data_size) { ESP_LOGD(TAG, "execute_event_cb : %d", event_id); if (prov_ctx) { network_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; network_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; /* Release the mutex before executing the callbacks. This is done so that * network_prov_mgr_event_handler() doesn't stay blocked for the duration */ RELEASE_LOCK(prov_ctx_lock); if (scheme_cb) { /* Call scheme specific event handler */ scheme_cb(scheme_data, event_id, event_data); } if (app_cb) { /* Call application specific event handler */ app_cb(app_data, event_id, event_data); } if (esp_event_post(NETWORK_PROV_EVENT, event_id, event_data, event_data_size, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event %d to default event loop", event_id); } ACQUIRE_LOCK(prov_ctx_lock); } } esp_err_t network_prov_mgr_set_app_info(const char *label, const char *version, const char **capabilities, size_t total_capabilities) { if (!label || !version || !capabilities) { return ESP_ERR_INVALID_ARG; } if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = ESP_FAIL; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state == NETWORK_PROV_STATE_IDLE) { if (!prov_ctx->app_info_json) { prov_ctx->app_info_json = cJSON_CreateObject(); } cJSON *new_entry_json = cJSON_CreateObject(); cJSON *capabilities_json = cJSON_CreateArray(); cJSON_AddItemToObject(prov_ctx->app_info_json, label, new_entry_json); /* Version ("ver") */ cJSON_AddStringToObject(new_entry_json, "ver", version); /* List of capabilities ("cap") */ cJSON_AddItemToObject(new_entry_json, "cap", capabilities_json); for (unsigned int i = 0; i < total_capabilities; i++) { if (capabilities[i]) { cJSON_AddItemToArray(capabilities_json, cJSON_CreateString(capabilities[i])); } } ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } RELEASE_LOCK(prov_ctx_lock); return ret; } static cJSON *network_prov_get_info_json(void) { cJSON *full_info_json = prov_ctx->app_info_json ? cJSON_Duplicate(prov_ctx->app_info_json, 1) : cJSON_CreateObject(); cJSON *prov_info_json = cJSON_CreateObject(); cJSON *prov_capabilities = cJSON_CreateArray(); /* Use label "prov" to indicate provisioning related information */ cJSON_AddItemToObject(full_info_json, "prov", prov_info_json); /* Version field */ cJSON_AddStringToObject(prov_info_json, "ver", prov_ctx->mgr_info.version); /* Security field */ cJSON_AddNumberToObject(prov_info_json, "sec_ver", prov_ctx->security); #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION int sec_ver = 0; uint8_t sec_patch_ver = 0; protocomm_get_sec_version(prov_ctx->pc, &sec_ver, &sec_patch_ver); assert(sec_ver == prov_ctx->security); cJSON_AddNumberToObject(prov_info_json, "sec_patch_ver", sec_patch_ver); #endif /* Capabilities field */ cJSON_AddItemToObject(prov_info_json, "cap", prov_capabilities); /* If Security / Proof of Possession is not used, indicate in capabilities */ if (prov_ctx->mgr_info.capabilities.no_sec) { cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_sec")); } else if (prov_ctx->mgr_info.capabilities.no_pop) { cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_pop")); } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /* Indicate capability for performing Wi-Fi provision */ cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_prov")); /* Indicate capability for performing Wi-Fi scan */ cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_scan")); #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /* Indicate capability for performing Thread provision */ cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_prov")); /* Indicate capability for performing Thread scan */ cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_scan")); #endif return full_info_json; } /* Declare the internal event handler */ static void network_prov_mgr_event_handler_internal(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static esp_err_t network_prov_mgr_start_service(const char *service_name, const char *service_key) { const network_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme; esp_err_t ret; /* Create new protocomm instance */ prov_ctx->pc = protocomm_new(); if (prov_ctx->pc == NULL) { ESP_LOGE(TAG, "Failed to create new protocomm instance"); return ESP_FAIL; } ret = scheme->set_config_service(prov_ctx->prov_scheme_config, service_name, service_key); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to configure service"); protocomm_delete(prov_ctx->pc); return ret; } /* Start provisioning */ ret = scheme->prov_start(prov_ctx->pc, prov_ctx->prov_scheme_config); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start service"); protocomm_delete(prov_ctx->pc); return ret; } /* Set protocomm security type for endpoint */ if (prov_ctx->security == 0) { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 ret = protocomm_set_security(prov_ctx->pc, "prov-session", &protocomm_security0, NULL); #else // Enable SECURITY_VERSION_0 in Protocomm configuration menu return ESP_ERR_NOT_SUPPORTED; #endif } else if (prov_ctx->security == 1) { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 ret = protocomm_set_security(prov_ctx->pc, "prov-session", &protocomm_security1, prov_ctx->protocomm_sec_params); #else // Enable SECURITY_VERSION_1 in Protocomm configuration menu return ESP_ERR_NOT_SUPPORTED; #endif } else if (prov_ctx->security == 2) { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 ret = protocomm_set_security(prov_ctx->pc, "prov-session", &protocomm_security2, prov_ctx->protocomm_sec_params); #else // Enable SECURITY_VERSION_2 in Protocomm configuration menu return ESP_ERR_NOT_SUPPORTED; #endif } else { ESP_LOGE(TAG, "Unsupported protocomm security version %d", prov_ctx->security); ret = ESP_ERR_INVALID_ARG; } if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set security endpoint"); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } /* Set version information / capabilities of provisioning service and application */ cJSON *version_json = network_prov_get_info_json(); char *version_str = cJSON_Print(version_json); ret = protocomm_set_version(prov_ctx->pc, "proto-ver", version_str); free(version_str); cJSON_Delete(version_json); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set version endpoint"); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } prov_ctx->network_prov_handlers = malloc(sizeof(network_prov_config_handlers_t)); ret = get_network_prov_handlers(prov_ctx->network_prov_handlers); if (ret != ESP_OK) { ESP_LOGD(TAG, "Failed to allocate memory for provisioning handlers"); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ESP_ERR_NO_MEM; } /* Add protocomm endpoint for network configuration */ ret = protocomm_add_endpoint(prov_ctx->pc, "prov-config", network_prov_config_data_handler, prov_ctx->network_prov_handlers); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set provisioning endpoint"); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } prov_ctx->network_scan_handlers = malloc(sizeof(network_prov_scan_handlers_t)); ret = get_network_scan_handlers(prov_ctx->network_scan_handlers); if (ret != ESP_OK) { ESP_LOGD(TAG, "Failed to allocate memory for network scan handlers"); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ESP_ERR_NO_MEM; } /* Add endpoint for scanning networks and sending scan list */ ret = protocomm_add_endpoint(prov_ctx->pc, "prov-scan", network_prov_scan_handler, prov_ctx->network_scan_handlers); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set network scan endpoint"); free(prov_ctx->network_scan_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } prov_ctx->network_ctrl_handlers = malloc(sizeof(network_ctrl_handlers_t)); ret = get_network_ctrl_handlers(prov_ctx->network_ctrl_handlers); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to allocate memory for network ctrl handlers"); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ESP_ERR_NO_MEM; } /* Add endpoint for controlling state of network provisioning */ ret = protocomm_add_endpoint(prov_ctx->pc, "prov-ctrl", network_ctrl_handler, prov_ctx->network_ctrl_handlers); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set network ctrl endpoint"); free(prov_ctx->network_ctrl_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } /* Register global event handler */ #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register WiFi event handler"); free(prov_ctx->network_scan_handlers); free(prov_ctx->network_ctrl_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, network_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register IP event handler"); esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal); free(prov_ctx->network_scan_handlers); free(prov_ctx->network_ctrl_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ret = esp_event_handler_register(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register OpenThread event handler"); free(prov_ctx->network_scan_handlers); free(prov_ctx->network_ctrl_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ret = esp_event_handler_register(NETWORK_PROV_MGR_PVT_EVENT, NETWORK_PROV_MGR_STOP, network_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register provisioning event handler"); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal); esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD free(prov_ctx->network_scan_handlers); free(prov_ctx->network_ctrl_handlers); free(prov_ctx->network_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } ESP_LOGI(TAG, "Provisioning started with service name : %s ", service_name ? service_name : ""); return ESP_OK; } esp_err_t network_prov_mgr_endpoint_create(const char *ep_name) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } esp_err_t err = ESP_FAIL; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state == NETWORK_PROV_STATE_IDLE) { err = prov_ctx->mgr_config.scheme.set_config_endpoint( prov_ctx->prov_scheme_config, ep_name, prov_ctx->endpoint_uuid_used + 1); } if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to create additional endpoint"); } else { prov_ctx->endpoint_uuid_used++; } RELEASE_LOCK(prov_ctx_lock); return err; } esp_err_t network_prov_mgr_endpoint_register(const char *ep_name, protocomm_req_handler_t handler, void *user_ctx) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } esp_err_t err = ESP_FAIL; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state > NETWORK_PROV_STATE_STARTING && prov_ctx->prov_state < NETWORK_PROV_STATE_STOPPING) { err = protocomm_add_endpoint(prov_ctx->pc, ep_name, handler, user_ctx); } RELEASE_LOCK(prov_ctx_lock); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register handler for endpoint"); } return err; } void network_prov_mgr_endpoint_unregister(const char *ep_name) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state > NETWORK_PROV_STATE_STARTING && prov_ctx->prov_state < NETWORK_PROV_STATE_STOPPING) { protocomm_remove_endpoint(prov_ctx->pc, ep_name); } RELEASE_LOCK(prov_ctx_lock); } static void prov_stop_and_notify(bool is_async) { esp_event_handler_unregister(NETWORK_PROV_MGR_PVT_EVENT, NETWORK_PROV_MGR_STOP, network_prov_mgr_event_handler_internal); if (prov_ctx->cleanup_delay_timer) { esp_timer_stop(prov_ctx->cleanup_delay_timer); esp_timer_delete(prov_ctx->cleanup_delay_timer); prov_ctx->cleanup_delay_timer = NULL; } network_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; network_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; /* This delay is so that the client side app is notified first * and then the provisioning is stopped. Generally 1000ms is enough. */ if (!is_async) { uint32_t cleanup_delay = prov_ctx->cleanup_delay > 100 ? prov_ctx->cleanup_delay : 100; vTaskDelay(cleanup_delay / portTICK_PERIOD_MS); } protocomm_remove_endpoint(prov_ctx->pc, "prov-ctrl"); protocomm_remove_endpoint(prov_ctx->pc, "prov-scan"); protocomm_remove_endpoint(prov_ctx->pc, "prov-config"); protocomm_unset_security(prov_ctx->pc, "prov-session"); protocomm_unset_version(prov_ctx->pc, "proto-ver"); /* All the extra application added endpoints are also * removed automatically when prov_stop is called */ prov_ctx->mgr_config.scheme.prov_stop(prov_ctx->pc); /* Delete protocomm instance */ protocomm_delete(prov_ctx->pc); prov_ctx->pc = NULL; /* Free provisioning handlers */ free(prov_ctx->network_prov_handlers->ctx); free(prov_ctx->network_prov_handlers); prov_ctx->network_prov_handlers = NULL; free(prov_ctx->network_scan_handlers->ctx); free(prov_ctx->network_scan_handlers); prov_ctx->network_scan_handlers = NULL; free(prov_ctx->network_ctrl_handlers); prov_ctx->network_ctrl_handlers = NULL; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /* Switch device to Wi-Fi STA mode irrespective of * whether provisioning was completed or not */ esp_wifi_set_mode(WIFI_MODE_STA); #endif ESP_LOGI(TAG, "Provisioning stopped"); if (is_async) { /* NOTE: While calling this API in an async fashion, * the context lock prov_ctx_lock has already been taken */ prov_ctx->prov_state = NETWORK_PROV_STATE_IDLE; ESP_LOGD(TAG, "execute_event_cb : %d", NETWORK_PROV_END); if (scheme_cb) { scheme_cb(scheme_data, NETWORK_PROV_END, NULL); } if (app_cb) { app_cb(app_data, NETWORK_PROV_END, NULL); } if (esp_event_post(NETWORK_PROV_EVENT, NETWORK_PROV_END, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event THREAD_PROV_END"); } } } /* This will do one of these: * 1) if blocking is false, start a cleanup timer for stopping the provisioning service (returns true) * 2) if blocking is true, stop provisioning service immediately (returns true) * 3) if service was already in the process of termination, in blocking mode this will * wait till the service is stopped (returns false) * 4) if service was not running, this will return immediately (returns false) * * NOTE: This function should be called only after ensuring that the context * is valid and the control mutex is locked * * NOTE: When blocking mode is selected, the event callbacks are not executed. * This help with de-initialization. */ static bool network_prov_mgr_stop_service(bool blocking) { if (blocking) { /* Wait for any ongoing calls to network_prov_mgr_start_service() or * network_prov_mgr_stop_service() from another thread to finish */ while (prov_ctx && ( prov_ctx->prov_state == NETWORK_PROV_STATE_STARTING || prov_ctx->prov_state == NETWORK_PROV_STATE_STOPPING)) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(100 / portTICK_PERIOD_MS); ACQUIRE_LOCK(prov_ctx_lock); } } else { /* Wait for any ongoing call to network_prov_mgr_start_service() * from another thread to finish */ while (prov_ctx && prov_ctx->prov_state == NETWORK_PROV_STATE_STARTING) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(100 / portTICK_PERIOD_MS); ACQUIRE_LOCK(prov_ctx_lock); } if (prov_ctx && prov_ctx->prov_state == NETWORK_PROV_STATE_STOPPING) { ESP_LOGD(TAG, "Provisioning is already stopping"); return false; } } if (!prov_ctx || prov_ctx->prov_state == NETWORK_PROV_STATE_IDLE) { ESP_LOGD(TAG, "Provisioning not running"); return false; } /* Timers not needed anymore */ if (prov_ctx->autostop_timer) { esp_timer_stop(prov_ctx->autostop_timer); esp_timer_delete(prov_ctx->autostop_timer); prov_ctx->autostop_timer = NULL; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (prov_ctx->wifi_connect_timer) { esp_timer_stop(prov_ctx->wifi_connect_timer); esp_timer_delete(prov_ctx->wifi_connect_timer); prov_ctx->wifi_connect_timer = NULL; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (prov_ctx->thread_timeout_timer) { esp_timer_stop(prov_ctx->thread_timeout_timer); esp_timer_delete(prov_ctx->thread_timeout_timer); prov_ctx->thread_timeout_timer = NULL; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ESP_LOGD(TAG, "Stopping provisioning"); prov_ctx->prov_state = NETWORK_PROV_STATE_STOPPING; /* Free proof of possession */ if (prov_ctx->protocomm_sec_params) { if (prov_ctx->security == 1) { // In case of security 1 we keep an internal copy of "pop". // Hence free it at this point uint8_t *pop = (uint8_t *)((protocomm_security1_params_t *) prov_ctx->protocomm_sec_params)->data; free(pop); } prov_ctx->protocomm_sec_params = NULL; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /* Delete all scan results */ for (uint16_t channel = 0; channel < 14; channel++) { free(prov_ctx->ap_list[channel]); prov_ctx->ap_list[channel] = NULL; } prov_ctx->scanning = false; for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) { prov_ctx->ap_list_sorted[i] = NULL; } /* Remove event handler */ esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal); esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /* Delete all scan results */ prov_ctx->scanning = false; for (uint8_t i = 0; i < MAX_SCAN_RESULTS; ++i) { if (prov_ctx->scan_result[i]) { free(prov_ctx->scan_result[i]); } prov_ctx->scan_result[i] = NULL; } prov_ctx->scan_result_count = 0; /* Remove event handler */ esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (blocking) { /* Run the cleanup without launching a separate task. Also the * NETWORK_PROV_END event is not emitted in this case */ RELEASE_LOCK(prov_ctx_lock); prov_stop_and_notify(false); ACQUIRE_LOCK(prov_ctx_lock); prov_ctx->prov_state = NETWORK_PROV_STATE_IDLE; } else { /* Launch cleanup timer to perform the cleanup asynchronously. * It is important to do this asynchronously because, there are * situations in which the transport level resources have to be * released - some duration after - returning from a call to * network_prov_mgr_stop_provisioning(), like when it is called * inside a protocomm handler */ uint64_t cleanup_delay_ms = prov_ctx->cleanup_delay > 100 ? prov_ctx->cleanup_delay : 100; esp_timer_start_once(prov_ctx->cleanup_delay_timer, cleanup_delay_ms * 1000U); ESP_LOGD(TAG, "Provisioning scheduled for stopping"); } return true; } /* Task spawned by timer callback */ static void stop_prov_timer_cb(void *arg) { network_prov_mgr_stop_provisioning(); } static void cleanup_delay_timer_cb(void *arg) { esp_err_t ret = esp_event_post(NETWORK_PROV_MGR_PVT_EVENT, NETWORK_PROV_MGR_STOP, NULL, 0, pdMS_TO_TICKS(100)); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to post NETWORK_PROV_MGR_STOP event! %d %s", ret, esp_err_to_name(ret)); } } esp_err_t network_prov_mgr_disable_auto_stop(uint32_t cleanup_delay) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = ESP_FAIL; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state == NETWORK_PROV_STATE_IDLE) { prov_ctx->mgr_info.capabilities.no_auto_stop = true; prov_ctx->cleanup_delay = cleanup_delay; ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } RELEASE_LOCK(prov_ctx_lock); return ret; } /* Call this if provisioning is completed before the timeout occurs */ esp_err_t network_prov_mgr_done(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } bool auto_stop_enabled = false; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && !prov_ctx->mgr_info.capabilities.no_auto_stop) { auto_stop_enabled = true; } RELEASE_LOCK(prov_ctx_lock); /* Stop provisioning if auto stop is enabled */ if (auto_stop_enabled) { network_prov_mgr_stop_provisioning(); } return ESP_OK; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI static esp_err_t update_wifi_scan_results(void) { if (!prov_ctx->scanning) { return ESP_ERR_INVALID_STATE; } ESP_LOGD(TAG, "Scan finished"); esp_err_t ret = ESP_FAIL; uint16_t count = 0; uint16_t curr_channel = prov_ctx->curr_channel; if (prov_ctx->ap_list[curr_channel]) { free(prov_ctx->ap_list[curr_channel]); prov_ctx->ap_list[curr_channel] = NULL; prov_ctx->ap_list_len[curr_channel] = 0; } if (esp_wifi_scan_get_ap_num(&count) != ESP_OK) { ESP_LOGE(TAG, "Failed to get count of scanned APs"); goto exit; } if (!count) { ESP_LOGD(TAG, "Scan result empty"); ret = ESP_OK; goto exit; } uint16_t get_count = MIN(count, MAX_SCAN_RESULTS); prov_ctx->ap_list[curr_channel] = (wifi_ap_record_t *) calloc(get_count, sizeof(wifi_ap_record_t)); if (!prov_ctx->ap_list[curr_channel]) { ESP_LOGE(TAG, "Failed to allocate memory for AP list"); esp_wifi_clear_ap_list(); goto exit; } if (esp_wifi_scan_get_ap_records(&get_count, prov_ctx->ap_list[curr_channel]) != ESP_OK) { ESP_LOGE(TAG, "Failed to get scanned AP records"); goto exit; } prov_ctx->ap_list_len[curr_channel] = get_count; if (prov_ctx->channels_per_group) { ESP_LOGD(TAG, "Scan results for channel %d :", curr_channel); } else { ESP_LOGD(TAG, "Scan results :"); } ESP_LOGD(TAG, "\tS.N. %-32s %-12s %s %s", "SSID", "BSSID", "RSSI", "AUTH"); for (uint8_t i = 0; i < prov_ctx->ap_list_len[curr_channel]; i++) { ESP_LOGD(TAG, "\t[%2d] %-32s %02x%02x%02x%02x%02x%02x %4d %4d", i, prov_ctx->ap_list[curr_channel][i].ssid, prov_ctx->ap_list[curr_channel][i].bssid[0], prov_ctx->ap_list[curr_channel][i].bssid[1], prov_ctx->ap_list[curr_channel][i].bssid[2], prov_ctx->ap_list[curr_channel][i].bssid[3], prov_ctx->ap_list[curr_channel][i].bssid[4], prov_ctx->ap_list[curr_channel][i].bssid[5], prov_ctx->ap_list[curr_channel][i].rssi, prov_ctx->ap_list[curr_channel][i].authmode); } /* Store results in sorted list */ { int rc = get_count; int is = MAX_SCAN_RESULTS - rc - 1; while (rc > 0 && is >= 0) { if (prov_ctx->ap_list_sorted[is]) { if (prov_ctx->ap_list_sorted[is]->rssi > prov_ctx->ap_list[curr_channel][rc - 1].rssi) { prov_ctx->ap_list_sorted[is + rc] = &prov_ctx->ap_list[curr_channel][rc - 1]; rc--; continue; } prov_ctx->ap_list_sorted[is + rc] = prov_ctx->ap_list_sorted[is]; } is--; } while (rc > 0) { prov_ctx->ap_list_sorted[rc - 1] = &prov_ctx->ap_list[curr_channel][rc - 1]; rc--; } } ret = ESP_OK; exit: if (!prov_ctx->channels_per_group) { /* All channel scan was performed * so nothing more to do */ prov_ctx->scanning = false; goto final; } curr_channel = prov_ctx->curr_channel = (prov_ctx->curr_channel + 1) % 14; if (ret != ESP_OK || curr_channel == 0) { prov_ctx->scanning = false; goto final; } if ((curr_channel % prov_ctx->channels_per_group) == 0) { vTaskDelay(120 / portTICK_PERIOD_MS); } ESP_LOGD(TAG, "Scan starting on channel %u...", curr_channel); prov_ctx->scan_cfg.channel = curr_channel; ret = esp_wifi_scan_start(&prov_ctx->scan_cfg, false); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start scan"); prov_ctx->scanning = false; goto final; } ESP_LOGD(TAG, "Scan started"); final: return ret; } esp_err_t network_prov_mgr_wifi_scan_start(bool blocking, bool passive, uint8_t group_channels, uint32_t period_ms) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } if (prov_ctx->scanning) { ESP_LOGD(TAG, "Scan already running"); RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } /* Clear sorted list for new entries */ for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) { prov_ctx->ap_list_sorted[i] = NULL; } if (passive) { prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_PASSIVE; /* We do not recommend scan configuration modification in Wi-Fi and BT coexistence mode */ #if !CONFIG_BT_ENABLED prov_ctx->scan_cfg.scan_time.passive = period_ms; #endif } else { prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_ACTIVE; /* We do not recommend scan configuration modification in Wi-Fi and BT coexistence mode */ #if !CONFIG_BT_ENABLED prov_ctx->scan_cfg.scan_time.active.min = period_ms; prov_ctx->scan_cfg.scan_time.active.max = period_ms; #endif } prov_ctx->channels_per_group = group_channels; if (prov_ctx->channels_per_group) { ESP_LOGD(TAG, "Scan starting on channel 1..."); prov_ctx->scan_cfg.channel = 1; } else { ESP_LOGD(TAG, "Scan starting..."); prov_ctx->scan_cfg.channel = 0; } if (esp_wifi_scan_start(&prov_ctx->scan_cfg, false) != ESP_OK) { ESP_LOGE(TAG, "Failed to start scan"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } ESP_LOGD(TAG, "Scan started"); prov_ctx->scanning = true; prov_ctx->curr_channel = prov_ctx->scan_cfg.channel; RELEASE_LOCK(prov_ctx_lock); /* If scan is to be non-blocking, return immediately */ if (!blocking) { return ESP_OK; } /* Loop till scan is complete */ bool scanning = true; while (scanning) { ACQUIRE_LOCK(prov_ctx_lock); scanning = (prov_ctx && prov_ctx->scanning); RELEASE_LOCK(prov_ctx_lock); /* 120ms delay is sufficient for Wi-Fi beacons to be sent */ vTaskDelay(120 / portTICK_PERIOD_MS); } return ESP_OK; } bool network_prov_mgr_wifi_scan_finished(void) { bool scan_finished = true; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return scan_finished; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return scan_finished; } scan_finished = !prov_ctx->scanning; RELEASE_LOCK(prov_ctx_lock); return scan_finished; } uint16_t network_prov_mgr_wifi_scan_result_count(void) { uint16_t rval = 0; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } while (rval < MAX_SCAN_RESULTS) { if (!prov_ctx->ap_list_sorted[rval]) { break; } rval++; } RELEASE_LOCK(prov_ctx_lock); return rval; } const wifi_ap_record_t *network_prov_mgr_wifi_scan_result(uint16_t index) { const wifi_ap_record_t *rval = NULL; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } if (index < MAX_SCAN_RESULTS) { rval = prov_ctx->ap_list_sorted[index]; } RELEASE_LOCK(prov_ctx_lock); return rval; } esp_err_t network_prov_mgr_get_wifi_state(network_prov_wifi_sta_state_t *state) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || state == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *state = prov_ctx->wifi_state; RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } esp_err_t network_prov_mgr_get_wifi_remaining_conn_attempts(uint32_t *attempts_remaining) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || attempts_remaining == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *attempts_remaining = prov_ctx->mgr_config.network_prov_wifi_conn_cfg.wifi_conn_attempts - prov_ctx->connection_attempts_completed; RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } esp_err_t network_prov_mgr_get_wifi_disconnect_reason(network_prov_wifi_sta_fail_reason_t *reason) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || reason == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } if (prov_ctx->wifi_state != NETWORK_PROV_WIFI_STA_DISCONNECTED) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *reason = prov_ctx->wifi_disconnect_reason; RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } static void debug_print_wifi_credentials(wifi_sta_config_t sta, const char *pretext) { size_t passlen = strlen((const char *) sta.password); ESP_LOGD(TAG, "%s Wi-Fi SSID : %.*s", pretext, strnlen((const char *) sta.ssid, sizeof(sta.ssid)), (const char *) sta.ssid); if (passlen) { /* Mask password partially if longer than 3, else mask it completely */ memset(sta.password + (passlen > 3), '*', passlen - 2 * (passlen > 3)); ESP_LOGD(TAG, "%s Wi-Fi Password : %s", pretext, (const char *) sta.password); } } esp_err_t network_prov_mgr_is_wifi_provisioned(bool *provisioned) { if (!provisioned) { return ESP_ERR_INVALID_ARG; } *provisioned = false; /* Get Wi-Fi Station configuration */ wifi_config_t wifi_cfg; if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) { return ESP_FAIL; } if (strlen((const char *) wifi_cfg.sta.ssid)) { *provisioned = true; debug_print_wifi_credentials(wifi_cfg.sta, "Found"); } return ESP_OK; } bool network_prov_mgr_is_sm_idle(void) { return (prov_ctx->prov_state == NETWORK_PROV_STATE_IDLE); } static void wifi_connect_timer_cb(void *arg) { if (esp_wifi_connect() != ESP_OK) { ESP_LOGE(TAG, "Failed to connect Wi-Fi"); } } esp_err_t network_prov_mgr_configure_wifi_sta(wifi_config_t *wifi_cfg) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Invalid state of Provisioning app"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } execute_event_cb(NETWORK_PROV_SET_WIFI_STA_CONFIG, (void *)wifi_cfg, sizeof(wifi_config_t)); if (prov_ctx->prov_state >= NETWORK_PROV_STATE_CRED_RECV) { ESP_LOGE(TAG, "Wi-Fi credentials already received by provisioning app"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } debug_print_wifi_credentials(wifi_cfg->sta, "Received"); /* Configure Wi-Fi as both AP and/or Station */ if (esp_wifi_set_mode(prov_ctx->mgr_config.scheme.wifi_mode) != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi mode"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Don't release mutex yet as it is possible that right after * esp_wifi_connect() is called below, the related Wi-Fi event * happens even before manager state is updated in the next * few lines causing the internal event handler to miss */ /* Set Wi-Fi storage again to flash to keep the newly * provided credentials on NVS */ if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) { ESP_LOGE(TAG, "Failed to set storage Wi-Fi"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Configure Wi-Fi station with host credentials * provided during provisioning */ if (esp_wifi_set_config(WIFI_IF_STA, wifi_cfg) != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi configuration"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Connect to AP after one second so that the response can * be sent to the client successfully, before a channel change happens*/ if (esp_timer_start_once(prov_ctx->wifi_connect_timer, 1000 * 1000U) != ESP_OK) { ESP_LOGE(TAG, "Failed to start Wi-Fi connect timer"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Reset Wi-Fi station state for provisioning app */ prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_CONNECTING; /* Reset connection attempts counter for new credentials */ prov_ctx->connection_attempts_completed = 0; prov_ctx->prov_state = NETWORK_PROV_STATE_CRED_RECV; /* Execute user registered callback handler */ execute_event_cb(NETWORK_PROV_WIFI_CRED_RECV, (void *)&wifi_cfg->sta, sizeof(wifi_cfg->sta)); RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD static char hex_byte_to_char(uint8_t byte) { byte = byte & 0x0f; if (byte <= 9) { return '0' + byte; } return 'A' + byte - 10; } static bool hex_to_string(uint8_t *hex, size_t hex_len, char *str, size_t str_len) { if (hex_len * 2 < str_len) { for (size_t i = 0; i < hex_len; ++i) { str[2 * i + 1] = hex_byte_to_char(hex[i]); str[2 * i] = hex_byte_to_char(hex[i] >> 4); } str[hex_len * 2] = 0; return true; } return false; } static void debug_print_thread_dataset(otOperationalDataset *dataset) { if (dataset) { ESP_LOGD(TAG, "Thread Network Name: %s", dataset->mNetworkName.m8); char str[33]; if (hex_to_string(dataset->mNetworkKey.m8, sizeof(dataset->mNetworkKey.m8), str, sizeof(str))) { ESP_LOGD(TAG, "Thread Network Key: %s", str); } if (hex_to_string(dataset->mExtendedPanId.m8, sizeof(dataset->mExtendedPanId.m8), str, sizeof(str))) { ESP_LOGD(TAG, "Thread Extended PAN ID: %s", str); } ESP_LOGD(TAG, "Thread Channel: %d", dataset->mChannel); ESP_LOGD(TAG, "Thread PAN ID: %d", dataset->mPanId); } } static void thread_timeout_timer_cb(void *arg) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Invalid state of Provisioning app"); RELEASE_LOCK(prov_ctx_lock); return; } prov_ctx->thread_state = NETWORK_PROV_THREAD_DETACHED; prov_ctx->prov_state = NETWORK_PROV_STATE_FAIL; prov_ctx->thread_detached_reason = NETWORK_PROV_THREAD_NETWORK_NOT_FOUND; execute_event_cb(NETWORK_PROV_THREAD_DATASET_FAIL, (void *)&prov_ctx->thread_detached_reason, sizeof(prov_ctx->thread_detached_reason)); RELEASE_LOCK(prov_ctx_lock); return; } static esp_err_t set_thread_enable(bool val) { esp_openthread_lock_acquire(portMAX_DELAY); otInstance *instance = esp_openthread_get_instance(); bool is_enabled = (otThreadGetDeviceRole(instance) != OT_DEVICE_ROLE_DISABLED); bool is_ip6_enabled = otIp6IsEnabled(instance); if (val && !is_ip6_enabled) { if (otIp6SetEnabled(instance, val) != OT_ERROR_NONE) { esp_openthread_lock_release(); return ESP_FAIL; } } if (val != is_enabled) { if (otThreadSetEnabled(instance, val) != OT_ERROR_NONE) { esp_openthread_lock_release(); return ESP_FAIL; } } if (!val && is_ip6_enabled) { if (otIp6SetEnabled(instance, val) != OT_ERROR_NONE) { esp_openthread_lock_release(); return ESP_FAIL; } } esp_openthread_lock_release(); return ESP_OK; } static esp_err_t set_thread_dataset(otOperationalDatasetTlvs *dataset_tlvs) { otError err = OT_ERROR_NONE; esp_openthread_lock_acquire(portMAX_DELAY); otInstance *instance = esp_openthread_get_instance(); err = otDatasetSetActiveTlvs(instance, dataset_tlvs); esp_openthread_lock_release(); return err == OT_ERROR_NONE ? ESP_OK : ESP_FAIL; } static void update_thread_scan_result(otActiveScanResult *result, void *context) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return; } if (!prov_ctx->scanning) { ESP_LOGD(TAG, "Scan not running"); RELEASE_LOCK(prov_ctx_lock); return; } if (!result) { prov_ctx->scanning = false; ESP_LOGD(TAG, "Scan finished"); RELEASE_LOCK(prov_ctx_lock); return; } otActiveScanResult *new_result = (otActiveScanResult *)malloc(sizeof(otActiveScanResult)); if (!new_result) { ESP_LOGE(TAG, "Failed to allocate memory for Thread scan result"); RELEASE_LOCK(prov_ctx_lock); return; } memcpy(new_result, result, sizeof(otActiveScanResult)); if (prov_ctx->scan_result_count < MAX_SCAN_RESULTS) { prov_ctx->scan_result[prov_ctx->scan_result_count] = new_result; prov_ctx->scan_result_count++; } RELEASE_LOCK(prov_ctx_lock); } esp_err_t network_prov_mgr_thread_scan_start(bool blocking, uint32_t channel_mask) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } if (prov_ctx->scanning) { ESP_LOGD(TAG, "Scan already running"); RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } /* Clear sorted list for new entries */ for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) { if (prov_ctx->scan_result[i]) { free(prov_ctx->scan_result[i]); prov_ctx->scan_result[i] = 0; } } prov_ctx->scan_result_count = 0; esp_openthread_lock_acquire(portMAX_DELAY); otInstance *instance = esp_openthread_get_instance(); // Make netif enabled before start scanning if (!otIp6IsEnabled(instance)) { if (otIp6SetEnabled(instance, true) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to enable netif"); RELEASE_LOCK(prov_ctx_lock); esp_openthread_lock_release(); return ESP_FAIL; } } if (otThreadDiscover(instance, channel_mask, OT_PANID_BROADCAST, false, false, update_thread_scan_result, NULL) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to start scan"); RELEASE_LOCK(prov_ctx_lock); esp_openthread_lock_release(); return ESP_FAIL; } esp_openthread_lock_release(); ESP_LOGI(TAG, "Scan started"); prov_ctx->scanning = true; RELEASE_LOCK(prov_ctx_lock); /* If scan is to be non-blocking, return immediately */ if (!blocking) { return ESP_OK; } /* Loop till scan is complete */ bool scanning = true; while (scanning) { ACQUIRE_LOCK(prov_ctx_lock); scanning = (prov_ctx && prov_ctx->scanning); RELEASE_LOCK(prov_ctx_lock); /* 500ms delay is sufficient for Thread beacons to be sent */ vTaskDelay(500 / portTICK_PERIOD_MS); } return ESP_OK; } bool network_prov_mgr_thread_scan_finished(void) { bool scan_finished = true; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return scan_finished; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return scan_finished; } scan_finished = !prov_ctx->scanning; RELEASE_LOCK(prov_ctx_lock); return scan_finished; } uint16_t network_prov_mgr_thread_scan_result_count(void) { uint16_t rval = 0; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } rval = prov_ctx->scan_result_count; RELEASE_LOCK(prov_ctx_lock); return rval; } const otActiveScanResult *network_prov_mgr_thread_scan_result(uint16_t index) { const otActiveScanResult *rval = NULL; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } if (index < MAX_SCAN_RESULTS) { rval = prov_ctx->scan_result[index]; } RELEASE_LOCK(prov_ctx_lock); return rval; } esp_err_t network_prov_mgr_get_thread_state(network_prov_thread_state_t *state) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || state == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *state = prov_ctx->thread_state; RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } esp_err_t network_prov_mgr_get_thread_detached_reason(network_prov_thread_fail_reason_t *reason) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || reason == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } if (prov_ctx->thread_state != NETWORK_PROV_THREAD_DETACHED) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *reason = prov_ctx->thread_detached_reason; RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } esp_err_t network_prov_mgr_is_thread_provisioned(bool *provisioned) { if (!provisioned) { return ESP_ERR_INVALID_ARG; } *provisioned = false; otOperationalDataset dataset; esp_openthread_lock_acquire(portMAX_DELAY); otError ot_err = otDatasetGetActive(esp_openthread_get_instance(), &dataset); esp_openthread_lock_release(); if (ot_err == OT_ERROR_NONE) { *provisioned = true; debug_print_thread_dataset(&dataset); } return ESP_OK; } esp_err_t network_prov_mgr_configure_thread_dataset(otOperationalDatasetTlvs *dataset_tlvs) { esp_err_t err = ESP_OK; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Invalid state of Provisioning app"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } if (prov_ctx->prov_state >= NETWORK_PROV_STATE_DATASET_RECV) { ESP_LOGE(TAG, "Dataset already received by provisioning app"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } otOperationalDataset dataset; otError ot_err = otDatasetParseTlvs(dataset_tlvs, &dataset); if (ot_err != OT_ERROR_NONE) { prov_ctx->thread_state = NETWORK_PROV_THREAD_DETACHED; prov_ctx->prov_state = NETWORK_PROV_STATE_FAIL; prov_ctx->thread_detached_reason = NETWORK_PROV_THREAD_DATASET_INVALID; execute_event_cb(NETWORK_PROV_THREAD_DATASET_FAIL, (void *)&prov_ctx->thread_detached_reason, sizeof(prov_ctx->thread_detached_reason)); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } debug_print_thread_dataset(&dataset); err = set_thread_enable(false); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to stop Thread"); RELEASE_LOCK(prov_ctx_lock); return err; } err = set_thread_dataset(dataset_tlvs); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set Thread dataset"); RELEASE_LOCK(prov_ctx_lock); return err; } err = set_thread_enable(true); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start Thread"); RELEASE_LOCK(prov_ctx_lock); return err; } /* Start the timeout timer*/ if (esp_timer_start_once(prov_ctx->thread_timeout_timer, 20000 * 1000U) != ESP_OK) { ESP_LOGE(TAG, "Failed to start Thread attaching timer"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Reset Thread state for provisioning app */ prov_ctx->thread_state = NETWORK_PROV_THREAD_ATTACHING; prov_ctx->prov_state = NETWORK_PROV_STATE_DATASET_RECV; /* Execute user registered callback handler */ execute_event_cb(NETWORK_PROV_THREAD_DATASET_RECV, &dataset, sizeof(dataset)); RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD static void network_prov_mgr_event_handler_internal( void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); /* If pointer to provisioning application data is NULL * then provisioning manager is not running, therefore * return with error to allow the global handler to act */ if (!prov_ctx) { RELEASE_LOCK(prov_ctx_lock); return; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /* If scan completed then update scan result */ if (prov_ctx->prov_state == NETWORK_PROV_STATE_STARTED && event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { update_wifi_scan_results(); } /* Only handle events when credential is received and * Wi-Fi STA is yet to complete trying the connection */ if (prov_ctx->prov_state < NETWORK_PROV_STATE_CRED_RECV) { RELEASE_LOCK(prov_ctx_lock); return; } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { ESP_LOGI(TAG, "STA Start"); /* Once configuration is received through protocomm, * device is started as station. Once station starts, * wait for connection to establish with configured * host SSID and password */ prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_CONNECTING; } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ESP_LOGI(TAG, "STA Got IP"); /* Station got IP. That means configuration is successful. */ prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_CONNECTED; prov_ctx->prov_state = NETWORK_PROV_STATE_SUCCESS; /* If auto stop is enabled (default), schedule timer to * stop provisioning after configured timeout. */ if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { ESP_LOGD(TAG, "Starting %d sec timer for stop_prov_timer_cb()", CONFIG_NETWORK_PROV_AUTOSTOP_TIMEOUT); esp_timer_start_once(prov_ctx->autostop_timer, CONFIG_NETWORK_PROV_AUTOSTOP_TIMEOUT * 1000000U); } /* Execute user registered callback handler */ execute_event_cb(NETWORK_PROV_WIFI_CRED_SUCCESS, NULL, 0); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (prov_ctx->mgr_config.network_prov_wifi_conn_cfg.wifi_conn_attempts > 0) { prov_ctx->connection_attempts_completed += 1; /* Increasing attempt after every failure */ if (prov_ctx->connection_attempts_completed < prov_ctx->mgr_config.network_prov_wifi_conn_cfg.wifi_conn_attempts) { /* Set WiFi state to NETWORK_PROV_WIFI_STA_CONN_ATTEMPT_FAILED only if the user configure wifi_conn_attempts * and connection_attempts_completed are less than wifi_conn_attempts. */ prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_CONN_ATTEMPT_FAILED; esp_wifi_connect(); } else { /* Station couldn't connect to configured host SSID */ ESP_LOGE(TAG, "STA Disconnected"); prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_DISCONNECTED; } } else { ESP_LOGE(TAG, "STA Disconnected"); prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_DISCONNECTED; } /* In case of disconnection, update state of service and * run the event handler with disconnection reason as data */ if (prov_ctx->wifi_state == NETWORK_PROV_WIFI_STA_DISCONNECTED) { prov_ctx->prov_state = NETWORK_PROV_STATE_FAIL; wifi_event_sta_disconnected_t *disconnected = (wifi_event_sta_disconnected_t *) event_data; ESP_LOGE(TAG, "Disconnect reason : %d", disconnected->reason); /* Set code corresponding to the reason for disconnection */ switch (disconnected->reason) { case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: case WIFI_REASON_AUTH_FAIL: case WIFI_REASON_HANDSHAKE_TIMEOUT: case WIFI_REASON_MIC_FAILURE: ESP_LOGE(TAG, "STA Auth Error"); prov_ctx->wifi_disconnect_reason = NETWORK_PROV_WIFI_STA_AUTH_ERROR; break; case WIFI_REASON_NO_AP_FOUND: ESP_LOGE(TAG, "STA AP Not found"); prov_ctx->wifi_disconnect_reason = NETWORK_PROV_WIFI_STA_AP_NOT_FOUND; break; default: if (prov_ctx->mgr_config.network_prov_wifi_conn_cfg.wifi_conn_attempts == 0) { /* If none of the expected reasons, retry connecting to host SSID */ prov_ctx->wifi_state = NETWORK_PROV_WIFI_STA_CONNECTING; esp_wifi_connect(); } } if (prov_ctx->wifi_state == NETWORK_PROV_WIFI_STA_DISCONNECTED) { /* Execute user registered callback handler */ network_prov_wifi_sta_fail_reason_t reason = prov_ctx->wifi_disconnect_reason; execute_event_cb(NETWORK_PROV_WIFI_CRED_FAIL, (void *)&reason, sizeof(reason)); } } } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /* Only handle events when dataset is received and * Thread is yet to complete trying the connection */ if (prov_ctx->prov_state < NETWORK_PROV_STATE_DATASET_RECV) { RELEASE_LOCK(prov_ctx_lock); return; } if (event_base == OPENTHREAD_EVENT && event_id == OPENTHREAD_EVENT_ATTACHED) { ESP_LOGI(TAG, "Thread attached"); prov_ctx->thread_state = NETWORK_PROV_THREAD_ATTACHED; prov_ctx->prov_state = NETWORK_PROV_STATE_SUCCESS; /* If auto stop is enabled (default), schedule timer to * stop provisioning after configured timeout. */ if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { ESP_LOGD(TAG, "Starting %d sec timer for stop_prov_timer_cb()", CONFIG_NETWORK_PROV_AUTOSTOP_TIMEOUT); esp_timer_start_once(prov_ctx->autostop_timer, CONFIG_NETWORK_PROV_AUTOSTOP_TIMEOUT * 1000000U); } esp_timer_stop(prov_ctx->thread_timeout_timer); /* Execute user registered callback handler */ execute_event_cb(NETWORK_PROV_THREAD_DATASET_SUCCESS, NULL, 0); } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (event_base == NETWORK_PROV_MGR_PVT_EVENT && event_id == NETWORK_PROV_MGR_STOP) { prov_stop_and_notify(true); } RELEASE_LOCK(prov_ctx_lock); } esp_err_t network_prov_mgr_init(network_prov_mgr_config_t config) { if (!prov_ctx_lock) { /* Create mutex if this is the first time init is being called. * This is created only once and never deleted because if some * other thread is trying to take this mutex while it is being * deleted from another thread then the reference may become * invalid and cause exception */ prov_ctx_lock = xSemaphoreCreateMutex(); if (!prov_ctx_lock) { ESP_LOGE(TAG, "Failed to create mutex"); return ESP_ERR_NO_MEM; } } void *fn_ptrs[] = { config.scheme.prov_stop, config.scheme.prov_start, config.scheme.new_config, config.scheme.delete_config, config.scheme.set_config_service, config.scheme.set_config_endpoint }; /* All function pointers in the scheme structure must be non-null */ for (size_t i = 0; i < sizeof(fn_ptrs) / sizeof(fn_ptrs[0]); i++) { if (!fn_ptrs[i]) { return ESP_ERR_INVALID_ARG; } } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx) { ESP_LOGE(TAG, "Provisioning manager already initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } /* Allocate memory for provisioning app data */ prov_ctx = (struct network_prov_mgr_ctx *) calloc(1, sizeof(struct network_prov_mgr_ctx)); if (!prov_ctx) { ESP_LOGE(TAG, "Error allocating memory for singleton instance"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_NO_MEM; } prov_ctx->mgr_config = config; prov_ctx->prov_state = NETWORK_PROV_STATE_IDLE; prov_ctx->mgr_info.version = NETWORK_PROV_MGR_VERSION; /* Allocate memory for provisioning scheme configuration */ const network_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme; esp_err_t ret = ESP_OK; prov_ctx->prov_scheme_config = scheme->new_config(); if (!prov_ctx->prov_scheme_config) { ESP_LOGE(TAG, "failed to allocate provisioning scheme configuration"); ret = ESP_ERR_NO_MEM; goto exit; } ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-ctrl", 0xFF4F); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure Network state control endpoint"); goto exit; } ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-scan", 0xFF50); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure Network scanning endpoint"); goto exit; } ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-session", 0xFF51); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure security endpoint"); goto exit; } ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-config", 0xFF52); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure Network configuration endpoint"); goto exit; } ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "proto-ver", 0xFF53); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure version endpoint"); goto exit; } /* Application specific custom endpoints will be assigned * incremental UUIDs starting after this value */ prov_ctx->endpoint_uuid_used = 0xFF53; /* This delay is so that the client side app is notified first * and then the provisioning is stopped. Default is 1000ms. */ prov_ctx->cleanup_delay = 1000; exit: if (ret != ESP_OK) { if (prov_ctx->prov_scheme_config) { config.scheme.delete_config(prov_ctx->prov_scheme_config); } free(prov_ctx); } else { execute_event_cb(NETWORK_PROV_INIT, NULL, 0); } RELEASE_LOCK(prov_ctx_lock); return ret; } void network_prov_mgr_wait(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } while (1) { ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state != NETWORK_PROV_STATE_IDLE) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } break; } RELEASE_LOCK(prov_ctx_lock); } esp_err_t network_prov_mgr_deinit(void) { esp_err_t ret = ESP_FAIL; if (!prov_ctx_lock) { ESP_LOGW(TAG, "Provisioning manager not initialized"); return ESP_OK; } ACQUIRE_LOCK(prov_ctx_lock); /* This will do one of these: * 1) if found running, stop the provisioning service (returns true) * 2) if service was already in the process of termination, this will * wait till the service is stopped (returns false) * 3) if service was not running, this will return immediately (returns false) */ bool service_was_running = network_prov_mgr_stop_service(1); /* If service was not running, its also possible that manager * was not even initialized */ if (!service_was_running && !prov_ctx) { ESP_LOGD(TAG, "Manager already de-initialized"); RELEASE_LOCK(prov_ctx_lock); vSemaphoreDelete(prov_ctx_lock); prov_ctx_lock = NULL; return ESP_OK; } if (prov_ctx->app_info_json) { cJSON_Delete(prov_ctx->app_info_json); } if (prov_ctx->prov_scheme_config) { prov_ctx->mgr_config.scheme.delete_config(prov_ctx->prov_scheme_config); } /* Extract the callbacks to be called post deinit */ network_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; network_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; /* Free manager context */ free(prov_ctx); prov_ctx = NULL; RELEASE_LOCK(prov_ctx_lock); /* If a running service was also stopped during de-initialization * then NETWORK_PROV_END event also needs to be emitted before deinit */ if (service_was_running) { ESP_LOGD(TAG, "execute_event_cb : %d", NETWORK_PROV_END); if (scheme_cb) { scheme_cb(scheme_data, NETWORK_PROV_END, NULL); } if (app_cb) { app_cb(app_data, NETWORK_PROV_END, NULL); } ret = esp_event_post(NETWORK_PROV_EVENT, NETWORK_PROV_END, NULL, 0, portMAX_DELAY); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to post event NETWORK_PROV_END"); return ret; } } ESP_LOGD(TAG, "execute_event_cb : %d", NETWORK_PROV_DEINIT); /* Execute deinit event callbacks */ if (scheme_cb) { scheme_cb(scheme_data, NETWORK_PROV_DEINIT, NULL); } if (app_cb) { app_cb(app_data, NETWORK_PROV_DEINIT, NULL); } if (esp_event_post(NETWORK_PROV_EVENT, NETWORK_PROV_DEINIT, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event NETWORK_PROV_DEINIT"); } vSemaphoreDelete(prov_ctx_lock); prov_ctx_lock = NULL; return ESP_OK; } esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, const void *network_prov_sec_params, const char *service_name, const char *service_key) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } if (prov_ctx->prov_state != NETWORK_PROV_STATE_IDLE) { ESP_LOGE(TAG, "Provisioning service already started"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } esp_err_t ret = ESP_OK; /* Update state so that parallel call to network_prov_mgr_start_provisioning() * or network_prov_mgr_stop_provisioning() or network_prov_mgr_deinit() from another * thread doesn't interfere with this process */ prov_ctx->prov_state = NETWORK_PROV_STATE_STARTING; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI uint8_t restore_wifi_flag = 0; /* Start Wi-Fi in Station Mode. * This is necessary for scanning to work */ ret = esp_wifi_set_mode(WIFI_MODE_STA); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi mode to STA"); goto err; } ret = esp_wifi_start(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start Wi-Fi"); goto err; } /* Change Wi-Fi storage to RAM temporarily and erase any old * credentials in RAM(i.e. without erasing the copy on NVS). Also * call disconnect to make sure device doesn't remain connected * to the AP whose credentials were present earlier */ wifi_config_t wifi_cfg_empty, wifi_cfg_old; memset(&wifi_cfg_empty, 0, sizeof(wifi_config_t)); esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg_old); ret = esp_wifi_set_storage(WIFI_STORAGE_RAM); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM"); goto err; } /* WiFi storage needs to be restored before exiting this API */ restore_wifi_flag |= WIFI_PROV_STORAGE_BIT; /* Erase Wi-Fi credentials in RAM, when call disconnect and user code * receive WIFI_EVENT_STA_DISCONNECTED and maybe call esp_wifi_connect, at * this time Wi-Fi will have no configuration to connect */ ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_empty); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials"); goto err; } /* WiFi settings needs to be restored if provisioning error before exiting this API */ restore_wifi_flag |= WIFI_PROV_SETTING_BIT; ret = esp_wifi_disconnect(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to disconnect"); goto err; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 /* Initialize app data */ if (security == NETWORK_PROV_SECURITY_0) { prov_ctx->mgr_info.capabilities.no_sec = true; } #endif #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 if (security == NETWORK_PROV_SECURITY_1) { if (network_prov_sec_params) { static protocomm_security1_params_t sec1_params; // Generate internal copy of "pop", that shall be freed at the end char *pop = strdup(network_prov_sec_params); if (pop == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for pop"); ret = ESP_ERR_NO_MEM; goto err; } sec1_params.data = (const uint8_t *)pop; sec1_params.len = strlen(pop); prov_ctx->protocomm_sec_params = (const void *) &sec1_params; } else { prov_ctx->mgr_info.capabilities.no_pop = true; } } #endif #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 if (security == NETWORK_PROV_SECURITY_2) { if (network_prov_sec_params) { prov_ctx->protocomm_sec_params = network_prov_sec_params; } } #endif prov_ctx->security = security; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_timer_create_args_t wifi_connect_timer_conf = { .callback = wifi_connect_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "network_prov_wifi_connect_tm" }; ret = esp_timer_create(&wifi_connect_timer_conf, &prov_ctx->wifi_connect_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create Wi-Fi connect timer"); goto err; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_timer_create_args_t thread_timeout_timer_conf = { .callback = thread_timeout_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "thread_prov_timeout_tm" }; ret = esp_timer_create(&thread_timeout_timer_conf, &prov_ctx->thread_timeout_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create Thread attaching timeout timer"); goto err; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /* If auto stop on completion is enabled (default) create the stopping timer */ if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { /* Create timer object as a member of app data */ esp_timer_create_args_t autostop_timer_conf = { .callback = stop_prov_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "network_prov_autostop_tm" }; ret = esp_timer_create(&autostop_timer_conf, &prov_ctx->autostop_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create auto-stop timer"); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_timer_delete(prov_ctx->wifi_connect_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_timer_delete(prov_ctx->thread_timeout_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD goto err; } } esp_timer_create_args_t cleanup_delay_timer = { .callback = cleanup_delay_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "cleanup_delay_tm" }; ret = esp_timer_create(&cleanup_delay_timer, &prov_ctx->cleanup_delay_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create cleanup delay timer"); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_timer_delete(prov_ctx->wifi_connect_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_timer_delete(prov_ctx->thread_timeout_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_timer_delete(prov_ctx->autostop_timer); goto err; } /* System APIs for BLE / Wi-Fi / Thread will be called inside network_prov_mgr_start_service(), * which may trigger system level events. Hence, releasing the context lock will * ensure that network_prov_mgr_event_handler() doesn't block the global event_loop * handler when system events need to be handled */ RELEASE_LOCK(prov_ctx_lock); /* Start provisioning service */ ret = network_prov_mgr_start_service(service_name, service_key); if (ret != ESP_OK) { esp_timer_delete(prov_ctx->autostop_timer); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_timer_delete(prov_ctx->wifi_connect_timer); #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_timer_delete(prov_ctx->thread_timeout_timer); #endif esp_timer_delete(prov_ctx->cleanup_delay_timer); } ACQUIRE_LOCK(prov_ctx_lock); if (ret == ESP_OK) { prov_ctx->prov_state = NETWORK_PROV_STATE_STARTED; /* Execute user registered callback handler */ execute_event_cb(NETWORK_PROV_START, NULL, 0); goto exit; } err: prov_ctx->prov_state = NETWORK_PROV_STATE_IDLE; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (restore_wifi_flag & WIFI_PROV_SETTING_BIT) { /* Restore current WiFi settings, since provisioning start has failed */ esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_old); } #endif exit: #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (restore_wifi_flag & WIFI_PROV_STORAGE_BIT) { /* Restore WiFi storage back to FLASH */ esp_wifi_set_storage(WIFI_STORAGE_FLASH); } #endif RELEASE_LOCK(prov_ctx_lock); return ret; } void network_prov_mgr_stop_provisioning(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); /* Launches task for stopping the provisioning service. This will do one of these: * 1) start a task for stopping the provisioning service (returns true) * 2) if service was already in the process of termination, this will * wait till the service is stopped (returns false) * 3) if service was not running, this will return immediately (returns false) */ network_prov_mgr_stop_service(0); RELEASE_LOCK(prov_ctx_lock); } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI esp_err_t network_prov_mgr_reset_wifi_provisioning(void) { esp_err_t ret = esp_wifi_restore(); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_wifi_restore fail, ret is %d", ret); ret = ESP_FAIL; } return ret; } esp_err_t network_prov_mgr_reset_wifi_sm_state_on_failure(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); esp_err_t err = ESP_OK; /* If already in STARTED state, reset has already been performed (e.g., by firmware). * Return success as the device is effectively already reset and in provisioning mode. */ if (prov_ctx->prov_state == NETWORK_PROV_STATE_STARTED) { ESP_LOGD(TAG, "Reset already performed, device already in provisioning mode"); goto exit; } if (prov_ctx->prov_state != NETWORK_PROV_STATE_FAIL) { ESP_LOGE(TAG, "Trying reset when not in failure state. Current state: %d", prov_ctx->prov_state); err = ESP_ERR_INVALID_STATE; goto exit; } wifi_config_t wifi_cfg = {0}; err = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set wifi config, 0x%x", err); goto exit; } /* Reset connection attempts counter to allow full wifi_conn_attempts retries */ prov_ctx->connection_attempts_completed = 0; prov_ctx->prov_state = NETWORK_PROV_STATE_STARTED; exit: RELEASE_LOCK(prov_ctx_lock); return err; } esp_err_t network_prov_mgr_reset_wifi_sm_state_for_reprovision(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); esp_err_t ret = ESP_OK; wifi_config_t wifi_cfg_empty = {0}; uint8_t restore_wifi_flag = 0; if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { ESP_LOGE(TAG, "Execute network_prov_mgr_disable_auto_stop() before calling this API"); ret = ESP_ERR_INVALID_STATE; goto exit; } ret = esp_wifi_set_storage(WIFI_STORAGE_RAM); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM"); goto exit; } restore_wifi_flag |= WIFI_PROV_STORAGE_BIT; ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_empty); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials, 0x%x", ret); goto exit; } ret = esp_wifi_disconnect(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to disconnect wifi, 0x%x", ret); goto exit; } prov_ctx->prov_state = NETWORK_PROV_STATE_STARTED; execute_event_cb(NETWORK_PROV_START, NULL, 0); exit: if (restore_wifi_flag & WIFI_PROV_STORAGE_BIT) { esp_wifi_set_storage(WIFI_STORAGE_FLASH); } RELEASE_LOCK(prov_ctx_lock); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t network_prov_mgr_reset_thread_provisioning(void) { otInstance *instance = esp_openthread_get_instance(); esp_openthread_lock_acquire(portMAX_DELAY); if (otInstanceErasePersistentInfo(instance) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to Erase Thread Network Info"); esp_openthread_lock_release(); return ESP_FAIL; } esp_openthread_lock_release(); return ESP_OK; } esp_err_t network_prov_mgr_reset_thread_sm_state_on_failure(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); esp_err_t err = ESP_OK; otInstance *instance = esp_openthread_get_instance(); esp_openthread_lock_acquire(portMAX_DELAY); /* If already in STARTED state, reset has already been performed (e.g., by firmware). * Return success as the device is effectively already reset and in provisioning mode. */ if (prov_ctx->prov_state == NETWORK_PROV_STATE_STARTED) { ESP_LOGD(TAG, "Reset already performed, device already in provisioning mode"); goto exit; } if (prov_ctx->prov_state != NETWORK_PROV_STATE_FAIL) { ESP_LOGE(TAG, "Trying reset when not in failure state. Current state: %d", prov_ctx->prov_state); err = ESP_ERR_INVALID_STATE; goto exit; } if (otThreadSetEnabled(instance, false) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to stop Thread"); err = ESP_FAIL; goto exit; } if (otIp6SetEnabled(instance, false) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to disable Thread netif"); err = ESP_FAIL; goto exit; } if (otInstanceErasePersistentInfo(instance) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to erase Thread network info"); err = ESP_FAIL; goto exit; } prov_ctx->prov_state = NETWORK_PROV_STATE_STARTED; exit: esp_openthread_lock_release(); RELEASE_LOCK(prov_ctx_lock); return err; } esp_err_t network_prov_mgr_reset_thread_sm_state_for_reprovision(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); esp_err_t ret = ESP_OK; otInstance *instance = esp_openthread_get_instance(); esp_openthread_lock_acquire(portMAX_DELAY); if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { ESP_LOGE(TAG, "Execute network_prov_mgr_disable_auto_stop() before calling this API"); ret = ESP_ERR_INVALID_STATE; goto exit; } if (otThreadSetEnabled(instance, false) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to stop Thread"); ret = ESP_FAIL; goto exit; } if (otIp6SetEnabled(instance, false) != OT_ERROR_NONE) { ESP_LOGE(TAG, "Failed to disable Thread netif"); ret = ESP_FAIL; goto exit; } prov_ctx->prov_state = NETWORK_PROV_STATE_STARTED; execute_event_cb(NETWORK_PROV_START, NULL, 0); exit: esp_openthread_lock_release(); RELEASE_LOCK(prov_ctx_lock); return ret; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD ================================================ FILE: network_provisioning/src/network_config.c ================================================ /* * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "network_constants.pb-c.h" #include "network_config.pb-c.h" #include static const char *TAG = "NetworkProvConfig"; typedef struct network_prov_config_cmd { int cmd_num; esp_err_t (*command_handler)(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data); } network_prov_config_cmd_t; static esp_err_t cmd_get_status_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data); static esp_err_t cmd_set_config_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data); static esp_err_t cmd_apply_config_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data); static network_prov_config_cmd_t cmd_table[] = { { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdGetWifiStatus, .command_handler = cmd_get_status_handler }, { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdSetWifiConfig, .command_handler = cmd_set_config_handler }, { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyWifiConfig, .command_handler = cmd_apply_config_handler }, { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdGetThreadStatus, .command_handler = cmd_get_status_handler }, { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdSetThreadConfig, .command_handler = cmd_set_config_handler }, { .cmd_num = NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyThreadConfig, .command_handler = cmd_apply_config_handler } }; static esp_err_t cmd_get_status_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data) { ESP_LOGD(TAG, "Enter cmd_get_status_handler"); network_prov_config_handlers_t *h = (network_prov_config_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdGetWifiStatus) { RespGetWifiStatus *resp_payload = (RespGetWifiStatus *) malloc(sizeof(RespGetWifiStatus)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_get_wifi_status__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI network_prov_config_get_wifi_data_t resp_data; if (h->wifi_get_status_handler) { if (h->wifi_get_status_handler(&resp_data, &h->ctx) == ESP_OK) { if (resp_data.wifi_state == NETWORK_PROV_WIFI_STA_CONNECTING) { resp_payload->wifi_sta_state = WIFI_STATION_STATE__Connecting; resp_payload->state_case = RESP_GET_WIFI_STATUS__STATE_WIFI_CONNECTED; } else if (resp_data.wifi_state == NETWORK_PROV_WIFI_STA_CONNECTED) { resp_payload->wifi_sta_state = WIFI_STATION_STATE__Connected; resp_payload->state_case = RESP_GET_WIFI_STATUS__STATE_WIFI_CONNECTED; WifiConnectedState *connected = (WifiConnectedState *)( malloc(sizeof(WifiConnectedState))); if (!connected) { free(resp_payload); ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_payload->wifi_connected = connected; wifi_connected_state__init(connected); connected->ip4_addr = strdup(resp_data.conn_info.ip_addr); if (connected->ip4_addr == NULL) { free(connected); free(resp_payload); return ESP_ERR_NO_MEM; } connected->bssid.len = sizeof(resp_data.conn_info.bssid); connected->bssid.data = (uint8_t *) strndup(resp_data.conn_info.bssid, sizeof(resp_data.conn_info.bssid)); if (connected->bssid.data == NULL) { free(connected->ip4_addr); free(connected); free(resp_payload); return ESP_ERR_NO_MEM; } connected->ssid.len = strlen(resp_data.conn_info.ssid); connected->ssid.data = (uint8_t *) strdup(resp_data.conn_info.ssid); if (connected->ssid.data == NULL) { free(connected->bssid.data); free(connected->ip4_addr); free(connected); free(resp_payload); return ESP_ERR_NO_MEM; } connected->channel = resp_data.conn_info.channel; connected->auth_mode = resp_data.conn_info.auth_mode; } else if (resp_data.wifi_state == NETWORK_PROV_WIFI_STA_DISCONNECTED) { resp_payload->wifi_sta_state = WIFI_STATION_STATE__ConnectionFailed; resp_payload->state_case = RESP_GET_WIFI_STATUS__STATE_WIFI_FAIL_REASON; if (resp_data.fail_reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) { resp_payload->wifi_fail_reason = WIFI_CONNECT_FAILED_REASON__AuthError; } else if (resp_data.fail_reason == NETWORK_PROV_WIFI_STA_AP_NOT_FOUND) { resp_payload->wifi_fail_reason = WIFI_CONNECT_FAILED_REASON__WifiNetworkNotFound; } } else if (resp_data.wifi_state == NETWORK_PROV_WIFI_STA_CONN_ATTEMPT_FAILED) { resp_payload->wifi_sta_state = WIFI_STATION_STATE__Connecting; resp_payload->state_case = RESP_GET_WIFI_STATUS__STATE_ATTEMPT_FAILED; WifiAttemptFailed *attempt_failed = (WifiAttemptFailed *)( calloc(1, sizeof(WifiAttemptFailed))); if (!attempt_failed) { free(resp_payload); ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } wifi_attempt_failed__init(attempt_failed); attempt_failed->attempts_remaining = resp_data.connecting_info.attempts_remaining; resp_payload->attempt_failed = attempt_failed; } resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } } else { resp_payload->status = STATUS__InternalError; } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_WIFI_STATUS; resp->resp_get_wifi_status = resp_payload; } else if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdGetThreadStatus) { RespGetThreadStatus *resp_payload = (RespGetThreadStatus *) malloc(sizeof(RespGetThreadStatus)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_get_thread_status__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD network_prov_config_get_thread_data_t resp_data; if (h->thread_get_status_handler) { if (h->thread_get_status_handler(&resp_data, &h->ctx) == ESP_OK) { if (resp_data.thread_state == NETWORK_PROV_THREAD_ATTACHING) { resp_payload->thread_state = THREAD_NETWORK_STATE__Attaching; resp_payload->state_case = RESP_GET_THREAD_STATUS__STATE_THREAD_ATTACHED; } else if (resp_data.thread_state == NETWORK_PROV_THREAD_ATTACHED) { resp_payload->thread_state = THREAD_NETWORK_STATE__Attached; resp_payload->state_case = RESP_GET_THREAD_STATUS__STATE_THREAD_ATTACHED; ThreadAttachState *attached = (ThreadAttachState *)malloc(sizeof(ThreadAttachState)); if (!attached) { free(resp_payload); ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_payload->thread_attached = attached; thread_attach_state__init(attached); attached->channel = resp_data.conn_info.channel; attached->ext_pan_id.len = sizeof(resp_data.conn_info.ext_pan_id); attached->ext_pan_id.data = (uint8_t *)malloc(attached->ext_pan_id.len); if (!attached->ext_pan_id.data) { free(attached); free(resp_payload); return ESP_ERR_NO_MEM; } memcpy(attached->ext_pan_id.data, resp_data.conn_info.ext_pan_id, sizeof(resp_data.conn_info.ext_pan_id)); attached->pan_id = resp_data.conn_info.pan_id; attached->name = (char *)malloc(sizeof(resp_data.conn_info.name)); if (!attached->name) { free(attached->ext_pan_id.data); free(attached); free(resp_payload); return ESP_ERR_NO_MEM; } memcpy(attached->name, resp_data.conn_info.name, sizeof(resp_data.conn_info.name)); } else if (resp_data.thread_state == NETWORK_PROV_THREAD_DETACHED) { resp_payload->thread_state = THREAD_NETWORK_STATE__AttachingFailed; resp_payload->state_case = RESP_GET_THREAD_STATUS__STATE_THREAD_FAIL_REASON; if (resp_data.fail_reason == NETWORK_PROV_THREAD_DATASET_INVALID) { resp_payload->thread_fail_reason = THREAD_ATTACH_FAILED_REASON__DatasetInvalid; } else if (resp_data.fail_reason == NETWORK_PROV_THREAD_NETWORK_NOT_FOUND) { resp_payload->thread_fail_reason = THREAD_ATTACH_FAILED_REASON__ThreadNetworkNotFound; } } resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } } else { resp_payload->status = STATUS__InternalError; } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_THREAD_STATUS; resp->resp_get_thread_status = resp_payload; } return ESP_OK; } static esp_err_t cmd_set_config_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data) { ESP_LOGD(TAG, "Enter cmd_set_config_handler"); network_prov_config_handlers_t *h = (network_prov_config_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdSetWifiConfig) { RespSetWifiConfig *resp_payload = (RespSetWifiConfig *) malloc(sizeof(RespSetWifiConfig)); if (resp_payload == NULL) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_set_wifi_config__init(resp_payload); if (req->payload_case != NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_WIFI_CONFIG || !req->cmd_set_wifi_config) { ESP_LOGE(TAG, "Invalid set WiFi config command"); resp->resp_set_wifi_config = resp_payload; return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI network_prov_config_set_wifi_data_t req_data; memset(&req_data, 0, sizeof(req_data)); /* Check arguments provided in protobuf packet: * - SSID / Passphrase string length must be within the standard limits * - BSSID must either be NULL or have length equal to that imposed by the standard * If any of these conditions are not satisfied, don't invoke the handler and * send error status without closing connection */ resp_payload->status = STATUS__InvalidArgument; if (req->cmd_set_wifi_config->bssid.len != 0 && req->cmd_set_wifi_config->bssid.len != sizeof(req_data.bssid)) { ESP_LOGD(TAG, "Received invalid BSSID"); } else if (req->cmd_set_wifi_config->ssid.len >= sizeof(req_data.ssid)) { ESP_LOGD(TAG, "Received invalid SSID"); } else if (req->cmd_set_wifi_config->passphrase.len >= sizeof(req_data.password)) { ESP_LOGD(TAG, "Received invalid Passphrase"); } else { /* The received SSID and Passphrase are not NULL terminated so * we memcpy over zeroed out arrays. Above length checks ensure * that there is at least 1 extra byte for null termination */ memcpy(req_data.ssid, req->cmd_set_wifi_config->ssid.data, req->cmd_set_wifi_config->ssid.len); memcpy(req_data.password, req->cmd_set_wifi_config->passphrase.data, req->cmd_set_wifi_config->passphrase.len); memcpy(req_data.bssid, req->cmd_set_wifi_config->bssid.data, req->cmd_set_wifi_config->bssid.len); req_data.channel = req->cmd_set_wifi_config->channel; if (h->wifi_set_config_handler(&req_data, &h->ctx) == ESP_OK) { resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_WIFI_CONFIG; resp->resp_set_wifi_config = resp_payload; } else if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdSetThreadConfig) { RespSetThreadConfig *resp_payload = (RespSetThreadConfig *) malloc(sizeof(RespSetThreadConfig)); if (resp_payload == NULL) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_set_thread_config__init(resp_payload); if (req->payload_case != NETWORK_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_THREAD_CONFIG || !req->cmd_set_thread_config) { ESP_LOGE(TAG, "Invalid set Thread config command"); resp->resp_set_thread_config = resp_payload; return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD network_prov_config_set_thread_data_t req_data; memset(&req_data, 0, sizeof(req_data)); resp_payload->status = STATUS__InvalidArgument; if (req->cmd_set_thread_config->dataset.len > sizeof(req_data.dataset)) { ESP_LOGD(TAG, "Received invalid dataset"); } else { memcpy(req_data.dataset, req->cmd_set_thread_config->dataset.data, req->cmd_set_thread_config->dataset.len); req_data.length = req->cmd_set_thread_config->dataset.len; if (h->thread_set_config_handler(&req_data, &h->ctx) == ESP_OK) { resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_THREAD_CONFIG; resp->resp_set_thread_config = resp_payload; } return ESP_OK; } static esp_err_t cmd_apply_config_handler(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data) { ESP_LOGD(TAG, "Enter cmd_apply_config_handler"); network_prov_config_handlers_t *h = (network_prov_config_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyWifiConfig) { RespApplyWifiConfig *resp_payload = (RespApplyWifiConfig *) malloc(sizeof(RespApplyWifiConfig)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_apply_wifi_config__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (h->wifi_apply_config_handler && h->wifi_apply_config_handler(&h->ctx) == ESP_OK) { resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_WIFI_CONFIG; resp->resp_apply_wifi_config = resp_payload; } else if (req->msg == NETWORK_CONFIG_MSG_TYPE__TypeCmdApplyThreadConfig) { RespApplyThreadConfig *resp_payload = (RespApplyThreadConfig *) malloc(sizeof(RespApplyThreadConfig)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_apply_thread_config__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (h->thread_apply_config_handler && h->thread_apply_config_handler(&h->ctx) == ESP_OK) { resp_payload->status = STATUS__Success; } else { resp_payload->status = STATUS__InternalError; } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp_payload->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp->payload_case = NETWORK_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_THREAD_CONFIG; resp->resp_apply_thread_config = resp_payload; } return ESP_OK; } static int lookup_cmd_handler(int cmd_id) { for (size_t i = 0; i < sizeof(cmd_table) / sizeof(network_prov_config_cmd_t); i++) { if (cmd_table[i].cmd_num == cmd_id) { return i; } } return -1; } static void network_prov_config_command_cleanup(NetworkConfigPayload *resp, void *priv_data) { if (!resp) { return; } switch (resp->msg) { case NETWORK_CONFIG_MSG_TYPE__TypeRespGetWifiStatus: { if (!resp->resp_get_wifi_status) { break; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI switch (resp->resp_get_wifi_status->wifi_sta_state) { case WIFI_STATION_STATE__Connecting: break; case WIFI_STATION_STATE__Connected: if (resp->resp_get_wifi_status->wifi_connected) { if (resp->resp_get_wifi_status->wifi_connected->ip4_addr) { free(resp->resp_get_wifi_status->wifi_connected->ip4_addr); } if (resp->resp_get_wifi_status->wifi_connected->bssid.data) { free(resp->resp_get_wifi_status->wifi_connected->bssid.data); } if (resp->resp_get_wifi_status->wifi_connected->ssid.data) { free(resp->resp_get_wifi_status->wifi_connected->ssid.data); } free(resp->resp_get_wifi_status->wifi_connected); } break; case WIFI_STATION_STATE__ConnectionFailed: break; default: break; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI free(resp->resp_get_wifi_status); } break; case NETWORK_CONFIG_MSG_TYPE__TypeRespGetThreadStatus: { if (!resp->resp_get_thread_status) { break; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD switch (resp->resp_get_thread_status->thread_state) { case THREAD_NETWORK_STATE__Attaching: break; case THREAD_NETWORK_STATE__Attached: if (resp->resp_get_thread_status->thread_attached) { if (resp->resp_get_thread_status->thread_attached->name) { free(resp->resp_get_thread_status->thread_attached->name); } free(resp->resp_get_thread_status->thread_attached); } break; case THREAD_NETWORK_STATE__AttachingFailed: break; default: break; } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD free(resp->resp_get_thread_status); } break; case NETWORK_CONFIG_MSG_TYPE__TypeRespSetWifiConfig: { free(resp->resp_set_wifi_config); } break; case NETWORK_CONFIG_MSG_TYPE__TypeRespSetThreadConfig: { free(resp->resp_set_thread_config); } break; case NETWORK_CONFIG_MSG_TYPE__TypeRespApplyWifiConfig: { free(resp->resp_apply_wifi_config); } break; case NETWORK_CONFIG_MSG_TYPE__TypeRespApplyThreadConfig: { free(resp->resp_apply_thread_config); } break; default: ESP_LOGE(TAG, "Unsupported response type in cleanup_handler"); break; } return; } static esp_err_t network_prov_config_command_dispatcher(NetworkConfigPayload *req, NetworkConfigPayload *resp, void *priv_data) { esp_err_t ret; ESP_LOGD(TAG, "In network_prov_config_command_dispatcher Cmd=%d", req->msg); int cmd_index = lookup_cmd_handler(req->msg); if (cmd_index < 0) { ESP_LOGE(TAG, "Invalid command handler lookup"); return ESP_FAIL; } ret = cmd_table[cmd_index].command_handler(req, resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Error executing command handler"); return ESP_FAIL; } return ESP_OK; } esp_err_t network_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) { NetworkConfigPayload *req; NetworkConfigPayload resp; esp_err_t ret; req = network_config_payload__unpack(NULL, inlen, inbuf); if (!req) { ESP_LOGE(TAG, "Unable to unpack config data"); return ESP_ERR_INVALID_ARG; } network_config_payload__init(&resp); /* Validate req->msg before arithmetic to avoid signed overflow on attacker-controlled * wire values. For unknown commands the dispatcher returns ESP_FAIL without calling * any handler, so nothing is allocated and resp.msg = 0 is safe for cleanup. */ if (lookup_cmd_handler(req->msg) >= 0) { resp.msg = req->msg + 1; } ret = network_prov_config_command_dispatcher(req, &resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Proto command dispatcher error %d", ret); ret = ESP_FAIL; goto exit; } *outlen = network_config_payload__get_packed_size(&resp); if (*outlen <= 0) { ESP_LOGE(TAG, "Invalid encoding for response"); ret = ESP_FAIL; goto exit; } *outbuf = (uint8_t *) malloc(*outlen); if (!*outbuf) { ESP_LOGE(TAG, "System out of memory"); ret = ESP_ERR_NO_MEM; goto exit; } network_config_payload__pack(&resp, *outbuf); exit: network_config_payload__free_unpacked(req, NULL); network_prov_config_command_cleanup(&resp, priv_data); return ret; } ================================================ FILE: network_provisioning/src/network_ctrl.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "network_ctrl.pb-c.h" #include "network_ctrl.h" static const char *TAG = "proto_network_ctrl"; typedef struct network_ctrl_cmd { int cmd_id; esp_err_t (*command_handler)(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data); } network_ctrl_cmd_t; static esp_err_t cmd_ctrl_reset_handler(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data); static esp_err_t cmd_ctrl_reprov_handler(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data); static network_ctrl_cmd_t cmd_table[] = { { .cmd_id = NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReset, .command_handler = cmd_ctrl_reset_handler }, { .cmd_id = NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReset, .command_handler = cmd_ctrl_reset_handler }, { .cmd_id = NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReprov, .command_handler = cmd_ctrl_reprov_handler }, { .cmd_id = NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReprov, .command_handler = cmd_ctrl_reprov_handler }, }; static esp_err_t cmd_ctrl_reset_handler(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data) { network_ctrl_handlers_t *h = (network_ctrl_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReset) { RespCtrlWifiReset *resp_payload = (RespCtrlWifiReset *) malloc(sizeof(RespCtrlWifiReset)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_ctrl_wifi_reset__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (h->wifi_ctrl_reset) { resp->status = (h->wifi_ctrl_reset() == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_WIFI_RESET; resp->resp_ctrl_wifi_reset = resp_payload; } else if (req->msg == NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReset) { RespCtrlThreadReset *resp_payload = (RespCtrlThreadReset *) malloc(sizeof(RespCtrlThreadReset)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_ctrl_thread_reset__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (h->thread_ctrl_reset) { resp->status = (h->thread_ctrl_reset() == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_THREAD_RESET; resp->resp_ctrl_thread_reset = resp_payload; } return ESP_OK; } static esp_err_t cmd_ctrl_reprov_handler(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data) { network_ctrl_handlers_t *h = (network_ctrl_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlWifiReprov) { RespCtrlWifiReprov *resp_payload = (RespCtrlWifiReprov *) malloc(sizeof(RespCtrlWifiReprov)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_ctrl_wifi_reprov__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (h->wifi_ctrl_reprov) { resp->status = (h->wifi_ctrl_reprov() == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_WIFI_REPROV; resp->resp_ctrl_wifi_reprov = resp_payload; } else if (req->msg == NETWORK_CTRL_MSG_TYPE__TypeCmdCtrlThreadReprov) { RespCtrlThreadReprov *resp_payload = (RespCtrlThreadReprov *) malloc(sizeof(RespCtrlThreadReprov)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_ctrl_thread_reprov__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (h->thread_ctrl_reprov) { resp->status = (h->thread_ctrl_reprov() == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_CTRL_PAYLOAD__PAYLOAD_RESP_CTRL_THREAD_REPROV; resp->resp_ctrl_thread_reprov = resp_payload; } return ESP_OK; } static int lookup_cmd_handler(int cmd_id) { for (size_t i = 0; i < sizeof(cmd_table) / sizeof(network_ctrl_cmd_t); i++) { if (cmd_table[i].cmd_id == cmd_id) { return i; } } return -1; } static void network_ctrl_cmd_cleanup(NetworkCtrlPayload *resp, void *priv_data) { switch (resp->msg) { case NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReset: { free(resp->resp_ctrl_wifi_reset); } break; case NETWORK_CTRL_MSG_TYPE__TypeRespCtrlWifiReprov: { free(resp->resp_ctrl_wifi_reprov); } break; case NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReset: { free(resp->resp_ctrl_thread_reset); } break; case NETWORK_CTRL_MSG_TYPE__TypeRespCtrlThreadReprov: { free(resp->resp_ctrl_thread_reprov); } break; default: ESP_LOGE(TAG, "Unsupported response type in cleanup_handler"); break; } return; } static esp_err_t network_ctrl_cmd_dispatcher(NetworkCtrlPayload *req, NetworkCtrlPayload *resp, void *priv_data) { esp_err_t ret; ESP_LOGD(TAG, "In network_ctrl_cmd_dispatcher Cmd=%d", req->msg); int cmd_index = lookup_cmd_handler(req->msg); if (cmd_index < 0) { ESP_LOGE(TAG, "Failed to find cmd with ID = %d in the command table", req->msg); return ESP_FAIL; } ret = cmd_table[cmd_index].command_handler(req, resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Error executing command handler"); } return ret; } esp_err_t network_ctrl_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) { NetworkCtrlPayload *req; NetworkCtrlPayload resp; esp_err_t ret = ESP_OK; req = network_ctrl_payload__unpack(NULL, inlen, inbuf); if (!req) { ESP_LOGE(TAG, "Unable to unpack ctrl message"); return ESP_ERR_INVALID_ARG; } network_ctrl_payload__init(&resp); ret = network_ctrl_cmd_dispatcher(req, &resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Command dispatcher error %02X", ret); ret = ESP_FAIL; goto exit; } resp.msg = req->msg + 1; /* Response is request + 1 */ *outlen = network_ctrl_payload__get_packed_size(&resp); if (*outlen <= 0) { ESP_LOGE(TAG, "Invalid encoding for response"); ret = ESP_FAIL; goto exit; } *outbuf = (uint8_t *) malloc(*outlen); if (!*outbuf) { ESP_LOGE(TAG, "Failed to allocate memory for the output buffer"); ret = ESP_ERR_NO_MEM; goto exit; } network_ctrl_payload__pack(&resp, *outbuf); ESP_LOGD(TAG, "Response packet size : %d", *outlen); exit: network_ctrl_payload__free_unpacked(req, NULL); network_ctrl_cmd_cleanup(&resp, priv_data); return ret; } ================================================ FILE: network_provisioning/src/network_ctrl.h ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #ifndef _PROV_NETWORK_CTRL_H_ #define _PROV_NETWORK_CTRL_H_ #include #ifdef __cplusplus extern "C" { #endif /** * @brief Internal handlers for receiving and responding to protocomm * requests from client * * This is to be passed as priv_data for protocomm request handler * (refer to `network_ctrl_handler()`) when calling `protocomm_add_endpoint()`. */ typedef struct network_ctrl_handlers { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * Handler functions called when ctrl reset command is received */ esp_err_t (*wifi_ctrl_reset)(void); /** * Handler functions called when ctrl reprov command is received */ esp_err_t (*wifi_ctrl_reprov)(void); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD esp_err_t (*thread_ctrl_reset)(void); esp_err_t (*thread_ctrl_reprov)(void); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD } network_ctrl_handlers_t; /** * @brief Handler for sending on demand network ctrl results * * This is to be registered as the `prov-ctrl` endpoint handler * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` */ esp_err_t network_ctrl_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data); #ifdef __cplusplus } #endif #endif ================================================ FILE: network_provisioning/src/network_provisioning_priv.h ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "network_provisioning/manager.h" #include "network_provisioning/network_config.h" #include "network_provisioning/network_scan.h" #include "network_ctrl.h" #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD #include "openthread/link.h" #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Notify manager that provisioning is done * * Stops the provisioning. This is called by the get_status_handler() * when the status is connected. This has no effect if main application * has disabled auto stop on completion by calling * network_prov_mgr_disable_auto_stop() * * @return * - ESP_OK : Provisioning will be stopped * - ESP_FAIL : Failed to stop provisioning */ esp_err_t network_prov_mgr_done(void); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI /** * @brief Start Wi-Fi AP Scan * * @param[in] blocking Set true to return only after scanning is complete * @param[in] passive Set true to perform passive scan instead of default active scan * @param[in] group_channels Number of channels to scan in one go * (set to 0 for scanning all channels in one go) * @param[in] period_ms Scan time (in milli-seconds) on each channel * * @return * - ESP_OK : Successfully started Wi-Fi scanning * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_wifi_scan_start(bool blocking, bool passive, uint8_t group_channels, uint32_t period_ms); /** * @brief Use to query the state of Wi-Fi scan * * @return * - true : Scan finished * - false : Scan running */ bool network_prov_mgr_wifi_scan_finished(void); /** * @brief Get the count of results in the scan list * * @return * - count : Number of Wi-Fi Access Points detected while scanning */ uint16_t network_prov_mgr_wifi_scan_result_count(void); /** * @brief Get AP record for a particular index in the scan list result * * @param[out] index Index of the result to fetch * * @return * - result : Pointer to Access Point record */ const wifi_ap_record_t *network_prov_mgr_wifi_scan_result(uint16_t index); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Start Thread network Scan * * @param[in] blocking Set true to return only after scanning is complete * * @return * - ESP_OK : Successfully started Thread scanning * - ESP_FAIL : Provisioning app not running */ esp_err_t network_prov_mgr_thread_scan_start(bool blocking, uint32_t channel_mask); /** * @brief Use to query the state of Thread scan * * @return * - true : Scan finished * - false : Scan running */ bool network_prov_mgr_thread_scan_finished(void); /** * @brief Get the count of results in the scan list * * @return * - count : Number of Thread networks detected while scanning */ uint16_t network_prov_mgr_thread_scan_result_count(void); /** * @brief Get Thread network record for a particular index in the scan list result * * @param[out] index Index of the result to fetch * * @return * - result : Pointer to Thread network record */ const otActiveScanResult *network_prov_mgr_thread_scan_result(uint16_t index); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD /** * @brief Get protocomm handlers for network_config provisioning endpoint * * @param[out] ptr pointer to structure to be set * * @return * - ESP_OK : success * - ESP_ERR_INVALID_ARG : null argument */ esp_err_t get_network_prov_handlers(network_prov_config_handlers_t *ptr); /** * @brief Get protocomm handlers for network_scan provisioning endpoint * * @param[out] ptr pointer to structure to be set * * @return * - ESP_OK : success * - ESP_ERR_INVALID_ARG : null argument */ esp_err_t get_network_scan_handlers(network_prov_scan_handlers_t *ptr); /** * @brief Get protocomm handlers for network_ctrl provisioning endpoint * * @param[in] ptr pointer to structure to be set * * @return * - ESP_OK : success * - ESP_ERR_INVALID_ARG : null argument */ esp_err_t get_network_ctrl_handlers(network_ctrl_handlers_t *ptr); /** * @brief Retrieve the remaining number of Wi-Fi connection attempts * * This function provides the number of Wi-Fi connection attempts left for * the network provisioning manager before reaching the maximum retry limit. * * @param[out] attempts_remaining Pointer to store the remaining connection attempts * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Null pointer provided for attempts_remaining * - ESP_FAIL: Failed to retrieve the remaining attempts */ esp_err_t network_prov_mgr_get_wifi_remaining_conn_attempts(uint32_t *attempts_remaining); ================================================ FILE: network_provisioning/src/network_scan.c ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "network_scan.pb-c.h" #include static const char *TAG = "proto_network_scan"; typedef struct network_prov_scan_cmd { int cmd_num; esp_err_t (*command_handler)(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data); } network_prov_scan_cmd_t; static esp_err_t cmd_scan_start_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data); static esp_err_t cmd_scan_status_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data); static esp_err_t cmd_scan_result_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data); static network_prov_scan_cmd_t cmd_table[] = { { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStart, .command_handler = cmd_scan_start_handler }, { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStatus, .command_handler = cmd_scan_status_handler }, { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiResult, .command_handler = cmd_scan_result_handler }, { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStart, .command_handler = cmd_scan_start_handler }, { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStatus, .command_handler = cmd_scan_status_handler }, { .cmd_num = NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadResult, .command_handler = cmd_scan_result_handler } }; static esp_err_t cmd_scan_start_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data) { network_prov_scan_handlers_t *h = (network_prov_scan_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStart) { RespScanWifiStart *resp_payload = (RespScanWifiStart *) malloc(sizeof(RespScanWifiStart)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_wifi_start__init(resp_payload); if (req->payload_case != NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_WIFI_START || !req->cmd_scan_wifi_start) { ESP_LOGE(TAG, "Invalid WiFi scan start command"); resp->resp_scan_wifi_start = resp_payload; return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (h->wifi_scan_start) { resp->status = (h->wifi_scan_start(req->cmd_scan_wifi_start->blocking, req->cmd_scan_wifi_start->passive, req->cmd_scan_wifi_start->group_channels, req->cmd_scan_wifi_start->period_ms, &h->ctx) == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_START; resp->resp_scan_wifi_start = resp_payload; } else if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStart) { RespScanThreadStart *resp_payload = (RespScanThreadStart *) malloc(sizeof(RespScanThreadStart)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_thread_start__init(resp_payload); if (req->payload_case != NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_THREAD_START || !req->cmd_scan_thread_start) { ESP_LOGE(TAG, "Invalid Thread scan start command"); resp->resp_scan_thread_start = resp_payload; return ESP_ERR_INVALID_ARG; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (h->thread_scan_start) { resp->status = (h->thread_scan_start(req->cmd_scan_thread_start->blocking, req->cmd_scan_thread_start->channel_mask, &h->ctx) == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_START; resp->resp_scan_thread_start = resp_payload; } return ESP_OK; } static esp_err_t cmd_scan_status_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data) { bool scan_finished = false; uint16_t result_count = 0; network_prov_scan_handlers_t *h = (network_prov_scan_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiStatus) { RespScanWifiStatus *resp_payload = (RespScanWifiStatus *) malloc(sizeof(RespScanWifiStatus)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_wifi_status__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (h->wifi_scan_status) { resp->status = (h->wifi_scan_status(&scan_finished, &result_count, &h->ctx) == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp_payload->scan_finished = scan_finished; resp_payload->result_count = result_count; resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_STATUS; resp->resp_scan_wifi_status = resp_payload; } else if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadStatus) { RespScanThreadStatus *resp_payload = (RespScanThreadStatus *) malloc(sizeof(RespScanThreadStatus)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_thread_status__init(resp_payload); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (h->thread_scan_status) { resp->status = (h->thread_scan_status(&scan_finished, &result_count, &h->ctx) == ESP_OK ? STATUS__Success : STATUS__InternalError); } else { resp->status = STATUS__InternalError; } #else resp->status = STATUS__InvalidArgument; #endif resp_payload->scan_finished = scan_finished; resp_payload->result_count = result_count; resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_STATUS; resp->resp_scan_thread_status = resp_payload; } return ESP_OK; } static esp_err_t cmd_scan_result_handler(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data) { esp_err_t err = ESP_OK; network_prov_scan_handlers_t *h = (network_prov_scan_handlers_t *) priv_data; if (!h) { ESP_LOGE(TAG, "Command invoked without handlers"); return ESP_ERR_INVALID_STATE; } if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanWifiResult) { RespScanWifiResult *resp_payload = (RespScanWifiResult *) malloc(sizeof(RespScanWifiResult)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_wifi_result__init(resp_payload); if (req->payload_case != NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_WIFI_RESULT || !req->cmd_scan_wifi_result) { ESP_LOGE(TAG, "Invalid WiFi scan result command"); resp->resp_scan_wifi_result = resp_payload; return ESP_ERR_INVALID_ARG; } if (req->cmd_scan_wifi_result->start_index >= CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES || req->cmd_scan_wifi_result->count > CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES - req->cmd_scan_wifi_result->start_index) { ESP_LOGE(TAG, "WiFi scan result count/start_index out of bounds"); resp->resp_scan_wifi_result = resp_payload; return ESP_ERR_INVALID_ARG; } resp->status = STATUS__Success; resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_WIFI_RESULT; resp->resp_scan_wifi_result = resp_payload; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI network_prov_scan_wifi_result_t scan_result = {{0}, {0}, 0, 0, 0}; WiFiScanResult **results = NULL; /* Allocate memory only if there are non-zero scan results */ if (req->cmd_scan_wifi_result->count) { results = (WiFiScanResult **) calloc(req->cmd_scan_wifi_result->count, sizeof(WiFiScanResult *)); if (!results) { ESP_LOGE(TAG, "Failed to allocate memory for results array"); return ESP_ERR_NO_MEM; } } resp_payload->entries = results; resp_payload->n_entries = req->cmd_scan_wifi_result->count; /* If req->cmd_scan_wifi_result->count is 0, the below loop will * be skipped. */ for (uint32_t i = 0; i < req->cmd_scan_wifi_result->count; i++) { if (!h->wifi_scan_result) { resp_payload->n_entries = i; resp->status = STATUS__InternalError; break; } /* start_index and count are validated above to sum to at most * CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES (max 255), so this cast is safe. */ uint16_t result_index = (uint16_t)(i + req->cmd_scan_wifi_result->start_index); err = h->wifi_scan_result(result_index, &scan_result, &h->ctx); if (err != ESP_OK) { resp_payload->n_entries = i; resp->status = STATUS__InternalError; break; } results[i] = (WiFiScanResult *) malloc(sizeof(WiFiScanResult)); if (!results[i]) { ESP_LOGE(TAG, "Failed to allocate memory for result entry"); resp_payload->n_entries = i; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } wi_fi_scan_result__init(results[i]); results[i]->ssid.len = strnlen(scan_result.ssid, 32); results[i]->ssid.data = (uint8_t *) strndup(scan_result.ssid, 32); if (!results[i]->ssid.data) { ESP_LOGE(TAG, "Failed to allocate memory for scan result entry SSID"); results[i]->ssid.len = 0; resp_payload->n_entries = i + 1; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } results[i]->channel = scan_result.channel; results[i]->rssi = scan_result.rssi; results[i]->auth = scan_result.auth; results[i]->bssid.len = sizeof(scan_result.bssid); results[i]->bssid.data = malloc(results[i]->bssid.len); if (!results[i]->bssid.data) { ESP_LOGE(TAG, "Failed to allocate memory for scan result entry BSSID"); results[i]->bssid.len = 0; resp_payload->n_entries = i + 1; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } memcpy(results[i]->bssid.data, scan_result.bssid, results[i]->bssid.len); } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI resp->status = STATUS__InvalidArgument; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI } else if (req->msg == NETWORK_SCAN_MSG_TYPE__TypeCmdScanThreadResult) { RespScanThreadResult *resp_payload = (RespScanThreadResult *) malloc(sizeof(RespScanThreadResult)); if (!resp_payload) { ESP_LOGE(TAG, "Error allocating memory"); return ESP_ERR_NO_MEM; } resp_scan_thread_result__init(resp_payload); if (req->payload_case != NETWORK_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_THREAD_RESULT || !req->cmd_scan_thread_result) { ESP_LOGE(TAG, "Invalid Thread scan result command"); resp->resp_scan_thread_result = resp_payload; return ESP_ERR_INVALID_ARG; } if (req->cmd_scan_thread_result->start_index >= CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES || req->cmd_scan_thread_result->count > CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES - req->cmd_scan_thread_result->start_index) { ESP_LOGE(TAG, "Thread scan result count/start_index out of bounds"); resp->resp_scan_thread_result = resp_payload; return ESP_ERR_INVALID_ARG; } resp->status = STATUS__Success; resp->payload_case = NETWORK_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_THREAD_RESULT; resp->resp_scan_thread_result = resp_payload; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD network_prov_scan_thread_result_t scan_result; memset(&scan_result, 0, sizeof(scan_result)); ThreadScanResult **results = NULL; /* Allocate memory only if there are non-zero scan results */ if (req->cmd_scan_thread_result->count) { results = (ThreadScanResult **) calloc(req->cmd_scan_thread_result->count, sizeof(ThreadScanResult *)); if (!results) { ESP_LOGE(TAG, "Failed to allocate memory for results array"); return ESP_ERR_NO_MEM; } } resp_payload->entries = results; resp_payload->n_entries = req->cmd_scan_thread_result->count; /* If req->cmd_scan_result->count is 0, the below loop will * be skipped. */ for (uint32_t i = 0; i < req->cmd_scan_thread_result->count; i++) { if (!h->thread_scan_result) { resp_payload->n_entries = i; resp->status = STATUS__InternalError; break; } /* start_index and count are validated above to sum to at most * CONFIG_NETWORK_PROV_SCAN_MAX_ENTRIES (max 255), so this cast is safe. */ uint16_t result_index = (uint16_t)(i + req->cmd_scan_thread_result->start_index); err = h->thread_scan_result(result_index, &scan_result, &h->ctx); if (err != ESP_OK) { resp_payload->n_entries = i; resp->status = STATUS__InternalError; break; } results[i] = (ThreadScanResult *) malloc(sizeof(ThreadScanResult)); if (!results[i]) { ESP_LOGE(TAG, "Failed to allocate memory for result entry"); resp_payload->n_entries = i; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } thread_scan_result__init(results[i]); results[i]->pan_id = scan_result.pan_id; results[i]->channel = scan_result.channel; results[i]->rssi = scan_result.rssi; results[i]->lqi = scan_result.lqi; results[i]->ext_addr.len = sizeof(scan_result.ext_addr); results[i]->ext_addr.data = (uint8_t *)malloc(results[i]->ext_addr.len); if (!results[i]->ext_addr.data) { ESP_LOGE(TAG, "Failed to allocate memory for scan result entry extended address"); results[i]->ext_addr.len = 0; resp_payload->n_entries = i + 1; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } memcpy(results[i]->ext_addr.data, scan_result.ext_addr, results[i]->ext_addr.len); results[i]->ext_pan_id.len = sizeof(scan_result.ext_pan_id); results[i]->ext_pan_id.data = (uint8_t *)malloc(results[i]->ext_pan_id.len); if (!results[i]->ext_pan_id.data) { ESP_LOGE(TAG, "Failed to allocate memory for scan result entry extended PAN ID"); results[i]->ext_pan_id.len = 0; resp_payload->n_entries = i + 1; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } memcpy(results[i]->ext_pan_id.data, scan_result.ext_pan_id, results[i]->ext_pan_id.len); results[i]->network_name = (char *)malloc(sizeof(scan_result.network_name)); if (!results[i]->network_name) { ESP_LOGE(TAG, "Failed to allocate memory for scan result entry network name"); resp_payload->n_entries = i + 1; resp->status = STATUS__InternalError; return ESP_ERR_NO_MEM; } memcpy(results[i]->network_name, scan_result.network_name, sizeof(scan_result.network_name)); } #else // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD resp->status = STATUS__InvalidArgument; err = ESP_ERR_INVALID_ARG; #endif // !CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD } return err; } static int lookup_cmd_handler(int cmd_id) { for (size_t i = 0; i < sizeof(cmd_table) / sizeof(network_prov_scan_cmd_t); i++) { if (cmd_table[i].cmd_num == cmd_id) { return i; } } return -1; } static void network_prov_scan_cmd_cleanup(NetworkScanPayload *resp, void *priv_data) { switch (resp->msg) { case NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStart: { free(resp->resp_scan_wifi_start); } break; case NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStart: { free(resp->resp_scan_thread_start); } break; case NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiStatus: { free(resp->resp_scan_wifi_status); } break; case NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadStatus: { free(resp->resp_scan_thread_status); } break; case NETWORK_SCAN_MSG_TYPE__TypeRespScanWifiResult: { if (!resp->resp_scan_wifi_result) { return; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI if (resp->resp_scan_wifi_result->entries) { for (uint32_t i = 0; i < resp->resp_scan_wifi_result->n_entries; i++) { if (!resp->resp_scan_wifi_result->entries[i]) { continue; } free(resp->resp_scan_wifi_result->entries[i]->ssid.data); free(resp->resp_scan_wifi_result->entries[i]->bssid.data); free(resp->resp_scan_wifi_result->entries[i]); } free(resp->resp_scan_wifi_result->entries); } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI free(resp->resp_scan_wifi_result); } break; case NETWORK_SCAN_MSG_TYPE__TypeRespScanThreadResult: { if (!resp->resp_scan_thread_result) { return; } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD if (resp->resp_scan_thread_result->entries) { for (size_t i = 0; i < resp->resp_scan_thread_result->n_entries; i++) { if (!resp->resp_scan_thread_result->entries[i]) { continue; } free(resp->resp_scan_thread_result->entries[i]->ext_addr.data); free(resp->resp_scan_thread_result->entries[i]->ext_pan_id.data); if (resp->resp_scan_thread_result->entries[i]->network_name != protobuf_c_empty_string) { free(resp->resp_scan_thread_result->entries[i]->network_name); } free(resp->resp_scan_thread_result->entries[i]); } free(resp->resp_scan_thread_result->entries); } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD free(resp->resp_scan_thread_result); } break; default: ESP_LOGE(TAG, "Unsupported response type in cleanup_handler"); break; } return; } static esp_err_t network_prov_scan_cmd_dispatcher(NetworkScanPayload *req, NetworkScanPayload *resp, void *priv_data) { esp_err_t ret; ESP_LOGD(TAG, "In network_prov_scan_cmd_dispatcher Cmd=%d", req->msg); int cmd_index = lookup_cmd_handler(req->msg); if (cmd_index < 0) { ESP_LOGE(TAG, "Invalid command handler lookup"); return ESP_FAIL; } ret = cmd_table[cmd_index].command_handler(req, resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Error executing command handler"); return ESP_FAIL; } return ESP_OK; } esp_err_t network_prov_scan_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) { NetworkScanPayload *req; NetworkScanPayload resp; esp_err_t ret = ESP_OK; req = network_scan_payload__unpack(NULL, inlen, inbuf); if (!req) { ESP_LOGE(TAG, "Unable to unpack scan message"); return ESP_ERR_INVALID_ARG; } network_scan_payload__init(&resp); /* Validate req->msg before arithmetic to avoid signed overflow on attacker-controlled * wire values. For unknown commands the dispatcher returns ESP_FAIL without calling * any handler, so nothing is allocated and resp.msg = 0 is safe for cleanup. */ if (lookup_cmd_handler(req->msg) >= 0) { resp.msg = req->msg + 1; } ret = network_prov_scan_cmd_dispatcher(req, &resp, priv_data); if (ret != ESP_OK) { ESP_LOGE(TAG, "Command dispatcher error %d", ret); ret = ESP_FAIL; goto exit; } *outlen = network_scan_payload__get_packed_size(&resp); if (*outlen <= 0) { ESP_LOGE(TAG, "Invalid encoding for response"); ret = ESP_FAIL; goto exit; } *outbuf = (uint8_t *) malloc(*outlen); if (!*outbuf) { ESP_LOGE(TAG, "System out of memory"); ret = ESP_ERR_NO_MEM; goto exit; } network_scan_payload__pack(&resp, *outbuf); ESP_LOGD(TAG, "Response packet size : %d", *outlen); exit: network_scan_payload__free_unpacked(req, NULL); network_prov_scan_cmd_cleanup(&resp, priv_data); return ret; } ================================================ FILE: network_provisioning/src/scheme_ble.c ================================================ /* * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #ifdef CONFIG_BT_CONTROLLER_ENABLED #include "esp_bt.h" #endif #include #include #include "network_provisioning/scheme_ble.h" #include "network_provisioning_priv.h" static const char *TAG = "network_prov_scheme_ble"; extern const network_prov_scheme_t network_prov_scheme_ble; static uint8_t *custom_service_uuid; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) static uint8_t *custom_ble_addr; static uint8_t custom_keep_ble_on; #endif static uint8_t *custom_manufacturer_data; static size_t custom_manufacturer_data_len; static esp_err_t prov_start(protocomm_t *pc, void *config) { if (!pc) { ESP_LOGE(TAG, "Protocomm handle cannot be null"); return ESP_ERR_INVALID_ARG; } if (!config) { ESP_LOGE(TAG, "Cannot start with null configuration"); return ESP_ERR_INVALID_ARG; } protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) ble_config->keep_ble_on = custom_keep_ble_on; #endif #if defined(CONFIG_NETWORK_PROV_BLE_BONDING) ble_config->ble_bonding = 1; #endif #if defined(CONFIG_NETWORK_PROV_BLE_SEC_CONN) || defined(CONFIG_BT_BLUEDROID_ENABLED) ble_config->ble_sm_sc = 1; #endif #if defined(CONFIG_NETWORK_PROV_BLE_FORCE_ENCRYPTION) ble_config->ble_link_encryption = 1; #endif #if defined(CONFIG_NETWORK_PROV_BLE_NOTIFY) ble_config->ble_notify = 1; #endif /* Start protocomm as BLE service */ if (protocomm_ble_start(pc, ble_config) != ESP_OK) { ESP_LOGE(TAG, "Failed to start protocomm BLE service"); return ESP_FAIL; } return ESP_OK; } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) esp_err_t network_prov_scheme_ble_set_random_addr(const uint8_t *addr) { if (!addr) { return ESP_ERR_INVALID_ARG; } custom_ble_addr = (uint8_t *) malloc(BLE_ADDR_LEN); if (custom_ble_addr == NULL) { ESP_LOGE(TAG, "Error allocating memory for random address"); return ESP_ERR_NO_MEM; } memcpy(custom_ble_addr, addr, BLE_ADDR_LEN); return ESP_OK; } esp_err_t network_prov_mgr_keep_ble_on(uint8_t is_on_after_ble_stop) { if (!is_on_after_ble_stop) { return ESP_ERR_INVALID_ARG; } custom_keep_ble_on = is_on_after_ble_stop; return ESP_OK; } #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) esp_err_t network_prov_scheme_ble_set_service_uuid(uint8_t *uuid128) { if (!uuid128) { return ESP_ERR_INVALID_ARG; } custom_service_uuid = uuid128; return ESP_OK; } esp_err_t network_prov_scheme_ble_set_mfg_data(uint8_t *mfg_data, ssize_t mfg_data_len) { if (!mfg_data || !mfg_data_len) { return ESP_ERR_INVALID_ARG; } custom_manufacturer_data = (uint8_t *) malloc(mfg_data_len); if (custom_manufacturer_data == NULL) { ESP_LOGE(TAG, "Error allocating memory for mfg_data"); return ESP_ERR_NO_MEM; } custom_manufacturer_data_len = mfg_data_len; memcpy(custom_manufacturer_data, mfg_data, mfg_data_len); return ESP_OK; } static void *new_config(void) { protocomm_ble_config_t *ble_config = calloc(1, sizeof(protocomm_ble_config_t)); if (!ble_config) { ESP_LOGE(TAG, "Error allocating memory for new configuration"); return NULL; } /* The default provisioning service UUID */ const uint8_t service_uuid[16] = { /* LSB <--------------------------------------- * ---------------------------------------> MSB */ 0x07, 0xed, 0x9b, 0x2d, 0x0f, 0x06, 0x7c, 0x87, 0x9b, 0x43, 0x43, 0x6b, 0x4d, 0x24, 0x75, 0x17, }; memcpy(ble_config->service_uuid, service_uuid, sizeof(ble_config->service_uuid)); return ble_config; } static void delete_config(void *config) { if (!config) { ESP_LOGE(TAG, "Cannot delete null configuration"); return; } protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; for (unsigned int i = 0; i < ble_config->nu_lookup_count; i++) { free((void *)ble_config->nu_lookup[i].name); } free(ble_config->nu_lookup); free(ble_config); } static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) { if (!config) { ESP_LOGE(TAG, "Cannot set null configuration"); return ESP_ERR_INVALID_ARG; } if (!service_name) { ESP_LOGE(TAG, "Service name cannot be NULL"); return ESP_ERR_INVALID_ARG; } protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; strlcpy(ble_config->device_name, service_name, sizeof(ble_config->device_name)); /* If a custom service UUID has been provided, override the default one */ if (custom_service_uuid) { memcpy(ble_config->service_uuid, custom_service_uuid, sizeof(ble_config->service_uuid)); } /* Set manufacturer data if it is provided by app */ if (custom_manufacturer_data) { size_t mfg_data_len = custom_manufacturer_data_len; size_t dev_name_len = strnlen(ble_config->device_name, MAX_BLE_DEVNAME_LEN); if ((dev_name_len + 2) >= MAX_BLE_MANUFACTURER_DATA_LEN) { /* No space left for manufacturer data */ ESP_LOGE(TAG, "No space left for Manufacturer data "); ble_config->manufacturer_data = NULL; ble_config->manufacturer_data_len = 0; } else { if ((mfg_data_len + (dev_name_len ? (dev_name_len + 2) : 0)) > MAX_BLE_MANUFACTURER_DATA_LEN) { ESP_LOGE(TAG, "Manufacturer data length is more than the max allowed size; expect truncated mfg_data "); /* Truncate the mfg_data to fit in the available length */ mfg_data_len = MAX_BLE_MANUFACTURER_DATA_LEN - (dev_name_len ? (dev_name_len + 2) : 0); } ble_config->manufacturer_data = custom_manufacturer_data; ble_config->manufacturer_data_len = mfg_data_len; } } else { ble_config->manufacturer_data = NULL; ble_config->manufacturer_data_len = 0; } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) if (custom_ble_addr) { ble_config->ble_addr = custom_ble_addr; } else { ble_config->ble_addr = NULL; } #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) return ESP_OK; } static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) { if (!config) { ESP_LOGE(TAG, "Cannot set null configuration"); return ESP_ERR_INVALID_ARG; } if (!endpoint_name) { ESP_LOGE(TAG, "EP name cannot be null"); return ESP_ERR_INVALID_ARG; } protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; char *copy_ep_name = strdup(endpoint_name); if (!copy_ep_name) { ESP_LOGE(TAG, "Error allocating memory for EP name"); return ESP_ERR_NO_MEM; } protocomm_ble_name_uuid_t *lookup_table = ( realloc(ble_config->nu_lookup, (ble_config->nu_lookup_count + 1) * sizeof(protocomm_ble_name_uuid_t))); if (!lookup_table) { ESP_LOGE(TAG, "Error allocating memory for EP-UUID lookup table"); free(copy_ep_name); return ESP_ERR_NO_MEM; } lookup_table[ble_config->nu_lookup_count].name = copy_ep_name; lookup_table[ble_config->nu_lookup_count].uuid = uuid; ble_config->nu_lookup = lookup_table; ble_config->nu_lookup_count += 1; return ESP_OK; } /* Used when both BT and BLE are not needed by application */ void network_prov_scheme_ble_event_cb_free_btdm(void *user_data, network_prov_cb_event_t event, void *event_data) { #ifdef CONFIG_BT_CONTROLLER_ENABLED esp_err_t err; switch (event) { case NETWORK_PROV_INIT: /* Release BT memory, as we need only BLE */ err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); if (err != ESP_OK) { ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); } else { ESP_LOGI(TAG, "BT memory released"); } break; case NETWORK_PROV_DEINIT: #ifndef CONFIG_NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV /* Release memory used by BLE and Bluedroid host stack */ err = esp_bt_mem_release(ESP_BT_MODE_BTDM); if (err != ESP_OK) { ESP_LOGE(TAG, "bt_mem_release of BTDM failed %d", err); } else { ESP_LOGI(TAG, "BTDM memory released"); } #endif /* !CONFIG_NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV */ break; default: break; } #endif /* CONFIG_BT_CONTROLLER_ENABLED */ } /* Used when BT is not needed by application */ void network_prov_scheme_ble_event_cb_free_bt(void *user_data, network_prov_cb_event_t event, void *event_data) { #ifdef CONFIG_BT_CONTROLLER_ENABLED esp_err_t err; switch (event) { case NETWORK_PROV_INIT: /* Release BT memory, as we need only BLE */ err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); if (err != ESP_OK) { ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); } else { ESP_LOGI(TAG, "BT memory released"); } break; default: break; } #endif /* CONFIG_BT_CONTROLLER_ENABLED */ } /* Used when BLE is not needed by application */ void network_prov_scheme_ble_event_cb_free_ble(void *user_data, network_prov_cb_event_t event, void *event_data) { #ifdef CONFIG_BT_CONTROLLER_ENABLED esp_err_t err; switch (event) { case NETWORK_PROV_DEINIT: #ifndef CONFIG_NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV /* Release memory used by BLE stack */ err = esp_bt_mem_release(ESP_BT_MODE_BLE); if (err != ESP_OK) { ESP_LOGE(TAG, "bt_mem_release of BLE failed %d", err); } else { ESP_LOGI(TAG, "BLE memory released"); } #endif /* !CONFIG_NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV */ break; default: break; } #endif /* CONFIG_BT_CONTROLLER_ENABLED */ } const network_prov_scheme_t network_prov_scheme_ble = { .prov_start = prov_start, .prov_stop = protocomm_ble_stop, .new_config = new_config, .delete_config = delete_config, .set_config_service = set_config_service, .set_config_endpoint = set_config_endpoint, #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI .wifi_mode = WIFI_MODE_STA #endif }; ================================================ FILE: network_provisioning/src/scheme_console.c ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include "network_provisioning/scheme_console.h" #include "network_provisioning_priv.h" static const char *TAG = "network_prov_scheme_console"; extern const network_prov_scheme_t network_prov_scheme_console; static esp_err_t prov_start(protocomm_t *pc, void *config) { if (!pc) { ESP_LOGE(TAG, "Protocomm handle cannot be null"); return ESP_ERR_INVALID_ARG; } if (!config) { ESP_LOGE(TAG, "Cannot start with null configuration"); return ESP_ERR_INVALID_ARG; } protocomm_console_config_t *console_config = (protocomm_console_config_t *) config; /* Start protocomm console */ esp_err_t err = protocomm_console_start(pc, console_config); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); return ESP_FAIL; } return ESP_OK; } static void *new_config(void) { protocomm_console_config_t *console_config = malloc(sizeof(protocomm_console_config_t)); if (!console_config) { ESP_LOGE(TAG, "Error allocating memory for new configuration"); return NULL; } protocomm_console_config_t default_config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG(); memcpy(console_config, &default_config, sizeof(default_config)); return console_config; } static void delete_config(void *config) { if (!config) { ESP_LOGE(TAG, "Cannot delete null configuration"); return; } free(config); } static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) { return ESP_OK; } static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) { return ESP_OK; } const network_prov_scheme_t network_prov_scheme_console = { .prov_start = prov_start, .prov_stop = protocomm_console_stop, .new_config = new_config, .delete_config = delete_config, .set_config_service = set_config_service, .set_config_endpoint = set_config_endpoint, #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI .wifi_mode = WIFI_MODE_STA #endif }; ================================================ FILE: network_provisioning/src/scheme_softap.c ================================================ /* * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include #include #include #include #include #include #include "network_provisioning/scheme_softap.h" #include "network_provisioning_priv.h" typedef struct softap_config { protocomm_httpd_config_t httpd_config; char ssid[33]; char password[65]; } network_prov_softap_config_t; static const char *TAG = "network_prov_scheme_softap"; extern const network_prov_scheme_t network_prov_scheme_softap; static void *scheme_softap_prov_httpd_handle; static esp_err_t start_wifi_ap(const char *ssid, const char *pass) { /* Build Wi-Fi configuration for AP mode */ wifi_config_t wifi_config = { .ap = { .max_connection = 5, }, }; /* SSID can be a non NULL terminated string if `ap.ssid_len` is specified * Hence, memcpy is used to support 32 bytes long SSID per 802.11 standard */ const size_t ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid)); memcpy(wifi_config.ap.ssid, ssid, ssid_len); wifi_config.ap.ssid_len = ssid_len; if (strlen(pass) == 0) { memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_OPEN; } else { strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; } /* Run Wi-Fi in AP + STA mode with configuration built above */ esp_err_t err = esp_wifi_set_mode(WIFI_MODE_APSTA); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi mode : %d", err); return err; } err = esp_wifi_set_config(WIFI_IF_AP, &wifi_config); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi config : %d", err); return err; } return ESP_OK; } static esp_err_t prov_start(protocomm_t *pc, void *config) { if (!pc) { ESP_LOGE(TAG, "Protocomm handle cannot be null"); return ESP_ERR_INVALID_ARG; } if (!config) { ESP_LOGE(TAG, "Cannot start with null configuration"); return ESP_ERR_INVALID_ARG; } network_prov_softap_config_t *softap_config = (network_prov_softap_config_t *) config; protocomm_httpd_config_t *httpd_config = &softap_config->httpd_config; if (scheme_softap_prov_httpd_handle) { httpd_config->ext_handle_provided = true; httpd_config->data.handle = scheme_softap_prov_httpd_handle; } /* Start protocomm server on top of HTTP */ esp_err_t err = protocomm_httpd_start(pc, httpd_config); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); return err; } /* Start Wi-Fi softAP with specified ssid and password */ err = start_wifi_ap(softap_config->ssid, softap_config->password); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start Wi-Fi AP"); protocomm_httpd_stop(pc); return err; } return ESP_OK; } static esp_err_t prov_stop(protocomm_t *pc) { esp_err_t err = protocomm_httpd_stop(pc); if (err != ESP_OK) { ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd"); } return err; } static void *new_config(void) { network_prov_softap_config_t *softap_config = calloc(1, sizeof(network_prov_softap_config_t)); if (!softap_config) { ESP_LOGE(TAG, "Error allocating memory for new configuration"); return NULL; } protocomm_httpd_config_t default_config = { .data = { .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() } }; softap_config->httpd_config = default_config; return softap_config; } static void delete_config(void *config) { if (!config) { ESP_LOGE(TAG, "Cannot delete null configuration"); return; } network_prov_softap_config_t *softap_config = (network_prov_softap_config_t *) config; free(softap_config); } static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) { if (!config) { ESP_LOGE(TAG, "Cannot set null configuration"); return ESP_ERR_INVALID_ARG; } if (!service_name) { ESP_LOGE(TAG, "Service name cannot be NULL"); return ESP_ERR_INVALID_ARG; } network_prov_softap_config_t *softap_config = (network_prov_softap_config_t *) config; if (service_key) { const int service_key_len = strlen(service_key); if (service_key_len < 8 || service_key_len >= sizeof(softap_config->password)) { ESP_LOGE(TAG, "Incorrect passphrase length for softAP: %d (Expected: Min - 8, Max - 64)", service_key_len); return ESP_ERR_INVALID_ARG; } strlcpy(softap_config->password, service_key, sizeof(softap_config->password)); } strlcpy(softap_config->ssid, service_name, sizeof(softap_config->ssid)); return ESP_OK; } static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) { return ESP_OK; } void network_prov_scheme_softap_set_httpd_handle(void *handle) { scheme_softap_prov_httpd_handle = handle; } const network_prov_scheme_t network_prov_scheme_softap = { .prov_start = prov_start, .prov_stop = prov_stop, .new_config = new_config, .delete_config = delete_config, .set_config_service = set_config_service, .set_config_endpoint = set_config_endpoint, #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI .wifi_mode = WIFI_MODE_APSTA #endif }; ================================================ FILE: network_provisioning/tool/esp_prov/README.md ================================================ # ESP Provisioning Tool ## Description `esp_prov` - A python-based utility for testing the provisioning examples over a host machine. Usage of `esp-prov` assumes that the provisioning app has specific protocomm endpoints active. These endpoints are active in the provisioning examples and accept specific protobuf data structure: | Endpoint Name | URI (HTTP server on ip:port) | Description | |---------------|------------------------------|------------------------------------------------------------------------------------------| | prov-session | http://ip:port/prov-session | Security endpoint used for session establishment | | prov-config | http://ip:port/prov-config | Endpoint used for configuring Wi-Fi credentials or Thread dataset on device | | proto-ver | http://ip:port/proto-ver | Version endpoint for checking protocol compatibility | | prov-scan | http://ip:port/prov-scan | Endpoint used for scanning Wi-Fi APs or Thread network | | prov-ctrl | http://ip:port/prov-ctrl | Endpoint used for controlling Wi-Fi / Thread provisioning state | | custom-data | http://ip:port/custom-data | Optional endpoint for sending custom data (refer `wifi_prov` or `thread_prov` example) | ## Usage ``` python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > [ Optional parameters... ] ``` ### Parameters * `--help` Print the list of options along with brief descriptions * `--verbose`, `-v` Sets the verbosity level of output log * `--transport ` - Three options are available: * `softap` - for SoftAP + HTTPD based provisioning * Requires the device to be running in Wi-Fi SoftAP mode and hosting an HTTP server supporting specific endpoint URIs * The client needs to be connected to the device softAP network before running the `esp_prov` tool. * `ble` - for Bluetooth LE based provisioning * Supports Linux, Windows and macOS; redirected to console if dependencies are not met * Assumes that the provisioning endpoints are active on the device with specific Bluetooth LE service UUIDs * `console` - for debugging via console-based provisioning * The client->device commands are printed to STDOUT and device->client messages are accepted via STDIN. * This is to be used when the device is accepting provisioning commands on UART console. * `httpd` - the script works the same as for `softap`. This could be used on any other network interface than WiFi soft AP, e.g. Ethernet or USB. * `--service_name ` (Optional) - When transport mode is `ble`, this specifies the Bluetooth LE device name to which connection is to be established for provisioned. If not provided, Bluetooth LE scanning is initiated and a list of nearby devices, as seen by the host, is displayed, of which the target device can be chosen. - When transport mode is `softap` or `httpd`, this specifies the HTTP server hostname / IP which is running the provisioning service, on the SoftAP network (or any other interface for `httpd` mode) of the device which is to be provisioned. This defaults to `192.168.4.1:80` if not specified * `--ssid ` (Optional) - For specifying the SSID of the Wi-Fi AP to which the device is to connect after provisioning. - If not provided, scanning is initiated and scan results, as seen by the device, are displayed, of which an SSID can be picked and the corresponding password specified. * `--passphrase ` (Optional) - For specifying the password of the Wi-Fi AP to which the device is to connect after provisioning. - Only used when corresponding SSID is provided using the `--ssid` option * `--dataset_tlvs ` (Optional) - For specifying the Dataset Tlvs of the Thread network to which the device is to connect after provisioning. * `--sec_ver ` - For specifying the version of protocomm endpoint security to use. Following 3 versions are supported: * `0` for `protocomm_security0` - No security * `1` for `protocomm_security1` - X25519 key exchange + Authentication using Proof of Possession (PoP) + AES-CTR encryption * `2` for `protocomm_security2` - Secure Remote Password protocol (SRP6a) + AES-GCM encryption * `--pop ` (Optional) - For specifying optional Proof of Possession string to use for protocomm endpoint security version 1 - Ignored when other security versions are used * `--sec2_username ` (Optional) - For specifying optional username to use for protocomm endpoint security version 2 - Ignored when other security versions are used * `--sec2_pwd ` (Optional) - For specifying optional password to use for protocomm endpoint security version 2 - Ignored when other security versions are used * `--sec2_gen_cred` (Optional) - For generating the `SRP6a` credentials (salt and verifier) from the provided username and password for protocomm endpoint security version 2 - Ignored when other security versions are used * `--sec2_salt_len ` (Optional) - For specifying the optional `SRP6a` salt length to be used for generating protocomm endpoint security version 2 credentials - Ignored when other security versions are used and the `--sec2_gen_cred` option is not set * `--reset` (Optional) - Resets internal state machine of the device and clears provisioned credentials; to be used only in case of provisioning failures * `--reprov` (Optional) - Resets internal state machine of the device and clears provisioned credentials; to be used only in case the device is to be provisioned again for new credentials after a previous successful provisioning * `--ble_adapter ` (Optional) - For specifying the HCI adapter to use for Bluetooth LE transport (default: `hci0`). - Useful when working with `vhci_bridge` for emulated devices, e.g. `--ble_adapter hci1`. - Only relevant when transport mode is `ble` * `--custom_data ` (Optional) An information string can be sent to the `custom-data` endpoint during provisioning using this argument. (Assumes the provisioning app has an endpoint called `custom-data` - see [wifi_prov](../../examples/wifi_prov/) example for implementation details). ### Example Usage Please refer to the `README.md` file with the `wifi_prov` or `thread_prov` example present under `{path-to-network_provisioning-component}/examples`. This example uses specific options of the `esp_prov` tool and gives an overview of simple as well as advanced usage scenarios. ## Dependencies This requires the following python libraries to run: * `bleak` * `protobuf` * `cryptography` To install the dependency packages needed, please run the following command in `$IDF_PATH` directory: For ESP-IDF v5.1: ```shell bash install.sh --enable-ttfw ``` For ESP-IDF v5.2 to v5.5: ```shell bash install.sh --enable-pytest ``` For ESP-IDF v6.0 or later: ```shell bash install.sh --enable-ci ``` **Note:** For troubleshooting errors with Bluetooth LE transport, please refer this [link](https://bleak.readthedocs.io/en/latest/troubleshooting.html). ================================================ FILE: network_provisioning/tool/esp_prov/__init__.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .esp_prov import * # noqa: export esp_prov module to users ================================================ FILE: network_provisioning/tool/esp_prov/esp_prov.py ================================================ #!/usr/bin/env python # # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # import argparse import asyncio import json import os import sys import textwrap import time from getpass import getpass from utils import int_to_hex_str try: import prov import security import transport except ImportError: idf_path = os.environ['IDF_PATH'] sys.path.insert(0, idf_path + '/components/protocomm/python') sys.path.insert(1, idf_path + '/tools/esp_prov') import prov import security import transport # Set this to true to allow exceptions to be thrown config_throw_except = False def on_except(err): if config_throw_except: raise RuntimeError(err) else: print(err) def get_security(secver, sec_patch_ver, username, password, pop='', verbose=False): if secver == 2: return security.Security2(sec_patch_ver, username, password, verbose) elif secver == 1: return security.Security1(pop, verbose) elif secver == 0: return security.Security0(verbose) return None def get_thread_dataset_tlvs(network_info, network_key): # Get the simple dataset tlvs from the PAN ID, Channel, Extended PAN ID, and Network Key pan_id = network_info['pan_id'] channel = network_info['channel'] ext_pan_id = network_info['ext_pan_id'] dataset_tlvs = '00030000' + int_to_hex_str(channel) + '0208' + ext_pan_id + '0510' + network_key + '0102' + int_to_hex_str(pan_id) return dataset_tlvs async def get_transport(sel_transport, service_name, ble_adapter=transport.DEFAULT_BLE_ADAPTER): try: tp = None if (sel_transport in ['softap', 'httpd']): if service_name is None: service_name = '192.168.4.1:80' tp = transport.Transport_HTTP(service_name) elif (sel_transport == 'ble'): # BLE client is now capable of automatically figuring out # the primary service from the advertisement data and the # characteristics corresponding to each endpoint. # Below, the service_uuid field and 16bit UUIDs in the nu_lookup # table are provided only to support devices running older firmware, # in which case, the automated discovery will fail and the client # will fallback to using the provided UUIDs instead nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'} tp = transport.Transport_BLE(service_uuid='021a9004-0382-4aea-bff4-6b3f1c5adfb4', nu_lookup=nu_lookup, ble_adapter=ble_adapter) await tp.connect(devname=service_name) elif (sel_transport == 'console'): tp = transport.Transport_Console() return tp except RuntimeError as e: on_except(e) return None async def version_match(tp, protover, verbose=False): try: response = await tp.send_data('proto-ver', protover) if verbose: print('proto-ver response : ', response) # First assume this to be a simple version string if response.lower() == protover.lower(): return True try: # Else interpret this as JSON structure containing # information with versions and capabilities of both # provisioning service and application info = json.loads(response) if info['prov']['ver'].lower() == protover.lower(): return True except ValueError: # If decoding as JSON fails, it means that capabilities # are not supported return False except Exception as e: on_except(e) return None async def has_capability(tp, capability='none', verbose=False): # Note : default value of `capability` argument cannot be empty string # because protocomm_httpd expects non zero content lengths try: response = await tp.send_data('proto-ver', capability) if verbose: print('proto-ver response : ', response) try: # Interpret this as JSON structure containing # information with versions and capabilities of both # provisioning service and application info = json.loads(response) supported_capabilities = info['prov']['cap'] if capability.lower() == 'none': # No specific capability to check, but capabilities # feature is present so return True return True elif capability in supported_capabilities: return True return False except ValueError: # If decoding as JSON fails, it means that capabilities # are not supported return False except RuntimeError as e: on_except(e) return False async def get_version(tp): response = None try: response = await tp.send_data('proto-ver', '---') except RuntimeError as e: on_except(e) response = '' return response async def get_sec_patch_ver(tp, verbose=False): response = await get_version(tp) if verbose: print('proto-ver response : ', response) try: # Interpret this as JSON structure containing # information with security version information info = json.loads(response) try: sec_patch_ver = info['prov']['sec_patch_ver'] except KeyError: sec_patch_ver = 0 return sec_patch_ver except ValueError: # If decoding as JSON fails, we assume default patch level return 0 async def establish_session(tp, sec): try: response = None while True: request = sec.security_session(response) if request is None: break response = await tp.send_data('prov-session', request) if (response is None): return False return True except RuntimeError as e: on_except(e) return None async def custom_config(tp, sec, custom_info, custom_ver): try: message = prov.custom_config_request(sec, custom_info, custom_ver) response = await tp.send_data('custom-config', message) return (prov.custom_config_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def custom_data(tp, sec, custom_data): try: message = prov.custom_data_request(sec, custom_data) response = await tp.send_data('custom-data', message) return (prov.custom_data_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def scan_wifi_APs(sel_transport, tp, sec): APs = [] group_channels = 0 readlen = 100 if sel_transport in ['softap', 'httpd']: # In case of softAP/httpd we must perform the scan on individual channels, one by one, # so that the Wi-Fi controller gets ample time to send out beacons (necessary to # maintain connectivity with authenticated stations. As scanning one channel at a # time will be slow, we can group more than one channels to be scanned in quick # succession, hence speeding up the scan process. Though if too many channels are # present in a group, the controller may again miss out on sending beacons. Hence, # the application must should use an optimum value. The following value usually # works out in most cases group_channels = 5 elif sel_transport == 'ble': # Read at most 4 entries at a time. This is because if we are using BLE transport # then the response packet size should not exceed the present limit of 256 bytes of # characteristic value imposed by protocomm_ble. This limit may be removed in the # future readlen = 4 try: message = prov.scan_start_request('wifi', sec, blocking=True, group_channels=group_channels) start_time = time.time() response = await tp.send_data('prov-scan', message) stop_time = time.time() print('++++ Scan process executed in ' + str(stop_time - start_time) + ' sec') prov.scan_start_response(sec, response) message = prov.scan_status_request('wifi', sec) response = await tp.send_data('prov-scan', message) result = prov.scan_status_response(sec, response) print('++++ Scan results : ' + str(result['count'])) if result['count'] != 0: index = 0 remaining = result['count'] while remaining: count = [remaining, readlen][remaining > readlen] message = prov.scan_result_request('wifi', sec, index, count) response = await tp.send_data('prov-scan', message) APs += prov.scan_result_response(sec, response) remaining -= count index += count except RuntimeError as e: on_except(e) return None return APs async def send_wifi_config(tp, sec, ssid, passphrase): try: message = prov.config_set_config_request('wifi', sec, ssid, passphrase) response = await tp.send_data('prov-config', message) return (prov.config_set_config_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def apply_wifi_config(tp, sec): try: message = prov.config_apply_config_request('wifi', sec) response = await tp.send_data('prov-config', message) return (prov.config_apply_config_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def get_wifi_config(tp, sec): try: message = prov.config_get_status_request('wifi', sec) response = await tp.send_data('prov-config', message) return prov.config_get_status_response(sec, response) except RuntimeError as e: on_except(e) return None async def wait_wifi_connected(tp, sec): """ Wait for provisioning to report Wi-Fi is connected Returns True if Wi-Fi connection succeeded, False if connection consistently failed """ TIME_PER_POLL = 5 retry = 3 while True: time.sleep(TIME_PER_POLL) print('\n==== Wi-Fi connection state ====') ret = await get_wifi_config(tp, sec) if ret == 'connecting': continue elif ret == 'connected': print('==== Provisioning was successful ====') return True elif retry > 0: retry -= 1 print('Waiting to poll status again (status %s, %d tries left)...' % (ret, retry)) else: print('---- Provisioning failed! ----') return False async def reset_wifi(tp, sec): try: message = prov.ctrl_reset_request('wifi', sec) response = await tp.send_data('prov-ctrl', message) prov.ctrl_reset_response(sec, response) except RuntimeError as e: on_except(e) return None async def reprov_wifi(tp, sec): try: message = prov.ctrl_reprov_request('wifi', sec) response = await tp.send_data('prov-ctrl', message) prov.ctrl_reprov_response(sec, response) except RuntimeError as e: on_except(e) return None async def scan_thread_networks(sel_transport, tp, sec): Networks = [] # Read at most 4 entries at a time. This is because if we are using BLE transport # then the response packet size should not exceed the present limit of 256 bytes of # characteristic value imposed by protocomm_ble. This limit may be removed in the # future readlen = 4 try: message = prov.scan_start_request('thread', sec, blocking=True, group_channels=0) start_time = time.time() response = await tp.send_data('prov-scan', message) stop_time = time.time() print('++++ Scan process executed in ' + str(stop_time - start_time) + ' sec') prov.scan_start_response(sec, response) message = prov.scan_status_request('thread', sec) response = await tp.send_data('prov-scan', message) result = prov.scan_status_response(sec, response) print('++++ Scan results : ' + str(result['count'])) if result['count'] != 0: index = 0 remaining = result['count'] while remaining: count = [remaining, readlen][remaining > readlen] message = prov.scan_result_request('thread', sec, index, count) response = await tp.send_data('prov-scan', message) Networks += prov.scan_result_response(sec, response) remaining -= count index += count except RuntimeError as e: on_except(e) return None return Networks async def send_thread_config(tp, sec, dataset_tlvs): try: message = prov.config_set_config_request('thread', sec, dataset_tlvs) response = await tp.send_data('prov-config', message) return (prov.config_set_config_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def apply_thread_config(tp, sec): try: message = prov.config_apply_config_request('thread', sec) response = await tp.send_data('prov-config', message) return (prov.config_apply_config_response(sec, response) == 0) except RuntimeError as e: on_except(e) return None async def get_thread_config(tp, sec): try: message = prov.config_get_status_request('thread', sec) response = await tp.send_data('prov-config', message) return prov.config_get_status_response(sec, response) except RuntimeError as e: on_except(e) return None async def wait_thread_connected(tp, sec): """ Wait for provisioning to report Thread is attached Returns True if Thread connection succeeded, False if connection consistently failed """ TIME_PER_POLL = 5 retry = 3 while True: time.sleep(TIME_PER_POLL) print('\n==== Thread connection state ====') ret = await get_thread_config(tp, sec) if ret == 'attaching': continue elif ret == 'attached': print('==== Provisioning was successful ====') return True elif retry > 0: retry -= 1 print('Waiting to poll status again (status %s, %d tries left)...' % (ret, retry)) else: print('---- Provisioning failed! ----') return False async def reset_thread(tp, sec): try: message = prov.ctrl_reset_request('thread', sec) response = await tp.send_data('prov-ctrl', message) prov.ctrl_reset_response(sec, response) except RuntimeError as e: on_except(e) return None async def reprov_thread(tp, sec): try: message = prov.ctrl_reprov_request('thread', sec) response = await tp.send_data('prov-ctrl', message) prov.ctrl_reprov_response(sec, response) except RuntimeError as e: on_except(e) return None def desc_format(*args): desc = '' for arg in args: desc += textwrap.fill(replace_whitespace=False, text=arg) + '\n' return desc async def main(): parser = argparse.ArgumentParser(description=desc_format( 'ESP Provisioning tool for configuring devices ' 'running protocomm based provisioning service.', 'See esp-idf/examples/provisioning for sample applications'), formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--transport', required=True, dest='mode', type=str, help=desc_format( 'Mode of transport over which provisioning is to be performed.', 'This should be one of "softap", "ble", "console" (or "httpd" which is an alias of softap)')) parser.add_argument('--service_name', dest='name', type=str, help=desc_format( 'This specifies the name of the provisioning service to connect to, ' 'depending upon the mode of transport :', '\t- transport "ble" : The BLE Device Name', '\t- transport "softap/httpd" : HTTP Server hostname or IP', '\t (default "192.168.4.1:80")')) parser.add_argument('--proto_ver', dest='version', type=str, default='', help=desc_format( 'This checks the protocol version of the provisioning service running ' 'on the device before initiating Wi-Fi configuration')) parser.add_argument('--sec_ver', dest='secver', type=int, default=None, help=desc_format( 'Protocomm security scheme used by the provisioning service for secure ' 'session establishment. Accepted values are :', '\t- 0 : No security', '\t- 1 : X25519 key exchange + AES-CTR encryption', '\t + Authentication using Proof of Possession (PoP)', '\t- 2 : SRP6a + AES-GCM encryption', 'In case device side application uses IDF\'s provisioning manager, ' 'the compatible security version is automatically determined from ' 'capabilities retrieved via the version endpoint')) parser.add_argument('--pop', dest='sec1_pop', type=str, default='', help=desc_format( 'This specifies the Proof of possession (PoP) when security scheme 1 ' 'is used')) parser.add_argument('--sec2_username', dest='sec2_usr', type=str, default='', help=desc_format( 'Username for security scheme 2 (SRP6a)')) parser.add_argument('--sec2_pwd', dest='sec2_pwd', type=str, default='', help=desc_format( 'Password for security scheme 2 (SRP6a)')) parser.add_argument('--sec2_gen_cred', help='Generate salt and verifier for security scheme 2 (SRP6a)', action='store_true') parser.add_argument('--sec2_salt_len', dest='sec2_salt_len', type=int, default=16, help=desc_format( 'Salt length for security scheme 2 (SRP6a)')) parser.add_argument('--ssid', dest='ssid', type=str, default='', help=desc_format( 'This configures the device to use SSID of the Wi-Fi network to which ' 'we would like it to connect to permanently, once provisioning is complete. ' 'If Wi-Fi scanning is supported by the provisioning service, this need not ' 'be specified')) parser.add_argument('--passphrase', dest='passphrase', type=str, help=desc_format( 'This configures the device to use Passphrase for the Wi-Fi network to which ' 'we would like it to connect to permanently, once provisioning is complete. ' 'If Wi-Fi scanning is supported by the provisioning service, this need not ' 'be specified')) parser.add_argument('--dataset_tlvs', dest='dataset_tlvs', type=str, help=desc_format( 'This configures the device to use Dataset Tlvs of the Thread network to ' 'which we would like it to attach to permanently, once provisioning is ' 'complete. If Thread scanning is supported by the provisioning service, this ' 'need not be specified')) parser.add_argument('--custom_data', dest='custom_data', type=str, default='', help=desc_format( 'This is an optional parameter, only intended for use with ' '"examples/wifi_prov"')) parser.add_argument('--reset', help='Reset WiFi', action='store_true') parser.add_argument('--reprov', help='Reprovision WiFi', action='store_true') parser.add_argument('-v','--verbose', help='Increase output verbosity', action='store_true') parser.add_argument('--ble_adapter', dest='ble_adapter', type=str, default=transport.DEFAULT_BLE_ADAPTER, help=desc_format( f'HCI adapter to use for BLE (default: {transport.DEFAULT_BLE_ADAPTER}).', 'Use with vhci_bridge for emulated devices, e.g. --ble_adapter hci1')) args = parser.parse_args() if args.secver == 2 and args.sec2_gen_cred: if not args.sec2_usr or not args.sec2_pwd: raise ValueError('Username/password cannot be empty for security scheme 2 (SRP6a)') print('==== Salt-verifier for security scheme 2 (SRP6a) ====') security.sec2_gen_salt_verifier(args.sec2_usr, args.sec2_pwd, args.sec2_salt_len) sys.exit() obj_transport = await get_transport(args.mode.lower(), args.name, args.ble_adapter) if obj_transport is None: raise RuntimeError('Failed to establish connection') try: sec_patch_ver = 0 # If security version not specified check in capabilities if args.secver is None: # First check if capabilities are supported or not if not await has_capability(obj_transport): print('Security capabilities could not be determined, please specify "--sec_ver" explicitly') raise ValueError('Invalid Security Version') # When no_sec is present, use security 0, else security 1 args.secver = int(not await has_capability(obj_transport, 'no_sec')) print(f'==== Security Scheme: {args.secver} ====') if (args.secver == 1): if not await has_capability(obj_transport, 'no_pop'): if len(args.sec1_pop) == 0: prompt_str = 'Proof of Possession required: ' args.sec1_pop = getpass(prompt_str) elif len(args.sec1_pop) != 0: print('Proof of Possession will be ignored') args.sec1_pop = '' if (args.secver == 2): sec_patch_ver = await get_sec_patch_ver(obj_transport, args.verbose) if len(args.sec2_usr) == 0: args.sec2_usr = input('Security Scheme 2 - SRP6a Username required: ') if len(args.sec2_pwd) == 0: prompt_str = 'Security Scheme 2 - SRP6a Password required: ' args.sec2_pwd = getpass(prompt_str) obj_security = get_security(args.secver, sec_patch_ver, args.sec2_usr, args.sec2_pwd, args.sec1_pop, args.verbose) if obj_security is None: raise ValueError('Invalid Security Version') if args.version != '': print('\n==== Verifying protocol version ====') if not await version_match(obj_transport, args.version, args.verbose): raise RuntimeError('Error in protocol version matching') print('==== Verified protocol version successfully ====') print('\n==== Starting Session ====') if not await establish_session(obj_transport, obj_security): print('Failed to establish session. Ensure that security scheme and proof of possession are correct') raise RuntimeError('Error in establishing session') print('==== Session Established ====') if await has_capability(obj_transport, 'thread_prov'): if args.reset: print('==== Resetting Thread====') await reset_thread(obj_transport, obj_security) sys.exit() if args.reprov: print('==== Reprovisioning Thread====') await reprov_thread(obj_transport, obj_security) sys.exit() if args.dataset_tlvs is None: if not await has_capability(obj_transport, 'thread_scan'): prompt_str = 'Enter Thread dataset tlvs string : ' args.dataset_tlvs = input(prompt_str) else: while True: print('\n==== Scanning Thread Networks ====') start_time = time.time() Networks = await scan_thread_networks(args.mode.lower(), obj_transport, obj_security) end_time = time.time() print('\n++++ Scan finished in ' + str(end_time - start_time) + ' sec') if Networks is None: raise RuntimeError('Error in scanning Thread Networks') if len(Networks) == 0: print('No Thread Networks found!') sys.exit() print('==== Thread Scan results ====') print('{0: >4} {1: <8} {2: <18} {3: <18} {4: <18} {5: <4} {6: <4} {7: <16}'.format( 'S.N.', 'PAN ID', 'EXT PAN ID', 'NAME', 'EXT ADDR', 'CHN', 'RSSI', 'LQI')) for i in range(len(Networks)): print('[{0: >2}] {1: <8} {2: <18} {3: <18} {4: <18} {5: <4} {6: <4} {7: <16}'.format( i + 1, Networks[i]['pan_id'], Networks[i]['ext_pan_id'], Networks[i]['network_name'], Networks[i]['ext_addr'], Networks[i]['channel'], Networks[i]['rssi'], Networks[i]['lqi'])) while True: try: select = int(input('Select Network by number (0 to rescan) : ')) if select < 0 or select > len(Networks): raise ValueError break except ValueError: print('Invalid input! Retry') if select != 0: network_key = getpass('Enter Thread network key string : ') args.dataset_tlvs = get_thread_dataset_tlvs(Networks[select], network_key) break print('\n==== Sending Thread Dataset to Target ====') if not await send_thread_config(obj_transport, obj_security, args.dataset_tlvs): raise RuntimeError('Error in send Thread config') print('==== Thread Dataset sent successfully ====') print('\n==== Applying Thread Config to Target ====') if not await apply_thread_config(obj_transport, obj_security): raise RuntimeError('Error in apply Thread config') print('==== Apply config sent successfully ====') await wait_thread_connected(obj_transport, obj_security) else: if not await has_capability(obj_transport, 'wifi_prov'): print('Use wifi_provisioning for device without "wifi_prov" capabilities') print('The device might be using previous wifi_provisioning in ESP-IDF') if args.reset: print('==== Resetting WiFi====') await reset_wifi(obj_transport, obj_security) sys.exit() if args.reprov: print('==== Reprovisioning WiFi====') await reprov_wifi(obj_transport, obj_security) sys.exit() if args.custom_data != '': print('\n==== Sending Custom data to Target ====') if not await custom_data(obj_transport, obj_security, args.custom_data): raise RuntimeError('Error in custom data') print('==== Custom data sent successfully ====') if args.ssid == '': if not await has_capability(obj_transport, 'wifi_scan'): raise RuntimeError('Wi-Fi Scan List is not supported by provisioning service') while True: print('\n==== Scanning Wi-Fi APs ====') start_time = time.time() APs = await scan_wifi_APs(args.mode.lower(), obj_transport, obj_security) end_time = time.time() print('\n++++ Scan finished in ' + str(end_time - start_time) + ' sec') if APs is None: raise RuntimeError('Error in scanning Wi-Fi APs') if len(APs) == 0: print('No APs found!') sys.exit() print('==== Wi-Fi Scan results ====') print('{0: >4} {1: <33} {2: <12} {3: >4} {4: <4} {5: <16}'.format( 'S.N.', 'SSID', 'BSSID', 'CHN', 'RSSI', 'AUTH')) for i in range(len(APs)): print('[{0: >2}] {1: <33} {2: <12} {3: >4} {4: <4} {5: <16}'.format( i + 1, APs[i]['ssid'], APs[i]['bssid'], APs[i]['channel'], APs[i]['rssi'], APs[i]['auth'])) while True: try: select = int(input('Select AP by number (0 to rescan) : ')) if select < 0 or select > len(APs): raise ValueError break except ValueError: print('Invalid input! Retry') if select != 0: break args.ssid = APs[select - 1]['ssid'] if args.passphrase is None: prompt_str = 'Enter passphrase for {0} : '.format(args.ssid) args.passphrase = getpass(prompt_str) print('\n==== Sending Wi-Fi Credentials to Target ====') if not await send_wifi_config(obj_transport, obj_security, args.ssid, args.passphrase): raise RuntimeError('Error in send Wi-Fi config') print('==== Wi-Fi Credentials sent successfully ====') print('\n==== Applying Wi-Fi Config to Target ====') if not await apply_wifi_config(obj_transport, obj_security): raise RuntimeError('Error in apply Wi-Fi config') print('==== Apply config sent successfully ====') await wait_wifi_connected(obj_transport, obj_security) finally: await obj_transport.disconnect() if __name__ == '__main__': asyncio.run(main()) ================================================ FILE: network_provisioning/tool/esp_prov/proto/__init__.py ================================================ # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # import importlib.util import os import sys from importlib.abc import Loader from typing import Any def _load_source(name: str, path: str) -> Any: spec = importlib.util.spec_from_file_location(name, path) if not spec: return None module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module assert isinstance(spec.loader, Loader) spec.loader.exec_module(module) return module idf_path = os.environ['IDF_PATH'] esp_prov_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # protocomm component related python files generated from .proto files constants_pb2 = _load_source('constants_pb2', idf_path + '/components/protocomm/python/constants_pb2.py') sec0_pb2 = _load_source('sec0_pb2', idf_path + '/components/protocomm/python/sec0_pb2.py') sec1_pb2 = _load_source('sec1_pb2', idf_path + '/components/protocomm/python/sec1_pb2.py') sec2_pb2 = _load_source('sec2_pb2', idf_path + '/components/protocomm/python/sec2_pb2.py') session_pb2 = _load_source('session_pb2', idf_path + '/components/protocomm/python/session_pb2.py') # network_provisioning component related python files generated from .proto files network_constants_pb2 = _load_source('network_constants_pb2', esp_prov_path + '/../../python/network_constants_pb2.py') network_config_pb2 = _load_source('network_config_pb2', esp_prov_path + '/../../python/network_config_pb2.py') network_scan_pb2 = _load_source('network_scan_pb2', esp_prov_path + '/../../python/network_scan_pb2.py') network_ctrl_pb2 = _load_source('network_ctrl_pb2', esp_prov_path + '/../../python/network_ctrl_pb2.py') ================================================ FILE: network_provisioning/tool/esp_prov/prov/__init__.py ================================================ # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .custom_prov import * # noqa F403 from .network_ctrl import * # noqa F403 from .network_prov import * # noqa F403 from .network_scan import * # noqa F403 ================================================ FILE: network_provisioning/tool/esp_prov/prov/custom_prov.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for `custom-config` protocomm endpoint from utils import str_to_bytes def print_verbose(security_ctx, data): if (security_ctx.verbose): print(f'\x1b[32;20m++++ {data} ++++\x1b[0m') def custom_data_request(security_ctx, data): # Encrypt the custom data enc_cmd = security_ctx.encrypt_data(str_to_bytes(data)) print_verbose(security_ctx, f'Client -> Device (CustomData cmd): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def custom_data_response(security_ctx, response_data): # Decrypt response packet decrypt = security_ctx.decrypt_data(str_to_bytes(response_data)) print(f'++++ CustomData response: {str(decrypt)}++++') return 0 ================================================ FILE: network_provisioning/tool/esp_prov/prov/network_ctrl.py ================================================ # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for Wi-Fi State Controlling import proto from utils import str_to_bytes def print_verbose(security_ctx, data): if (security_ctx.verbose): print(f'\x1b[32;20m++++ {data} ++++\x1b[0m') def ctrl_reset_request(network_type, security_ctx): # Form protobuf request packet for CtrlReset command cmd = proto.network_ctrl_pb2.NetworkCtrlPayload() if network_type == 'wifi': cmd.msg = proto.network_ctrl_pb2.TypeCmdCtrlWifiReset elif network_type == 'thread': cmd.msg = proto.network_ctrl_pb2.TypeCmdCtrlThreadReset else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdCtrlReset): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def ctrl_reset_response(security_ctx, response_data): # Interpret protobuf response packet from CtrlReset command dec_resp = security_ctx.decrypt_data(str_to_bytes(response_data)) resp = proto.network_ctrl_pb2.NetworkCtrlPayload() resp.ParseFromString(dec_resp) print_verbose(security_ctx, f'CtrlReset status: 0x{str(resp.status)}') if resp.status != 0: raise RuntimeError def ctrl_reprov_request(network_type, security_ctx): # Form protobuf request packet for CtrlReprov command cmd = proto.network_ctrl_pb2.NetworkCtrlPayload() if network_type == 'wifi': cmd.msg = proto.network_ctrl_pb2.TypeCmdCtrlWifiReprov elif network_type == 'thread': cmd.msg = proto.network_ctrl_pb2.TypeCmdCtrlThreadReprov else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdCtrlReset): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def ctrl_reprov_response(security_ctx, response_data): # Interpret protobuf response packet from CtrlReprov command dec_resp = security_ctx.decrypt_data(str_to_bytes(response_data)) resp = proto.network_ctrl_pb2.NetworkCtrlPayload() resp.ParseFromString(dec_resp) print_verbose(security_ctx, f'CtrlReset status: 0x{str(resp.status)}') if resp.status != 0: raise RuntimeError ================================================ FILE: network_provisioning/tool/esp_prov/prov/network_prov.py ================================================ # SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for Wi-Fi provisioning import proto from utils import hex_str_to_bytes, str_to_bytes def print_verbose(security_ctx, data): if (security_ctx.verbose): print(f'++++ {data} ++++') def config_get_status_request(network_type, security_ctx): # Form protobuf request packet for GetStatus command cfg1 = proto.network_config_pb2.NetworkConfigPayload() if network_type == 'wifi': cfg1.msg = proto.network_config_pb2.TypeCmdGetWifiStatus cmd_get_wifi_status = proto.network_config_pb2.CmdGetWifiStatus() cfg1.cmd_get_wifi_status.MergeFrom(cmd_get_wifi_status) elif network_type == 'thread': cfg1.msg = proto.network_config_pb2.TypeCmdGetThreadStatus cmd_get_thread_status = proto.network_config_pb2.CmdGetThreadStatus() cfg1.cmd_get_thread_status.MergeFrom(cmd_get_thread_status) else: raise RuntimeError encrypted_cfg = security_ctx.encrypt_data(cfg1.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdGetStatus): 0x{encrypted_cfg.hex()}') return encrypted_cfg.decode('latin-1') def config_get_status_response(security_ctx, response_data): # Interpret protobuf response packet from GetStatus command decrypted_message = security_ctx.decrypt_data(str_to_bytes(response_data)) cmd_resp1 = proto.network_config_pb2.NetworkConfigPayload() cmd_resp1.ParseFromString(decrypted_message) print_verbose(security_ctx, f'CmdGetStatus type: {str(cmd_resp1.msg)}') if cmd_resp1.msg == proto.network_config_pb2.TypeRespGetWifiStatus: print_verbose(security_ctx, f'CmdGetStatus status: {str(cmd_resp1.resp_get_wifi_status.status)}') if cmd_resp1.resp_get_wifi_status.wifi_sta_state == 0: print('==== WiFi state: Connected ====') return 'connected' elif cmd_resp1.resp_get_wifi_status.wifi_sta_state == 1: print('++++ WiFi state: Connecting... ++++') if cmd_resp1.resp_get_wifi_status.HasField('attempt_failed'): if cmd_resp1.resp_get_wifi_status.attempt_failed.attempts_remaining: print(cmd_resp1.resp_get_wifi_status) else: print('attempt_failed {\n attempts_remaining: 0\n}') return 'connecting' elif cmd_resp1.resp_get_wifi_status.wifi_sta_state == 2: print('---- WiFi state: Disconnected ----') return 'disconnected' elif cmd_resp1.resp_get_wifi_status.wifi_sta_state == 3: print('---- WiFi state: Connection Failed ----') if cmd_resp1.resp_get_wifi_status.wifi_fail_reason == 0: print('---- Failure reason: Incorrect Password ----') elif cmd_resp1.resp_get_wifi_status.wifi_fail_reason == 1: print('---- Failure reason: Incorrect SSID ----') return 'failed' elif cmd_resp1.msg == proto.network_config_pb2.TypeRespGetThreadStatus: print_verbose(security_ctx, f'CmdGetStatus status: {str(cmd_resp1.resp_get_thread_status.status)}') if cmd_resp1.resp_get_thread_status.thread_state == 0: print('==== Thread state: Attached ====') return 'attached' elif cmd_resp1.resp_get_thread_status.thread_state == 1: print('==== Thread state: Attaching ====') return 'attaching' elif cmd_resp1.resp_get_thread_status.thread_state == 2: print('==== Thread state: Detached ====') return 'detached' elif cmd_resp1.resp_get_thread_status.thread_state == 3: print('==== Thread state: Attaching Failed ====') if cmd_resp1.resp_get_thread_status.thread_fail_reason == 0: print('---- Failure reason: Invalid Dataset ----') elif cmd_resp1.resp_get_thread_status.thread_fail_reason == 1: print('---- Failure reason: Network Not Found ----') return 'failed' return 'unknown' def config_set_config_request(network_type, security_ctx, ssid_or_dataset_tlvs, passphrase=''): # Form protobuf request packet for SetConfig command cmd = proto.network_config_pb2.NetworkConfigPayload() if network_type == 'wifi': cmd.msg = proto.network_config_pb2.TypeCmdSetWifiConfig cmd.cmd_set_wifi_config.ssid = str_to_bytes(ssid_or_dataset_tlvs) cmd.cmd_set_wifi_config.passphrase = str_to_bytes(passphrase) elif network_type == 'thread': cmd.msg = proto.network_config_pb2.TypeCmdSetThreadConfig cmd.cmd_set_thread_config.dataset = hex_str_to_bytes(ssid_or_dataset_tlvs) else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (SetConfig cmd): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def config_set_config_response(security_ctx, response_data): # Interpret protobuf response packet from SetConfig command decrypt = security_ctx.decrypt_data(str_to_bytes(response_data)) cmd_resp4 = proto.network_config_pb2.NetworkConfigPayload() cmd_resp4.ParseFromString(decrypt) if cmd_resp4.msg == proto.network_config_pb2.TypeRespSetWifiConfig: print_verbose(security_ctx, f'SetConfig status: 0x{str(cmd_resp4.resp_set_wifi_config.status)}') return cmd_resp4.resp_set_wifi_config.status elif cmd_resp4.msg == proto.network_config_pb2.TypeRespSetThreadConfig: print_verbose(security_ctx, f'SetConfig status: 0x{str(cmd_resp4.resp_set_thread_config.status)}') return cmd_resp4.resp_set_thread_config.status def config_apply_config_request(network_type, security_ctx): # Form protobuf request packet for ApplyConfig command cmd = proto.network_config_pb2.NetworkConfigPayload() if network_type == 'wifi': cmd.msg = proto.network_config_pb2.TypeCmdApplyWifiConfig elif network_type == 'thread': cmd.msg = proto.network_config_pb2.TypeCmdApplyThreadConfig else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (ApplyConfig cmd): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def config_apply_config_response(security_ctx, response_data): # Interpret protobuf response packet from ApplyConfig command decrypt = security_ctx.decrypt_data(str_to_bytes(response_data)) cmd_resp5 = proto.network_config_pb2.NetworkConfigPayload() cmd_resp5.ParseFromString(decrypt) if cmd_resp5.msg == proto.network_config_pb2.TypeRespApplyWifiConfig: print_verbose(security_ctx, f'ApplyConfig status: 0x{str(cmd_resp5.resp_apply_wifi_config.status)}') return cmd_resp5.resp_apply_wifi_config.status elif cmd_resp5.msg == proto.network_config_pb2.TypeRespApplyThreadConfig: print_verbose(security_ctx, f'ApplyConfig status: 0x{str(cmd_resp5.resp_apply_thread_config.status)}') return cmd_resp5.resp_apply_thread_config.status ================================================ FILE: network_provisioning/tool/esp_prov/prov/network_scan.py ================================================ # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for Wi-Fi Scanning import proto from utils import str_to_bytes def print_verbose(security_ctx, data): if (security_ctx.verbose): print(f'\x1b[32;20m++++ {data} ++++\x1b[0m') def scan_start_request(network_type, security_ctx, blocking=True, passive=False, group_channels=5, period_ms=120): # Form protobuf request packet for ScanStart command cmd = proto.network_scan_pb2.NetworkScanPayload() if network_type == 'wifi': cmd.msg = proto.network_scan_pb2.TypeCmdScanWifiStart cmd.cmd_scan_wifi_start.blocking = blocking cmd.cmd_scan_wifi_start.passive = passive cmd.cmd_scan_wifi_start.group_channels = group_channels cmd.cmd_scan_wifi_start.period_ms = period_ms elif network_type == 'thread': cmd.msg = proto.network_scan_pb2.TypeCmdScanThreadStart cmd.cmd_scan_thread_start.blocking = blocking cmd.cmd_scan_thread_start.channel_mask = 0 else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdScanStart): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def scan_start_response(security_ctx, response_data): # Interpret protobuf response packet from ScanStart command dec_resp = security_ctx.decrypt_data(str_to_bytes(response_data)) resp = proto.network_scan_pb2.NetworkScanPayload() resp.ParseFromString(dec_resp) print_verbose(security_ctx, f'ScanStart status: 0x{str(resp.status)}') if resp.status != 0: raise RuntimeError def scan_status_request(network_type, security_ctx): # Form protobuf request packet for ScanStatus command cmd = proto.network_scan_pb2.NetworkScanPayload() if network_type == 'wifi': cmd.msg = proto.network_scan_pb2.TypeCmdScanWifiStatus elif network_type == 'thread': cmd.msg = proto.network_scan_pb2.TypeCmdScanThreadStatus else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdScanStatus): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def scan_status_response(security_ctx, response_data): # Interpret protobuf response packet from ScanStatus command dec_resp = security_ctx.decrypt_data(str_to_bytes(response_data)) resp = proto.network_scan_pb2.NetworkScanPayload() resp.ParseFromString(dec_resp) print_verbose(security_ctx, f'ScanStatus status: 0x{str(resp.status)}') if resp.status != 0: raise RuntimeError if resp.msg == proto.network_scan_pb2.TypeRespScanWifiStatus: return {'finished': resp.resp_scan_wifi_status.scan_finished, 'count': resp.resp_scan_wifi_status.result_count} elif resp.msg == proto.network_scan_pb2.TypeRespScanThreadStatus: return {'finished': resp.resp_scan_thread_status.scan_finished, 'count': resp.resp_scan_thread_status.result_count} else: raise RuntimeError def scan_result_request(network_type, security_ctx, index, count): # Form protobuf request packet for ScanResult command cmd = proto.network_scan_pb2.NetworkScanPayload() if network_type == 'wifi': cmd.msg = proto.network_scan_pb2.TypeCmdScanWifiResult cmd.cmd_scan_wifi_result.start_index = index cmd.cmd_scan_wifi_result.count = count elif network_type == 'thread': cmd.msg = proto.network_scan_pb2.TypeCmdScanThreadResult cmd.cmd_scan_thread_result.start_index = index cmd.cmd_scan_thread_result.count = count else: raise RuntimeError enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()) print_verbose(security_ctx, f'Client -> Device (Encrypted CmdScanResult): 0x{enc_cmd.hex()}') return enc_cmd.decode('latin-1') def scan_result_response(security_ctx, response_data): # Interpret protobuf response packet from ScanResult command dec_resp = security_ctx.decrypt_data(str_to_bytes(response_data)) resp = proto.network_scan_pb2.NetworkScanPayload() resp.ParseFromString(dec_resp) print_verbose(security_ctx, f'ScanResult status: 0x{str(resp.status)}') if resp.status != 0: raise RuntimeError results = [] if resp.msg == proto.network_scan_pb2.TypeRespScanWifiResult: authmode_str = ['Open', 'WEP', 'WPA_PSK', 'WPA2_PSK', 'WPA_WPA2_PSK', 'WPA2_ENTERPRISE', 'WPA3_PSK', 'WPA2_WPA3_PSK'] for entry in resp.resp_scan_wifi_result.entries: results += [{'ssid': entry.ssid.decode('latin-1').rstrip('\x00'), 'bssid': entry.bssid.hex(), 'channel': entry.channel, 'rssi': entry.rssi, 'auth': authmode_str[entry.auth]}] print_verbose(security_ctx, f"ScanResult SSID : {str(results[-1]['ssid'])}") print_verbose(security_ctx, f"ScanResult BSSID : {str(results[-1]['bssid'])}") print_verbose(security_ctx, f"ScanResult Channel : {str(results[-1]['channel'])}") print_verbose(security_ctx, f"ScanResult RSSI : {str(results[-1]['rssi'])}") print_verbose(security_ctx, f"ScanResult AUTH : {str(results[-1]['auth'])}") elif resp.msg == proto.network_scan_pb2.TypeRespScanThreadResult: for entry in resp.resp_scan_thread_result.entries: results += [{'pan_id': entry.pan_id, 'ext_pan_id': entry.ext_pan_id.hex(), 'network_name': entry.network_name, 'channel': entry.channel, 'rssi': entry.rssi, 'lqi': entry.lqi, 'ext_addr': entry.ext_addr.hex()}] else: raise RuntimeError return results ================================================ FILE: network_provisioning/tool/esp_prov/security/__init__.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .security0 import * # noqa: F403, F401 from .security1 import * # noqa: F403, F401 from .security2 import * # noqa: F403, F401 ================================================ FILE: network_provisioning/tool/esp_prov/security/security.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # Base class for protocomm security class Security: def __init__(self, security_session): self.security_session = security_session ================================================ FILE: network_provisioning/tool/esp_prov/security/security0.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for # protocomm endpoint with security type protocomm_security0 import proto from utils import str_to_bytes from .security import Security class Security0(Security): def __init__(self, verbose): # Initialize state of the security1 FSM self.session_state = 0 self.verbose = verbose Security.__init__(self, self.security0_session) def security0_session(self, response_data): # protocomm security0 FSM which interprets/forms # protobuf packets according to present state of session if (self.session_state == 0): self.session_state = 1 return self.setup0_request() if (self.session_state == 1): self.setup0_response(response_data) return None def setup0_request(self): # Form protocomm security0 request packet setup_req = proto.session_pb2.SessionData() setup_req.sec_ver = 0 session_cmd = proto.sec0_pb2.S0SessionCmd() setup_req.sec0.sc.MergeFrom(session_cmd) return setup_req.SerializeToString().decode('latin-1') def setup0_response(self, response_data): # Interpret protocomm security0 response packet setup_resp = proto.session_pb2.SessionData() setup_resp.ParseFromString(str_to_bytes(response_data)) # Check if security scheme matches if setup_resp.sec_ver != proto.session_pb2.SecScheme0: raise RuntimeError('Incorrect security scheme') def encrypt_data(self, data): # Passive. No encryption when security0 used return data def decrypt_data(self, data): # Passive. No encryption when security0 used return data ================================================ FILE: network_provisioning/tool/esp_prov/security/security1.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # APIs for interpreting and creating protobuf packets for # protocomm endpoint with security type protocomm_security1 import proto from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from utils import long_to_bytes, str_to_bytes from .security import Security def a_xor_b(a: bytes, b: bytes) -> bytes: return b''.join(long_to_bytes(a[i] ^ b[i]) for i in range(0, len(b))) # Enum for state of protocomm_security1 FSM class security_state: REQUEST1 = 0 RESPONSE1_REQUEST2 = 1 RESPONSE2 = 2 FINISHED = 3 class Security1(Security): def __init__(self, pop, verbose): # Initialize state of the security1 FSM self.session_state = security_state.REQUEST1 self.pop = str_to_bytes(pop) self.verbose = verbose Security.__init__(self, self.security1_session) def security1_session(self, response_data): # protocomm security1 FSM which interprets/forms # protobuf packets according to present state of session if (self.session_state == security_state.REQUEST1): self.session_state = security_state.RESPONSE1_REQUEST2 return self.setup0_request() elif (self.session_state == security_state.RESPONSE1_REQUEST2): self.session_state = security_state.RESPONSE2 self.setup0_response(response_data) return self.setup1_request() elif (self.session_state == security_state.RESPONSE2): self.session_state = security_state.FINISHED self.setup1_response(response_data) return None print('Unexpected state') return None def __generate_key(self): # Generate private and public key pair for client self.client_private_key = X25519PrivateKey.generate() self.client_public_key = self.client_private_key.public_key().public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) def _print_verbose(self, data): if (self.verbose): print(f'\x1b[32;20m++++ {data} ++++\x1b[0m') def setup0_request(self): # Form SessionCmd0 request packet using client public key setup_req = proto.session_pb2.SessionData() setup_req.sec_ver = proto.session_pb2.SecScheme1 self.__generate_key() setup_req.sec1.sc0.client_pubkey = self.client_public_key self._print_verbose(f'Client Public Key:\t0x{self.client_public_key.hex()}') return setup_req.SerializeToString().decode('latin-1') def setup0_response(self, response_data): # Interpret SessionResp0 response packet setup_resp = proto.session_pb2.SessionData() setup_resp.ParseFromString(str_to_bytes(response_data)) self._print_verbose('Security version:\t' + str(setup_resp.sec_ver)) if setup_resp.sec_ver != proto.session_pb2.SecScheme1: raise RuntimeError('Incorrect security scheme') self.device_public_key = setup_resp.sec1.sr0.device_pubkey # Device random is the initialization vector device_random = setup_resp.sec1.sr0.device_random self._print_verbose(f'Device Public Key:\t0x{self.device_public_key.hex()}') self._print_verbose(f'Device Random:\t0x{device_random.hex()}') # Calculate Curve25519 shared key using Client private key and Device public key sharedK = self.client_private_key.exchange(X25519PublicKey.from_public_bytes(self.device_public_key)) self._print_verbose(f'Shared Key:\t0x{sharedK.hex()}') # If PoP is provided, XOR SHA256 of PoP with the previously # calculated Shared Key to form the actual Shared Key if len(self.pop) > 0: # Calculate SHA256 of PoP h = hashes.Hash(hashes.SHA256(), backend=default_backend()) h.update(self.pop) digest = h.finalize() # XOR with and update Shared Key sharedK = a_xor_b(sharedK, digest) self._print_verbose(f'Updated Shared Key (Shared key XORed with PoP):\t0x{sharedK.hex()}') # Initialize the encryption engine with Shared Key and initialization vector cipher = Cipher(algorithms.AES(sharedK), modes.CTR(device_random), backend=default_backend()) self.cipher = cipher.encryptor() def setup1_request(self): # Form SessionCmd1 request packet using encrypted device public key setup_req = proto.session_pb2.SessionData() setup_req.sec_ver = proto.session_pb2.SecScheme1 setup_req.sec1.msg = proto.sec1_pb2.Session_Command1 # Encrypt device public key and attach to the request packet client_verify = self.cipher.update(self.device_public_key) self._print_verbose(f'Client Proof:\t0x{client_verify.hex()}') setup_req.sec1.sc1.client_verify_data = client_verify return setup_req.SerializeToString().decode('latin-1') def setup1_response(self, response_data): # Interpret SessionResp1 response packet setup_resp = proto.session_pb2.SessionData() setup_resp.ParseFromString(str_to_bytes(response_data)) # Ensure security scheme matches if setup_resp.sec_ver == proto.session_pb2.SecScheme1: # Read encrypyed device verify string device_verify = setup_resp.sec1.sr1.device_verify_data self._print_verbose(f'Device Proof:\t0x{device_verify.hex()}') # Decrypt the device verify string enc_client_pubkey = self.cipher.update(setup_resp.sec1.sr1.device_verify_data) # Match decrypted string with client public key if enc_client_pubkey != self.client_public_key: raise RuntimeError('Failed to verify device!') else: raise RuntimeError('Unsupported security protocol') def encrypt_data(self, data): return self.cipher.update(data) def decrypt_data(self, data): return self.cipher.update(data) ================================================ FILE: network_provisioning/tool/esp_prov/security/security2.py ================================================ # SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # APIs for interpreting and creating protobuf packets for # protocomm endpoint with security type protocomm_security2 import struct from typing import Any from typing import Type import proto from cryptography.hazmat.primitives.ciphers.aead import AESGCM from utils import long_to_bytes from utils import str_to_bytes from .security import Security from .srp6a import generate_salt_and_verifier from .srp6a import Srp6a AES_KEY_LEN = 256 // 8 # Enum for state of protocomm_security1 FSM class security_state: REQUEST1 = 0 RESPONSE1_REQUEST2 = 1 RESPONSE2 = 2 FINISHED = 3 def sec2_gen_salt_verifier(username: str, password: str, salt_len: int) -> Any: salt, verifier = generate_salt_and_verifier(username, password, len_s=salt_len) salt_str = ', '.join([format(b, '#04x') for b in salt]) salt_c_arr = '\n '.join(salt_str[i: i + 96] for i in range(0, len(salt_str), 96)) print(f'static const char sec2_salt[] = {{\n {salt_c_arr}\n}};\n') # noqa E702 verifier_str = ', '.join([format(b, '#04x') for b in verifier]) verifier_c_arr = '\n '.join(verifier_str[i: i + 96] for i in range(0, len(verifier_str), 96)) print(f'static const char sec2_verifier[] = {{\n {verifier_c_arr}\n}};\n') # noqa E702 class Security2(Security): def __init__(self, sec_patch_ver:int, username: str, password: str, verbose: bool) -> None: # Initialize state of the security2 FSM self.session_state = security_state.REQUEST1 self.sec_patch_ver = sec_patch_ver self.username = username self.password = password self.verbose = verbose self.srp6a_ctx: Type[Srp6a] self.cipher: Type[AESGCM] self.client_pop_key = None self.nonce = bytearray() Security.__init__(self, self.security2_session) def security2_session(self, response_data: bytes) -> Any: # protocomm security2 FSM which interprets/forms # protobuf packets according to present state of session if (self.session_state == security_state.REQUEST1): self.session_state = security_state.RESPONSE1_REQUEST2 return self.setup0_request() if (self.session_state == security_state.RESPONSE1_REQUEST2): self.session_state = security_state.RESPONSE2 self.setup0_response(response_data) return self.setup1_request() if (self.session_state == security_state.RESPONSE2): self.session_state = security_state.FINISHED self.setup1_response(response_data) return None print('---- Unexpected state! ----') return None def _print_verbose(self, data: str) -> None: if (self.verbose): print(f'\x1b[32;20m++++ {data} ++++\x1b[0m') # noqa E702 def setup0_request(self) -> Any: # Form SessionCmd0 request packet using client public key setup_req = proto.session_pb2.SessionData() setup_req.sec_ver = proto.session_pb2.SecScheme2 setup_req.sec2.msg = proto.sec2_pb2.S2Session_Command0 setup_req.sec2.sc0.client_username = str_to_bytes(self.username) self.srp6a_ctx = Srp6a(self.username, self.password) if self.srp6a_ctx is None: raise RuntimeError('Failed to initialize SRP6a instance!') client_pubkey = long_to_bytes(self.srp6a_ctx.A) setup_req.sec2.sc0.client_pubkey = client_pubkey self._print_verbose(f'Client Public Key:\t0x{client_pubkey.hex()}') return setup_req.SerializeToString().decode('latin-1') def setup0_response(self, response_data: bytes) -> None: # Interpret SessionResp0 response packet setup_resp = proto.session_pb2.SessionData() setup_resp.ParseFromString(str_to_bytes(response_data)) self._print_verbose(f'Security version:\t{str(setup_resp.sec_ver)}') if setup_resp.sec_ver != proto.session_pb2.SecScheme2: raise RuntimeError('Incorrect security scheme') # Device public key, random salt and password verifier device_pubkey = setup_resp.sec2.sr0.device_pubkey device_salt = setup_resp.sec2.sr0.device_salt self._print_verbose(f'Device Public Key:\t0x{device_pubkey.hex()}') self.client_pop_key = self.srp6a_ctx.process_challenge(device_salt, device_pubkey) def setup1_request(self) -> Any: # Form SessionCmd1 request packet using encrypted device public key setup_req = proto.session_pb2.SessionData() setup_req.sec_ver = proto.session_pb2.SecScheme2 setup_req.sec2.msg = proto.sec2_pb2.S2Session_Command1 # Encrypt device public key and attach to the request packet if self.client_pop_key is None: raise RuntimeError('Failed to generate client proof!') self._print_verbose(f'Client Proof:\t0x{self.client_pop_key.hex()}') setup_req.sec2.sc1.client_proof = self.client_pop_key return setup_req.SerializeToString().decode('latin-1') def setup1_response(self, response_data: bytes) -> Any: # Interpret SessionResp1 response packet setup_resp = proto.session_pb2.SessionData() setup_resp.ParseFromString(str_to_bytes(response_data)) # Ensure security scheme matches if setup_resp.sec_ver == proto.session_pb2.SecScheme2: # Read encrypyed device proof string device_proof = setup_resp.sec2.sr1.device_proof self._print_verbose(f'Device Proof:\t0x{device_proof.hex()}') self.srp6a_ctx.verify_session(device_proof) if not self.srp6a_ctx.authenticated(): raise RuntimeError('Failed to verify device proof') else: raise RuntimeError('Unsupported security protocol') # Getting the shared secret shared_secret = self.srp6a_ctx.get_session_key() self._print_verbose(f'Shared Secret:\t0x{shared_secret.hex()}') # Using the first 256 bits of a 512 bit key session_key = shared_secret[:AES_KEY_LEN] self._print_verbose(f'Session Key:\t0x{session_key.hex()}') # 96-bit nonce self.nonce = bytearray(setup_resp.sec2.sr1.device_nonce) if self.nonce is None: raise RuntimeError('Received invalid nonce from device!') self._print_verbose(f'Nonce:\t0x{self.nonce.hex()}') # Initialize the encryption engine with Shared Key and initialization vector self.cipher = AESGCM(session_key) if self.cipher is None: raise RuntimeError('Failed to initialize AES-GCM cryptographic engine!') def _increment_nonce(self) -> None: """Increment the last 4 bytes of nonce (big-endian counter).""" if self.sec_patch_ver == 1: counter = struct.unpack('>I', self.nonce[8:])[0] # Read last 4 bytes as big-endian integer counter += 1 # Increment counter if counter > 0xFFFFFFFF: # Check for overflow raise RuntimeError('Nonce counter overflow') self.nonce[8:] = struct.pack('>I', counter) # Store back as big-endian def encrypt_data(self, data: bytes) -> Any: self._print_verbose(f'Nonce:\t0x{self.nonce.hex()}') ciphertext = self.cipher.encrypt(self.nonce, data, None) self._increment_nonce() return ciphertext def decrypt_data(self, data: bytes) -> Any: self._print_verbose(f'Nonce:\t0x{self.nonce.hex()}') plaintext = self.cipher.decrypt(self.nonce, data, None) self._increment_nonce() return plaintext ================================================ FILE: network_provisioning/tool/esp_prov/security/srp6a.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # N A large safe prime (N = 2q+1, where q is prime) [All arithmetic is done modulo N] # g A generator modulo N # k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) # s User's salt # Iu Username # p Cleartext Password # H() One-way hash function # ^ (Modular) Exponentiation # u Random scrambling parameter # a, b Secret ephemeral values # A, B Public ephemeral values # x Private key (derived from p and s) # v Password verifier import hashlib import os from typing import Any, Callable, Optional, Tuple from utils import bytes_to_long, long_to_bytes SHA1 = 0 SHA224 = 1 SHA256 = 2 SHA384 = 3 SHA512 = 4 NG_1024 = 0 NG_2048 = 1 NG_3072 = 2 NG_4096 = 3 NG_8192 = 4 _hash_map = {SHA1: hashlib.sha1, SHA224: hashlib.sha224, SHA256: hashlib.sha256, SHA384: hashlib.sha384, SHA512: hashlib.sha512} _ng_const = ( # 1024-bit ('''\ EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496\ EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E\ F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA\ 9AFD5138FE8376435B9FC61D2FC0EB06E3''', '2'), # 2048 ('''\ AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4\ A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60\ 95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF\ 747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907\ 8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861\ 60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB\ FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73''', '2'), # 3072 ('''\ FFFFFFFFFFFFFFFFC90FDAA22168C2\ 34C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E\ 3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B5\ 76625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE\ 9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D3\ 9A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED5290770\ 96966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E77\ 2C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558171839\ 95497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A\ 33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6\ E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA\ 06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C77\ 0988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2\ CAFFFFFFFFFFFFFFFF''', '5'), # 4096 ('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199\ FFFFFFFFFFFFFFFF''', '5'), # 8192 ('''\ FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ 8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ 302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ 49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ 180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ 3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ 04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ 1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ 99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ 04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ 233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ 36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406\ AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918\ DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151\ 2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03\ F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F\ BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA\ CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B\ B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632\ 387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ 6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA\ 3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C\ 5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9\ 22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886\ 2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6\ 6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5\ 0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268\ 359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6\ FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71\ 60C980DD98EDD3DFFFFFFFFFFFFFFFFF''', '0x13') ) def get_ng(ng_type: int) -> Tuple[int, int]: n_hex, g_hex = _ng_const[ng_type] return int(n_hex, 16), int(g_hex, 16) def get_random(nbytes: int) -> Any: return bytes_to_long(os.urandom(nbytes)) def get_random_of_length(nbytes: int) -> Any: offset = (nbytes * 8) - 1 return get_random(nbytes) | (1 << offset) def H(hash_class: Callable, *args: Any, **kwargs: Any) -> int: width = kwargs.get('width', None) h = hash_class() for s in args: if s is not None: data = long_to_bytes(s) if isinstance(s, int) else s if width is not None: h.update(bytes(width - len(data))) h.update(data) return int(h.hexdigest(), 16) def H_N_xor_g(hash_class: Callable, N: int, g: int) -> bytes: bin_N = long_to_bytes(N) bin_g = long_to_bytes(g) padding = len(bin_N) - len(bin_g) hN = hash_class(bin_N).digest() hg = hash_class(b''.join([b'\0' * padding, bin_g])).digest() return b''.join(long_to_bytes(hN[i] ^ hg[i]) for i in range(0, len(hN))) def calculate_x(hash_class: Callable, s: Any, Iu: str, p: str) -> int: _Iu = Iu.encode() _p = p.encode() return H(hash_class, s, H(hash_class, _Iu + b':' + _p)) def generate_salt_and_verifier(Iu: str, p: str, len_s: int, hash_alg: int = SHA512, ng_type: int = NG_3072) -> Tuple[bytes, bytes]: hash_class = _hash_map[hash_alg] N, g = get_ng(ng_type) _s = long_to_bytes(get_random(len_s)) _v = long_to_bytes(pow(g, calculate_x(hash_class, _s, Iu, p), N)) return _s, _v def calculate_M(hash_class: Callable, N: int, g: int, Iu: str, s: int, A: int, B: int, K: bytes) -> Any: _Iu = Iu.encode() h = hash_class() h.update(H_N_xor_g(hash_class, N, g)) h.update(hash_class(_Iu).digest()) h.update(long_to_bytes(s)) h.update(long_to_bytes(A)) h.update(long_to_bytes(B)) h.update(K) return h.digest() def calculate_H_AMK(hash_class: Callable, A: int, M: bytes, K: bytes) -> Any: h = hash_class() h.update(long_to_bytes(A)) h.update(M) h.update(K) return h.digest() class Srp6a (object): def __init__(self, username: str, password: str, hash_alg: int = SHA512, ng_type: int = NG_3072): hash_class = _hash_map[hash_alg] N, g = get_ng(ng_type) k = H(hash_class, N, g, width=len(long_to_bytes(N))) self.Iu = username self.p = password self.a = get_random_of_length(32) self.A = pow(g, self.a, N) self.v: Optional[int] = None self.K: Optional[bytes] = None self.H_AMK = None self._authenticated = False self.hash_class = hash_class self.N = N self.g = g self.k = k def authenticated(self) -> bool: return self._authenticated def get_username(self) -> str: return self.Iu def get_ephemeral_secret(self) -> Any: return long_to_bytes(self.a) def get_session_key(self) -> Any: return self.K if self._authenticated else None def start_authentication(self) -> Tuple[str, bytes]: return (self.Iu, long_to_bytes(self.A)) # Returns M or None if SRP-6a safety check is violated def process_challenge(self, bytes_s: bytes, bytes_B: bytes) -> Any: s = bytes_to_long(bytes_s) B = bytes_to_long(bytes_B) N = self.N g = self.g k = self.k hash_class = self.hash_class # SRP-6a safety check if (B % N) == 0: return None u = H(hash_class, self.A, B, width=len(long_to_bytes(N))) if u == 0: # SRP-6a safety check return None x = calculate_x(hash_class, s, self.Iu, self.p) v = pow(g, x, N) S = pow((B - k * v), (self.a + u * x), N) self.K = hash_class(long_to_bytes(S)).digest() M = calculate_M(hash_class, N, g, self.Iu, s, self.A, B, self.K) if not M: return None self.H_AMK = calculate_H_AMK(hash_class, self.A, M, self.K) return M def verify_session(self, host_HAMK: bytes) -> None: if self.H_AMK == host_HAMK: self._authenticated = True class AuthenticationFailed (Exception): pass ================================================ FILE: network_provisioning/tool/esp_prov/transport/__init__.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .transport_ble import * # noqa: F403, F401 from .transport_console import * # noqa: F403, F401 from .transport_http import * # noqa: F403, F401 ================================================ FILE: network_provisioning/tool/esp_prov/transport/ble_cli.py ================================================ # SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # import platform from utils import hex_str_to_bytes, str_to_bytes fallback = True # Check if required packages are installed # else fallback to console mode try: import bleak fallback = False except ImportError: pass # -------------------------------------------------------------------- def device_sort(device): return device[0].address class BLE_Bleak_Client: def __init__(self): self.adapter = None self.adapter_props = None self.characteristics = dict() self.chrc_names = None self.device = None self.devname = None self.iface = None self.nu_lookup = None self.services = None self.srv_uuid_adv = None self.srv_uuid_fallback = None async def connect(self, devname, iface, chrc_names, fallback_srv_uuid): self.devname = devname self.srv_uuid_fallback = fallback_srv_uuid self.chrc_names = [name.lower() for name in chrc_names] self.iface = iface print('Discovering...') try: discovery = await bleak.BleakScanner.discover(return_adv=True, adapter=self.iface) devices = list(discovery.values()) except bleak.exc.BleakDBusError as e: if str(e) == '[org.bluez.Error.NotReady] Resource Not Ready': raise RuntimeError('Bluetooth is not ready. Maybe try `bluetoothctl power on`?') raise found_device = None if self.devname is None: if len(devices) == 0: print('No devices found!') exit(1) while True: devices.sort(key=device_sort) print('==== BLE Discovery results ====') print('{0: >4} {1: <33} {2: <12}'.format( 'S.N.', 'Name', 'Address')) for i, _ in enumerate(devices): print('[{0: >2}] {1: <33} {2: <12}'.format(i + 1, devices[i][0].name or 'Unknown', devices[i][0].address)) while True: try: select = int(input('Select device by number (0 to rescan) : ')) if select < 0 or select > len(devices): raise ValueError break except ValueError: print('Invalid input! Retry') if select != 0: break discovery = await bleak.BleakScanner.discover(return_adv=True, adapter=self.iface) devices = list(discovery.values()) self.devname = devices[select - 1][0].name found_device = devices[select - 1] else: for d in devices: if d[0].name == self.devname: found_device = d if not found_device: raise RuntimeError('Device not found') uuids = found_device[1].service_uuids # There should be 1 service UUID in advertising data # If bluez had cached an old version of the advertisement data # the list of uuids may be incorrect, in which case connection # or service discovery may fail the first time. If that happens # the cache will be refreshed before next retry if len(uuids) == 1: self.srv_uuid_adv = uuids[0] print('Connecting...') self.device = bleak.BleakClient(found_device[0].address, adapter=self.iface) await self.device.connect() # must be paired on Windows to access characteristics; # cannot be paired on Mac if platform.system() == 'Windows': await self.device.pair() print('Getting Services...') services = self.device.services service = services[self.srv_uuid_adv] or services[self.srv_uuid_fallback] if not service: await self.device.disconnect() self.device = None raise RuntimeError('Provisioning service not found') nu_lookup = dict() for characteristic in service.characteristics: for descriptor in characteristic.descriptors: if descriptor.uuid[4:8] != '2901': continue readval = await self.device.read_gatt_descriptor(descriptor.handle) found_name = ''.join(chr(b) for b in readval).lower() nu_lookup[found_name] = characteristic.uuid self.characteristics[characteristic.uuid] = characteristic match_found = True for name in self.chrc_names: if name not in nu_lookup: # Endpoint name not present match_found = False break # Create lookup table only if all endpoint names found self.nu_lookup = [None, nu_lookup][match_found] return True def get_nu_lookup(self): return self.nu_lookup def has_characteristic(self, uuid): print('checking for characteristic ' + uuid) if uuid in self.characteristics: return True return False async def disconnect(self): if self.device: print('Disconnecting...') if platform.system() == 'Windows': await self.device.unpair() await self.device.disconnect() self.device = None self.nu_lookup = None self.characteristics = dict() async def send_data(self, characteristic_uuid, data): await self.device.write_gatt_char(characteristic_uuid, bytearray(data.encode('latin-1')), True) readval = await self.device.read_gatt_char(characteristic_uuid) return ''.join(chr(b) for b in readval) # -------------------------------------------------------------------- # Console based BLE client for Cross Platform support class BLE_Console_Client: async def connect(self, devname, iface, chrc_names, fallback_srv_uuid): print('BLE client is running in console mode') print('\tThis could be due to your platform not being supported or dependencies not being met') print('\tPlease ensure all pre-requisites are met to run the full fledged client') print('BLECLI >> Please connect to BLE device `' + devname + '` manually using your tool of choice') resp = input('BLECLI >> Was the device connected successfully? [y/n] ') if resp != 'Y' and resp != 'y': return False print('BLECLI >> List available attributes of the connected device') resp = input("BLECLI >> Is the service UUID '" + fallback_srv_uuid + "' listed among available attributes? [y/n] ") if resp != 'Y' and resp != 'y': return False return True def get_nu_lookup(self): return None def has_characteristic(self, uuid): resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ") if resp != 'Y' and resp != 'y': return False return True async def disconnect(self): pass async def send_data(self, characteristic_uuid, data): print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :") print('\t>> ' + str_to_bytes(data).hex()) print('BLECLI >> Enter data read from characteristic (in hex) :') resp = input('\t<< ') return hex_str_to_bytes(resp) # -------------------------------------------------------------------- # Function to get client instance depending upon platform def get_client(): if fallback: return BLE_Console_Client() return BLE_Bleak_Client() ================================================ FILE: network_provisioning/tool/esp_prov/transport/transport.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # Base class for protocomm transport import abc class Transport(): @abc.abstractmethod def send_session_data(self, data): pass @abc.abstractmethod def send_config_data(self, data): pass async def disconnect(self): pass ================================================ FILE: network_provisioning/tool/esp_prov/transport/transport_ble.py ================================================ # SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from . import ble_cli from .transport import Transport DEFAULT_BLE_ADAPTER = 'hci0' class Transport_BLE(Transport): def __init__(self, service_uuid, nu_lookup, ble_adapter=DEFAULT_BLE_ADAPTER): self.nu_lookup = nu_lookup self.service_uuid = service_uuid self.name_uuid_lookup = None self.ble_adapter = ble_adapter # Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb' for name in nu_lookup.keys(): # Calculate characteristic UUID for each endpoint nu_lookup[name] = service_uuid[:4] + '{:02x}'.format( int(nu_lookup[name], 16) & int(service_uuid[4:8], 16)) + service_uuid[8:] # Get BLE client module self.cli = ble_cli.get_client() async def connect(self, devname): # Use client to connect to BLE device and bind to service if not await self.cli.connect(devname=devname, iface=self.ble_adapter, chrc_names=self.nu_lookup.keys(), fallback_srv_uuid=self.service_uuid): raise RuntimeError('Failed to initialize transport') # Irrespective of provided parameters, let the client # generate a lookup table by reading advertisement data # and characteristic user descriptors self.name_uuid_lookup = self.cli.get_nu_lookup() # If that doesn't work, use the lookup table provided as parameter if self.name_uuid_lookup is None: self.name_uuid_lookup = self.nu_lookup # Check if expected characteristics are provided by the service for name in self.name_uuid_lookup.keys(): if not self.cli.has_characteristic(self.name_uuid_lookup[name]): raise RuntimeError(f"'{name}' endpoint not found") async def disconnect(self): await self.cli.disconnect() async def send_data(self, ep_name, data): # Write (and read) data to characteristic corresponding to the endpoint if ep_name not in self.name_uuid_lookup.keys(): raise RuntimeError(f'Invalid endpoint: {ep_name}') return await self.cli.send_data(self.name_uuid_lookup[ep_name], data) ================================================ FILE: network_provisioning/tool/esp_prov/transport/transport_console.py ================================================ # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from utils import hex_str_to_bytes, str_to_bytes from .transport import Transport class Transport_Console(Transport): async def send_data(self, path, data, session_id=0): print('Client->Device msg :', path, session_id, str_to_bytes(data).hex()) try: resp = input('Enter device->client msg : ') except Exception as err: print('error:', err) return None return hex_str_to_bytes(resp) ================================================ FILE: network_provisioning/tool/esp_prov/transport/transport_http.py ================================================ # SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # import socket from http.client import HTTPConnection, HTTPSConnection from utils import str_to_bytes from .transport import Transport class Transport_HTTP(Transport): def __init__(self, hostname, ssl_context=None): try: socket.gethostbyname(hostname.split(':')[0]) except socket.gaierror: raise RuntimeError(f'Unable to resolve hostname: {hostname}') if ssl_context is None: self.conn = HTTPConnection(hostname, timeout=60) else: self.conn = HTTPSConnection(hostname, context=ssl_context, timeout=60) try: print(f'++++ Connecting to {hostname}++++') self.conn.connect() except Exception as err: raise RuntimeError('Connection Failure : ' + str(err)) self.headers = {'Content-type': 'application/x-www-form-urlencoded','Accept': 'text/plain'} def _send_post_request(self, path, data): data = str_to_bytes(data) if isinstance(data, str) else data try: self.conn.request('POST', path, data, self.headers) response = self.conn.getresponse() # While establishing a session, the device sends the Set-Cookie header # with value 'session=cookie_session_id' in its first response of the session to the tool. # To maintain the same session, successive requests from the tool should include # an additional 'Cookie' header with the above received value. for hdr_key, hdr_val in response.getheaders(): if hdr_key == 'Set-Cookie': self.headers['Cookie'] = hdr_val if response.status == 200: return response.read().decode('latin-1') except Exception as err: raise RuntimeError('Connection Failure : ' + str(err)) raise RuntimeError('Server responded with error code ' + str(response.status)) async def send_data(self, ep_name, data): return self._send_post_request('/' + ep_name, data) ================================================ FILE: network_provisioning/tool/esp_prov/utils/__init__.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .convenience import * # noqa: F403, F401 ================================================ FILE: network_provisioning/tool/esp_prov/utils/convenience.py ================================================ # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # Convenience functions for commonly used data type conversions def bytes_to_long(s: bytes) -> int: return int.from_bytes(s, 'big') def long_to_bytes(n: int) -> bytes: if n == 0: return b'\x00' return n.to_bytes((n.bit_length() + 7) // 8, 'big') # 'deadbeef' -> b'deadbeef' def str_to_bytes(s: str) -> bytes: return bytes(s, encoding='latin-1') # 'deadbeef' -> b'\xde\xad\xbe\xef' def hex_str_to_bytes(s: str) -> bytes: return bytes.fromhex(s) def int_to_hex_str(n: int) -> str: hex_string = format(n, 'x') if len(hex_string) % 2 != 0: hex_string = '0' + hex_string return hex_string ================================================ FILE: nghttp/CMakeLists.txt ================================================ set(srcs "nghttp2/lib/nghttp2_buf.c" "nghttp2/lib/nghttp2_callbacks.c" "nghttp2/lib/nghttp2_debug.c" "nghttp2/lib/nghttp2_extpri.c" "nghttp2/lib/nghttp2_frame.c" "nghttp2/lib/nghttp2_hd.c" "nghttp2/lib/nghttp2_hd_huffman.c" "nghttp2/lib/nghttp2_hd_huffman_data.c" "nghttp2/lib/nghttp2_helper.c" "nghttp2/lib/nghttp2_http.c" "nghttp2/lib/nghttp2_map.c" "nghttp2/lib/nghttp2_mem.c" "nghttp2/lib/nghttp2_alpn.c" "nghttp2/lib/nghttp2_option.c" "nghttp2/lib/nghttp2_outbound_item.c" "nghttp2/lib/nghttp2_pq.c" "nghttp2/lib/nghttp2_priority_spec.c" "nghttp2/lib/nghttp2_queue.c" "nghttp2/lib/nghttp2_ratelim.c" "nghttp2/lib/nghttp2_rcbuf.c" "nghttp2/lib/nghttp2_session.c" "nghttp2/lib/nghttp2_stream.c" "nghttp2/lib/nghttp2_submit.c" "nghttp2/lib/nghttp2_time.c" "nghttp2/lib/nghttp2_version.c" "nghttp2/lib/sfparse.c") idf_component_register(SRCS "${srcs}" INCLUDE_DIRS port/include nghttp2/lib/includes PRIV_INCLUDE_DIRS nghttp2/lib port/private_include) target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DHAVE_CONFIG_H") # Override nghttp2_session.h with our own version to override inbound buffer length target_compile_options(${COMPONENT_LIB} PRIVATE "SHELL:-include \"${CMAKE_CURRENT_SOURCE_DIR}/port/private_include/esp_nghttp2_session.h\"") ================================================ FILE: nghttp/Kconfig ================================================ menu "NGHTTP configuration" config ESP_NGHTTP2_INBOUND_BUFFER_LENGTH int "Nghttp2 inbound buffer length" default 4096 help Set the size of the inbound buffer for nghttp2. This buffer is used to store incoming HTTP/2 frames. The default value is 16KB as per nghttp2 library configuration but here we have set it to 4KB. The receive buffer gets placed on the stack and hence adjust this value based on your application needs and available stack size. endmenu ================================================ FILE: nghttp/idf_component.yml ================================================ version: "1.68.1" description: "nghttp2 - HTTP/2 C Library" url: https://github.com/espressif/idf-extra-components/tree/master/nghttp dependencies: idf: ">=5.0" sbom: manifests: - path: sbom_nghttp2.yml dest: nghttp2 ================================================ FILE: nghttp/port/include/nghttp2/nghttp2ver.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa * * 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * 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. */ #ifndef NGHTTP2VER_H #define NGHTTP2VER_H /** * @macro * Version number of the nghttp2 library release */ #define NGHTTP2_VERSION "1.68.1" /** * @macro * Numerical representation of the version number of the nghttp2 library * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ #define NGHTTP2_VERSION_NUM 0x014400 #endif /* NGHTTP2VER_H */ ================================================ FILE: nghttp/port/private_include/config.h ================================================ #ifndef __HAVE_CONFIG_H_ #define __HAVE_CONFIG_H_ #define _U_ #define SIZEOF_INT_P 2 #include "stdio.h" #include "stdlib.h" #include "string.h" /* Define to 1 if you have the `clock_gettime' function. */ #define HAVE_CLOCK_GETTIME 1 /* Define to 1 if you have the header file. */ #define HAVE_TIME_H 1 #if (!defined(nghttp_unlikely)) #define nghttp_unlikely(Expression) !!(Expression) #endif #define nghttp_ASSERT(Expression) do{if (!(Expression)) printf("%d\n", __LINE__);}while(0) #define CU_ASSERT(a) nghttp_ASSERT(a) #define CU_ASSERT_FATAL(a) nghttp_ASSERT(a) #define NGHTTP_PLATFORM_HTONS(_n) ((uint16_t)((((_n) & 0xff) << 8) | (((_n) >> 8) & 0xff))) #define NGHTTP_PLATFORM_HTONL(_n) ((uint32_t)( (((_n) & 0xff) << 24) | (((_n) & 0xff00) << 8) | (((_n) >> 8) & 0xff00) | (((_n) >> 24) & 0xff) )) #define htons(x) NGHTTP_PLATFORM_HTONS(x) #define ntohs(x) NGHTTP_PLATFORM_HTONS(x) #define htonl(x) NGHTTP_PLATFORM_HTONL(x) #define ntohl(x) NGHTTP_PLATFORM_HTONL(x) #endif // __HAVE_CONFIG_H_ ================================================ FILE: nghttp/port/private_include/esp_nghttp2_session.h ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "nghttp2_session.h" #include "sdkconfig.h" // Redefine NGHTTP2_INBOUND_BUFFER_LENGTH to the value from sdkconfig #undef NGHTTP2_INBOUND_BUFFER_LENGTH #define NGHTTP2_INBOUND_BUFFER_LENGTH CONFIG_ESP_NGHTTP2_INBOUND_BUFFER_LENGTH ================================================ FILE: nghttp/sbom_nghttp2.yml ================================================ name: nghttp2 version: 1.68.1 cpe: cpe:2.3:a:nghttp2:nghttp2:{}:*:*:*:*:*:*:* supplier: 'Organization: nghttp2 This file is automatically generated by esp-doxybook. DO NOT edit it manually. ================================================ FILE: onewire_bus/docs/src/index.md ================================================ # 1-Wire Bus Programming Guide The 1-Wire bus driver provides a generic interface for communicating with Dallas/Maxim 1-Wire devices. It supports multiple hardware backends (RMT and UART) and handles the low-level timing requirements of the 1-Wire protocol automatically. ## Overview 1-Wire is a device communications bus system that uses a single data line plus ground for communication. Common 1-Wire devices include temperature sensors (DS18B20), EEPROMs (DS2431), and real-time clocks (DS3234). This driver provides: - Automatic 1-Wire bus initialization with RMT or UART backend - Device discovery and enumeration on the bus - Read/write operations at bit and byte level - Built-in CRC8 calculation for data integrity ## Add the Component to Your Project Add the `onewire_bus` component to your project via the ESP Component Registry: ```bash idf.py add-dependency "espressif/onewire_bus" ``` ## Allocate 1-Wire Bus with RMT Backend The RMT backend is the recommended approach for most ESP32 chips that support the RMT peripheral. It provides precise timing control for 1-Wire communication. ```c #include "onewire_bus.h" // 1-Wire bus configuration onewire_bus_config_t bus_config = { .bus_gpio_num = 4, // GPIO pin connected to the 1-Wire bus data line .flags = { .en_pull_up = false, // Set true to enable internal pull-up (external pull-up recommended) } }; // RMT backend specific configuration onewire_bus_rmt_config_t rmt_config = { .max_rx_bytes = 10, // Maximum bytes expected in a single receive operation }; // Create the 1-Wire bus handle onewire_bus_handle_t bus = NULL; ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config, &rmt_config, &bus)); ``` ### Notes on RMT Backend - The RMT backend uses a pair of RMT TX and RX channels internally - The `max_rx_bytes` value determines the size of the internal receive buffer. Set this based on the maximum response size you expect from your devices - An external 4.7kΩ pull-up resistor is recommended for reliable communication, especially when multiple devices are on the bus or cable lengths are long ## Allocate 1-Wire Bus with UART Backend The UART backend is an alternative that uses the UART peripheral with open-drain configuration. This is useful when RMT channels are not available. ```c #include "onewire_bus.h" // 1-Wire bus configuration onewire_bus_config_t bus_config = { .bus_gpio_num = 4, // GPIO pin connected to the 1-Wire bus data line .flags = { .en_pull_up = false, // Set true to enable internal pull-up (external pull-up recommended) } }; // UART backend specific configuration onewire_bus_uart_config_t uart_config = { .uart_port_num = 1, // UART port number to use }; // Create the 1-Wire bus handle onewire_bus_handle_t bus = NULL; ESP_ERROR_CHECK(onewire_new_bus_uart(&bus_config, &uart_config, &bus)); ``` ### Notes on UART Backend - Both the UART TX and RX paths are configured to the same GPIO pin (`bus_gpio_num`) - The GPIO is automatically configured as open-drain mode ## Enumerate Devices on the Bus After initializing the bus, you can discover all 1-Wire devices connected to it. Each 1-Wire device has a unique 64-bit ROM address. ```c // Create a device iterator onewire_device_iter_handle_t iter = NULL; ESP_ERROR_CHECK(onewire_new_device_iter(bus, &iter)); // Enumerate all devices on the bus onewire_device_t dev; while (onewire_device_iter_get_next(iter, &dev) == ESP_OK) { ESP_LOGI("example", "Found device with address: %016llX", dev.address); } // Delete the iterator when done ESP_ERROR_CHECK(onewire_del_device_iter(iter)); ``` ### Notes on Device Enumeration - The iterator performs a 1-Wire search algorithm to find all devices on the bus - Each call to `onewire_device_iter_get_next()` returns the next device found - The device address contains the family code (first byte), serial number (middle 6 bytes), and CRC (last byte) - Call the iterator functions again if you need to re-scan the bus for newly connected devices ## Communicate with Devices ### Reset the Bus Before each communication sequence, send a reset pulse to check for device presence: ```c esp_err_t ret = onewire_bus_reset(bus); if (ret == ESP_OK) { ESP_LOGI("example", "Device(s) present on the bus"); } else if (ret == ESP_ERR_NOT_FOUND) { ESP_LOGW("example", "No devices found on the bus"); } ``` ### Send Commands and Data You can communicate with devices using the byte-level or bit-level functions: ```c // Send a command byte (e.g., SKIP ROM command to address all devices) uint8_t cmd = ONEWIRE_CMD_SKIP_ROM; ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1)); // Write a read temperature command to a DS18B20 uint8_t convert_cmd = 0x44; ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &convert_cmd, 1)); // Read response data uint8_t scratchpad[9]; ESP_ERROR_CHECK(onewire_bus_read_bytes(bus, scratchpad, 9)); ``` ### Working with a Specific Device When multiple devices are on the bus, use the MATCH ROM command to address a specific device: ```c // First, find the device address using the iterator (shown above) onewire_device_iter_handle_t iter = NULL; ESP_ERROR_CHECK(onewire_new_device_iter(bus, &iter)); onewire_device_t dev; if (onewire_device_iter_get_next(iter, &dev) == ESP_OK) { // Reset and send MATCH ROM command followed by the device address ESP_ERROR_CHECK(onewire_bus_reset(bus)); uint8_t match_cmd = ONEWIRE_CMD_MATCH_ROM; ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &match_cmd, 1)); ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, (uint8_t *)&dev.address, 8)); // Now you can communicate with this specific device uint8_t read_cmd = 0xBE; // Read scratchpad command for DS18B20 ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &read_cmd, 1)); uint8_t data[9]; ESP_ERROR_CHECK(onewire_bus_read_bytes(bus, data, 9)); } ESP_ERROR_CHECK(onewire_del_device_iter(iter)); ``` ## Verify Data with CRC The 1-Wire protocol uses CRC8 for data integrity. The driver provides a CRC8 utility function: ```c #include "onewire_crc.h" // Calculate CRC8 for received data uint8_t data[9]; // Received scratchpad data ESP_ERROR_CHECK(onewire_bus_read_bytes(bus, data, 9)); // Verify CRC - the result should be 0 if data is correct uint8_t crc = onewire_crc8(0, data, 9); if (crc == 0) { ESP_LOGI("example", "Data CRC verified OK"); } else { ESP_LOGE("example", "Data CRC mismatch"); } ``` ## Free Resources When you are done using the 1-Wire bus, free the allocated resources: ```c ESP_ERROR_CHECK(onewire_bus_del(bus)); ``` ## Common 1-Wire Commands The driver provides commonly used 1-Wire command definitions: | Command | Value | Description | |---------|-------|-------------| | `ONEWIRE_CMD_SEARCH_NORMAL` | 0xF0 | Search for all devices on the bus | | `ONEWIRE_CMD_MATCH_ROM` | 0x55 | Address a specific device by its ROM address | | `ONEWIRE_CMD_SKIP_ROM` | 0xCC | Address all devices on the bus simultaneously | | `ONEWIRE_CMD_SEARCH_ALARM` | 0xEC | Search for devices in alarm condition | | `ONEWIRE_CMD_READ_POWER_SUPPLY` | 0xB4 | Check if devices are parasitically powered | ## FAQ - **Do I need an external pull-up resistor?** - Yes, a 4.7kΩ pull-up resistor is recommended for reliable communication. The internal pull-up may not provide enough current for some devices, especially with longer cables or multiple devices. - **Which backend should I use, RMT or UART?** - Use RMT backend if your chip supports it and you have free RMT channels. It provides more precise timing. Use UART backend when RMT is unavailable or all channels are in use. - **How many devices can I connect to a single 1-Wire bus?** - The 1-Wire protocol supports many devices on a single bus (limited by the 64-bit address space). In practice, the limit is determined by bus capacitance and power supply capabilities. - **How do I communicate with a specific device when multiple devices are on the bus?** - First enumerate devices using the device iterator to get their addresses. Then use the `ONEWIRE_CMD_MATCH_ROM` command followed by the 8-byte device address to select a specific device before sending commands. - **Where can I find a complete example?** - See the [DS18B20 device driver](https://components.espressif.com/components/espressif/ds18b20) and the [DS18B20 Example](https://github.com/espressif/esp-bsp/tree/master/components/ds18b20/examples/ds18b20_read) for a complete working implementation based on this 1-Wire bus driver. ================================================ FILE: onewire_bus/idf_component.yml ================================================ version: "1.1.0" description: Driver for Dallas 1-Wire bus url: https://github.com/espressif/idf-extra-components/tree/master/onewire_bus repository: https://github.com/espressif/idf-extra-components.git documentation: https://espressif.github.io/idf-extra-components/latest/onewire_bus/index.html issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.0" ================================================ FILE: onewire_bus/include/onewire_bus.h ================================================ /* * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "soc/soc_caps.h" #include "onewire_types.h" #if SOC_RMT_SUPPORTED #include "onewire_bus_impl_rmt.h" #endif #if SOC_UART_SUPPORTED #include "onewire_bus_impl_uart.h" #endif #ifdef __cplusplus extern "C" { #endif /** * @brief Write bytes to 1-wire bus * * @param[in] bus 1-Wire bus handle * @param[in] tx_data pointer to data to be sent * @param[in] tx_data_size size of data to be sent, in bytes * @return * - ESP_OK: Write bytes to 1-Wire bus successfully * - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument * - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors */ esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size); /** * @brief Read bytes from 1-wire bus * * @param[in] bus 1-wire bus handle * @param[out] rx_buf pointer to buffer to store received data * @param[in] rx_buf_size size of buffer to store received data, in bytes * @return * - ESP_OK: Read bytes from 1-Wire bus successfully * - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument * - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors */ esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size); /** * @brief Write a bit to 1-wire bus, this is a blocking function * * @param[in] bus 1-wire bus handle * @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit * @return * - ESP_OK Write bit to 1-wire bus successfully. * - ESP_ERR_INVALID_ARG Invalid argument. */ esp_err_t onewire_bus_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit); /** * @brief Read a bit from 1-wire bus * * @param[in] bus 1-wire bus handle * @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit * @return * - ESP_OK Read bit from 1-wire bus successfully. * - ESP_ERR_INVALID_ARG Invalid argument. */ esp_err_t onewire_bus_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit); /** * @brief Send reset pulse to the bus, and check if there are devices attached to the bus * * @param[in] bus 1-Wire bus handle * * @return * - ESP_OK: Reset 1-Wire bus successfully and find device on the bus * - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus * - ESP_FAIL: Reset 1-Wire bus failed because of other errors */ esp_err_t onewire_bus_reset(onewire_bus_handle_t bus); /** * @brief Free 1-Wire bus resources * * @param[in] bus 1-Wire bus handle * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t onewire_bus_del(onewire_bus_handle_t bus); #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/include/onewire_bus_impl_rmt.h ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "onewire_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief 1-Wire bus RMT specific configuration */ typedef struct { uint32_t max_rx_bytes; /*!< Set the largest possible single receive size, which determines the size of the internal buffer that used to save the receiving RMT symbols */ } onewire_bus_rmt_config_t; /** * @brief Create 1-Wire bus with RMT backend * * @note One 1-Wire bus utilizes a pair of RMT TX and RX channels * * @param[in] bus_config 1-Wire bus configuration * @param[in] rmt_config RMT specific configuration * @param[out] ret_bus Returned 1-Wire bus handle * @return * - ESP_OK: create 1-Wire bus handle successfully * - ESP_ERR_INVALID_ARG: create 1-Wire bus handle failed because of invalid argument * - ESP_ERR_NO_MEM: create 1-Wire bus handle failed because of out of memory * - ESP_FAIL: create 1-Wire bus handle failed because some other error */ esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus); #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/include/onewire_bus_impl_uart.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "onewire_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief 1-Wire bus UART specific configuration */ typedef struct { int uart_port_num; /*!< UART port number, e.g. UART_NUM_1 */ } onewire_bus_uart_config_t; /** * @brief Create 1-Wire bus with UART backend * * @note TX and RX will both be configured to bus_config->bus_gpio_num. * And this GPIO will be configured as open-drain mode. * * @param[in] bus_config 1-Wire bus configuration * @param[in] uart_config UART specific configuration * @param[out] ret_bus Returned 1-Wire bus handle * @return * - ESP_OK: create 1-Wire bus handle successfully * - ESP_ERR_INVALID_ARG: create 1-Wire bus handle failed because of invalid argument * - ESP_ERR_NO_MEM: create 1-Wire bus handle failed because of out of memory * - ESP_FAIL: create 1-Wire bus handle failed because some other error */ esp_err_t onewire_new_bus_uart(const onewire_bus_config_t *bus_config, const onewire_bus_uart_config_t *uart_config, onewire_bus_handle_t *ret_bus); #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/include/onewire_cmd.h ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #define ONEWIRE_CMD_SEARCH_NORMAL 0xF0 #define ONEWIRE_CMD_MATCH_ROM 0x55 #define ONEWIRE_CMD_SKIP_ROM 0xCC #define ONEWIRE_CMD_SEARCH_ALARM 0xEC #define ONEWIRE_CMD_READ_POWER_SUPPLY 0xB4 ================================================ FILE: onewire_bus/include/onewire_crc.h ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #ifdef __cplusplus extern "C" { #endif /** * @brief Calculate Dallas CRC8 value of a given buffer * * @param[in] init_crc Initial CRC value * @param[in] input Input buffer to calculate CRC value * @param[in] input_size Size of input buffer, in bytes * @return CRC8 result of the input buffer */ uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size); #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/include/onewire_device.h ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "onewire_types.h" #ifdef __cplusplus extern "C" { #endif /** * @brief 1-Wire device generic type */ typedef struct onewire_device_t { onewire_bus_handle_t bus; /*!< Which bus the 1-Wire device is attached to */ onewire_device_address_t address; /*!< Device address (represented by its internal ROM ID) */ } onewire_device_t; /** * @brief Create an iterator to enumerate the 1-Wire devices on the bus * * @param[in] bus 1-Wire bus handle * @param[out] ret_iter Returned created device iterator * @return * - ESP_OK: Create device iterator successfully * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_NO_MEM: No memory to create device iterator * - ESP_FAIL: Other errors */ esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter); /** * @brief Delete the device iterator * * @param[in] iter Device iterator handle * @return * - ESP_OK: Delete device iterator successfully * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_FAIL: Other errors */ esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter); /** * @brief Get the next 1-Wire device from the iterator * * @param[in] iter Device iterator handle * @param[out] dev Returned 1-Wire device handle * @return * - ESP_OK: Get next device successfully * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_NOT_FOUND: No more device to get * - ESP_FAIL: Other errors */ esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev); #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/include/onewire_types.h ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #ifdef __cplusplus extern "C" { #endif /** * @brief Type of 1-Wire bus handle */ typedef struct onewire_bus_t *onewire_bus_handle_t; /** * @brief Type of the address for a 1-Wire compatible device */ typedef uint64_t onewire_device_address_t; /** * @brief Type of 1-Wire device iterator handle */ typedef struct onewire_device_iter_t *onewire_device_iter_handle_t; /** * @brief 1-Wire bus configuration */ typedef struct { int bus_gpio_num; /*!< GPIO number that used by the 1-Wire bus */ struct onewire_bus_config_flags { uint32_t en_pull_up: 1; /*!< Set true to enable internal pull-up resistor. Please note the internal pull-up resistor cannot provide enough current for some devices, so external pull-up resistor is still recommended. */ } flags; /*!< Configuration flags for the bus */ } onewire_bus_config_t; #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/interface/onewire_bus_interface.h ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif typedef struct onewire_bus_t onewire_bus_t; /*!< Type of 1-Wire bus */ /** * @brief 1-Wire bus interface definition */ struct onewire_bus_t { /** * @brief Write bytes to 1-wire bus * * @note This is a blocking function * * @param[in] bus 1-Wire bus handle * @param[in] tx_data pointer to data to be sent * @param[in] tx_data_size size of data to be sent, in bytes * @return * - ESP_OK: Write bytes to 1-Wire bus successfully * - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument * - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors */ esp_err_t (*write_bytes)(onewire_bus_t *bus, const uint8_t *tx_data, uint8_t tx_data_size); /** * @brief Read bytes from 1-wire bus * * @param[in] bus 1-wire bus handle * @param[out] rx_buf pointer to buffer to store received data * @param[in] rx_buf_size size of buffer to store received data, in bytes * @return * - ESP_OK: Read bytes from 1-Wire bus successfully * - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument * - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors */ esp_err_t (*read_bytes)(onewire_bus_t *bus, uint8_t *rx_buf, size_t rx_buf_size); /** * @brief Write a bit to 1-wire bus, this is a blocking function * * @param[in] handle 1-wire bus handle * @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit * @return * - ESP_OK Write bit to 1-wire bus successfully. * - ESP_ERR_INVALID_ARG Invalid argument. */ esp_err_t (*write_bit)(onewire_bus_handle_t handle, uint8_t tx_bit); /** * @brief Read a bit from 1-wire bus * * @param[in] handle 1-wire bus handle * @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit * @return * - ESP_OK Read bit from 1-wire bus successfully. * - ESP_ERR_INVALID_ARG Invalid argument. */ esp_err_t (*read_bit)(onewire_bus_handle_t handle, uint8_t *rx_bit); /** * @brief Send reset pulse to the bus, and check if there are devices attached to the bus * * @param[in] bus 1-Wire bus handle * * @return * - ESP_OK: Reset 1-Wire bus successfully and find device on the bus * - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus * - ESP_FAIL: Reset 1-Wire bus failed because of other errors */ esp_err_t (*reset)(onewire_bus_t *bus); /** * @brief Free 1-Wire bus resources * * @param[in] bus 1-Wire bus handle * * @return * - ESP_OK: Free resources successfully * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t (*del)(onewire_bus_t *bus); }; #ifdef __cplusplus } #endif ================================================ FILE: onewire_bus/src/onewire_bus_api.c ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_log.h" #include "esp_check.h" #include "onewire_types.h" #include "onewire_bus_interface.h" static const char *TAG = "1-wire"; esp_err_t onewire_bus_reset(onewire_bus_handle_t bus) { ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->reset(bus); } esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size) { ESP_RETURN_ON_FALSE(bus && tx_data && tx_data_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->write_bytes(bus, tx_data, tx_data_size); } esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size) { ESP_RETURN_ON_FALSE(bus && rx_buf && rx_buf_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->read_bytes(bus, rx_buf, rx_buf_size); } esp_err_t onewire_bus_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit) { ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->write_bit(bus, tx_bit); } esp_err_t onewire_bus_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit) { ESP_RETURN_ON_FALSE(bus && rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->read_bit(bus, rx_bit); } esp_err_t onewire_bus_del(onewire_bus_handle_t bus) { ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); return bus->del(bus); } ================================================ FILE: onewire_bus/src/onewire_bus_impl_rmt.c ================================================ /* * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "esp_check.h" #include "esp_attr.h" #include "driver/rmt_tx.h" #include "driver/rmt_rx.h" #include "driver/gpio.h" #include "esp_private/gpio.h" #include "onewire_bus_impl_rmt.h" #include "onewire_bus_interface.h" #include "esp_idf_version.h" static const char *TAG = "1-wire.rmt"; #define ONEWIRE_RMT_RESOLUTION_HZ 1000000 // RMT channel default resolution for 1-wire bus, 1MHz, 1tick = 1us #define ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE 4 // the memory size of each RMT channel, in words (4 bytes) #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64 #else #define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48 #endif // for chips whose RMT RX channel doesn't support ping-pong, we need the user to tell the maximum number of bytes will be received #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 // one RMT symbol represents one bit, so x8 #define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE (rmt_config->max_rx_bytes * 8) #else // otherwise, we just use one memory block, to save resources #define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS #endif /* Reset Pulse: | RESET_PULSE | RESET_WAIT_DURATION | | _DURATION | | | | | | RESET | | | | * | | _PRESENCE | | | | | | _DURATION | | ----------+ +-----+ +-------------- | | | | | | | | | | | | +-------------+ +-----------+ *: RESET_PRESENCE_WAIT_DURATION */ #define ONEWIRE_RESET_PULSE_DURATION 500 // duration of reset bit #define ONEWIRE_RESET_WAIT_DURATION 200 // how long should master wait for device to show its presence #define ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN 15 // minimum duration for master to wait device to show its presence #define ONEWIRE_RESET_PRESENCE_DURATION_MIN 60 // minimum duration for master to recognize device as present /* Write 1 bit: | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT | _DURATION | _DURATION | _DURATION | SLOT | | | | ----------+ +------------------------------------- | | | | | | +------------+ Write 0 bit: | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT | _DURATION | _DURATION | _DURATION | SLOT | | | | ----------+ +------------------------- | | | | | | +------------------------+ Read 1 bit: | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT | _DURATION | | _DURATION | SLOT | | SLOT_BIT_ | | | | | SAMPLE_TIME | | | ----------+ +---------------------------------------------- | | | | | | +------------+ Read 0 bit: | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT | _DURATION | | _DURATION | SLOT | | SLOT_BIT_ | | | | | SAMPLE_TIME | | | ----------+ | | +----------------------------- | | | | | PULLED DOWN | | | BY DEVICE | +-----------------------------+ */ #define ONEWIRE_SLOT_START_DURATION 2 // bit start pulse duration #define ONEWIRE_SLOT_BIT_DURATION 60 // duration for each bit to transmit // refer to https://www.maximintegrated.com/en/design/technical-documents/app-notes/3/3829.html for more information #define ONEWIRE_SLOT_RECOVERY_DURATION 5 // recovery time between each bit, should be longer in parasite power mode #define ONEWIRE_SLOT_BIT_SAMPLE_TIME 15 // how long after bit start pulse should the master sample from the bus typedef struct { onewire_bus_t base; /*!< base class */ rmt_channel_handle_t tx_channel; /*!< rmt tx channel handler */ rmt_channel_handle_t rx_channel; /*!< rmt rx channel handler */ gpio_num_t data_gpio_num; /*!< GPIO number for 1-wire bus */ rmt_encoder_handle_t tx_bytes_encoder; /*!< used to encode commands and data */ rmt_encoder_handle_t tx_copy_encoder; /*!< used to encode reset pulse and bits */ rmt_symbol_word_t *rx_symbols_buf; /*!< hold rmt raw symbols */ size_t max_rx_bytes; /*!< buffer size in byte for single receive transaction */ QueueHandle_t receive_queue; SemaphoreHandle_t bus_mutex; } onewire_bus_rmt_obj_t; static rmt_symbol_word_t onewire_reset_pulse_symbol = { .level0 = 0, .duration0 = ONEWIRE_RESET_PULSE_DURATION, .level1 = 1, .duration1 = ONEWIRE_RESET_WAIT_DURATION }; static rmt_symbol_word_t onewire_bit0_symbol = { .level0 = 0, .duration0 = ONEWIRE_SLOT_START_DURATION + ONEWIRE_SLOT_BIT_DURATION, .level1 = 1, .duration1 = ONEWIRE_SLOT_RECOVERY_DURATION }; static rmt_symbol_word_t onewire_bit1_symbol = { .level0 = 0, .duration0 = ONEWIRE_SLOT_START_DURATION, .level1 = 1, .duration1 = ONEWIRE_SLOT_BIT_DURATION + ONEWIRE_SLOT_RECOVERY_DURATION }; const static rmt_transmit_config_t onewire_rmt_tx_config = { .loop_count = 0, // no transfer loop .flags.eot_level = 1 // onewire bus should be released in IDLE }; const static rmt_receive_config_t onewire_rmt_rx_config = { .signal_range_min_ns = 1000000000 / ONEWIRE_RMT_RESOLUTION_HZ, .signal_range_max_ns = (ONEWIRE_RESET_PULSE_DURATION + ONEWIRE_RESET_WAIT_DURATION) * 1000, }; static esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit); static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit); static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size); static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size); static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus); static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus); static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt); IRAM_ATTR bool onewire_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) { BaseType_t task_woken = pdFALSE; onewire_bus_rmt_obj_t *bus_rmt = (onewire_bus_rmt_obj_t *)user_data; xQueueSendFromISR(bus_rmt->receive_queue, edata, &task_woken); return task_woken; } /* [0].0 means symbol[0].duration0 First reset pulse after rmt channel init: Bus is low | Reset | Wait | Device | Bus Idle after init | Pulse | | Presence | +------+ +----------- | | | | | | | | | -------------------+ +----------+ 1 2 3 [0].1 [0].0 [1].1 [1].0 Following reset pulses: Bus is high | Reset | Wait | Device | Bus Idle after init | Pulse | | Presence | ------------+ +------+ +----------- | | | | | | | | | | | | +-------+ +----------+ 1 2 3 4 [0].0 [0].1 [1].0 [1].1 */ static bool onewire_rmt_check_presence_pulse(rmt_symbol_word_t *rmt_symbols, size_t symbol_num) { bool ret = false; if (symbol_num >= 2) { // there should be at lease 2 symbols(3 or 4 edges) if (rmt_symbols[0].level1 == 1) { // bus is high before reset pulse if (rmt_symbols[0].duration1 > ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN && rmt_symbols[1].duration0 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) { ret = true; } } else { // bus is low before reset pulse(first pulse after rmt channel init) if (rmt_symbols[0].duration0 > ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN && rmt_symbols[1].duration1 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) { ret = true; } } } return ret; } static void onewire_rmt_decode_data(rmt_symbol_word_t *rmt_symbols, size_t symbol_num, uint8_t *rx_buf, size_t rx_buf_size) { size_t byte_pos = 0; size_t bit_pos = 0; for (size_t i = 0; i < symbol_num; i ++) { if (rmt_symbols[i].duration0 > ONEWIRE_SLOT_BIT_SAMPLE_TIME) { // 0 bit rx_buf[byte_pos] &= ~(1 << bit_pos); // LSB first } else { // 1 bit rx_buf[byte_pos] |= 1 << bit_pos; } bit_pos ++; if (bit_pos >= 8) { bit_pos = 0; byte_pos ++; if (byte_pos >= rx_buf_size) { break; } } } } esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus) { esp_err_t ret = ESP_OK; onewire_bus_rmt_obj_t *bus_rmt = NULL; ESP_RETURN_ON_FALSE(bus_config && rmt_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); bus_rmt = calloc(1, sizeof(onewire_bus_rmt_obj_t)); ESP_RETURN_ON_FALSE(bus_rmt, ESP_ERR_NO_MEM, TAG, "no mem for onewire_bus_rmt_obj_t"); bus_rmt->data_gpio_num = GPIO_NUM_NC; // create rmt bytes encoder to transmit 1-wire commands and data rmt_bytes_encoder_config_t bytes_encoder_config = { .bit0 = onewire_bit0_symbol, .bit1 = onewire_bit1_symbol, .flags.msb_first = 0, }; ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &bus_rmt->tx_bytes_encoder), err, TAG, "create bytes encoder failed"); // create rmt copy encoder to transmit 1-wire reset pulse or bits rmt_copy_encoder_config_t copy_encoder_config = {}; ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &bus_rmt->tx_copy_encoder), err, TAG, "create copy encoder failed"); // create RX and TX channels and bind them to the same GPIO rmt_rx_channel_config_t onewire_rx_channel_cfg = { .clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, .gpio_num = bus_config->bus_gpio_num, .mem_block_symbols = ONEWIRE_RMT_RX_MEM_BLOCK_SIZE, }; ESP_GOTO_ON_ERROR(rmt_new_rx_channel(&onewire_rx_channel_cfg, &bus_rmt->rx_channel), err, TAG, "create rmt rx channel failed"); rmt_tx_channel_config_t onewire_tx_channel_cfg = { .clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, .gpio_num = bus_config->bus_gpio_num, .mem_block_symbols = ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS, .trans_queue_depth = ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE, #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) .flags.io_loop_back = true, .flags.io_od_mode = true, #endif }; ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&onewire_tx_channel_cfg, &bus_rmt->tx_channel), err, TAG, "create rmt tx channel failed"); bus_rmt->data_gpio_num = bus_config->bus_gpio_num; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) // enable open-drain mode for 1-wire bus gpio_od_enable(bus_rmt->data_gpio_num); #endif if (bus_config->flags.en_pull_up) { // enable internal pull-up resistor and disable pull-down resistor gpio_set_pull_mode(bus_rmt->data_gpio_num, GPIO_PULLUP_ONLY); } else { // disable internal pull-up and pull-down resistors gpio_set_pull_mode(bus_rmt->data_gpio_num, GPIO_FLOATING); } // allocate rmt rx symbol buffer, one RMT symbol represents one bit, so x8 bus_rmt->rx_symbols_buf = malloc(rmt_config->max_rx_bytes * sizeof(rmt_symbol_word_t) * 8); ESP_GOTO_ON_FALSE(bus_rmt->rx_symbols_buf, ESP_ERR_NO_MEM, err, TAG, "no mem to store received RMT symbols"); bus_rmt->max_rx_bytes = rmt_config->max_rx_bytes; bus_rmt->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); ESP_GOTO_ON_FALSE(bus_rmt->receive_queue, ESP_ERR_NO_MEM, err, TAG, "receive queue creation failed"); bus_rmt->bus_mutex = xSemaphoreCreateMutex(); ESP_GOTO_ON_FALSE(bus_rmt->bus_mutex, ESP_ERR_NO_MEM, err, TAG, "bus mutex creation failed"); // register rmt rx done callback rmt_rx_event_callbacks_t cbs = { .on_recv_done = onewire_rmt_rx_done_callback }; ESP_GOTO_ON_ERROR(rmt_rx_register_event_callbacks(bus_rmt->rx_channel, &cbs, bus_rmt), err, TAG, "enable rmt rx channel failed"); // enable rmt channels ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->rx_channel), err, TAG, "enable rmt rx channel failed"); ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->tx_channel), err, TAG, "enable rmt tx channel failed"); // release the bus by sending a special RMT symbol static rmt_symbol_word_t release_symbol = { .level0 = 1, .duration0 = 1, .level1 = 1, .duration1 = 0, }; ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &release_symbol, sizeof(release_symbol), &onewire_rmt_tx_config), err, TAG, "release bus failed"); bus_rmt->base.del = onewire_bus_rmt_del; bus_rmt->base.reset = onewire_bus_rmt_reset; bus_rmt->base.write_bit = onewire_bus_rmt_write_bit; bus_rmt->base.write_bytes = onewire_bus_rmt_write_bytes; bus_rmt->base.read_bit = onewire_bus_rmt_read_bit; bus_rmt->base.read_bytes = onewire_bus_rmt_read_bytes; *ret_bus = &bus_rmt->base; return ret; err: if (bus_rmt) { onewire_bus_rmt_destroy(bus_rmt); } return ret; } static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt) { if (bus_rmt->tx_bytes_encoder) { rmt_del_encoder(bus_rmt->tx_bytes_encoder); } if (bus_rmt->tx_copy_encoder) { rmt_del_encoder(bus_rmt->tx_copy_encoder); } if (bus_rmt->rx_channel) { rmt_disable(bus_rmt->rx_channel); rmt_del_channel(bus_rmt->rx_channel); } if (bus_rmt->tx_channel) { rmt_disable(bus_rmt->tx_channel); rmt_del_channel(bus_rmt->tx_channel); } if (bus_rmt->receive_queue) { vQueueDelete(bus_rmt->receive_queue); } if (bus_rmt->bus_mutex) { vSemaphoreDelete(bus_rmt->bus_mutex); } if (bus_rmt->rx_symbols_buf) { free(bus_rmt->rx_symbols_buf); } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) if (bus_rmt->data_gpio_num != GPIO_NUM_NC) { gpio_od_disable(bus_rmt->data_gpio_num); } #endif free(bus_rmt); return ESP_OK; } static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); return onewire_bus_rmt_destroy(bus_rmt); } static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); esp_err_t ret = ESP_OK; xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); // send reset pulse while receive presence pulse ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t) * 2, &onewire_rmt_rx_config), err, TAG, "1-wire reset pulse receive failed"); ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_reset_pulse_symbol, sizeof(onewire_reset_pulse_symbol), &onewire_rmt_tx_config), err, TAG, "1-wire reset pulse transmit failed"); // wait and check presence pulse rmt_rx_done_event_data_t rmt_rx_evt_data; ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT, err, TAG, "1-wire reset pulse receive timeout"); if (onewire_rmt_check_presence_pulse(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols) == false) { ret = ESP_ERR_NOT_FOUND; } err: xSemaphoreGive(bus_rmt->bus_mutex); return ret; } static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); esp_err_t ret = ESP_OK; xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); // transmit data with the bytes encoder ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_data, tx_data_size, &onewire_rmt_tx_config), err, TAG, "1-wire data transmit failed"); // wait the transmission to complete ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire data transmit failed"); err: xSemaphoreGive(bus_rmt->bus_mutex); return ret; } // While receiving data, we use rmt transmit channel to send 0xFF to generate read pulse, // at the same time, receive channel is used to record weather the bus is pulled down by device. static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(rx_buf_size <= bus_rmt->max_rx_bytes, ESP_ERR_INVALID_ARG, TAG, "rx_buf_size too large for buffer to hold"); memset(rx_buf, 0, rx_buf_size); xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); // transmit one bits to generate read clock uint8_t tx_buffer[rx_buf_size]; memset(tx_buffer, 0xFF, rx_buf_size); // transmit 1 bits while receiving ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, rx_buf_size * 8 * sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), err, TAG, "1-wire data receive failed"); ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_buffer, sizeof(tx_buffer), &onewire_rmt_tx_config), err, TAG, "1-wire data transmit failed"); // wait the transmission finishes and decode data rmt_rx_done_event_data_t rmt_rx_evt_data; ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT, err, TAG, "1-wire data receive timeout"); onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_buf, rx_buf_size); err: xSemaphoreGive(bus_rmt->bus_mutex); return ret; } static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); const rmt_symbol_word_t *symbol_to_transmit = tx_bit ? &onewire_bit1_symbol : &onewire_bit0_symbol; esp_err_t ret = ESP_OK; xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); // transmit bit ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, symbol_to_transmit, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config), err, TAG, "1-wire bit transmit failed"); // wait the transmission to complete ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire bit transmit failed"); err: xSemaphoreGive(bus_rmt->bus_mutex); return ret; } static esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit) { onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); esp_err_t ret = ESP_OK; xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); // transmit 1 bit while receiving ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), err, TAG, "1-wire bit receive failed"); ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_bit1_symbol, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config), err, TAG, "1-wire bit transmit failed"); // wait the transmission finishes and decode data rmt_rx_done_event_data_t rmt_rx_evt_data; ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT, err, TAG, "1-wire bit receive timeout"); uint8_t rx_buffer = 0; onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, &rx_buffer, sizeof(rx_buffer)); *rx_bit = rx_buffer & 0x01; err: xSemaphoreGive(bus_rmt->bus_mutex); return ret; } ================================================ FILE: onewire_bus/src/onewire_bus_impl_uart.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "esp_check.h" #include "driver/uart.h" #include "driver/gpio.h" #include "onewire_bus_impl_uart.h" #include "onewire_bus_interface.h" #include "esp_idf_version.h" static const char *TAG = "1-wire.uart"; #define ONEWIRE_UART_DEFAULT_TIMEOUT_MS 100 // refer to https://www.analog.com/en/resources/technical-articles/using-a-uart-to-implement-a-1wire-bus-master.html for more information #define ONEWIRE_UART_BAUD_RESET 9600 // baud rate for reset pulse and presence detect #define ONEWIRE_UART_BAUD_SLOT 115200 // baud rate for read and write operations #define ONEWIRE_UART_RESET_TX 0xF0 // TX value for reset pulse and presence detect #define ONEWIRE_UART_RESET_RX_NO_DEVICE 0xF0 // RX value when no device is present #define ONEWIRE_UART_SLOT_TX_WRITE_1 0xFF // TX value for write 1 #define ONEWIRE_UART_SLOT_TX_WRITE_0 0x00 // TX value for write 0 #define ONEWIRE_UART_SLOT_TX_READ 0xFF // TX value for read #define ONEWIRE_UART_SLOT_RX_READ_1 0xFF // RX value when read 1 typedef struct { onewire_bus_t base; /*!< base class */ uart_port_t uart_port_num; /*!< UART port number */ gpio_num_t data_gpio_num; /*!< GPIO number for 1-wire bus */ uint32_t current_baud_rate; /*!< Note: the baud rate returned by uart_get_baudrate() could have a slight deviation from the user-configured baud rate. That's why we store the configured baud rate here. */ SemaphoreHandle_t bus_mutex; } onewire_bus_uart_obj_t; static esp_err_t onewire_bus_uart_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit); static esp_err_t onewire_bus_uart_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit); static esp_err_t onewire_bus_uart_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size); static esp_err_t onewire_bus_uart_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size); static esp_err_t onewire_bus_uart_reset(onewire_bus_handle_t bus); static esp_err_t onewire_bus_uart_del(onewire_bus_handle_t bus); static esp_err_t onewire_bus_uart_destroy(onewire_bus_uart_obj_t *bus_uart); static esp_err_t onewire_bus_uart_set_baud_rate(onewire_bus_uart_obj_t *bus_uart, uint32_t baud_rate); static esp_err_t onewire_bus_uart_exchange_byte(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_data, uint8_t *rx_data); static esp_err_t onewire_bus_uart_write_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_bit); static esp_err_t onewire_bus_uart_read_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t *rx_bit); esp_err_t onewire_new_bus_uart(const onewire_bus_config_t *bus_config, const onewire_bus_uart_config_t *uart_config, onewire_bus_handle_t *ret_bus) { esp_err_t ret = ESP_OK; onewire_bus_uart_obj_t *bus_uart = NULL; ESP_RETURN_ON_FALSE(bus_config && uart_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(bus_config->bus_gpio_num), ESP_ERR_INVALID_ARG, TAG, "invalid GPIO number"); bus_uart = calloc(1, sizeof(onewire_bus_uart_obj_t)); ESP_RETURN_ON_FALSE(bus_uart, ESP_ERR_NO_MEM, TAG, "no mem for onewire_bus_uart_obj_t"); bus_uart->uart_port_num = UART_NUM_MAX; bus_uart->data_gpio_num = GPIO_NUM_NC; const gpio_config_t io_conf = { .pin_bit_mask = (1ULL << bus_config->bus_gpio_num), .mode = GPIO_MODE_INPUT_OUTPUT_OD, .pull_up_en = bus_config->flags.en_pull_up ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "gpio config failed"); bus_uart->data_gpio_num = bus_config->bus_gpio_num; // Simulate a 1-Wire bus using UART 8N1 mode // refer to https://www.analog.com/en/resources/technical-articles/using-a-uart-to-implement-a-1wire-bus-master.html for more information const uart_config_t uart_cfg = { .baud_rate = ONEWIRE_UART_BAUD_RESET, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; ESP_GOTO_ON_ERROR(uart_param_config(uart_config->uart_port_num, &uart_cfg), err, TAG, "uart param config failed"); bus_uart->current_baud_rate = uart_cfg.baud_rate; ESP_GOTO_ON_ERROR(uart_set_pin(uart_config->uart_port_num, bus_config->bus_gpio_num, bus_config->bus_gpio_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), err, TAG, "uart set pin failed"); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) // Set the RX buffer to minimum size because we only need to receive 1 byte at a time // Disable the TX buffer because we only need to send 1 byte at a time ESP_GOTO_ON_ERROR(uart_driver_install(uart_config->uart_port_num, UART_HW_FIFO_LEN(uart_config->uart_port_num) + 1, 0, 0, NULL, 0), err, TAG, "uart driver install failed"); #else ESP_GOTO_ON_ERROR(uart_driver_install(uart_config->uart_port_num, UART_FIFO_LEN + 1, 0, 0, NULL, 0), err, TAG, "uart driver install failed"); #endif bus_uart->uart_port_num = uart_config->uart_port_num; // Configuration optimized for this scenario // We only need to receive 1 byte at a time, so set the rx full threshold to 1 for faster response // Normally, the RX timeout interrupt is not expected to trigger. Setting the timeout to 1 here is simply to ensure a fast response ESP_GOTO_ON_ERROR(uart_set_rx_full_threshold(uart_config->uart_port_num, 1), err, TAG, "uart set rx full threshold failed"); ESP_GOTO_ON_ERROR(uart_set_rx_timeout(uart_config->uart_port_num, 1), err, TAG, "uart set rx timeout failed"); bus_uart->bus_mutex = xSemaphoreCreateMutex(); ESP_GOTO_ON_FALSE(bus_uart->bus_mutex, ESP_ERR_NO_MEM, err, TAG, "bus mutex creation failed"); bus_uart->base.del = onewire_bus_uart_del; bus_uart->base.reset = onewire_bus_uart_reset; bus_uart->base.write_bit = onewire_bus_uart_write_bit; bus_uart->base.write_bytes = onewire_bus_uart_write_bytes; bus_uart->base.read_bit = onewire_bus_uart_read_bit; bus_uart->base.read_bytes = onewire_bus_uart_read_bytes; *ret_bus = &bus_uart->base; return ret; err: if (bus_uart) { onewire_bus_uart_destroy(bus_uart); } return ret; } static esp_err_t onewire_bus_uart_destroy(onewire_bus_uart_obj_t *bus_uart) { if (bus_uart->bus_mutex) { vSemaphoreDelete(bus_uart->bus_mutex); } if (bus_uart->uart_port_num != UART_NUM_MAX) { uart_driver_delete(bus_uart->uart_port_num); } if (bus_uart->data_gpio_num != GPIO_NUM_NC) { gpio_reset_pin(bus_uart->data_gpio_num); } free(bus_uart); return ESP_OK; } static esp_err_t onewire_bus_uart_set_baud_rate(onewire_bus_uart_obj_t *bus_uart, uint32_t baud_rate) { if (bus_uart->current_baud_rate == baud_rate) { return ESP_OK; } esp_err_t ret = uart_set_baudrate(bus_uart->uart_port_num, baud_rate); if (ret != ESP_OK) { return ret; } bus_uart->current_baud_rate = baud_rate; return ESP_OK; } /** * @brief Send and receive one byte over the UART bus. * * @note This function is used for: * - reset pulse and presence detect * - write or read one bit on the 1-wire bus */ static esp_err_t onewire_bus_uart_exchange_byte(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_data, uint8_t *rx_data) { esp_err_t ret = uart_flush_input(bus_uart->uart_port_num); if (ret != ESP_OK) { return ret; } if (uart_tx_chars(bus_uart->uart_port_num, (const char *)&tx_data, 1) != 1) { return ESP_FAIL; } if (uart_read_bytes(bus_uart->uart_port_num, rx_data, 1, pdMS_TO_TICKS(ONEWIRE_UART_DEFAULT_TIMEOUT_MS)) != 1) { return ESP_ERR_TIMEOUT; } return ESP_OK; } static esp_err_t onewire_bus_uart_write_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_bit) { uint8_t rx_data = 0; const uint8_t tx_data = tx_bit ? ONEWIRE_UART_SLOT_TX_WRITE_1 : ONEWIRE_UART_SLOT_TX_WRITE_0; esp_err_t ret = onewire_bus_uart_exchange_byte(bus_uart, tx_data, &rx_data); if (ret != ESP_OK) { return ret; } return (rx_data == tx_data) ? ESP_OK : ESP_ERR_INVALID_STATE; // Check if the sent data is corrupted } static esp_err_t onewire_bus_uart_read_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t *rx_bit) { uint8_t rx_data = 0; esp_err_t ret = onewire_bus_uart_exchange_byte(bus_uart, ONEWIRE_UART_SLOT_TX_READ, &rx_data); if (ret != ESP_OK) { return ret; } *rx_bit = (rx_data == ONEWIRE_UART_SLOT_RX_READ_1) ? 1 : 0; return ESP_OK; } ////////////////////////////// implementation of onewire_bus_t functions ////////////////////////////// static esp_err_t onewire_bus_uart_del(onewire_bus_handle_t bus) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); return onewire_bus_uart_destroy(bus_uart); } static esp_err_t onewire_bus_uart_reset(onewire_bus_handle_t bus) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); esp_err_t ret = ESP_OK; uint8_t rx_data = 0; xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY); ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_RESET), err, TAG, "set reset baudrate failed"); ESP_GOTO_ON_ERROR(onewire_bus_uart_exchange_byte(bus_uart, ONEWIRE_UART_RESET_TX, &rx_data), err, TAG, "create reset pulse failed"); ESP_GOTO_ON_FALSE(rx_data != ONEWIRE_UART_RESET_RX_NO_DEVICE, ESP_ERR_NOT_FOUND, err, TAG, "no 1-wire device found"); err: xSemaphoreGive(bus_uart->bus_mutex); return ret; } static esp_err_t onewire_bus_uart_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(tx_data && tx_data_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY); ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed"); for (uint8_t i = 0; i < tx_data_size; i++) { uint8_t current = tx_data[i]; for (int bit = 0; bit < 8; bit++) { ESP_GOTO_ON_ERROR(onewire_bus_uart_write_bit_nolock(bus_uart, current & 0x01), err, TAG, "write bit failed"); current >>= 1; } } err: xSemaphoreGive(bus_uart->bus_mutex); return ret; } static esp_err_t onewire_bus_uart_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(rx_buf && rx_buf_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); memset(rx_buf, 0, rx_buf_size); xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY); ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed"); for (size_t i = 0; i < rx_buf_size; i++) { uint8_t current = 0; for (int bit = 0; bit < 8; bit++) { uint8_t rx_bit = 0; ESP_GOTO_ON_ERROR(onewire_bus_uart_read_bit_nolock(bus_uart, &rx_bit), err, TAG, "read bit failed"); if (rx_bit) { current |= (1 << bit); } } rx_buf[i] = current; } err: xSemaphoreGive(bus_uart->bus_mutex); return ret; } static esp_err_t onewire_bus_uart_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); esp_err_t ret = ESP_OK; xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY); ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed"); ESP_GOTO_ON_ERROR(onewire_bus_uart_write_bit_nolock(bus_uart, tx_bit), err, TAG, "write bit failed"); err: xSemaphoreGive(bus_uart->bus_mutex); return ret; } static esp_err_t onewire_bus_uart_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit) { onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base); ESP_RETURN_ON_FALSE(rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); esp_err_t ret = ESP_OK; xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY); ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed"); ESP_GOTO_ON_ERROR(onewire_bus_uart_read_bit_nolock(bus_uart, rx_bit), err, TAG, "read bit failed"); err: xSemaphoreGive(bus_uart->bus_mutex); return ret; } ================================================ FILE: onewire_bus/src/onewire_crc.c ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "onewire_crc.h" #define FAST_CRC 1 // define this to use the fast CRC table #if FAST_CRC static const uint8_t dalas_crc8_table[] = { 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 }; uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size) { uint8_t crc = init_crc; for (size_t i = 0; i < input_size; i ++) { crc = dalas_crc8_table[crc ^ input[i]]; } return crc; } #else // FAST_CRC uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size) { uint8_t crc = init_crc; for (size_t i = 0; i < input_size; i++) { uint8_t byte = input[i]; for (int j = 0; j < 8; j++) { uint8_t x = (byte ^ crc) & 0x01; crc >>= 1; if (x != 0) { crc ^= 0x8C; } byte >>= 1; } } return crc; } #endif // FAST_CRC ================================================ FILE: onewire_bus/src/onewire_device.c ================================================ /* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_check.h" #include "esp_log.h" #include "onewire_bus.h" #include "onewire_device.h" #include "onewire_crc.h" #include "onewire_cmd.h" static const char *TAG = "1-wire.device"; typedef struct onewire_device_iter_t { onewire_bus_handle_t bus; uint16_t last_discrepancy; bool is_last_device; uint8_t rom_number[sizeof(onewire_device_address_t)]; } onewire_device_iter_t; esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter) { ESP_RETURN_ON_FALSE(bus && ret_iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); onewire_device_iter_t *iter = calloc(1, sizeof(onewire_device_iter_t)); ESP_RETURN_ON_FALSE(iter, ESP_ERR_NO_MEM, TAG, "no mem for device iterator"); iter->bus = bus; *ret_iter = iter; return ESP_OK; } esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter) { ESP_RETURN_ON_FALSE(iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); free(iter); return ESP_OK; } // Search algorithm inspired by https://www.analog.com/en/app-notes/1wire-search-algorithm.html esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev) { ESP_RETURN_ON_FALSE(iter && dev, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); // we don't treat iterator ending and ESP_ERR_NOT_FOUND as an error condition, so just print debug message here if (iter->is_last_device) { ESP_LOGD(TAG, "1-wire rom search finished"); return ESP_ERR_NOT_FOUND; } onewire_bus_handle_t bus = iter->bus; esp_err_t reset_result = onewire_bus_reset(bus); if (reset_result == ESP_ERR_NOT_FOUND) { ESP_LOGW(TAG, "reset bus failed: no devices found"); return ESP_ERR_NOT_FOUND; } ESP_RETURN_ON_ERROR(reset_result, TAG, "reset bus failed"); // send rom search command and start search algorithm ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(bus, (uint8_t[]) { ONEWIRE_CMD_SEARCH_NORMAL }, 1), TAG, "send ONEWIRE_CMD_SEARCH_NORMAL failed"); uint8_t last_zero = 0; for (uint16_t rom_bit_index = 0; rom_bit_index < sizeof(onewire_device_address_t) * 8; rom_bit_index ++) { uint8_t rom_byte_index = rom_bit_index / 8; uint8_t rom_bit_mask = 1 << (rom_bit_index % 8); // calculate byte index and bit mask in advance for convenience uint8_t rom_bit = 0; uint8_t rom_bit_complement = 0; ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit), TAG, "read rom_bit error"); // write 1 bit to read from the bus ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit_complement), TAG, "read rom_bit_complement error"); // read a bit and its complement // No devices participating in search. if (rom_bit && rom_bit_complement) { ESP_LOGE(TAG, "no devices participating in search"); return ESP_ERR_NOT_FOUND; } uint8_t search_direction; if (rom_bit != rom_bit_complement) { // There are only 0s or 1s in the bit of the participating ROM numbers. search_direction = rom_bit; // just go ahead } else { // There are both 0s and 1s in the current bit position of the participating ROM numbers. This is a discrepancy. if (rom_bit_index < iter->last_discrepancy) { // current id bit is before the last discrepancy bit search_direction = (iter->rom_number[rom_byte_index] & rom_bit_mask) ? 0x01 : 0x00; // follow previous way } else { search_direction = (rom_bit_index == iter->last_discrepancy) ? 0x01 : 0x00; // search for 0 bit first } if (search_direction == 0) { // record zero's position in last zero last_zero = rom_bit_index; } } if (search_direction == 1) { // set corresponding rom bit by search direction iter->rom_number[rom_byte_index] |= rom_bit_mask; } else { iter->rom_number[rom_byte_index] &= ~rom_bit_mask; } // set search direction ESP_RETURN_ON_ERROR(onewire_bus_write_bit(bus, search_direction), TAG, "write direction bit error"); } // if the search was successful iter->last_discrepancy = last_zero; if (iter->last_discrepancy == 0) { // last zero loops back to the first bit iter->is_last_device = true; } // check crc ESP_RETURN_ON_FALSE(onewire_crc8(0, iter->rom_number, 7) == iter->rom_number[7], ESP_ERR_INVALID_CRC, TAG, "bad device crc"); // save the ROM number as the device address memcpy(&dev->address, iter->rom_number, sizeof(onewire_device_address_t)); dev->bus = bus; ESP_LOGD(TAG, "new 1-Wire device found, address: %016llX", dev->address); return ESP_OK; } ================================================ FILE: onewire_bus/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(onewire_bus_test) ================================================ FILE: onewire_bus/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "onewire_bus_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: onewire_bus/test_apps/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice EXAMPLE_ONEWIRE_BACKEND prompt "1-Wire backend" default EXAMPLE_ONEWIRE_BACKEND_RMT if SOC_RMT_SUPPORTED default EXAMPLE_ONEWIRE_BACKEND_UART if SOC_UART_SUPPORTED help Select which low-level backend the test app uses. config EXAMPLE_ONEWIRE_BACKEND_RMT bool "RMT backend" depends on SOC_RMT_SUPPORTED config EXAMPLE_ONEWIRE_BACKEND_UART bool "UART backend" depends on SOC_UART_SUPPORTED endchoice config EXAMPLE_ONEWIRE_UART_PORT_NUM int "UART port number" depends on EXAMPLE_ONEWIRE_BACKEND_UART default 1 range 0 2 help UART port used by UART backend. Valid UART port numbers differ across targets. Note: UART0 is typically used by the console output. config EXAMPLE_ONEWIRE_BUS_GPIO int "1-Wire data GPIO" default 0 range 0 63 help GPIO number used for the 1-Wire data line. Valid GPIO numbers depend on the selected target; choose a data-capable GPIO within this range for your chip. config EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP bool "Enable internal pull-up" default y help Enable internal pull-up resistor on the GPIO pin used for the 1-Wire data line. This is useful when no external pull-up resistor is present. config EXAMPLE_ONEWIRE_MAX_DEVICES int "Maximum devices to search" default 2 range 1 64 help Maximum number of devices to search on the 1-Wire bus. This test app performs SEARCH ROM to collect device addresses (64-bit ROM IDs). endmenu ================================================ FILE: onewire_bus/test_apps/main/idf_component.yml ================================================ dependencies: espressif/onewire_bus: version: "*" override_path: "../.." ================================================ FILE: onewire_bus/test_apps/main/onewire_bus_test.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "esp_log.h" #include "esp_check.h" #include "sdkconfig.h" #include "onewire_bus.h" #include "onewire_device.h" static const char *TAG = "test-app"; #if CONFIG_EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP #define EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP 1 #else #define EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP 0 #endif #if CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART #define EXAMPLE_ONEWIRE_UART_PORT_NUM CONFIG_EXAMPLE_ONEWIRE_UART_PORT_NUM #endif #define EXAMPLE_ONEWIRE_BUS_GPIO CONFIG_EXAMPLE_ONEWIRE_BUS_GPIO #define EXAMPLE_ONEWIRE_MAX_DEVICES CONFIG_EXAMPLE_ONEWIRE_MAX_DEVICES void app_main(void) { // install new 1-wire bus onewire_bus_handle_t bus; onewire_bus_config_t bus_config = { .bus_gpio_num = EXAMPLE_ONEWIRE_BUS_GPIO, .flags = { .en_pull_up = EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP, } }; #if CONFIG_EXAMPLE_ONEWIRE_BACKEND_RMT onewire_bus_rmt_config_t rmt_config = { .max_rx_bytes = 10, // 1byte ROM command + 8byte ROM number + 1byte device command }; ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config, &rmt_config, &bus)); ESP_LOGI(TAG, "1-Wire bus installed on GPIO%d by RMT backend", EXAMPLE_ONEWIRE_BUS_GPIO); #elif CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART onewire_bus_uart_config_t uart_config = { .uart_port_num = EXAMPLE_ONEWIRE_UART_PORT_NUM, }; ESP_ERROR_CHECK(onewire_new_bus_uart(&bus_config, &uart_config, &bus)); ESP_LOGI(TAG, "1-Wire bus installed on GPIO%d by UART backend (UART%d)", EXAMPLE_ONEWIRE_BUS_GPIO, EXAMPLE_ONEWIRE_UART_PORT_NUM); #else #error "No 1-Wire backend selected in menuconfig" #endif int onewire_device_found = 0; onewire_device_iter_handle_t iter = NULL; onewire_device_t next_onewire_device; esp_err_t search_result = ESP_OK; // create 1-wire device iterator, which is used for device search ESP_ERROR_CHECK(onewire_new_device_iter(bus, &iter)); ESP_LOGI(TAG, "Device iterator created, start searching..."); do { search_result = onewire_device_iter_get_next(iter, &next_onewire_device); // found a new device if (search_result == ESP_OK) { ESP_LOGI(TAG, "Found a new device, address: %016llX", next_onewire_device.address); onewire_device_found++; if (onewire_device_found >= EXAMPLE_ONEWIRE_MAX_DEVICES) { ESP_LOGI(TAG, "Max device number reached, stop searching..."); break; } } } while (search_result != ESP_ERR_NOT_FOUND); ESP_ERROR_CHECK(onewire_del_device_iter(iter)); ESP_LOGI(TAG, "Searching done, %d device(s) found", onewire_device_found); // delete the bus ESP_LOGI(TAG, "Deleting bus..."); ESP_ERROR_CHECK(onewire_bus_del(bus)); } ================================================ FILE: onewire_bus/test_apps/pytest_onewire_bus.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut @pytest.mark.generic @pytest.mark.parametrize('config', ['rmt', 'uart'], indirect=True) def test_onewire_bus(dut: Dut, config: str) -> None: if config == 'rmt': dut.expect_exact('test-app: 1-Wire bus installed on GPIO0 by RMT backend') elif config == 'uart': dut.expect_exact('test-app: 1-Wire bus installed on GPIO0 by UART backend (UART1)') else: raise ValueError(f'Unknown test config: {config}') dut.expect_exact('test-app: Device iterator created, start searching') dut.expect_exact('test-app: Searching done') ================================================ FILE: onewire_bus/test_apps/sdkconfig.ci.rmt ================================================ CONFIG_EXAMPLE_ONEWIRE_BACKEND_RMT=y CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART=n ================================================ FILE: onewire_bus/test_apps/sdkconfig.ci.uart ================================================ CONFIG_EXAMPLE_ONEWIRE_BACKEND_RMT=n CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART=y ================================================ FILE: pcap/.build-test-rules.yml ================================================ ================================================ FILE: pcap/CMakeLists.txt ================================================ idf_component_register(SRCS "src/pcap.c" INCLUDE_DIRS "include") ================================================ FILE: pcap/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: pcap/README.md ================================================ # Simple PCAP file writer [![Component Registry](https://components.espressif.com/components/espressif/pcap/badge.svg)](https://components.espressif.com/components/espressif/pcap) This component allows users to trace their captured packets in .pcap file format. More details about PCAP format can be found [here](https://wiki.wireshark.org/Development/LibpcapFileFormat). ================================================ FILE: pcap/idf_component.yml ================================================ version: "1.0.2" description: PCAP file writer url: https://github.com/espressif/idf-extra-components/tree/master/pcap dependencies: idf: ">=4.4" ================================================ FILE: pcap/include/pcap.h ================================================ /* * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif #define PCAP_DEFAULT_VERSION_MAJOR 0x02 /*!< Major Version */ #define PCAP_DEFAULT_VERSION_MINOR 0x04 /*!< Minor Version */ #define PCAP_DEFAULT_TIME_ZONE_GMT 0x00 /*!< Time Zone */ /** * @brief Type of pcap file handle * */ typedef struct pcap_file_t *pcap_file_handle_t; /** * @brief Link layer Type Definition, used for Pcap reader to decode payload * */ typedef enum { PCAP_LINK_TYPE_LOOPBACK = 0, /*!< Loopback devices, except for later OpenBSD */ PCAP_LINK_TYPE_ETHERNET = 1, /*!< Ethernet, and Linux loopback devices */ PCAP_LINK_TYPE_TOKEN_RING = 6, /*!< 802.5 Token Ring */ PCAP_LINK_TYPE_ARCNET = 7, /*!< ARCnet */ PCAP_LINK_TYPE_SLIP = 8, /*!< SLIP */ PCAP_LINK_TYPE_PPP = 9, /*!< PPP */ PCAP_LINK_TYPE_FDDI = 10, /*!< FDDI */ PCAP_LINK_TYPE_ATM = 100, /*!< LLC/SNAP encapsulated ATM */ PCAP_LINK_TYPE_RAW_IP = 101, /*!< Raw IP, without link */ PCAP_LINK_TYPE_BSD_SLIP = 102, /*!< BSD/OS SLIP */ PCAP_LINK_TYPE_BSD_PPP = 103, /*!< BSD/OS PPP */ PCAP_LINK_TYPE_CISCO_HDLC = 104, /*!< Cisco HDLC */ PCAP_LINK_TYPE_802_11 = 105, /*!< 802.11 */ PCAP_LINK_TYPE_BSD_LOOPBACK = 108, /*!< OpenBSD loopback devices(with AF_value in network byte order) */ PCAP_LINK_TYPE_LOCAL_TALK = 114, /*!< LocalTalk */ PCAP_LINK_TYPE_USBPCAP = 249, /*!< USB packets, beginning with a USBPcap header */ } pcap_link_type_t; /** * @brief Pcap configuration Type Definition * */ typedef struct { FILE *fp; /*!< Pointer to a standard file handle */ unsigned int major_version; /*!< Pcap version: major */ unsigned int minor_version; /*!< Pcap version: minor */ unsigned int time_zone; /*!< Pcap timezone code */ struct { unsigned int little_endian: 1; /*!< Whether the pcap file is recorded in little endian format */ } flags; } pcap_config_t; /** * @brief Create a new pcap session, and returns pcap file handle * * @note This function won't create the low level FILE* object, the user should take care of the creation of the File Stream. * * @param[in] config pcap file configuration * @param[out] ret_pcap Returned pcap file handle * @return * - ESP_OK: Create pcap file successfully * - ESP_ERR_INVALID_ARG: Create pcap file failed because of invalid argument * - ESP_ERR_NO_MEM: Create pcap file failed because out of memory * - ESP_FAIL: Create pcap file failed */ esp_err_t pcap_new_session(const pcap_config_t *config, pcap_file_handle_t *ret_pcap); /** * @brief Delete the pcap session, and close the File Stream * * @param[in] pcap pcap file handle created by `pcap_new_session()` * @return * - ESP_OK: Delete pcap session successfully * - ESP_ERR_INVALID_ARG: Delete pcap session failed because of invalid argument * - ESP_FAIL: Delete pcap session failed */ esp_err_t pcap_del_session(pcap_file_handle_t pcap); /** * @brief Write pcap file header * * @param[in] pcap pcap file handle created by `pcap_new_session()` * @param[in] link_type Network link layer type * @return * - ESP_OK: Write pcap file header successfully * - ESP_ERR_INVALID_ARG: Write pcap file header failed because of invalid argument * - ESP_FAIL: Write pcap file header failed */ esp_err_t pcap_write_header(pcap_file_handle_t pcap, pcap_link_type_t link_type); /** * @brief Capture one packet into pcap file * * @param[in] pcap pcap file handle created by `pcap_new_session()` * @param[in] payload pointer of the captured data buffer * @param[in] length length of captured data buffer * @param[in] seconds second of capture time * @param[in] microseconds microsecond of capture time * @return * - ESP_OK: Write network packet into pcap file successfully * - ESP_ERR_INVALID_ARG: Write network packet into pcap file failed because of invalid argument * - ESP_FAIL: Write network packet into pcap file failed */ esp_err_t pcap_capture_packet(pcap_file_handle_t pcap, void *payload, uint32_t length, uint32_t seconds, uint32_t microseconds); /** * @brief Print the summary of pcap file into stream * * @param[in] pcap pcap file handle created by `pcap_new_session()` * @param[in] print_file the file stream to save the summary * @return * - ESP_OK: Print pcap file summary successfully * - ESP_ERR_INVALID_ARG: Print pcap file summary failed because of invalid argument * - ESP_FAIL: Print pcap file summary failed */ esp_err_t pcap_print_summary(pcap_file_handle_t pcap, FILE *print_file); #ifdef __cplusplus } #endif ================================================ FILE: pcap/src/pcap.c ================================================ /* * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "esp_log.h" #include "esp_check.h" #include "pcap.h" static const char *TAG = "pcap"; #define PCAP_MAGIC_BIG_ENDIAN 0xA1B2C3D4 /*!< Big-Endian */ #define PCAP_MAGIC_LITTLE_ENDIAN 0xD4C3B2A1 /*!< Little-Endian */ typedef struct pcap_file_t pcap_file_t; /** * @brief Pcap File Header * */ typedef struct { uint32_t magic; /*!< Magic Number */ uint16_t major; /*!< Major Version */ uint16_t minor; /*!< Minor Version */ uint32_t zone; /*!< Time Zone Offset */ uint32_t sigfigs; /*!< Timestamp Accuracy */ uint32_t snaplen; /*!< Max Length to Capture */ uint32_t link_type; /*!< Link Layer Type */ } pcap_file_header_t; /** * @brief Pcap Packet Header * */ typedef struct { uint32_t seconds; /*!< Number of seconds since January 1st, 1970, 00:00:00 GMT */ uint32_t microseconds; /*!< Number of microseconds when the packet was captured (offset from seconds) */ uint32_t capture_length; /*!< Number of bytes of captured data, no longer than packet_length */ uint32_t packet_length; /*!< Actual length of current packet */ } pcap_packet_header_t; /** * @brief Pcap Runtime Handle * */ struct pcap_file_t { FILE *file; /*!< File handle */ pcap_link_type_t link_type; /*!< Pcap Link Type */ unsigned int major_version; /*!< Pcap version: major */ unsigned int minor_version; /*!< Pcap version: minor */ unsigned int time_zone; /*!< Pcap timezone code */ uint32_t endian_magic; /*!< Magic value related to endian format */ }; esp_err_t pcap_new_session(const pcap_config_t *config, pcap_file_handle_t *ret_pcap) { esp_err_t ret = ESP_OK; pcap_file_t *pcap = NULL; ESP_GOTO_ON_FALSE(config && ret_pcap, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); ESP_GOTO_ON_FALSE(config->fp, ESP_ERR_INVALID_ARG, err, TAG, "pcap file handle can't be NULL"); pcap = calloc(1, sizeof(pcap_file_t)); ESP_GOTO_ON_FALSE(pcap, ESP_ERR_NO_MEM, err, TAG, "no mem for pcap file object"); pcap->file = config->fp; pcap->major_version = config->major_version; pcap->minor_version = config->minor_version; pcap->endian_magic = config->flags.little_endian ? PCAP_MAGIC_LITTLE_ENDIAN : PCAP_MAGIC_BIG_ENDIAN; pcap->time_zone = config->time_zone; *ret_pcap = pcap; return ret; err: if (pcap) { free(pcap); } return ret; } esp_err_t pcap_del_session(pcap_file_handle_t pcap) { ESP_RETURN_ON_FALSE(pcap, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); if (pcap->file) { fclose(pcap->file); pcap->file = NULL; } free(pcap); return ESP_OK; } esp_err_t pcap_write_header(pcap_file_handle_t pcap, pcap_link_type_t link_type) { ESP_RETURN_ON_FALSE(pcap, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); /* Write Pcap File header */ pcap_file_header_t header = { .magic = pcap->endian_magic, .major = pcap->major_version, .minor = pcap->minor_version, .zone = pcap->time_zone, .sigfigs = 0, .snaplen = 0x40000, .link_type = link_type, }; size_t real_write = fwrite(&header, sizeof(header), 1, pcap->file); ESP_RETURN_ON_FALSE(real_write == 1, ESP_FAIL, TAG, "write pcap file header failed"); /* Save the link type to pcap file object */ pcap->link_type = link_type; /* Flush content in the buffer into device */ fflush(pcap->file); return ESP_OK; } esp_err_t pcap_capture_packet(pcap_file_handle_t pcap, void *payload, uint32_t length, uint32_t seconds, uint32_t microseconds) { ESP_RETURN_ON_FALSE(pcap && payload, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); size_t real_write = 0; pcap_packet_header_t header = { .seconds = seconds, .microseconds = microseconds, .capture_length = length, .packet_length = length }; real_write = fwrite(&header, sizeof(header), 1, pcap->file); ESP_RETURN_ON_FALSE(real_write == 1, ESP_FAIL, TAG, "write packet header failed"); real_write = fwrite(payload, sizeof(uint8_t), length, pcap->file); ESP_RETURN_ON_FALSE(real_write == length, ESP_FAIL, TAG, "write packet payload failed"); /* Flush content in the buffer into device */ fflush(pcap->file); return ESP_OK; } esp_err_t pcap_print_summary(pcap_file_handle_t pcap, FILE *print_file) { esp_err_t ret = ESP_OK; long size = 0; char *packet_payload = NULL; ESP_RETURN_ON_FALSE(pcap && print_file, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); // get file size fseek(pcap->file, 0L, SEEK_END); size = ftell(pcap->file); fseek(pcap->file, 0L, SEEK_SET); // file empty is allowed, so return ESP_OK ESP_RETURN_ON_FALSE(size, ESP_OK, TAG, "pcap file is empty"); // packet index (by bytes) uint32_t index = 0; pcap_file_header_t file_header; size_t real_read = fread(&file_header, sizeof(pcap_file_header_t), 1, pcap->file); ESP_RETURN_ON_FALSE(real_read == 1, ESP_FAIL, TAG, "read pcap file header failed"); index += sizeof(pcap_file_header_t); //print pcap header information fprintf(print_file, "------------------------------------------------------------------------\n"); fprintf(print_file, "Pcap packet Head:\n"); fprintf(print_file, "------------------------------------------------------------------------\n"); fprintf(print_file, "Magic Number: %"PRIx32"\n", file_header.magic); fprintf(print_file, "Major Version: %d\n", file_header.major); fprintf(print_file, "Minor Version: %d\n", file_header.minor); fprintf(print_file, "SnapLen: %"PRIu32"\n", file_header.snaplen); fprintf(print_file, "LinkType: %"PRIu32"\n", file_header.link_type); fprintf(print_file, "------------------------------------------------------------------------\n"); uint32_t packet_num = 0; pcap_packet_header_t packet_header; while (index < size) { real_read = fread(&packet_header, sizeof(pcap_packet_header_t), 1, pcap->file); ESP_GOTO_ON_FALSE(real_read == 1, ESP_FAIL, err, TAG, "read pcap packet header failed"); // print packet header information fprintf(print_file, "Packet %"PRIu32":\n", packet_num); fprintf(print_file, "Timestamp (Seconds): %"PRIu32"\n", packet_header.seconds); fprintf(print_file, "Timestamp (Microseconds): %"PRIu32"\n", packet_header.microseconds); fprintf(print_file, "Capture Length: %"PRIu32"\n", packet_header.capture_length); fprintf(print_file, "Packet Length: %"PRIu32"\n", packet_header.packet_length); size_t payload_length = packet_header.capture_length; packet_payload = malloc(payload_length); ESP_GOTO_ON_FALSE(packet_payload, ESP_ERR_NO_MEM, err, TAG, "no mem to save packet payload"); real_read = fread(packet_payload, payload_length, 1, pcap->file); ESP_GOTO_ON_FALSE(real_read == 1, ESP_FAIL, err, TAG, "read payload error"); // print packet information if (file_header.link_type == PCAP_LINK_TYPE_802_11) { // Frame Control Field is coded as LSB first fprintf(print_file, "Frame Type: %2x\n", (packet_payload[0] >> 2) & 0x03); fprintf(print_file, "Frame Subtype: %2x\n", (packet_payload[0] >> 4) & 0x0F); fprintf(print_file, "Destination: "); for (int j = 0; j < 5; j++) { fprintf(print_file, "%2x ", packet_payload[4 + j]); } fprintf(print_file, "%2x\n", packet_payload[9]); fprintf(print_file, "Source: "); for (int j = 0; j < 5; j++) { fprintf(print_file, "%2x ", packet_payload[10 + j]); } fprintf(print_file, "%2x\n", packet_payload[15]); fprintf(print_file, "------------------------------------------------------------------------\n"); } else if (file_header.link_type == PCAP_LINK_TYPE_ETHERNET) { fprintf(print_file, "Destination: "); for (int j = 0; j < 5; j++) { fprintf(print_file, "%2x ", packet_payload[j]); } fprintf(print_file, "%2x\n", packet_payload[5]); fprintf(print_file, "Source: "); for (int j = 0; j < 5; j++) { fprintf(print_file, "%2x ", packet_payload[6 + j]); } fprintf(print_file, "%2x\n", packet_payload[11]); fprintf(print_file, "Type: 0x%x\n", packet_payload[13] | (packet_payload[12] << 8)); fprintf(print_file, "------------------------------------------------------------------------\n"); } else { fprintf(print_file, "Unknown link type:%"PRIu32"\n", file_header.link_type); fprintf(print_file, "------------------------------------------------------------------------\n"); } free(packet_payload); packet_payload = NULL; index += packet_header.capture_length + sizeof(pcap_packet_header_t); packet_num ++; } fprintf(print_file, "Pcap packet Number: %"PRIu32"\n", packet_num); fprintf(print_file, "------------------------------------------------------------------------\n"); return ret; err: if (packet_payload) { free(packet_payload); } return ret; } ================================================ FILE: pcap/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(pcap_test) ================================================ FILE: pcap/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "pcap_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: pcap/test_apps/main/idf_component.yml ================================================ dependencies: espressif/pcap: version: "*" override_path: "../.." ================================================ FILE: pcap/test_apps/main/pcap_test.c ================================================ #include void app_main(void) { } ================================================ FILE: pid_ctrl/.build-test-rules.yml ================================================ ================================================ FILE: pid_ctrl/CMakeLists.txt ================================================ set(srcs "src/pid_ctrl.c") idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "include") ================================================ FILE: pid_ctrl/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: pid_ctrl/README.md ================================================ # Proportional integral derivative controller [![Component Registry](https://components.espressif.com/components/espressif/pid_ctrl/badge.svg)](https://components.espressif.com/components/espressif/pid_ctrl) ================================================ FILE: pid_ctrl/idf_component.yml ================================================ version: "0.2.0" description: Proportional-integral-derivative controller url: https://github.com/espressif/idf-extra-components/tree/master/pid_ctrl dependencies: idf: ">=4.4" ================================================ FILE: pid_ctrl/include/pid_ctrl.h ================================================ /* * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** * @brief PID calculation type * */ typedef enum { PID_CAL_TYPE_INCREMENTAL, /*!< Incremental PID control */ PID_CAL_TYPE_POSITIONAL, /*!< Positional PID control */ } pid_calculate_type_t; /** * @brief Type of PID control block handle * */ typedef struct pid_ctrl_block_t *pid_ctrl_block_handle_t; /** * @brief PID control parameters * */ typedef struct { float kp; // PID Kp parameter float ki; // PID Ki parameter float kd; // PID Kd parameter float max_output; // PID maximum output limitation float min_output; // PID minimum output limitation float max_integral; // PID maximum integral value limitation float min_integral; // PID minimum integral value limitation pid_calculate_type_t cal_type; // PID calculation type } pid_ctrl_parameter_t; /** * @brief PID control configuration * */ typedef struct { pid_ctrl_parameter_t init_param; // Initial parameters } pid_ctrl_config_t; /** * @brief Create a new PID control session, returns the handle of control block * * @param[in] config PID control configuration * @param[out] ret_pid Returned PID control block handle * @return * - ESP_OK: Created PID control block successfully * - ESP_ERR_INVALID_ARG: Created PID control block failed because of invalid argument * - ESP_ERR_NO_MEM: Created PID control block failed because out of memory */ esp_err_t pid_new_control_block(const pid_ctrl_config_t *config, pid_ctrl_block_handle_t *ret_pid); /** * @brief Delete the PID control block * * @param[in] pid PID control block handle, created by `pid_new_control_block()` * @return * - ESP_OK: Delete PID control block successfully * - ESP_ERR_INVALID_ARG: Delete PID control block failed because of invalid argument */ esp_err_t pid_del_control_block(pid_ctrl_block_handle_t pid); /** * @brief Update PID parameters * * @param[in] pid PID control block handle, created by `pid_new_control_block()` * @param[in] params PID parameters * @return * - ESP_OK: Update PID parameters successfully * - ESP_ERR_INVALID_ARG: Update PID parameters failed because of invalid argument */ esp_err_t pid_update_parameters(pid_ctrl_block_handle_t pid, const pid_ctrl_parameter_t *params); /** * @brief Input error and get PID control result * * @param[in] pid PID control block handle, created by `pid_new_control_block()` * @param[in] input_error error data that feed to the PID controller * @param[out] ret_result result after PID calculation * @return * - ESP_OK: Run a PID compute successfully * - ESP_ERR_INVALID_ARG: Run a PID compute failed because of invalid argument */ esp_err_t pid_compute(pid_ctrl_block_handle_t pid, float input_error, float *ret_result); /** * @brief Reset the accumulation in pid_ctrl_block * * @param[in] pid PID control block handle, created by `pid_new_control_block()` * @return * - ESP_OK: Reset successfully * - ESP_ERR_INVALID_ARG: Reset failed because of invalid argument */ esp_err_t pid_reset_ctrl_block(pid_ctrl_block_handle_t pid); #ifdef __cplusplus } #endif ================================================ FILE: pid_ctrl/src/pid_ctrl.c ================================================ /* * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_check.h" #include "esp_log.h" #include "pid_ctrl.h" static const char *TAG = "pid_ctrl"; typedef struct pid_ctrl_block_t pid_ctrl_block_t; typedef float (*pid_cal_func_t)(pid_ctrl_block_t *pid, float error); struct pid_ctrl_block_t { float Kp; // PID Kp value float Ki; // PID Ki value float Kd; // PID Kd value float previous_err1; // e(k-1) float previous_err2; // e(k-2) float integral_err; // Sum of error float last_output; // PID output in last control period float max_output; // PID maximum output limitation float min_output; // PID minimum output limitation float max_integral; // PID maximum integral value limitation float min_integral; // PID minimum integral value limitation pid_cal_func_t calculate_func; // calculation function, depends on actual PID type set by user }; static float pid_calc_positional(pid_ctrl_block_t *pid, float error) { float output = 0; /* Add current error to the integral error */ pid->integral_err += error; /* If the integral error is out of the range, it will be limited */ pid->integral_err = MIN(pid->integral_err, pid->max_integral); pid->integral_err = MAX(pid->integral_err, pid->min_integral); /* Calculate the pid control value by location formula */ /* u(k) = e(k)*Kp + (e(k)-e(k-1))*Kd + integral*Ki */ output = error * pid->Kp + (error - pid->previous_err1) * pid->Kd + pid->integral_err * pid->Ki; /* If the output is out of the range, it will be limited */ output = MIN(output, pid->max_output); output = MAX(output, pid->min_output); /* Update previous error */ pid->previous_err1 = error; return output; } static float pid_calc_incremental(pid_ctrl_block_t *pid, float error) { float output = 0; /* Calculate the pid control value by increment formula */ /* du(k) = (e(k)-e(k-1))*Kp + (e(k)-2*e(k-1)+e(k-2))*Kd + e(k)*Ki */ /* u(k) = du(k) + u(k-1) */ output = (error - pid->previous_err1) * pid->Kp + (error - 2 * pid->previous_err1 + pid->previous_err2) * pid->Kd + error * pid->Ki + pid->last_output; /* If the output is beyond the range, it will be limited */ output = MIN(output, pid->max_output); output = MAX(output, pid->min_output); /* Update previous error */ pid->previous_err2 = pid->previous_err1; pid->previous_err1 = error; /* Update last output */ pid->last_output = output; return output; } esp_err_t pid_new_control_block(const pid_ctrl_config_t *config, pid_ctrl_block_handle_t *ret_pid) { esp_err_t ret = ESP_OK; pid_ctrl_block_t *pid = NULL; /* Check the input pointer */ ESP_GOTO_ON_FALSE(config && ret_pid, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); pid = calloc(1, sizeof(pid_ctrl_block_t)); ESP_GOTO_ON_FALSE(pid, ESP_ERR_NO_MEM, err, TAG, "no mem for PID control block"); ESP_GOTO_ON_ERROR(pid_update_parameters(pid, &config->init_param), err, TAG, "init PID parameters failed"); *ret_pid = pid; return ret; err: if (pid) { free(pid); } return ret; } esp_err_t pid_del_control_block(pid_ctrl_block_handle_t pid) { ESP_RETURN_ON_FALSE(pid, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); free(pid); return ESP_OK; } esp_err_t pid_compute(pid_ctrl_block_handle_t pid, float input_error, float *ret_result) { ESP_RETURN_ON_FALSE(pid && ret_result, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); *ret_result = pid->calculate_func(pid, input_error); return ESP_OK; } esp_err_t pid_update_parameters(pid_ctrl_block_handle_t pid, const pid_ctrl_parameter_t *params) { ESP_RETURN_ON_FALSE(pid && params, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); pid->Kp = params->kp; pid->Ki = params->ki; pid->Kd = params->kd; pid->max_output = params->max_output; pid->min_output = params->min_output; pid->max_integral = params->max_integral; pid->min_integral = params->min_integral; /* Set the calculate function according to the PID type */ switch (params->cal_type) { case PID_CAL_TYPE_INCREMENTAL: pid->calculate_func = pid_calc_incremental; break; case PID_CAL_TYPE_POSITIONAL: pid->calculate_func = pid_calc_positional; break; default: ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid PID calculation type:%d", params->cal_type); } return ESP_OK; } esp_err_t pid_reset_ctrl_block(pid_ctrl_block_handle_t pid) { ESP_RETURN_ON_FALSE(pid, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); pid->integral_err = 0; pid->previous_err1 = 0; pid->previous_err2 = 0; pid->last_output = 0; return ESP_OK; } ================================================ FILE: pid_ctrl/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(pid_ctrl_test) ================================================ FILE: pid_ctrl/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "pid_ctrl_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: pid_ctrl/test_apps/main/idf_component.yml ================================================ dependencies: espressif/pid_ctrl: version: "*" override_path: "../.." ================================================ FILE: pid_ctrl/test_apps/main/pid_ctrl_test.c ================================================ #include void app_main(void) { } ================================================ FILE: pytest.ini ================================================ [pytest] # only the files with prefix `pytest_` would be recognized as pytest test scripts. python_files = pytest_*.py # set traceback to "short" to prevent the overwhelming tracebacks addopts = -s -vv --embedded-services esp,idf --tb short markers = # env markers generic: generic runner ethernet: ethernet runners spi_nand_flash: runner with SPI NAND flash connected qemu: QEMU runner host_test: xxx # ignore PytestExperimentalApiWarning for record_xml_attribute filterwarnings = ignore::_pytest.warning_types.PytestExperimentalApiWarning # log related log_cli = True log_cli_level = INFO log_cli_format = %(asctime)s %(levelname)s %(message)s log_cli_date_format = %Y-%m-%d %H:%M:%S # junit related junit_family = xunit1 ## log all to `system-out` when case fail junit_logging = stdout junit_log_passing_tests = False ================================================ FILE: qrcode/.build-test-rules.yml ================================================ ================================================ FILE: qrcode/CMakeLists.txt ================================================ idf_component_register(SRCS "esp_qrcode_main.c" "esp_qrcode_wrapper.c" "qrcodegen.c" INCLUDE_DIRS "include" ) ================================================ FILE: qrcode/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: qrcode/README.md ================================================ # QR Code generator component [![Component Registry](https://components.espressif.com/components/espressif/qrcode/badge.svg)](https://components.espressif.com/components/espressif/qrcode) This component contains a QR code generator written in C. This component is based on [QR-Code-generator](https://github.com/nayuki/QR-Code-generator). This component is used as part of the following ESP-IDF examples: - [DPP Enrollee Example](https://github.com/espressif/esp-idf/tree/master/examples/wifi/wifi_easy_connect/dpp-enrollee). To learn more about how to use this component, please check API Documentation from header file [qrcode.h](https://github.com/espressif/idf-extra-components/tree/master/qrcode/include/qrcode.h). ================================================ FILE: qrcode/esp_qrcode_main.c ================================================ /* * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "qrcodegen.h" #include "qrcode.h" static const char *TAG = "QRCODE"; static const char *lt[] = { /* 0 */ " ", /* 1 */ "\u2580 ", /* 2 */ " \u2580", /* 3 */ "\u2580\u2580", /* 4 */ "\u2584 ", /* 5 */ "\u2588 ", /* 6 */ "\u2584\u2580", /* 7 */ "\u2588\u2580", /* 8 */ " \u2584", /* 9 */ "\u2580\u2584", /* 10 */ " \u2588", /* 11 */ "\u2580\u2588", /* 12 */ "\u2584\u2584", /* 13 */ "\u2588\u2584", /* 14 */ "\u2584\u2588", /* 15 */ "\u2588\u2588", }; void esp_qrcode_print_console(esp_qrcode_handle_t qrcode) { int size = qrcodegen_getSize(qrcode); int border = 2; unsigned char num = 0; for (int y = -border; y < size + border; y += 2) { for (int x = -border; x < size + border; x += 2) { num = 0; if (qrcodegen_getModule(qrcode, x, y)) { num |= 1 << 0; } if ((x < size + border) && qrcodegen_getModule(qrcode, x + 1, y)) { num |= 1 << 1; } if ((y < size + border) && qrcodegen_getModule(qrcode, x, y + 1)) { num |= 1 << 2; } if ((x < size + border) && (y < size + border) && qrcodegen_getModule(qrcode, x + 1, y + 1)) { num |= 1 << 3; } printf("%s", lt[num]); } printf("\n"); } printf("\n"); } esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text) { enum qrcodegen_Ecc ecc_lvl; uint8_t *qrcode, *tempbuf; esp_err_t err = ESP_FAIL; qrcode = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version)); if (!qrcode) { return ESP_ERR_NO_MEM; } tempbuf = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version)); if (!tempbuf) { free(qrcode); return ESP_ERR_NO_MEM; } switch (cfg->qrcode_ecc_level) { case ESP_QRCODE_ECC_LOW: ecc_lvl = qrcodegen_Ecc_LOW; break; case ESP_QRCODE_ECC_MED: ecc_lvl = qrcodegen_Ecc_MEDIUM; break; case ESP_QRCODE_ECC_QUART: ecc_lvl = qrcodegen_Ecc_QUARTILE; break; case ESP_QRCODE_ECC_HIGH: ecc_lvl = qrcodegen_Ecc_HIGH; break; default: ecc_lvl = qrcodegen_Ecc_LOW; break; } ESP_LOGI(TAG, "Encoding below text with ECC LVL %d & QR Code Version %d", ecc_lvl, cfg->max_qrcode_version); ESP_LOGI(TAG, "%s", text); // Make and print the QR Code symbol bool ok = qrcodegen_encodeText(text, tempbuf, qrcode, ecc_lvl, qrcodegen_VERSION_MIN, cfg->max_qrcode_version, qrcodegen_Mask_AUTO, true); if (ok && cfg->display_func) { // If user_data is provided, use callback version // Otherwise use simple version (backward compatible) if (cfg->user_data != NULL) { // Use callback version with user_data cfg->display_func_with_cb((esp_qrcode_handle_t)qrcode, cfg->user_data); } else { // Use simple version without user_data (backward compatible) cfg->display_func((esp_qrcode_handle_t)qrcode); } err = ESP_OK; } free(qrcode); free(tempbuf); return err; } ================================================ FILE: qrcode/esp_qrcode_wrapper.c ================================================ /* * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "qrcodegen.h" #include "qrcode.h" int esp_qrcode_get_size(esp_qrcode_handle_t qrcode) { return qrcodegen_getSize(qrcode); } bool esp_qrcode_get_module(esp_qrcode_handle_t qrcode, int x, int y) { return qrcodegen_getModule(qrcode, x, y); } ================================================ FILE: qrcode/idf_component.yml ================================================ version: "0.2.0" description: QR Code generator url: https://github.com/espressif/idf-extra-components/tree/master/qrcode ================================================ FILE: qrcode/include/qrcode.h ================================================ /* * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #ifdef __cplusplus extern "C" { #endif /** * @brief QR Code handle used by the display function */ typedef const uint8_t *esp_qrcode_handle_t; /** * @brief QR Code configuration options */ typedef struct { union { void (*display_func)(esp_qrcode_handle_t qrcode); /**< Function called for displaying the QR Code (without user_data, for backward compatibility) */ void (*display_func_with_cb)(esp_qrcode_handle_t qrcode, void *user_data); /**< Function called for displaying the QR Code with user_data callback */ }; /**< Display function union for QR Code display */ int max_qrcode_version; /**< Max QR Code Version to be used. Range: 2 - 40 */ int qrcode_ecc_level; /**< Error Correction Level for QR Code */ void *user_data; /**< User data */ } esp_qrcode_config_t; /** * @brief Error Correction Level in a QR Code Symbol */ enum { ESP_QRCODE_ECC_LOW, /**< QR Code Error Tolerance of 7% */ ESP_QRCODE_ECC_MED, /**< QR Code Error Tolerance of 15% */ ESP_QRCODE_ECC_QUART, /**< QR Code Error Tolerance of 25% */ ESP_QRCODE_ECC_HIGH /**< QR Code Error Tolerance of 30% */ }; /** * @brief Encodes the given string into a QR Code and calls the display function * * @attention 1. Can successfully encode a UTF-8 string of up to 2953 bytes or an alphanumeric * string of up to 4296 characters or any digit string of up to 7089 characters * * @param cfg Configuration used for QR Code encoding. * @param text String to encode into a QR Code. * * @return * - ESP_OK: succeed * - ESP_FAIL: Failed to encode string into a QR Code * - ESP_ERR_NO_MEM: Failed to allocate buffer for given max_qrcode_version */ esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text); /** * @brief Displays QR Code on the console * * @param qrcode QR Code handle used by the display function. */ void esp_qrcode_print_console(esp_qrcode_handle_t qrcode); /** * @brief Returns the side length of the given QR Code * * @param qrcode QR Code handle used by the display function. * * @return * - val[21, 177]: Side length of QR Code */ int esp_qrcode_get_size(esp_qrcode_handle_t qrcode); /** * @brief Returns the Pixel value for the given coordinates * False indicates White and True indicates Black * * @attention 1. Coordinates for top left corner are (x=0, y=0) * @attention 2. For out of bound coordinates false (White) is returned * * @param qrcode QR Code handle used by the display function. * @param x X-Coordinate of QR Code module * @param y Y-Coordinate of QR Code module * * @return * - true: (x, y) Pixel is Black * - false: (x, y) Pixel is White */ bool esp_qrcode_get_module(esp_qrcode_handle_t qrcode, int x, int y); #define ESP_QRCODE_CONFIG_DEFAULT() (esp_qrcode_config_t) { \ .display_func = esp_qrcode_print_console, \ .max_qrcode_version = 10, \ .qrcode_ecc_level = ESP_QRCODE_ECC_LOW, \ .user_data = NULL, \ } #ifdef __cplusplus } #endif ================================================ FILE: qrcode/qrcodegen.c ================================================ /* * QR Code generator library (C) * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * 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: * - The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * - 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. */ #include #include #include #include #include "qrcodegen.h" #ifndef QRCODEGEN_TEST #define testable static // Keep functions private #else #define testable // Expose private functions #endif /*---- Forward declarations for private functions ----*/ // Regarding all public and private functions defined in this source file: // - They require all pointer/array arguments to be not null unless the array length is zero. // - They only read input scalar/array arguments, write to output pointer/array // arguments, and return scalar values; they are "pure" functions. // - They don't read mutable global variables or write to any global variables. // - They don't perform I/O, read the clock, print to console, etc. // - They allocate a small and constant amount of stack memory. // - They don't allocate or free any memory on the heap. // - They don't recurse or mutually recurse. All the code // could be inlined into the top-level public functions. // - They run in at most quadratic time with respect to input arguments. // Most functions run in linear time, and some in constant time. // There are no unbounded loops or non-obvious termination conditions. // - They are completely thread-safe if the caller does not give the // same writable buffer to concurrent calls to these functions. testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); testable int getNumRawDataModules(int ver); testable void reedSolomonComputeDivisor(int degree, uint8_t result[]); testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]); testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y); testable void initializeFunctionModules(int version, uint8_t qrcode[]); static void drawWhiteFunctionModules(uint8_t qrcode[], int version); static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); testable int getAlignmentPatternPositions(int version, uint8_t result[7]); static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); static long getPenaltyScore(const uint8_t qrcode[]); static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize); static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize); static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]); testable bool getModule(const uint8_t qrcode[], int x, int y); testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); static bool getBit(int x, int i); testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); static int numCharCountBits(enum qrcodegen_Mode mode, int version); /*---- Private tables of constants ----*/ // The set of all legal characters in alphanumeric mode, where each character // value maps to the index in the string. For checking text and encoding segments. static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; // For generating error correction codes. testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High }; #define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above // For generating error correction codes. testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High }; // For automatic mask pattern selection. static const int PENALTY_N1 = 3; static const int PENALTY_N2 = 3; static const int PENALTY_N3 = 40; static const int PENALTY_N4 = 10; /*---- High-level QR Code encoding functions ----*/ // Public function - see documentation comment in header file. bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { size_t textLen = strlen(text); if (textLen == 0) { return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); } size_t bufLen = (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); struct qrcodegen_Segment seg; if (qrcodegen_isNumeric(text)) { if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) { goto fail; } seg = qrcodegen_makeNumeric(text, tempBuffer); } else if (qrcodegen_isAlphanumeric(text)) { if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) { goto fail; } seg = qrcodegen_makeAlphanumeric(text, tempBuffer); } else { if (textLen > bufLen) { goto fail; } for (size_t i = 0; i < textLen; i++) { tempBuffer[i] = (uint8_t)text[i]; } seg.mode = qrcodegen_Mode_BYTE; seg.bitLength = calcSegmentBitLength(seg.mode, textLen); if (seg.bitLength == -1) { goto fail; } seg.numChars = (int)textLen; seg.data = tempBuffer; } return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); fail: qrcode[0] = 0; // Set size to invalid value for safety return false; } // Public function - see documentation comment in header file. bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { struct qrcodegen_Segment seg; seg.mode = qrcodegen_Mode_BYTE; seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); if (seg.bitLength == -1) { qrcode[0] = 0; // Set size to invalid value for safety return false; } seg.numChars = (int)dataLen; seg.data = dataAndTemp; return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); } // Appends the given number of low-order bits of the given value to the given byte-based // bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) { buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); } } /*---- Low-level QR Code encoding functions ----*/ // Public function - see documentation comment in header file. bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true, tempBuffer, qrcode); } // Public function - see documentation comment in header file. bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { assert(segs != NULL || len == 0); assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); // Find the minimal version number to use int version, dataUsedBits; for (version = minVersion; ; version++) { int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available dataUsedBits = getTotalBits(segs, len, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) { break; // This version number is found to be suitable } if (version >= maxVersion) { // All versions in the range could not fit the given data qrcode[0] = 0; // Set size to invalid value for safety return false; } } assert(dataUsedBits != -1); // Increase the error correction level while the data still fits in the current version number for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) { ecl = (enum qrcodegen_Ecc)i; } } // Concatenate all segments to create the data bit string memset(qrcode, 0, (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); int bitLen = 0; for (size_t i = 0; i < len; i++) { const struct qrcodegen_Segment *seg = &segs[i]; appendBitsToBuffer((unsigned int)seg->mode, 4, qrcode, &bitLen); appendBitsToBuffer((unsigned int)seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); for (int j = 0; j < seg->bitLength; j++) { int bit = (seg->data[j >> 3] >> (7 - (j & 7))) & 1; appendBitsToBuffer((unsigned int)bit, 1, qrcode, &bitLen); } } assert(bitLen == dataUsedBits); // Add terminator and pad up to a byte if applicable int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; assert(bitLen <= dataCapacityBits); int terminatorBits = dataCapacityBits - bitLen; if (terminatorBits > 4) { terminatorBits = 4; } appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); assert(bitLen % 8 == 0); // Pad with alternating bytes until data capacity is reached for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) { appendBitsToBuffer(padByte, 8, qrcode, &bitLen); } // Draw function and data codeword modules addEccAndInterleave(qrcode, version, ecl, tempBuffer); initializeFunctionModules(version, qrcode); drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); drawWhiteFunctionModules(qrcode, version); initializeFunctionModules(version, tempBuffer); // Handle masking if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask long minPenalty = LONG_MAX; for (int i = 0; i < 8; i++) { enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; applyMask(tempBuffer, qrcode, msk); drawFormatBits(ecl, msk, qrcode); long penalty = getPenaltyScore(qrcode); if (penalty < minPenalty) { mask = msk; minPenalty = penalty; } applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR } } assert(0 <= (int)mask && (int)mask <= 7); applyMask(tempBuffer, qrcode, mask); drawFormatBits(ecl, mask, qrcode); return true; } /*---- Error correction code generation functions ----*/ // Appends error correction bytes to each block of the given data array, then interleaves // bytes from the blocks and stores them in the result array. data[0 : dataLen] contains // the input data. data[dataLen : rawCodewords] is used as a temporary work area and will // be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { // Calculate parameter numbers assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; int rawCodewords = getNumRawDataModules(version) / 8; int dataLen = getNumDataCodewords(version, ecl); int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; // Split data into blocks, calculate ECC, and interleave // (not concatenate) the bytes into a single sequence uint8_t rsdiv[qrcodegen_REED_SOLOMON_DEGREE_MAX]; reedSolomonComputeDivisor(blockEccLen, rsdiv); const uint8_t *dat = data; for (int i = 0; i < numBlocks; i++) { int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); uint8_t *ecc = &data[dataLen]; // Temporary storage reedSolomonComputeRemainder(dat, datLen, rsdiv, blockEccLen, ecc); for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data if (j == shortBlockDataLen) { k -= numShortBlocks; } result[k] = dat[j]; } for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) { // Copy ECC result[k] = ecc[j]; } dat += datLen; } } // Returns the number of 8-bit codewords that can be used for storing data (not ECC), // for the given version number and error correction level. The result is in the range [9, 2956]. testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { int v = version, e = (int)ecl; assert(0 <= e && e < 4); return getNumRawDataModules(v) / 8 - ECC_CODEWORDS_PER_BLOCK [e][v] * NUM_ERROR_CORRECTION_BLOCKS[e][v]; } // Returns the number of data bits that can be stored in a QR Code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. testable int getNumRawDataModules(int ver) { assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); int result = (16 * ver + 128) * ver + 64; if (ver >= 2) { int numAlign = ver / 7 + 2; result -= (25 * numAlign - 10) * numAlign - 55; if (ver >= 7) { result -= 36; } } assert(208 <= result && result <= 29648); return result; } /*---- Reed-Solomon ECC generator functions ----*/ // Computes a Reed-Solomon ECC generator polynomial for the given degree, storing in result[0 : degree]. // This could be implemented as a lookup table over all possible parameter values, instead of as an algorithm. testable void reedSolomonComputeDivisor(int degree, uint8_t result[]) { assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. memset(result, 0, (size_t)degree * sizeof(result[0])); result[degree - 1] = 1; // Start off with the monomial x^0 // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // drop the highest monomial term which is always 1x^degree. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). uint8_t root = 1; for (int i = 0; i < degree; i++) { // Multiply the current product by (x - r^i) for (int j = 0; j < degree; j++) { result[j] = reedSolomonMultiply(result[j], root); if (j + 1 < degree) { result[j] ^= result[j + 1]; } } root = reedSolomonMultiply(root, 0x02); } } // Computes the Reed-Solomon error correction codeword for the given data and divisor polynomials. // The remainder when data[0 : dataLen] is divided by divisor[0 : degree] is stored in result[0 : degree]. // All polynomials are in big endian, and the generator has an implicit leading 1 term. testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]) { assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); memset(result, 0, (size_t)degree * sizeof(result[0])); for (int i = 0; i < dataLen; i++) { // Polynomial division uint8_t factor = data[i] ^ result[0]; memmove(&result[0], &result[1], (size_t)(degree - 1) * sizeof(result[0])); result[degree - 1] = 0; for (int j = 0; j < degree; j++) { result[j] ^= reedSolomonMultiply(generator[j], factor); } } } #undef qrcodegen_REED_SOLOMON_DEGREE_MAX // Returns the product of the two given field elements modulo GF(2^8/0x11D). // All inputs are valid. This could be implemented as a 256*256 lookup table. testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y) { // Russian peasant multiplication uint8_t z = 0; for (int i = 7; i >= 0; i--) { z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); z ^= ((y >> i) & 1) * x; } return z; } /*---- Drawing function modules ----*/ // Clears the given QR Code grid with white modules for the given // version's size, then marks every function module as black. testable void initializeFunctionModules(int version, uint8_t qrcode[]) { // Initialize QR Code int qrsize = version * 4 + 17; memset(qrcode, 0, (size_t)((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); qrcode[0] = (uint8_t)qrsize; // Fill horizontal and vertical timing patterns fillRectangle(6, 0, 1, qrsize, qrcode); fillRectangle(0, 6, qrsize, 1, qrcode); // Fill 3 finder patterns (all corners except bottom right) and format bits fillRectangle(0, 0, 9, 9, qrcode); fillRectangle(qrsize - 8, 0, 8, 9, qrcode); fillRectangle(0, qrsize - 8, 9, 8, qrcode); // Fill numerous alignment patterns uint8_t alignPatPos[7]; int numAlign = getAlignmentPatternPositions(version, alignPatPos); for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { // Don't draw on the three finder corners if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) { fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); } } } // Fill version blocks if (version >= 7) { fillRectangle(qrsize - 11, 0, 3, 6, qrcode); fillRectangle(0, qrsize - 11, 6, 3, qrcode); } } // Draws white function modules and possibly some black modules onto the given QR Code, without changing // non-function modules. This does not draw the format bits. This requires all function modules to be previously // marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules. static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { // Draw horizontal and vertical timing patterns int qrsize = qrcodegen_getSize(qrcode); for (int i = 7; i < qrsize - 7; i += 2) { setModule(qrcode, 6, i, false); setModule(qrcode, i, 6, false); } // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) for (int dy = -4; dy <= 4; dy++) { for (int dx = -4; dx <= 4; dx++) { int dist = abs(dx); if (abs(dy) > dist) { dist = abs(dy); } if (dist == 2 || dist == 4) { setModuleBounded(qrcode, 3 + dx, 3 + dy, false); setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false); setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false); } } } // Draw numerous alignment patterns uint8_t alignPatPos[7]; int numAlign = getAlignmentPatternPositions(version, alignPatPos); for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) { continue; // Don't draw on the three finder corners } for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); } } } } // Draw version blocks if (version >= 7) { // Calculate error correction code and pack bits int rem = version; // version is uint6, in the range [7, 40] for (int i = 0; i < 12; i++) { rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); } long bits = (long)version << 12 | rem; // uint18 assert(bits >> 18 == 0); // Draw two copies for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) { int k = qrsize - 11 + j; setModule(qrcode, k, i, (bits & 1) != 0); setModule(qrcode, i, k, (bits & 1) != 0); bits >>= 1; } } } } // Draws two copies of the format bits (with its own error correction code) based // on the given mask and error correction level. This always draws all modules of // the format bits, unlike drawWhiteFunctionModules() which might skip black modules. static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { // Calculate error correction code and pack bits assert(0 <= (int)mask && (int)mask <= 7); static const int table[] = {1, 0, 3, 2}; int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 int rem = data; for (int i = 0; i < 10; i++) { rem = (rem << 1) ^ ((rem >> 9) * 0x537); } int bits = (data << 10 | rem) ^ 0x5412; // uint15 assert(bits >> 15 == 0); // Draw first copy for (int i = 0; i <= 5; i++) { setModule(qrcode, 8, i, getBit(bits, i)); } setModule(qrcode, 8, 7, getBit(bits, 6)); setModule(qrcode, 8, 8, getBit(bits, 7)); setModule(qrcode, 7, 8, getBit(bits, 8)); for (int i = 9; i < 15; i++) { setModule(qrcode, 14 - i, 8, getBit(bits, i)); } // Draw second copy int qrsize = qrcodegen_getSize(qrcode); for (int i = 0; i < 8; i++) { setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); } for (int i = 8; i < 15; i++) { setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); } setModule(qrcode, 8, qrsize - 8, true); // Always black } // Calculates and stores an ascending list of positions of alignment patterns // for this version number, returning the length of the list (in the range [0,7]). // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { if (version == 1) { return 0; } int numAlign = version / 7 + 2; int step = (version == 32) ? 26 : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) { result[i] = (uint8_t)pos; } result[0] = 6; return numAlign; } // Sets every pixel in the range [left : left + width] * [top : top + height] to black. static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { for (int dy = 0; dy < height; dy++) { for (int dx = 0; dx < width; dx++) { setModule(qrcode, left + dx, top + dy, true); } } } /*---- Drawing data modules and masking ----*/ // Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of // the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { int qrsize = qrcodegen_getSize(qrcode); int i = 0; // Bit index into the data // Do the funny zigzag scan for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair if (right == 6) { right = 5; } for (int vert = 0; vert < qrsize; vert++) { // Vertical counter for (int j = 0; j < 2; j++) { int x = right - j; // Actual x coordinate bool upward = ((right + 1) & 2) == 0; int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate if (!getModule(qrcode, x, y) && i < dataLen * 8) { bool black = getBit(data[i >> 3], 7 - (i & 7)); setModule(qrcode, x, y, black); i++; } // If this QR Code has any remainder bits (0 to 7), they were assigned as // 0/false/white by the constructor and are left unchanged by this method } } } assert(i == dataLen * 8); } // XORs the codeword modules in this QR Code with the given mask pattern. // The function modules must be marked and the codeword bits must be drawn // before masking. Due to the arithmetic of XOR, calling applyMask() with // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO int qrsize = qrcodegen_getSize(qrcode); for (int y = 0; y < qrsize; y++) { for (int x = 0; x < qrsize; x++) { if (getModule(functionModules, x, y)) { continue; } bool invert; switch ((int)mask) { case 0: invert = (x + y) % 2 == 0; break; case 1: invert = y % 2 == 0; break; case 2: invert = x % 3 == 0; break; case 3: invert = (x + y) % 3 == 0; break; case 4: invert = (x / 3 + y / 2) % 2 == 0; break; case 5: invert = x * y % 2 + x * y % 3 == 0; break; case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; default: assert(false); return; } bool val = getModule(qrcode, x, y); setModule(qrcode, x, y, val ^ invert); } } } // Calculates and returns the penalty score based on state of the given QR Code's current modules. // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. static long getPenaltyScore(const uint8_t qrcode[]) { int qrsize = qrcodegen_getSize(qrcode); long result = 0; // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < qrsize; y++) { bool runColor = false; int runX = 0; int runHistory[7] = {0}; int padRun = qrsize; // Add white border to initial run for (int x = 0; x < qrsize; x++) { if (getModule(qrcode, x, y) == runColor) { runX++; if (runX == 5) { result += PENALTY_N1; } else if (runX > 5) { result++; } } else { finderPenaltyAddHistory(runX + padRun, runHistory); padRun = 0; if (!runColor) { result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; } runColor = getModule(qrcode, x, y); runX = 1; } } result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory, qrsize) * PENALTY_N3; } // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < qrsize; x++) { bool runColor = false; int runY = 0; int runHistory[7] = {0}; int padRun = qrsize; // Add white border to initial run for (int y = 0; y < qrsize; y++) { if (getModule(qrcode, x, y) == runColor) { runY++; if (runY == 5) { result += PENALTY_N1; } else if (runY > 5) { result++; } } else { finderPenaltyAddHistory(runY + padRun, runHistory); padRun = 0; if (!runColor) { result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; } runColor = getModule(qrcode, x, y); runY = 1; } } result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory, qrsize) * PENALTY_N3; } // 2*2 blocks of modules having same color for (int y = 0; y < qrsize - 1; y++) { for (int x = 0; x < qrsize - 1; x++) { bool color = getModule(qrcode, x, y); if ( color == getModule(qrcode, x + 1, y) && color == getModule(qrcode, x, y + 1) && color == getModule(qrcode, x + 1, y + 1)) { result += PENALTY_N2; } } } // Balance of black and white modules int black = 0; for (int y = 0; y < qrsize; y++) { for (int x = 0; x < qrsize; x++) { if (getModule(qrcode, x, y)) { black++; } } } int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2 // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1; result += k * PENALTY_N4; return result; } // Can only be called immediately after a white run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize) { int n = runHistory[1]; assert(n <= qrsize * 3); bool core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; // The maximum QR Code size is 177, hence the black run length n <= 177. // Arithmetic is promoted to int, so n*4 will not overflow. return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); } // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize) { if (currentRunColor) { // Terminate black run finderPenaltyAddHistory(currentRunLength, runHistory); currentRunLength = 0; } currentRunLength += qrsize; // Add white border to final run finderPenaltyAddHistory(currentRunLength, runHistory); return finderPenaltyCountPatterns(runHistory, qrsize); } // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]) { memmove(&runHistory[1], &runHistory[0], 6 * sizeof(runHistory[0])); runHistory[0] = currentRunLength; } /*---- Basic QR Code information ----*/ // Public function - see documentation comment in header file. int qrcodegen_getSize(const uint8_t qrcode[]) { assert(qrcode != NULL); int result = qrcode[0]; assert((qrcodegen_VERSION_MIN * 4 + 17) <= result && result <= (qrcodegen_VERSION_MAX * 4 + 17)); return result; } // Public function - see documentation comment in header file. bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { assert(qrcode != NULL); int qrsize = qrcode[0]; return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); } // Gets the module at the given coordinates, which must be in bounds. testable bool getModule(const uint8_t qrcode[], int x, int y) { int qrsize = qrcode[0]; assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); int index = y * qrsize + x; return getBit(qrcode[(index >> 3) + 1], index & 7); } // Sets the module at the given coordinates, which must be in bounds. testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { int qrsize = qrcode[0]; assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); int index = y * qrsize + x; int bitIndex = index & 7; int byteIndex = (index >> 3) + 1; if (isBlack) { qrcode[byteIndex] |= 1 << bitIndex; } else { qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; } } // Sets the module at the given coordinates, doing nothing if out of bounds. testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) { int qrsize = qrcode[0]; if (0 <= x && x < qrsize && 0 <= y && y < qrsize) { setModule(qrcode, x, y, isBlack); } } // Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. static bool getBit(int x, int i) { return ((x >> i) & 1) != 0; } /*---- Segment handling ----*/ // Public function - see documentation comment in header file. bool qrcodegen_isAlphanumeric(const char *text) { assert(text != NULL); for (; *text != '\0'; text++) { if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) { return false; } } return true; } // Public function - see documentation comment in header file. bool qrcodegen_isNumeric(const char *text) { assert(text != NULL); for (; *text != '\0'; text++) { if (*text < '0' || *text > '9') { return false; } } return true; } // Public function - see documentation comment in header file. size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { int temp = calcSegmentBitLength(mode, numChars); if (temp == -1) { return SIZE_MAX; } assert(0 <= temp && temp <= INT16_MAX); return ((size_t)temp + 7) / 8; } // Returns the number of data bits needed to represent a segment // containing the given number of characters using the given mode. Notes: // - Returns -1 on failure, i.e. numChars > INT16_MAX or // the number of needed bits exceeds INT16_MAX (i.e. 32767). // - Otherwise, all valid results are in the range [0, INT16_MAX]. // - For byte mode, numChars measures the number of bytes, not Unicode code points. // - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. // An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { // All calculations are designed to avoid overflow on all platforms if (numChars > (unsigned int)INT16_MAX) { return -1; } long result = (long)numChars; if (mode == qrcodegen_Mode_NUMERIC) { result = (result * 10 + 2) / 3; // ceil(10/3 * n) } else if (mode == qrcodegen_Mode_ALPHANUMERIC) { result = (result * 11 + 1) / 2; // ceil(11/2 * n) } else if (mode == qrcodegen_Mode_BYTE) { result *= 8; } else if (mode == qrcodegen_Mode_KANJI) { result *= 13; } else if (mode == qrcodegen_Mode_ECI && numChars == 0) { result = 3 * 8; } else { // Invalid argument assert(false); return -1; } assert(result >= 0); if (result > INT16_MAX) { return -1; } return (int)result; } // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { assert(data != NULL || len == 0); struct qrcodegen_Segment result; result.mode = qrcodegen_Mode_BYTE; result.bitLength = calcSegmentBitLength(result.mode, len); assert(result.bitLength != -1); result.numChars = (int)len; if (len > 0) { memcpy(buf, data, len * sizeof(buf[0])); } result.data = buf; return result; } // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { assert(digits != NULL); struct qrcodegen_Segment result; size_t len = strlen(digits); result.mode = qrcodegen_Mode_NUMERIC; int bitLen = calcSegmentBitLength(result.mode, len); assert(bitLen != -1); result.numChars = (int)len; if (bitLen > 0) { memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); } result.bitLength = 0; unsigned int accumData = 0; int accumCount = 0; for (; *digits != '\0'; digits++) { char c = *digits; assert('0' <= c && c <= '9'); accumData = accumData * 10 + (unsigned int)(c - '0'); accumCount++; if (accumCount == 3) { appendBitsToBuffer(accumData, 10, buf, &result.bitLength); accumData = 0; accumCount = 0; } } if (accumCount > 0) { // 1 or 2 digits remaining appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); } assert(result.bitLength == bitLen); result.data = buf; return result; } // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { assert(text != NULL); struct qrcodegen_Segment result; size_t len = strlen(text); result.mode = qrcodegen_Mode_ALPHANUMERIC; int bitLen = calcSegmentBitLength(result.mode, len); assert(bitLen != -1); result.numChars = (int)len; if (bitLen > 0) { memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); } result.bitLength = 0; unsigned int accumData = 0; int accumCount = 0; for (; *text != '\0'; text++) { const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); assert(temp != NULL); accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); accumCount++; if (accumCount == 2) { appendBitsToBuffer(accumData, 11, buf, &result.bitLength); accumData = 0; accumCount = 0; } } if (accumCount > 0) { // 1 character remaining appendBitsToBuffer(accumData, 6, buf, &result.bitLength); } assert(result.bitLength == bitLen); result.data = buf; return result; } // Public function - see documentation comment in header file. struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { struct qrcodegen_Segment result; result.mode = qrcodegen_Mode_ECI; result.numChars = 0; result.bitLength = 0; if (assignVal < 0) { assert(false); } else if (assignVal < (1 << 7)) { memset(buf, 0, 1 * sizeof(buf[0])); appendBitsToBuffer((unsigned int)assignVal, 8, buf, &result.bitLength); } else if (assignVal < (1 << 14)) { memset(buf, 0, 2 * sizeof(buf[0])); appendBitsToBuffer(2, 2, buf, &result.bitLength); appendBitsToBuffer((unsigned int)assignVal, 14, buf, &result.bitLength); } else if (assignVal < 1000000L) { memset(buf, 0, 3 * sizeof(buf[0])); appendBitsToBuffer(6, 3, buf, &result.bitLength); appendBitsToBuffer((unsigned int)(assignVal >> 10), 11, buf, &result.bitLength); appendBitsToBuffer((unsigned int)(assignVal & 0x3FF), 10, buf, &result.bitLength); } else { assert(false); } result.data = buf; return result; } // Calculates the number of bits needed to encode the given segments at the given version. // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too // many characters to fit its length field, or the total bits exceeds INT16_MAX. testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { assert(segs != NULL || len == 0); long result = 0; for (size_t i = 0; i < len; i++) { int numChars = segs[i].numChars; int bitLength = segs[i].bitLength; assert(0 <= numChars && numChars <= INT16_MAX); assert(0 <= bitLength && bitLength <= INT16_MAX); int ccbits = numCharCountBits(segs[i].mode, version); assert(0 <= ccbits && ccbits <= 16); if (numChars >= (1L << ccbits)) { return -1; // The segment's length doesn't fit the field's bit width } result += 4L + ccbits + bitLength; if (result > INT16_MAX) { return -1; // The sum might overflow an int type } } assert(0 <= result && result <= INT16_MAX); return (int)result; } // Returns the bit width of the character count field for a segment in the given mode // in a QR Code at the given version number. The result is in the range [0, 16]. static int numCharCountBits(enum qrcodegen_Mode mode, int version) { assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); int i = (version + 7) / 17; switch (mode) { case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } case qrcodegen_Mode_ECI : return 0; default: assert(false); return -1; // Dummy value } } ================================================ FILE: qrcode/qrcodegen.h ================================================ /* * QR Code generator library (C) * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * 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: * - The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * - 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. */ #pragma once #include #include #include #ifdef __cplusplus extern "C" { #endif /* * This library creates QR Code symbols, which is a type of two-dimension barcode. * Invented by Denso Wave and described in the ISO/IEC 18004 standard. * A QR Code structure is an immutable square grid of black and white cells. * The library provides functions to create a QR Code from text or binary data. * The library covers the QR Code Model 2 specification, supporting all versions (sizes) * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. * * Ways to create a QR Code object: * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). * - Low level: Custom-make the list of segments and call * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). * (Note that all ways require supplying the desired error correction level and various byte buffers.) */ /*---- Enum and struct types----*/ /* * The error correction level in a QR Code symbol. */ enum qrcodegen_Ecc { // Must be declared in ascending order of error protection // so that an internal qrcodegen function works properly qrcodegen_Ecc_LOW = 0, // The QR Code can tolerate about 7% erroneous codewords qrcodegen_Ecc_MEDIUM, // The QR Code can tolerate about 15% erroneous codewords qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords qrcodegen_Ecc_HIGH, // The QR Code can tolerate about 30% erroneous codewords }; /* * The mask pattern used in a QR Code symbol. */ enum qrcodegen_Mask { // A special value to tell the QR Code encoder to // automatically select an appropriate mask pattern qrcodegen_Mask_AUTO = -1, // The eight actual mask patterns qrcodegen_Mask_0 = 0, qrcodegen_Mask_1, qrcodegen_Mask_2, qrcodegen_Mask_3, qrcodegen_Mask_4, qrcodegen_Mask_5, qrcodegen_Mask_6, qrcodegen_Mask_7, }; /* * Describes how a segment's data bits are interpreted. */ enum qrcodegen_Mode { qrcodegen_Mode_NUMERIC = 0x1, qrcodegen_Mode_ALPHANUMERIC = 0x2, qrcodegen_Mode_BYTE = 0x4, qrcodegen_Mode_KANJI = 0x8, qrcodegen_Mode_ECI = 0x7, }; /* * A segment of character/binary/control data in a QR Code symbol. * The mid-level way to create a segment is to take the payload data * and call a factory function such as qrcodegen_makeNumeric(). * The low-level way to create a segment is to custom-make the bit buffer * and initialize a qrcodegen_Segment struct with appropriate values. * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. * Any segment longer than this is meaningless for the purpose of generating QR Codes. * Moreover, the maximum allowed bit length is 32767 because * the largest QR Code (version 40) has 31329 modules. */ struct qrcodegen_Segment { // The mode indicator of this segment. enum qrcodegen_Mode mode; // The length of this segment's unencoded data. Measured in characters for // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. // Always zero or positive. Not the same as the data's bit length. int numChars; // The data bits of this segment, packed in bitwise big endian. // Can be null if the bit length is zero. uint8_t *data; // The number of valid data bits used in the buffer. Requires // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. // The character count (numChars) must agree with the mode and the bit buffer length. int bitLength; }; /*---- Macro constants and functions ----*/ #define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard #define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard // Calculates the number of bytes needed to store any QR Code up to and including the given version number, // as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' // can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). // Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. #define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) // The worst-case number of bytes needed to store one QR Code, up to and including // version 40. This value equals 3918, which is just under 4 kilobytes. // Use this more convenient value to avoid calculating tighter memory bounds for buffers. #define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) /*---- Functions (high level) to generate QR Codes ----*/ /* * Encodes the given text string to a QR Code, returning true if encoding succeeded. * If the data is too long to fit in any version in the given range * at the given ECC level, then false is returned. * - The input text must be encoded in UTF-8 and contain no NULs. * - The variables ecl and mask must correspond to enum constant values. * - Requires 1 <= minVersion <= maxVersion <= 40. * - The arrays tempBuffer and qrcode must each have a length * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). * - After the function returns, tempBuffer contains no useful data. * - If successful, the resulting QR Code may use numeric, * alphanumeric, or byte mode to encode the text. * - In the most optimistic case, a QR Code at version 40 with low ECC * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string * up to 4296 characters, or any digit string up to 7089 characters. * These numbers represent the hard upper limit of the QR Code standard. * - Please consult the QR Code specification for information on * data capacities per version, ECC level, and text encoding mode. */ bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); /* * Encodes the given binary data to a QR Code, returning true if encoding succeeded. * If the data is too long to fit in any version in the given range * at the given ECC level, then false is returned. * - The input array range dataAndTemp[0 : dataLen] should normally be * valid UTF-8 text, but is not required by the QR Code standard. * - The variables ecl and mask must correspond to enum constant values. * - Requires 1 <= minVersion <= maxVersion <= 40. * - The arrays dataAndTemp and qrcode must each have a length * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). * - After the function returns, the contents of dataAndTemp may have changed, * and does not represent useful data anymore. * - If successful, the resulting QR Code will use byte mode to encode the data. * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte * sequence up to length 2953. This is the hard upper limit of the QR Code standard. * - Please consult the QR Code specification for information on * data capacities per version, ECC level, and text encoding mode. */ bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); /*---- Functions (low level) to generate QR Codes ----*/ /* * Renders a QR Code representing the given segments at the given error correction level. * The smallest possible QR Code version is automatically chosen for the output. Returns true if * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level * of the result may be higher than the ecl argument if it can be done without increasing the version. * This function allows the user to create a custom sequence of segments that switches * between modes (such as alphanumeric and byte) to encode text in less space. * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will * result in them being clobbered, but the QR Code output will still be correct. * But the qrcode array must not overlap tempBuffer or any segment's data buffer. */ bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); /* * Renders a QR Code representing the given segments with the given encoding parameters. * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions. * The smallest possible QR Code version within the given range is automatically * chosen for the output. Iff boostEcl is true, then the ECC level of the result * may be higher than the ecl argument if it can be done without increasing the * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). * This function allows the user to create a custom sequence of segments that switches * between modes (such as alphanumeric and byte) to encode text in less space. * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will * result in them being clobbered, but the QR Code output will still be correct. * But the qrcode array must not overlap tempBuffer or any segment's data buffer. */ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); /* * Tests whether the given string can be encoded as a segment in alphanumeric mode. * A string is encodable iff each character is in the following set: 0 to 9, A to Z * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. */ bool qrcodegen_isAlphanumeric(const char *text); /* * Tests whether the given string can be encoded as a segment in numeric mode. * A string is encodable iff each character is in the range 0 to 9. */ bool qrcodegen_isNumeric(const char *text); /* * Returns the number of bytes (uint8_t) needed for the data buffer of a segment * containing the given number of characters using the given mode. Notes: * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or * the number of needed bits exceeds INT16_MAX (i.e. 32767). * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. * - It is okay for the user to allocate more bytes for the buffer than needed. * - For byte mode, numChars measures the number of bytes, not Unicode code points. * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. */ size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); /* * Returns a segment representing the given binary data encoded in * byte mode. All input byte arrays are acceptable. Any text string * can be converted to UTF-8 bytes and encoded as a byte mode segment. */ struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); /* * Returns a segment representing the given string of decimal digits encoded in numeric mode. */ struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); /* * Returns a segment representing the given text string encoded in alphanumeric mode. * The characters allowed are: 0 to 9, A to Z (uppercase only), space, * dollar, percent, asterisk, plus, hyphen, period, slash, colon. */ struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); /* * Returns a segment representing an Extended Channel Interpretation * (ECI) designator with the given assignment value. */ struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); /*---- Functions to extract raw data from QR Codes ----*/ /* * Returns the side length of the given QR Code, assuming that encoding succeeded. * The result is in the range [21, 177]. Note that the length of the array buffer * is related to the side length - every 'uint8_t qrcode[]' must have length at least * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). */ int qrcodegen_getSize(const uint8_t qrcode[]); /* * Returns the color of the module (pixel) at the given coordinates, which is false * for white or true for black. The top left corner has the coordinates (x=0, y=0). * If the given coordinates are out of bounds, then false (white) is returned. */ bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); #ifdef __cplusplus } #endif ================================================ FILE: qrcode/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(qrcode_test) ================================================ FILE: qrcode/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "qrcode_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: qrcode/test_apps/main/idf_component.yml ================================================ dependencies: espressif/qrcode: version: "*" override_path: "../.." ================================================ FILE: qrcode/test_apps/main/qrcode_test.c ================================================ #include void app_main(void) { } ================================================ FILE: quirc/.build-test-rules.yml ================================================ quirc/test_apps: enable: - if: IDF_TARGET in ["esp32", "esp32c3"] reason: "Sufficient to test on one Xtensa and one RISC-V target" ================================================ FILE: quirc/CMakeLists.txt ================================================ idf_component_register(SRCS quirc/lib/decode.c quirc/lib/identify.c quirc/lib/quirc.c quirc/lib/version_db.c INCLUDE_DIRS quirc/lib) # Performance optimization; see quirc README.md for an explanation of these options target_compile_definitions(${COMPONENT_LIB} PRIVATE QUIRC_FLOAT_TYPE=float) target_compile_definitions(${COMPONENT_LIB} PRIVATE QUIRC_USE_TGMATH) ================================================ FILE: quirc/LICENSE ================================================ quirc -- QR-code recognition library Copyright (C) 2010-2012 Daniel Beer ISC License =========== Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: quirc/README.md ================================================ # Quirc QR code decoding library [![Component Registry](https://components.espressif.com/components/espressif/quirc/badge.svg)](https://components.espressif.com/components/espressif/quirc) This is an ESP-IDF component for [quirc](https://github.com/dlbeer/quirc), a QR code decoding library. Please refer to https://github.com/dlbeer/quirc#library-use for the introduction to this library. See also the `qrcode` component for generation of QR codes ([registry](https://components.espressif.com/components/espressif/qrcode), [source](../qrcode/README.md)). ================================================ FILE: quirc/idf_component.yml ================================================ version: "1.2.0" description: Quirc QR code decoding library url: https://github.com/espressif/idf-extra-components/tree/master/quirc repository: https://github.com/espressif/idf-extra-components.git issues: https://github.com/espressif/idf-extra-components/issues documentation: https://github.com/dlbeer/quirc#library-use dependencies: idf: ">=4.3.0" ================================================ FILE: quirc/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(quirc_test) ================================================ FILE: quirc/test_apps/main/CMakeLists.txt ================================================ idf_component_register( SRCS test_quirc.c test_main.c PRIV_REQUIRES unity EMBED_FILES test_qrcode.pgm WHOLE_ARCHIVE) ================================================ FILE: quirc/test_apps/main/idf_component.yml ================================================ dependencies: espressif/quirc: version: "*" override_path: "../.." ================================================ FILE: quirc/test_apps/main/test_main.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(0); } void app_main(void) { printf("Running quirc component tests\n"); unity_run_menu(); } ================================================ FILE: quirc/test_apps/main/test_quirc.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "quirc.h" #include "unity.h" static const char *TAG = "test_quirc"; extern const uint8_t test_qrcode_pgm_start[] asm("_binary_test_qrcode_pgm_start"); extern const uint8_t test_qrcode_pgm_end[] asm("_binary_test_qrcode_pgm_end"); static void copy_test_image_into_quirc_buffer(struct quirc *q) { // get the size of the image from the PGM header const uint8_t *p = test_qrcode_pgm_start; int width, height; sscanf((const char *)p, "P5 %d %d 255", &width, &height); TEST_ASSERT_EQUAL_INT(128, width); TEST_ASSERT_EQUAL_INT(113, height); // resize the quirc buffer to match the image TEST_ASSERT_EQUAL_INT(0, quirc_resize(q, width, height)); // find the start of the image data p = memchr(p, '\n', test_qrcode_pgm_end - p) + 1; // copy the image into the quirc buffer memcpy(quirc_begin(q, NULL, NULL), p, width * height); } typedef struct { struct quirc *q; struct quirc_code code; struct quirc_data data; SemaphoreHandle_t done; } quirc_decode_task_args_t; static void quirc_decode_task(void *arg) { quirc_decode_task_args_t *args = (quirc_decode_task_args_t *)arg; quirc_end(args->q); TEST_ASSERT_EQUAL_INT(1, quirc_count(args->q)); quirc_extract(args->q, 0, &args->code); TEST_ASSERT_EQUAL(QUIRC_SUCCESS, quirc_decode(&args->code, &args->data)); const size_t stack_space_free = uxTaskGetStackHighWaterMark(NULL); ESP_LOGI(TAG, "quirc_decode_task stack space free: %d", stack_space_free); xSemaphoreGive(args->done); vTaskDelete(NULL); } TEST_CASE("quirc can load a QR code", "[quirc]") { struct quirc *q = quirc_new(); TEST_ASSERT_NOT_NULL(q); // load the test image into the quirc buffer copy_test_image_into_quirc_buffer(q); // decode the QR code in the image // quirc uses a lot of stack space (around 10kB on ESP32 for this particular QR code), // so do this in a separate task quirc_decode_task_args_t *args = calloc(1, sizeof(*args)); TEST_ASSERT_NOT_NULL(args); args->q = q; args->done = xSemaphoreCreateBinary(); TEST_ASSERT(xTaskCreate(quirc_decode_task, "quirc_decode_task", 12000, args, 5, NULL)); TEST_ASSERT(xSemaphoreTake(args->done, pdMS_TO_TICKS(10000))); vSemaphoreDelete(args->done); // check the QR code data TEST_ASSERT_EQUAL_INT(1, args->data.version); TEST_ASSERT_EQUAL_INT(1, args->data.ecc_level); TEST_ASSERT_EQUAL_INT(4, args->data.data_type); TEST_ASSERT_EQUAL_INT(13, args->data.payload_len); TEST_ASSERT_EQUAL_STRING("test of quirc", args->data.payload); free(args); quirc_destroy(q); vTaskDelay(2); // allow the task to clean up } ================================================ FILE: quirc/test_apps/pytest_quirc.py ================================================ import pytest @pytest.mark.generic def test_quirc(dut) -> None: dut.run_all_single_board_cases() ================================================ FILE: quirc/test_apps/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration # CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: sh2lib/CHANGELOG.md ================================================ ## 1.1.0 ### Features - Make task stack size configurable - Add configurable inbound buffer length support - Add TCP keepalive configuration - Capture and report TLS errors - Forward underlying nghttp2 execution error codes - Log and report underlying get/post error codes ### Fixes - Update API documentation for return values - Fix CI build error due to an undefined Kconfig option ## 1.0.5 - Added http2_request example in the component. ================================================ FILE: sh2lib/CMakeLists.txt ================================================ idf_component_register(SRCS "sh2lib.c" INCLUDE_DIRS . REQUIRES http_parser PRIV_REQUIRES lwip esp-tls) ================================================ FILE: sh2lib/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: sh2lib/README.md ================================================ # Abstraction layer for HTTP2 with TLS [![Component Registry](https://components.espressif.com/components/espressif/sh2lib/badge.svg)](https://components.espressif.com/components/espressif/sh2lib) This component contains an abstraction layer which exposes simpler set of APIs combining `nghttp2` (HTTP/2 C Library) and `esp-tls` (from ESP-IDF) components. ================================================ FILE: sh2lib/examples/http2_request/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) # (Not part of the boilerplate) # This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(http2_request) ================================================ FILE: sh2lib/examples/http2_request/README.md ================================================ # HTTP/2 Request Example Establish an HTTP/2 connection with https://http2.github.io - Performs a GET on /index.html ## How to use example Before project configuration and build, be sure to set the correct chip target using `idf.py set-target `. ### Hardware Required * A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.) * A USB cable for power supply and programming ### Configure the project ``` idf.py menuconfig ``` Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (5609) example_connect: - IPv4 address: 192.168.0.103 I (5609) example_connect: - IPv6 address: fe80:0000:0000:0000:ae67:b2ff:fe45:0194, type: ESP_IP6_ADDR_IS_LINK_LOCAL Connecting to server Connection done [get-response] . . . Body of index.html . . . . [get-response] Frame fully received [get-response] Stream Closed ``` ================================================ FILE: sh2lib/examples/http2_request/main/CMakeLists.txt ================================================ idf_component_register(SRCS "http2_request_example_main.c" INCLUDE_DIRS ".") ================================================ FILE: sh2lib/examples/http2_request/main/Kconfig.projbuild ================================================ menu "Example Configuration" config EXAMPLE_NGHTTP2_TASK_STACK_SIZE int "Nghttp2 task stack size" default 8192 help This is the stack size for the nghttp task. endmenu ================================================ FILE: sh2lib/examples/http2_request/main/http2_request_example_main.c ================================================ /* HTTP2 GET Example using nghttp2 Contacts http2.github.io and executes the GET request. A thin API wrapper on top of nghttp2, to properly demonstrate the interactions. This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_event.h" #include "esp_system.h" #include "nvs_flash.h" #include "protocol_examples_common.h" #include "esp_netif.h" #include "esp_idf_version.h" #if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(5, 1, 0) #include "lwip/apps/sntp.h" #else #include "esp_netif_sntp.h" #endif #include "sdkconfig.h" #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #include "esp_crt_bundle.h" #endif #include "sh2lib.h" // Compile-time check to ensure stack size is larger than inbound buffer length #if CONFIG_EXAMPLE_NGHTTP2_TASK_STACK_SIZE <= CONFIG_ESP_NGHTTP2_INBOUND_BUFFER_LENGTH #warning "CONFIG_EXAMPLE_NGHTTP2_TASK_STACK_SIZE should be larger than CONFIG_ESP_NGHTTP2_INBOUND_BUFFER_LENGTH" #endif /* The HTTP/2 server to connect to */ #define HTTP2_SERVER_URI "https://http2.github.io" /* A GET request that keeps streaming current time every second */ #define HTTP2_STREAMING_GET_PATH "/index.html" int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags) { if (len) { printf("[get-response] %.*s\n", (int)len, data); } if (flags == DATA_RECV_FRAME_COMPLETE) { printf("[get-response] Frame fully received\n"); } if (flags == DATA_RECV_RST_STREAM) { printf("[get-response] Stream Closed\n"); printf("Remaining task stask size: %d\n", uxTaskGetStackHighWaterMark(NULL)); } return 0; } int handle_echo_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags) { if (len) { printf("[echo-response] %.*s\n", (int)len, data); } if (flags == DATA_RECV_FRAME_COMPLETE) { printf("[echo-response] Frame fully received\n"); } if (flags == DATA_RECV_RST_STREAM) { printf("[echo-response] Stream Closed\n"); } return 0; } int send_put_data(struct sh2lib_handle *handle, char *buf, size_t length, uint32_t *data_flags) { #define DATA_TO_SEND "Hello World" int copylen = strlen(DATA_TO_SEND); if (copylen < length) { printf("[data-prvd] Sending %d bytes\n", copylen); memcpy(buf, DATA_TO_SEND, copylen); } else { copylen = 0; } (*data_flags) |= NGHTTP2_DATA_FLAG_EOF; return copylen; } static void set_time(void) { struct timeval tv = { .tv_sec = 1509449941, }; struct timezone tz = { 0, 0 }; settimeofday(&tv, &tz); /* Start SNTP service */ #if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(5, 1, 0) sntp_setoperatingmode(SNTP_OPMODE_POLL); sntp_init(); #else esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("time.windows.com"); esp_netif_sntp_init(&config); if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) != ESP_OK) { printf("Failed to update system time, continuing"); } esp_netif_sntp_deinit(); #endif } static void http2_task(void *args) { /* Set current time: proper system time is required for TLS based * certificate verification. */ set_time(); /* HTTP2: one connection multiple requests. Do the TLS/TCP connection first */ printf("Connecting to server\n"); struct sh2lib_config_t cfg = { .uri = HTTP2_SERVER_URI, #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE .crt_bundle_attach = esp_crt_bundle_attach, #endif }; struct sh2lib_handle hd; if (sh2lib_connect(&cfg, &hd) != 0) { printf("Failed to connect\n"); vTaskDelete(NULL); return; } printf("Connection done\n"); /* HTTP GET with proper error handling */ int stream_id = sh2lib_do_get(&hd, HTTP2_STREAMING_GET_PATH, handle_get_response); if (stream_id < 0) { printf("Failed to setup GET request, error code: %d\n", stream_id); goto end; } printf("GET request setup successful, stream ID: %d\n", stream_id); while (1) { /* Process HTTP2 send/receive */ if (sh2lib_execute(&hd) < 0) { printf("Error in send/receive, tls error code: %d\n", hd.http2_tls_rc); break; } vTaskDelay(2); } end: sh2lib_free(&hd); vTaskDelete(NULL); return; } void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); xTaskCreate(&http2_task, "http2_task", CONFIG_EXAMPLE_NGHTTP2_TASK_STACK_SIZE, NULL, 5, NULL); } ================================================ FILE: sh2lib/examples/http2_request/main/idf_component.yml ================================================ ## IDF Component Manager Manifest File version: "1.0.0" description: HTTP2 Request Examples dependencies: espressif/sh2lib: version: "^1.0.0" override_path: '../../../' ================================================ FILE: sh2lib/examples/http2_request/pytest_http2_request.py ================================================ #!/usr/bin/env python # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import http.client import logging import os import pytest from pytest_embedded import Dut HTTP_OK = 200 TEST_SERVER = 'http2.github.io' def is_test_server_available(): # type: () -> bool # 443 - default https port try: conn = http.client.HTTPSConnection(TEST_SERVER, 443, timeout=10) conn.request('GET', '/') resp = conn.getresponse() return True if resp.status == HTTP_OK else False except Exception as msg: logging.info('Exception occurred when connecting to {}: {}'.format(TEST_SERVER, msg)) return False finally: conn.close() @pytest.mark.ethernet def test_examples_protocol_http2_request(dut: Dut) -> None: """ steps: | 1. join AP 2. connect to http2.github.io 3. send http2 request 4. send http2 put response """ # check and log bin size binary_file = os.path.join(dut.app.binary_path, 'http2_request.bin') bin_size = os.path.getsize(binary_file) logging.info('http2_request_bin_size : {}KB'.format(bin_size // 1024)) # start the test # check if test server is available test_server_available = is_test_server_available() # Skip the test if the server test server (http2.github.io) is not available at the moment. if test_server_available: logging.info('test server \"{}\" is available'.format(TEST_SERVER)) # check for connection dut.expect('Connection done', timeout=30) # check for get response dut.expect('Frame fully received') else: logging.info('test server \"{0}\" is not available at the moment.\nSkipping the test with status = success.'.format(TEST_SERVER)) ================================================ FILE: sh2lib/examples/http2_request/sdkconfig.ci ================================================ CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y CONFIG_EXAMPLE_CONNECT_ETHERNET=y CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 CONFIG_EXAMPLE_ETH_PHY_ADDR=1 CONFIG_EXAMPLE_CONNECT_IPV6=y ================================================ FILE: sh2lib/examples/http2_request/sdkconfig.ci.esp32 ================================================ CONFIG_SPIRAM=y ================================================ FILE: sh2lib/examples/http2_request/sdkconfig.defaults ================================================ CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y ================================================ FILE: sh2lib/idf_component.yml ================================================ version: "1.1.0" description: HTTP2 TLS Abstraction Layer url: https://github.com/espressif/idf-extra-components/tree/master/sh2lib dependencies: idf: ">=5.0" espressif/nghttp: version: ">=1.41.0" override_path: "../nghttp" ================================================ FILE: sh2lib/sh2lib.c ================================================ /* * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sh2lib.h" static const char *TAG = "sh2lib"; #define DBG_FRAME_SEND 1 /* * The implementation of nghttp2_send_callback type. Here we write * |data| with size |length| to the network and return the number of * bytes actually written. See the documentation of * nghttp2_send_callback for the details. */ static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data, size_t length) { int rv = esp_tls_conn_write(hd->http2_tls, data, length); if (rv <= 0) { if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) { rv = NGHTTP2_ERR_WOULDBLOCK; } else { hd->http2_tls_rc = rv; rv = NGHTTP2_ERR_CALLBACK_FAILURE; } } return rv; } static int callback_error(nghttp2_session *session, int lib_error_code, const char *msg, size_t len, void *user_data) { ESP_LOGE(TAG, "[error] code %i msg:%.*s", lib_error_code, len, msg); return 0; } static ssize_t callback_send(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) { ssize_t rv = 0; struct sh2lib_handle *hd = user_data; size_t copy_offset = 0; size_t pending_data = length; /* Send data in 1000 byte chunks */ while (copy_offset < length) { size_t chunk_len = pending_data > 1000 ? 1000 : pending_data; ssize_t subrv = callback_send_inner(hd, data + copy_offset, chunk_len); if (subrv <= 0) { if (copy_offset == 0) { /* If no data is transferred, send the error code */ rv = subrv; } break; } copy_offset += subrv; pending_data -= subrv; rv += subrv; } return rv; } /* * The implementation of nghttp2_recv_callback type. Here we read data * from the network and write them in |buf|. The capacity of |buf| is * |length| bytes. Returns the number of bytes stored in |buf|. See * the documentation of nghttp2_recv_callback for the details. */ static ssize_t callback_recv(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *user_data) { struct sh2lib_handle *hd = user_data; int rv; rv = esp_tls_conn_read(hd->http2_tls, (char *)buf, (int)length); if (rv < 0) { if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) { rv = NGHTTP2_ERR_WOULDBLOCK; } else { hd->http2_tls_rc = rv; rv = NGHTTP2_ERR_CALLBACK_FAILURE; } } else if (rv == 0) { rv = NGHTTP2_ERR_EOF; } return rv; } const char *sh2lib_frame_type_str(int type) { switch (type) { case NGHTTP2_HEADERS: return "HEADERS"; break; case NGHTTP2_RST_STREAM: return "RST_STREAM"; break; case NGHTTP2_GOAWAY: return "GOAWAY"; break; case NGHTTP2_DATA: return "DATA"; break; case NGHTTP2_SETTINGS: return "SETTINGS"; break; case NGHTTP2_PUSH_PROMISE: return "PUSH_PROMISE"; break; case NGHTTP2_PING: return "PING"; break; default: return "other"; break; } } static int callback_on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { ESP_LOGD(TAG, "[frame-send] frame type %s", sh2lib_frame_type_str(frame->hd.type)); switch (frame->hd.type) { case NGHTTP2_HEADERS: if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) { ESP_LOGD(TAG, "[frame-send] C ----------------------------> S (HEADERS)"); #if DBG_FRAME_SEND ESP_LOGD(TAG, "[frame-send] headers nv-len = %d", frame->headers.nvlen); for (size_t i = 0; i < frame->headers.nvlen; ++i) { ESP_LOGD(TAG, "[frame-send] %s : %s", frame->headers.nva[i].name, frame->headers.nva[i].value); } #endif } break; } return 0; } static int callback_on_frame_not_send(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data) { ESP_LOGW(TAG, "[frame-not-send] code %i frame type %s", lib_error_code, sh2lib_frame_type_str(frame->hd.type)); return 0; } static int callback_on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { struct sh2lib_handle *h2 = user_data; ESP_LOGD(TAG, "[frame-recv][sid: %" PRIi32 "] frame type %s", frame->hd.stream_id, sh2lib_frame_type_str(frame->hd.type)); sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if (frame->hd.type == NGHTTP2_DATA && data_recv_cb) { (*data_recv_cb)(h2, NULL, 0, DATA_RECV_FRAME_COMPLETE); } if (frame->hd.type == NGHTTP2_GOAWAY) { ESP_LOGW(TAG, "[frame-recv] GOAWAY: no more requests may be sent"); h2->http2_goaway = true; } return 0; } static int callback_on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { ESP_LOGD(TAG, "[stream-close][sid %" PRIi32 "]", stream_id); sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id); if (data_recv_cb) { struct sh2lib_handle *h2 = user_data; (*data_recv_cb)(h2, NULL, 0, DATA_RECV_RST_STREAM); } return 0; } static int callback_on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { sh2lib_frame_data_recv_cb_t data_recv_cb; ESP_LOGD(TAG, "[data-chunk][sid: %" PRIi32 "]", stream_id); data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id); if (data_recv_cb) { ESP_LOGD(TAG, "[data-chunk] C <---------------------------- S (DATA chunk)" "%lu bytes", (unsigned long int)len); struct sh2lib_handle *h2 = user_data; (*data_recv_cb)(h2, (char *)data, len, 0); /* TODO: What to do with the return value: look for pause/abort */ } return 0; } static int callback_on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data) { ESP_LOGD(TAG, "[hdr-recv][sid: %" PRIi32 "] %s : %s", frame->hd.stream_id, name, value); return 0; } static int callback_on_invalid_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data) { ESP_LOGW(TAG, "[inv-hdr][sid: %" PRIi32 "] %s : %s", frame->hd.stream_id, name, value); return 0; } static int do_http2_connect(struct sh2lib_handle *hd) { int ret; nghttp2_session_callbacks *callbacks; ret = nghttp2_session_callbacks_new(&callbacks); if (ret != 0) { ESP_LOGE(TAG, "[sh2-connect] Failed to create session callbacks"); return -1; } nghttp2_session_callbacks_set_error_callback2(callbacks, callback_error); nghttp2_session_callbacks_set_send_callback(callbacks, callback_send); nghttp2_session_callbacks_set_recv_callback(callbacks, callback_recv); nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, callback_on_frame_send); nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks, callback_on_frame_not_send); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, callback_on_frame_recv); nghttp2_session_callbacks_set_on_invalid_header_callback(callbacks, callback_on_invalid_header); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, callback_on_stream_close); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, callback_on_data_chunk_recv); nghttp2_session_callbacks_set_on_header_callback(callbacks, callback_on_header); ret = nghttp2_session_client_new(&hd->http2_sess, callbacks, hd); if (ret != 0) { ESP_LOGE(TAG, "[sh2-connect] New http2 session failed"); nghttp2_session_callbacks_del(callbacks); return -1; } nghttp2_session_callbacks_del(callbacks); /* Create the SETTINGS frame */ ret = nghttp2_submit_settings(hd->http2_sess, NGHTTP2_FLAG_NONE, NULL, 0); if (ret != 0) { ESP_LOGE(TAG, "[sh2-connect] Submit settings failed"); /* Clean up session to prevent memory leak on error path */ nghttp2_session_del(hd->http2_sess); hd->http2_sess = NULL; return -1; } return 0; } int sh2lib_connect(struct sh2lib_config_t *cfg, struct sh2lib_handle *hd) { if (hd == NULL || cfg == NULL || cfg->uri == NULL) { ESP_LOGE(TAG, "[sh2-connect] Invalid argument"); return -1; } memset(hd, 0, sizeof(*hd)); const char *proto[] = {"h2", NULL}; esp_tls_cfg_t tls_cfg = { .alpn_protos = proto, .cacert_buf = cfg->cacert_buf, .cacert_bytes = cfg->cacert_bytes, .keep_alive_cfg = cfg->keep_alive_cfg, .crt_bundle_attach = cfg->crt_bundle_attach, .non_block = true, .timeout_ms = 10 * 1000, }; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) hd->http2_tls = esp_tls_init(); if (!hd->http2_tls) { ESP_LOGE(TAG, "Failed to allocate esp_tls handle!"); goto error; } // NOTE: This API is an alternative to previous `esp_tls_conn_http_new` from ESP-IDF v5.0 onwards. if (esp_tls_conn_http_new_sync(cfg->uri, &tls_cfg, hd->http2_tls) != 1) { #else if ((hd->http2_tls = esp_tls_conn_http_new(cfg->uri, &tls_cfg)) == NULL) { #endif ESP_LOGE(TAG, "[sh2-connect] esp-tls connection failed"); goto error; } struct http_parser_url u; http_parser_url_init(&u); if (http_parser_parse_url(cfg->uri, strlen(cfg->uri), 0, &u) != 0) { ESP_LOGE(TAG, "[sh2-connect] Failed to parse URI"); goto error; } if (!(u.field_set & (1 << UF_HOST))) { ESP_LOGE(TAG, "[sh2-connect] Host field not present in URI"); goto error; } hd->hostname = strndup(&cfg->uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len); if (!hd->hostname) { ESP_LOGE(TAG, "[sh2-connect] Failed to allocate memory for hostname"); goto error; } /* HTTP/2 Connection */ if (do_http2_connect(hd) != 0) { ESP_LOGE(TAG, "[sh2-connect] HTTP2 Connection failed with %s", cfg->uri); goto error; } return 0; error: sh2lib_free(hd); return -1; } void sh2lib_free(struct sh2lib_handle *hd) { if (hd == NULL) { return; } if (hd->http2_sess) { nghttp2_session_del(hd->http2_sess); hd->http2_sess = NULL; } if (hd->http2_tls) { esp_tls_conn_destroy(hd->http2_tls); hd->http2_tls = NULL; } if (hd->hostname) { free(hd->hostname); hd->hostname = NULL; } } int sh2lib_execute(struct sh2lib_handle *hd) { if (hd == NULL) { ESP_LOGE(TAG, "[sh2-execute] Invalid argument"); return -1; } int ret; ret = sh2lib_execute_send(hd); if (ret != 0) { return ret; } ret = sh2lib_execute_recv(hd); if (ret != 0) { return ret; } return 0; } int sh2lib_execute_recv(struct sh2lib_handle *hd) { if (hd == NULL) { ESP_LOGE(TAG, "[sh2-execute-recv] Invalid argument"); return -1; } int ret = nghttp2_session_recv(hd->http2_sess); if (ret != 0) { ESP_LOGE(TAG, "[sh2-execute-recv] HTTP2 session recv failed %d", ret); } return ret; } int sh2lib_execute_send(struct sh2lib_handle *hd) { if (hd == NULL) { ESP_LOGE(TAG, "[sh2-execute-send] Invalid argument"); return -1; } int ret = nghttp2_session_send(hd->http2_sess); if (ret != 0) { ESP_LOGE(TAG, "[sh2-execute-send] HTTP2 session send failed %d", ret); } return ret; } int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb) { if (hd == NULL || (nva == NULL && nvlen > 0)) { ESP_LOGE(TAG, "[sh2-do-get] Invalid argument"); return -1; } int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, NULL, recv_cb); if (ret < 0) { ESP_LOGE(TAG, "[sh2-do-get] HEADERS call failed %i", ret); } return ret; } int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb) { if (hd == NULL || path == NULL || hd->hostname == NULL) { ESP_LOGE(TAG, "[sh2-do-get] Invalid argument"); return -1; } const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"), SH2LIB_MAKE_NV(":scheme", "https"), SH2LIB_MAKE_NV(":authority", hd->hostname), SH2LIB_MAKE_NV(":path", path), }; return sh2lib_do_get_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), recv_cb); } ssize_t sh2lib_data_provider_cb(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { struct sh2lib_handle *h2 = user_data; sh2lib_putpost_data_cb_t data_cb = source->ptr; return (*data_cb)(h2, (char *)buf, length, data_flags); } int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb) { if (hd == NULL || (nva == NULL && nvlen > 0) || send_cb == NULL) { ESP_LOGE(TAG, "[sh2-do-putpost] Invalid argument"); return -1; } nghttp2_data_provider sh2lib_data_provider; sh2lib_data_provider.read_callback = sh2lib_data_provider_cb; sh2lib_data_provider.source.ptr = send_cb; int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, &sh2lib_data_provider, recv_cb); if (ret < 0) { ESP_LOGE(TAG, "[sh2-do-putpost] HEADERS call failed %i", ret); } return ret; } int sh2lib_do_post(struct sh2lib_handle *hd, const char *path, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb) { if (hd == NULL || path == NULL || send_cb == NULL || hd->hostname == NULL) { ESP_LOGE(TAG, "[sh2-do-post] Invalid argument"); return -1; } const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"), SH2LIB_MAKE_NV(":scheme", "https"), SH2LIB_MAKE_NV(":authority", hd->hostname), SH2LIB_MAKE_NV(":path", path), }; return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb); } int sh2lib_do_put(struct sh2lib_handle *hd, const char *path, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb) { if (hd == NULL || path == NULL || send_cb == NULL || hd->hostname == NULL) { ESP_LOGE(TAG, "[sh2-do-put] Invalid argument"); return -1; } const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "PUT"), SH2LIB_MAKE_NV(":scheme", "https"), SH2LIB_MAKE_NV(":authority", hd->hostname), SH2LIB_MAKE_NV(":path", path), }; return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb); } ================================================ FILE: sh2lib/sh2lib.h ================================================ /* * SPDX-FileCopyrightText: 2027-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_tls.h" #include #ifdef __cplusplus extern "C" { #endif /* * This is a thin API wrapper over nghttp2 that offers simplified APIs for usage * in the application. The intention of this wrapper is to act as a stepping * stone to quickly get started with using the HTTP/2 client. Since the focus is * on simplicity, not all the features of HTTP/2 are supported through this * wrapper. Once you are fairly comfortable with nghttp2, feel free to directly * use nghttp2 APIs or make changes to sh2lib.c for realising your objectives. * * TODO: * - Allowing to query response code, content-type etc in the receive callback * - A simple function for per-stream header callback */ /** * @brief Handle for working with sh2lib APIs */ struct sh2lib_handle { nghttp2_session *http2_sess; /*!< Pointer to the HTTP2 session handle */ char *hostname; /*!< The hostname we are connected to */ struct esp_tls *http2_tls; /*!< Pointer to the TLS session handle */ int http2_tls_rc; /*!< Error code from http2_tls */ bool http2_goaway; /*!< HTTP2 server sent GOAWAY */ }; /** * @brief sh2lib configuration structure */ struct sh2lib_config_t { const char *uri; /*!< Pointer to the URI that should be connected to */ const unsigned char *cacert_buf; /*!< Pointer to the buffer containing CA certificate */ unsigned int cacert_bytes; /*!< Size of the CA certifiacte pointed by cacert_buf */ esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification bundle for server verification, must be enabled in menuconfig */ tls_keep_alive_cfg_t *keep_alive_cfg;/*!< Enable TCP keep-alive timeout for SSL connection */ }; /** Flag indicating receive stream is reset */ #define DATA_RECV_RST_STREAM 1 /** Flag indicating frame is completely received */ #define DATA_RECV_FRAME_COMPLETE 2 /** * @brief Function Prototype for data receive callback * * This function gets called whenever data is received on any stream. The * function is also called for indicating events like frame receive complete, or * end of stream. The function may get called multiple times as long as data is * received on the stream. * * @param[in] handle Pointer to the sh2lib handle. * @param[in] data Pointer to a buffer that contains the data received. * @param[in] len The length of valid data stored at the 'data' pointer. * @param[in] flags Flags indicating whether the stream is reset (DATA_RECV_RST_STREAM) or * this particularly frame is completely received * DATA_RECV_FRAME_COMPLETE). * * @return The function should return 0 */ typedef int (*sh2lib_frame_data_recv_cb_t)(struct sh2lib_handle *handle, const char *data, size_t len, int flags); /** * @brief Function Prototype for callback to send data in PUT/POST * * This function gets called whenever nghttp2 wishes to send data, like for * PUT/POST requests to the server. The function keeps getting called until this * function sets the flag NGHTTP2_DATA_FLAG_EOF to indicate end of data. * * @param[in] handle Pointer to the sh2lib handle. * @param[out] data Pointer to a buffer that should contain the data to send. * @param[in] len The maximum length of data that can be sent out by this function. * @param[out] data_flags Pointer to the data flags. The NGHTTP2_DATA_FLAG_EOF * should be set in the data flags to indicate end of new data. * * @return The function should return the number of valid bytes stored in the * data pointer */ typedef int (*sh2lib_putpost_data_cb_t)(struct sh2lib_handle *handle, char *data, size_t len, uint32_t *data_flags); /** * @brief Connect to a URI using HTTP/2 * * This API opens an HTTP/2 connection with the provided URI. If successful, the * hd pointer is populated with a valid handle for subsequent communication. * * Only 'https' URIs are supported. * * @param[in] cfg Pointer to the sh2lib configurations of the type 'struct sh2lib_config_t'. * @param[out] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @return * - 0 if the connection was successful * - -1 if the connection fails */ int sh2lib_connect(struct sh2lib_config_t *cfg, struct sh2lib_handle *hd); /** * @brief Free a sh2lib handle * * This API frees-up an sh2lib handle, thus closing any open connections that * may be associated with this handle, and freeing up any resources. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * */ void sh2lib_free(struct sh2lib_handle *hd); /** * @brief Setup an HTTP GET request stream * * This API sets up an HTTP GET request to be sent out to the server. A new * stream is created for handling the request. Once the request is setup, the * API sh2lib_execute() must be called to actually perform the socket I/O with * the server. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @param[in] path Pointer to the string that contains the resource to * perform the HTTP GET operation on (for example, /users). * @param[in] recv_cb The callback function that should be called for * processing the request's response * * @return * - Stream ID (positive integer) if request setup is successful * - Negative error code if the request setup fails */ int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb); /** * @brief Setup an HTTP POST request stream * * This API sets up an HTTP POST request to be sent out to the server. A new * stream is created for handling the request. Once the request is setup, the * API sh2lib_execute() must be called to actually perform the socket I/O with * the server. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @param[in] path Pointer to the string that contains the resource to * perform the HTTP POST operation on (for example, /users). * @param[in] send_cb The callback function that should be called for * sending data as part of this request. * @param[in] recv_cb The callback function that should be called for * processing the request's response * * @return * - Stream ID (positive integer) if request setup is successful * - Negative error code if the request setup fails */ int sh2lib_do_post(struct sh2lib_handle *hd, const char *path, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb); /** * @brief Setup an HTTP PUT request stream * * This API sets up an HTTP PUT request to be sent out to the server. A new * stream is created for handling the request. Once the request is setup, the * API sh2lib_execute() must be called to actually perform the socket I/O with * the server. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @param[in] path Pointer to the string that contains the resource to * perform the HTTP PUT operation on (for example, /users). * @param[in] send_cb The callback function that should be called for * sending data as part of this request. * @param[in] recv_cb The callback function that should be called for * processing the request's response * * @return * - Stream ID (positive integer) if request setup is successful * - Negative error code if the request setup fails */ int sh2lib_do_put(struct sh2lib_handle *hd, const char *path, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb); /** * @brief Execute send/receive on an HTTP/2 connection * * While the API sh2lib_do_get(), sh2lib_do_post() setup the requests to be * initiated with the server, this API performs the actual data send/receive * operations on the HTTP/2 connection. The callback functions are accordingly * called during the processing of these requests. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle' * * @return * - 0 if it succeeds * - Negative error code from nghttp2 on failure */ int sh2lib_execute(struct sh2lib_handle *hd); /** * @brief Execute receive on an HTTP/2 connection * * While the API sh2lib_do_get(), sh2lib_do_post() setup the requests to be * initiated with the server, this API performs the actual data send/receive * operations on the HTTP/2 connection. The callback functions are accordingly * called during the processing of these requests. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle' * * @return * - 0 if it succeeds * - Negative error code from nghttp2 on failure */ int sh2lib_execute_recv(struct sh2lib_handle *hd); /** * @brief Execute send on an HTTP/2 connection * * While the API sh2lib_do_get(), sh2lib_do_post() setup the requests to be * initiated with the server, this API performs the actual data send/receive * operations on the HTTP/2 connection. The callback functions are accordingly * called during the processing of these requests. * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle' * * @return * - 0 if it succeeds * - Negative error code from nghttp2 on failure */ int sh2lib_execute_send(struct sh2lib_handle *hd); #define SH2LIB_MAKE_NV(NAME, VALUE) \ { \ (uint8_t *)NAME, (uint8_t *)VALUE, strlen(NAME), strlen(VALUE), \ NGHTTP2_NV_FLAG_NONE \ } /** * @brief Setup an HTTP GET request stream with custom name-value pairs * * For a simpler version of the API, please refer to sh2lib_do_get(). * * This API sets up an HTTP GET request to be sent out to the server. A new * stream is created for handling the request. Once the request is setup, the * API sh2lib_execute() must be called to actually perform the socket I/O with * the server. * * Please note that the following name value pairs MUST be a part of the request * - name:value * - ":method":"GET" * - ":scheme":"https" * - ":path": (for example, /users) * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @param[in] nva An array of name-value pairs that should be part of the request. * @param[in] nvlen The number of elements in the array pointed to by 'nva'. * @param[in] recv_cb The callback function that should be called for * processing the request's response * * @return * - Stream ID (positive integer) if request setup is successful * - Negative error code if the request setup fails */ int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb); /** * @brief Setup an HTTP PUT/POST request stream with custom name-value pairs * * For a simpler version of the API, please refer to sh2lib_do_put() or * sh2lib_do_post(). * * This API sets up an HTTP PUT/POST request to be sent out to the server. A new * stream is created for handling the request. Once the request is setup, the * API sh2lib_execute() must be called to actually perform the socket I/O with * the server. * * Please note that the following name value pairs MUST be a part of the request * - name:value * - ":method":"PUT" (or POST) * - ":scheme":"https" * - ":path": (for example, /users) * * @param[in] hd Pointer to a variable of the type 'struct sh2lib_handle'. * @param[in] nva An array of name-value pairs that should be part of the request. * @param[in] nvlen The number of elements in the array pointed to by 'nva'. * @param[in] send_cb The callback function that should be called for * sending data as part of this request. * @param[in] recv_cb The callback function that should be called for * processing the request's response * * @return * - Stream ID (positive integer) if request setup is successful * - Negative error code if the request setup fails */ int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_putpost_data_cb_t send_cb, sh2lib_frame_data_recv_cb_t recv_cb); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/CHANGELOG.md ================================================ # Changelog Versioning policy: see [VERSIONING.md](VERSIONING.md). From **v1.0.0** onward this component follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). ## [1.0.2] ### Fixes - BDL error logging: correct format specifiers for size fields on 64-bit Linux (avoids undefined behavior and wrong log output). - Linux NAND emulation: OOB markers and free-page detection aligned with the real hardware path. - Linux mmap: correct backing file path selection for the emulated image. - Linux mmap layout: on-disk stride accounts for interleaved OOB vs user-visible erase block size (bad-block handling, erase, and OOB clearing). ### Documentation - Linux mmap emulator config: note how backing file size maps to interleaved OOB and reported user capacity. ### Testing - Linux host tests: broader FTL and BDL coverage. - Shared buffer pattern helpers (including seeded patterns) for host tests and the in-tree test application. ## [1.0.1] - fix: fix incorrect flash geometry parameter for flash GD5F4GM8xExxG ## [1.0.0] ### Breaking Changes - FATFS integration has been moved to a separate `spi_nand_flash_fatfs` component. Projects using FATFS with NAND flash must add `spi_nand_flash_fatfs` as a dependency. FatFs on SPI NAND still requires the **legacy** init path: keep **`CONFIG_NAND_FLASH_ENABLE_BDL` disabled** for that use case (no FatFs-on-BDL support in this release). - When `CONFIG_NAND_FLASH_ENABLE_BDL` is enabled, the legacy `spi_nand_flash_init_device()` returns `ESP_ERR_NOT_SUPPORTED`. Use `spi_nand_flash_init_with_layers()` instead for block-device consumers. - `spi_nand_erase_chip()` performs a physical full-media erase. Previously, the driver incorrectly attempted to erase every block without checking bad-block markers, which could erase factory-marked bad blocks. It now skips bad blocks and physically erases only blocks that are not marked bad. ### New Features - Added Block Device Layer (BDL) support, available from ESP-IDF v6.0. Provides standard `esp_blockdev_t` interfaces for both raw flash access and wear-leveling. - Added `spi_nand_flash_init_with_layers()` API for layered block device initialization. - Added page-based API terminology (`read_page`, `write_page`, `get_page_count`, `get_page_size`) with backward-compatible sector aliases. ### Improvements - Refactor the component for improved structure, maintainability and readability - Sector API functions are now deprecated aliases for the page API equivalents. **Migration:** See **Migration Guide (0.x → 1.0.0)** in [layered_architecture.md](layered_architecture.md). ## [0.21.0] - fix: spi_nand_read fails in case buffer is not DMA aligned (https://github.com/espressif/idf-extra-components/issues/708) ## [0.20.0] - feat: added support for Gigadevice (GD5F1GM7xExxG) NAND flash ## [0.19.0] - fix: spi_nand_program_load fails in case buffer is not DMA aligned (https://github.com/espressif/idf-extra-components/issues/684) ## [0.18.0] - fix: Update esp_vfs_fat_register prototype to esp_vfs_fat_register_cfg to align with ESP-IDF v6.0. The cfg version is now the primary API and remains aliased for compatibility. ## [0.17.0] - fix: fix a compilation error caused by the missing freertos/FreeRTOS.h header when building with ESP-IDF v6.0 and later. - update: improvements to the lower-level APIs following updates in esp_driver_spi. ## [0.16.0] - fix: fix nand flash issue caused by data length unalignment on esp32p4 ## [0.15.0] - feat: added support for Gigadevice (GD5F2GM7xExxG) NAND flash ## [0.14.0] - feat: added support for XTX (XT26G08D) and Gigadevice (GD5F4GM8) NAND flash ## [0.13.0] - feat: added support for Zetta (ZD35Q1GC) NAND flash and Winbond (W25N02KVxxIR/U, W25N04KVxxIR/U) NAND flash chips. ## [0.12.0] - feat: added micron dual-plane nand flash chip (MT29F2G) support - fix: fixed build failure in host tests ## [0.11.0] - feat: added QIO mode support ## [0.10.0] - feat: added support for standard SPI mode (full-duplex) and DIO mode ## [0.9.0] - feat: added linux target support - fix: fixed memory alignment issue occurring on esp32c3 ## [0.8.0] - feat: added diagnostics application ## [0.7.0] - feat: exposed lower-level API and make it usable without dhara library ## [0.6.0] - feat: implemented CTRL_TRIM in fatfs diskio layer - feat: added data refresh threshold for ECC correction ## [0.5.0] - feat: added Kconfig option to verify write operations - feat: added support for Micron MT29F1G01ABAFDSF-AAT:F ## [0.4.1] - update: handled alignment and DMA requirements for work buffers ## [0.4.0] - fix: fixed memory leaks in test, add performance log ## [0.3.1] - fix: correct calloc call arguments for GCC14 compat ## [0.3.0] - fix: use 32 bit sector size and id ## [0.2.0] - feat: added support for Micron MT29F nand flash ## [0.1.0] - Initial release with basic NAND flash support based on dhara nand library - Support for Winbond, Alliance and Gigadevice devices - Added test application for the same ================================================ FILE: spi_nand_flash/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) set(reqs) set(inc include) set(priv_inc priv_include) set(srcs "src/nand.c" "src/dhara_glue.c" "src/nand_impl_wrap.c") if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" GREATER_EQUAL "6.0") list(APPEND reqs esp_blockdev) if(CONFIG_NAND_FLASH_ENABLE_BDL) list(APPEND srcs "src/nand_flash_blockdev.c" "src/nand_wl_blockdev.c") endif() endif() if(${target} STREQUAL "linux") list(APPEND srcs "src/nand_impl_linux.c" "src/nand_linux_mmap_emul.c" "src/spi_nand_flash_test_helpers.c") else() list(APPEND srcs "src/devices/nand_winbond.c" "src/devices/nand_gigadevice.c" "src/devices/nand_alliance.c" "src/devices/nand_micron.c" "src/devices/nand_zetta.c" "src/devices/nand_xtx.c" "src/nand_impl.c" "src/nand_diag_api.c" "src/spi_nand_flash_test_helpers.c" "src/spi_nand_oper.c") set(priv_reqs esp_mm) if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3") list(APPEND reqs esp_driver_spi) else() list(APPEND reqs driver) endif() endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${inc} PRIV_INCLUDE_DIRS ${priv_inc} REQUIRES ${reqs} PRIV_REQUIRES ${priv_reqs}) ================================================ FILE: spi_nand_flash/Kconfig ================================================ menu "SPI NAND Flash configuration" config NAND_FLASH_VERIFY_WRITE bool "Verify SPI NAND flash writes" default n help If this option is enabled, any time SPI NAND flash is written then the data will be read back and verified. This can catch hardware problems with SPI NAND flash, or flash which was not erased before verification. config NAND_FLASH_ENABLE_BDL bool "Enable Block Device Layer (BDL) support" depends on IDF_INIT_VERSION >= "6.0" default n help Enable Block Device Layer (BDL) support for SPI NAND Flash. This provides: - Standard esp_blockdev_t interface for layered architecture - Flash Block Device Layer (raw NAND flash access) - Wear-Leveling Block Device Layer (logical sector access with wear leveling) - Advanced layered API (spi_nand_flash_init_with_layers) When disabled, only the legacy API is available. When enabled, the legacy spi_nand_flash_init_device() is not available (returns ESP_ERR_NOT_SUPPORTED). Use spi_nand_flash_init_with_layers(). Note: This option requires ESP-IDF >= 6.0 (esp_blockdev component). Enabling this on ESP-IDF < 6.0 will result in a build error. config NAND_ENABLE_STATS bool "Host test statistics enabled" depends on IDF_TARGET_LINUX default n help This option enables gathering host test statistics and SPI NAND flash wear levelling simulation. endmenu ================================================ FILE: spi_nand_flash/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: spi_nand_flash/README.md ================================================ # SPI NAND Flash Driver This driver is designed to support SPI NAND Flash with ESP chipsets. This component incorporates the [dhara library](https://github.com/dlbeer/dhara), licenced under the [LICENCE](https://github.com/dlbeer/dhara/blob/master/LICENSE) ## About SPI NAND Flash SPI NAND Flash combines the benefits of NAND Flash technology with the simplicity of the SPI interface, providing an efficient and cost-effective solution for non-volatile data storage in diverse applications. Its versatility, reliability, and affordability make it a popular choice for many embedded systems and electronic devices. ### Key Features: * Non-Volatile Storage: SPI NAND Flash provides non-volatile storage, retaining data even when power is removed. This characteristic makes it ideal for storing critical system information and application data. * SPI Interface: The SPI protocol allows for straightforward communication between the microcontroller and the NAND Flash. This simplicity in interface design facilitates easy integration into embedded systems. * Cost-Effective: SPI NAND Flash offers a cost-effective storage solution, making it attractive for applications with budget constraints. Its competitive pricing makes it a viable option for a wide range of projects. * High Density: NAND Flash technology inherently supports high-density storage, enabling the storage of large amounts of data in a compact form factor. This is advantageous for applications requiring extensive data storage in constrained spaces. * Fast Read/Write Operations: The SPI interface enables reasonably fast read and write operations, making it suitable for applications where data access speed is crucial. ### Implementation Architecture The component now features a layered architecture for better maintainability and modularity: ```mermaid graph TD A[Application/FS] --> B[SPI NAND Flash API] B --> C[NAND Wear-Leveling BDL] C --> D[NAND Flash BDL] D --> E{Target} E -->|ESP Chips| F[SPI NAND Operations] F --> G[Hardware via SPI] E -->|Linux| H[NAND Emulation] H --> I[Memory-Mapped File] subgraph "Layered Architecture" B["spi_nand_flash.h
(Backward Compatible)"] C["nand_wl_bdl
(Wear Leveling)"] D["nand_flash_bdl
(Physical Flash)"] end subgraph "Hardware Layer" F["spi_nand_oper
(SPI Commands)"] H["nand_linux_mmap_emul
(Host Test)"] end ``` **Key Benefits:** - **Backward Compatible**: Existing code works unchanged; sector-named APIs are retained as aliases of the page API - **Page terminology**: Public API uses *page* (read_page, write_page, get_page_count, get_page_size) to align with NAND flash; sector names remain for compatibility - **Modular Design**: Clear separation between wear-leveling and flash management - **Enhanced Features**: Direct access to flash and wear-leveling layers **📖 Architecture and migration:** For layered architecture, BDL usage, API details, and **upgrading from 0.x to 1.0.0** (including the FATFS component split and legacy vs BDL init), see: - [Layered Architecture Guide](layered_architecture.md) — includes the **Migration Guide (0.x → 1.0.0)** section ## ESP-IDF version and API modes - **ESP-IDF 5.0–5.x:** Use the **legacy** API only (`spi_nand_flash_init_device()`, page/sector helpers). The BDL Kconfig option is not available on these IDF versions. Component **1.0.0** remains compatible with this range when BDL is not used. - **ESP-IDF 6.0 and newer:** You may enable **`CONFIG_NAND_FLASH_ENABLE_BDL`** and use **`spi_nand_flash_init_with_layers()`** with **`esp_blockdev_t`** for block-device consumers. If BDL is **disabled**, the legacy API behaves as on older IDF versions. **Linux mmap emulation (host tests):** On the Linux target, the driver can use a memory-mapped backing file instead of SPI hardware. Configuration examples and how to build the host test app live in [`host_test/README.md`](host_test/README.md). ## Supported SPI NAND Flash chips At present, `spi_nand_flash` component is compatible with the chips produced by the following manufacturers and and their respective model numbers: * Winbond - W25N01GVxxxG/T/R, W25N512GVxIG/IT, W25N512GWxxR/T, W25N01JWxxxG/T, W25N02KVxxIR/U, W25N04KVxxIR/U * Gigadevice - GD5F1GQ5UExxG, GD5F1GQ5RExxG, GD5F2GQ5UExxG, GD5F2GQ5RExxG, GD5F2GM7xExxG, GD5F4GQ6UExxG, GD5F4GQ6RExxG, GD5F4GM8xExxG, GD5F1GM7xExxG * Alliance - AS5F31G04SND-08LIN, AS5F32G04SND-08LIN, AS5F12G04SND-10LIN, AS5F34G04SND-08LIN, AS5F14G04SND-10LIN, AS5F38G04SND-08LIN, AS5F18G04SND-10LIN * Micron - MT29F4G01ABAFDWB, MT29F1G01ABAFDSF-AAT:F, MT29F2G01ABAGDWB-IT:G * Zetta - ZD35Q1GC * XTX - XT26G08D ## FATFS Integration For FATFS filesystem support, use the separate [`spi_nand_flash_fatfs`](../spi_nand_flash_fatfs) component: - Provides diskio adapters and VFS mount helpers for the **legacy** `spi_nand_flash_device_t` path only - **Do not enable BDL** if you use this FatFs stack on the same NAND instance (see [`spi_nand_flash_fatfs/README.md`](../spi_nand_flash_fatfs/README.md)) ## Troubleshooting To verify SPI NAND Flash writes, enable the `NAND_FLASH_VERIFY_WRITE` option in menuconfig. When this option is enabled, every time data is written to the SPI NAND Flash, it will be read back and verified. This helps in identifying hardware issues with the SPI NAND Flash. To configure the project for this setting, follow these steps: ``` idf.py menuconfig -> Component config -> SPI NAND Flash configuration -> NAND_FLASH_VERIFY_WRITE ``` Run `idf.py -p PORT flash monitor` and if the write verification fails, an error log will be printed to the console. ================================================ FILE: spi_nand_flash/VERSIONING.md ================================================ # Versioning From **v1.0.0** onward, this component follows **[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)**. The published version is the `version` field in [`idf_component.yml`](idf_component.yml). ## Summary | Level | When to bump | |--------|----------------| | **MAJOR** | Breaking changes for integrators: removed or incompatible APIs, required migration, or other incompatible contract changes called out under **Breaking Changes** in [`CHANGELOG.md`](CHANGELOG.md). | | **MINOR** | Backward-compatible additions: new APIs, new supported devices or modes, new optional Kconfig (default preserves prior behavior). | | **PATCH** | Backward-compatible fixes only: bug fixes, documentation corrections, tests, or build/CI changes that do not widen the public API or break existing callers. | Pre-**1.0.0** releases (`0.x.y` in the changelog) did not consistently apply this split; treat them as historical versioning. Use **1.x.y** and the rules above for current releases. ================================================ FILE: spi_nand_flash/host_test/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(nand_flash_host_test) ================================================ FILE: spi_nand_flash/host_test/README.md ================================================ | Supported Targets | Linux | | ----------------- | ----- | # Host Test for SPI NAND Flash Emulation Linux host tests use the mmap-backed NAND emulator (`nand_linux_mmap_emul`). Full field semantics (including how OOB is interleaved in the backing file and how that affects usable capacity) are documented on `nand_file_mmap_emul_config_t` in [`nand_linux_mmap_emul.h`](../include/nand_linux_mmap_emul.h). ## NAND Flash Emulation Configuration The NAND flash emulation can be configured using the `nand_file_mmap_emul_config_t` structure: ```c #include "nand_linux_mmap_emul.h" // Configuration structure for NAND emulation nand_file_mmap_emul_config_t cfg = { .flash_file_name = "", // Empty string for temporary file, or specify path .flash_file_size = EMULATED_NAND_SIZE, // Default is 128MB .keep_dump = true // true to keep file after tests }; ``` ### Configuration Options: 1. **flash_file_name**: - Empty string ("") - Creates temporary file with pattern "/tmp/idf-nand-XXXXXX" - Custom path - Creates file at specified location - Maximum length: 256 characters 2. **flash_file_size**: - Default: `EMULATED_NAND_SIZE` (128MB) - Must be a multiple of the chip's **user-visible** erase-block size (`page_size * pages_per_block`). The on-disk image is larger per page because OOB bytes are interleaved after each page; see the struct documentation in `nand_linux_mmap_emul.h`. 3. **keep_dump**: - true: Keeps the memory-mapped file on disk after testing (for debugging or data persistence) - false: Removes the backing file on cleanup ### Usage Example: #### Option 1: Direct Device API ```c #include "nand_linux_mmap_emul.h" #include "spi_nand_flash.h" // Initialize with custom settings nand_file_mmap_emul_config_t cfg = { .flash_file_name = "/tmp/my_nand.bin", .flash_file_size = 50 * 1024 * 1024, // 50MB .keep_dump = false }; spi_nand_flash_config_t nand_flash_config = {&cfg, 0, SPI_NAND_IO_MODE_SIO, 0}; // Initialize NAND flash with emulation spi_nand_flash_device_t *handle; spi_nand_flash_init_device(&nand_flash_config, &handle); // Use direct NAND operations (page API preferred; sector API is also supported) uint32_t page_size; spi_nand_flash_get_page_size(handle, &page_size); uint8_t *buffer = malloc(page_size); uint32_t page_id = 0; spi_nand_flash_read_page(handle, buffer, page_id); spi_nand_flash_write_page(handle, buffer, page_id); // Cleanup spi_nand_flash_deinit_device(handle); ``` #### Option 2: Block Device API Requires **ESP-IDF 6.0+** with **`CONFIG_NAND_FLASH_ENABLE_BDL`** enabled (same as on-target BDL tests). ```c #include "nand_linux_mmap_emul.h" #include "spi_nand_flash.h" #include "esp_nand_blockdev.h" // Initialize with block device interface nand_file_mmap_emul_config_t cfg = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&cfg, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t nand_bdl; // Create Flash Block Device Layer nand_flash_get_blockdev(&nand_flash_config, &nand_bdl); // Use block device operations uint32_t page_size = nand_bdl->geometry.read_size; // BDL geometry uses page size nand_bdl->ops->read(nand_bdl, buffer, page_size, offset, size); nand_bdl->ops->write(nand_bdl, buffer, offset, size); // Cleanup nand_bdl->ops->release(nand_bdl); ``` ## Building and running From this directory (with ESP-IDF environment loaded): ```bash idf.py --preview set-target linux idf.py build monitor ``` Catch2-based suites are selected from `test_app_main.cpp` according to Kconfig (see the **Linux Host Testing** section in [`layered_architecture.md`](../layered_architecture.md)): legacy raw/device tests vs BDL-enabled sources such as `test_nand_flash_bdl.cpp` and `test_nand_flash_ftl.cpp`. ================================================ FILE: spi_nand_flash/host_test/main/CMakeLists.txt ================================================ set(src "test_app_main.cpp") if(CONFIG_NAND_FLASH_ENABLE_BDL) list(APPEND src "test_nand_flash_bdl.cpp") else() list(APPEND src "test_nand_flash.cpp" "test_nand_flash_ftl.cpp") endif() idf_component_register(SRCS ${src} WHOLE_ARCHIVE ) target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) ================================================ FILE: spi_nand_flash/host_test/main/idf_component.yml ================================================ dependencies: espressif/catch2: "^3.4.0" espressif/spi_nand_flash: version: '*' override_path: '../../' ================================================ FILE: spi_nand_flash/host_test/main/test_app_main.cpp ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include extern "C" void app_main(void) { int argc = 1; const char *argv[2] = { "target_test_main", NULL }; auto result = Catch::Session().run(argc, argv); if (result != 0) { printf("Test failed with result %d\n", result); } else { printf("Test passed.\n"); } fflush(stdout); exit(result); } ================================================ FILE: spi_nand_flash/host_test/main/test_nand_flash.cpp ================================================ /* * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "spi_nand_flash.h" #include "spi_nand_flash_test_helpers.h" #include "nand_linux_mmap_emul.h" #include "nand_private/nand_impl_wrap.h" #include TEST_CASE("verify mark_bad_block works", "[spi_nand_flash]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, true}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; spi_nand_flash_device_t *device_handle; REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK); uint32_t block_num; REQUIRE(spi_nand_flash_get_block_num(device_handle, &block_num) == 0); uint32_t test_block = 15; REQUIRE((test_block < block_num) == true); bool is_bad_status = false; // Verify if test_block is not bad block REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0); REQUIRE(is_bad_status == false); // mark test_block as a bad block REQUIRE(nand_wrap_mark_bad(device_handle, test_block) == 0); // Verify if test_block is marked as bad block REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0); REQUIRE(is_bad_status == true); spi_nand_flash_deinit_device(device_handle); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works", "[spi_nand_flash]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; spi_nand_flash_device_t *device_handle; REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK); uint32_t sector_num, sector_size, block_size; REQUIRE(spi_nand_flash_get_capacity(device_handle, §or_num) == 0); REQUIRE(spi_nand_flash_get_sector_size(device_handle, §or_size) == 0); REQUIRE(spi_nand_flash_get_block_size(device_handle, &block_size) == 0); uint8_t *pattern_buf = (uint8_t *)malloc(sector_size); REQUIRE(pattern_buf != NULL); uint8_t *temp_buf = (uint8_t *)malloc(sector_size); REQUIRE(temp_buf != NULL); spi_nand_flash_fill_buffer(pattern_buf, sector_size / sizeof(uint32_t)); bool is_page_free = true; uint32_t test_block = 20; uint32_t test_page = test_block * (block_size / sector_size); //(block_num * pages_per_block) uint32_t dst_page = test_page + 1; REQUIRE((test_page < sector_num) == true); // Verify if test_page is free REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0); REQUIRE(is_page_free == true); // Write/program test_page REQUIRE(nand_wrap_prog(device_handle, test_page, pattern_buf) == 0); // Verify if test_page is used/programmed REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0); REQUIRE(is_page_free == false); REQUIRE(nand_wrap_read(device_handle, test_page, 0, sector_size, temp_buf) == 0); REQUIRE(spi_nand_flash_check_buffer(temp_buf, sector_size / sizeof(uint32_t)) == 0); REQUIRE(nand_wrap_copy(device_handle, test_page, dst_page) == 0); REQUIRE(nand_wrap_read(device_handle, dst_page, 0, sector_size, temp_buf) == 0); REQUIRE(spi_nand_flash_check_buffer(temp_buf, sector_size / sizeof(uint32_t)) == 0); free(pattern_buf); free(temp_buf); spi_nand_flash_deinit_device(device_handle); } ================================================ FILE: spi_nand_flash/host_test/main/test_nand_flash_bdl.cpp ================================================ /* * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "spi_nand_flash.h" #include "spi_nand_flash_test_helpers.h" #include "nand_linux_mmap_emul.h" #include "esp_blockdev.h" #include "esp_nand_blockdev.h" #include /* Linux mmap file: each page is k_page data + k_oob, so the file uses k_ppb * (k_page + k_oob) * bytes per erase block. chip.block_size is k_ppb * k_page (same as real chips); num_blocks * is file_size / (k_ppb * (k_page + k_oob)). BDL disk_size must be num_blocks * chip.block_size. * Regression: storing the file stride in chip.block_size broke BDL erase decode. */ TEST_CASE("BDL geometry matches Linux mmap file and chip.block_size", "[spi_nand_flash][bdl]") { constexpr uint32_t k_file_bytes = 16u * 1024u * 1024u; constexpr uint32_t k_page = 2048u; constexpr uint32_t k_oob = 64u; constexpr uint32_t k_ppb = 64u; const uint32_t file_bytes_per_physical_block = k_ppb * (k_page + k_oob); const uint32_t bdl_bytes_per_erase_block = k_ppb * k_page; const uint32_t num_physical_blocks = k_file_bytes / file_bytes_per_physical_block; const uint64_t expected_disk_size = (uint64_t)num_physical_blocks * bdl_bytes_per_erase_block; nand_file_mmap_emul_config_t conf = {"", k_file_bytes, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); REQUIRE(bdl->geometry.write_size == k_page); REQUIRE(bdl->geometry.erase_size == bdl_bytes_per_erase_block); REQUIRE(bdl->geometry.disk_size == expected_disk_size); bdl->ops->release(bdl); } TEST_CASE("verify mark_bad_block works with bdl interface", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, true}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t nand_bdl; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &nand_bdl) == 0); uint32_t block_size = nand_bdl->geometry.erase_size; uint32_t block_num = nand_bdl->geometry.disk_size / block_size; uint32_t test_block = 15; REQUIRE((test_block < block_num) == true); REQUIRE(nand_bdl->ops->erase(nand_bdl, test_block * block_size, block_size) == 0); // Verify if test_block is not bad block esp_blockdev_cmd_arg_is_bad_block_t bad_block_status = {test_block, false}; REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &bad_block_status) == 0); REQUIRE(bad_block_status.status == false); // mark test_block as a bad block uint32_t block = test_block; REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK, &block) == 0); // Verify if test_block is marked as bad block REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &bad_block_status) == 0); REQUIRE(bad_block_status.status == true); nand_bdl->ops->release(nand_bdl); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works with bdl interface", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t nand_bdl; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &nand_bdl) == 0); uint32_t block_size = nand_bdl->geometry.erase_size; uint32_t sector_size = nand_bdl->geometry.write_size; uint32_t sector_num = nand_bdl->geometry.disk_size / sector_size; uint8_t *pattern_buf = (uint8_t *)malloc(sector_size); REQUIRE(pattern_buf != NULL); uint8_t *temp_buf = (uint8_t *)malloc(sector_size); REQUIRE(temp_buf != NULL); spi_nand_flash_fill_buffer(pattern_buf, sector_size / sizeof(uint32_t)); uint32_t test_block = 20; uint32_t test_page = test_block * (block_size / sector_size); //(block_num * pages_per_block) REQUIRE((test_page < sector_num) == true); // Verify if test_page is free esp_blockdev_cmd_arg_is_free_page_t page_free_status = {test_page, true}; REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &page_free_status) == 0); REQUIRE(page_free_status.status == true); // Write/program test_page REQUIRE(nand_bdl->ops->write(nand_bdl, pattern_buf, test_page * sector_size, sector_size) == 0); // Verify if test_page is used/programmed REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &page_free_status) == 0); REQUIRE(page_free_status.status == false); REQUIRE(nand_bdl->ops->read(nand_bdl, temp_buf, sector_size, test_page * sector_size, sector_size) == 0); REQUIRE(spi_nand_flash_check_buffer(temp_buf, sector_size / sizeof(uint32_t)) == 0); free(pattern_buf); free(temp_buf); nand_bdl->ops->release(nand_bdl); } TEST_CASE("WL BDL on host: create, geometry, write/read, release", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); REQUIRE(flash_bdl != nullptr); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); REQUIRE(wl_bdl != nullptr); REQUIRE(wl_bdl->geometry.disk_size > 0); REQUIRE(wl_bdl->geometry.read_size > 0); REQUIRE(wl_bdl->geometry.write_size > 0); REQUIRE(wl_bdl->geometry.erase_size > 0); uint32_t page_size = wl_bdl->geometry.write_size; REQUIRE(page_size > 0); uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); REQUIRE(num_pages > 0); uint8_t *pattern_buf = (uint8_t *)malloc(page_size); uint8_t *read_buf = (uint8_t *)malloc(page_size); REQUIRE(pattern_buf != nullptr); REQUIRE(read_buf != nullptr); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, pattern_buf, 0, page_size) == ESP_OK); memset(read_buf, 0, page_size); REQUIRE(wl_bdl->ops->read(wl_bdl, read_buf, page_size, 0, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(read_buf, page_size / sizeof(uint32_t)) == 0); free(pattern_buf); free(read_buf); wl_bdl->ops->release(wl_bdl); } TEST_CASE("Flash BDL geometry and ops on host", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); REQUIRE(bdl != nullptr); uint32_t block_size = bdl->geometry.erase_size; uint32_t num_blocks = (uint32_t)(bdl->geometry.disk_size / block_size); REQUIRE(bdl->geometry.disk_size == (uint64_t)num_blocks * block_size); REQUIRE(bdl->geometry.read_size == bdl->geometry.write_size); REQUIRE(bdl->geometry.read_size > 0); REQUIRE(bdl->geometry.erase_size > 0); REQUIRE(bdl->ops != nullptr); REQUIRE(bdl->ops->read != nullptr); REQUIRE(bdl->ops->write != nullptr); REQUIRE(bdl->ops->erase != nullptr); REQUIRE(bdl->ops->ioctl != nullptr); REQUIRE(bdl->ops->release != nullptr); bdl->ops->release(bdl); } TEST_CASE("Flash BDL COPY_PAGE ioctl on host", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t nand_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &nand_bdl) == ESP_OK); REQUIRE(nand_bdl != nullptr); uint32_t block_size = nand_bdl->geometry.erase_size; uint32_t page_size = nand_bdl->geometry.write_size; uint32_t pages_per_block = block_size / page_size; uint32_t src_page = 5 * pages_per_block; uint32_t dst_page = 6 * pages_per_block; REQUIRE(nand_bdl->ops->erase(nand_bdl, src_page * (uint64_t)page_size, block_size) == ESP_OK); uint8_t *pattern_buf = (uint8_t *)malloc(page_size); uint8_t *read_buf = (uint8_t *)malloc(page_size); REQUIRE(pattern_buf != nullptr); REQUIRE(read_buf != nullptr); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); REQUIRE(nand_bdl->ops->write(nand_bdl, pattern_buf, src_page * (uint64_t)page_size, page_size) == ESP_OK); esp_blockdev_cmd_arg_copy_page_t copy_cmd = { .src_page = src_page, .dst_page = dst_page }; REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd) == ESP_OK); memset(read_buf, 0, page_size); REQUIRE(nand_bdl->ops->read(nand_bdl, read_buf, page_size, dst_page * (uint64_t)page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(read_buf, page_size / sizeof(uint32_t)) == 0); free(pattern_buf); free(read_buf); nand_bdl->ops->release(nand_bdl); } TEST_CASE("Flash BDL GET_NAND_FLASH_INFO and GET_BAD_BLOCKS_COUNT on host", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t nand_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &nand_bdl) == ESP_OK); REQUIRE(nand_bdl != nullptr); esp_blockdev_cmd_arg_nand_flash_info_t flash_info; memset(&flash_info, 0, sizeof(flash_info)); REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, &flash_info) == ESP_OK); REQUIRE(strnlen((const char *)flash_info.device_info.chip_name, sizeof(flash_info.device_info.chip_name)) > 0); uint32_t total_blocks = nand_bdl->geometry.disk_size / nand_bdl->geometry.erase_size; REQUIRE(flash_info.geometry.num_blocks == total_blocks); uint32_t bad_block_count = 0xFFFF; REQUIRE(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT, &bad_block_count) == ESP_OK); REQUIRE(bad_block_count <= total_blocks); nand_bdl->ops->release(nand_bdl); } TEST_CASE("Error path: nand_flash_get_blockdev NULL/invalid args", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 50 * 1024 * 1024, false}; spi_nand_flash_config_t config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t out = nullptr; REQUIRE(nand_flash_get_blockdev(nullptr, &out) == ESP_ERR_INVALID_ARG); REQUIRE(out == nullptr); REQUIRE(nand_flash_get_blockdev(&config, nullptr) == ESP_ERR_INVALID_ARG); } TEST_CASE("Release and no use-after-free: create, release, create again, minimal r/w", "[spi_nand_flash][bdl]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); REQUIRE(bdl != nullptr); bdl->ops->release(bdl); REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); REQUIRE(bdl != nullptr); uint32_t page_size = bdl->geometry.write_size; uint8_t *buf = (uint8_t *)malloc(page_size); REQUIRE(buf != nullptr); spi_nand_flash_fill_buffer(buf, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, buf, 0, page_size) == ESP_OK); memset(buf, 0, page_size); REQUIRE(bdl->ops->read(bdl, buf, page_size, 0, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(buf, page_size / sizeof(uint32_t)) == 0); free(buf); bdl->ops->release(bdl); } TEST_CASE("Flash BDL last physical page write/read", "[spi_nand_flash][bdl][raw]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; const uint64_t disk_size = bdl->geometry.disk_size; const uint32_t num_pages = (uint32_t)(disk_size / page_size); const uint32_t pages_per_block = block_size / page_size; const uint32_t last_page = num_pages - 1u; const uint32_t last_block = last_page / pages_per_block; const uint64_t block_addr = (uint64_t)last_block * block_size; REQUIRE(bdl->ops->erase(bdl, block_addr, block_size) == ESP_OK); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); const uint64_t last_off = (uint64_t)last_page * page_size; REQUIRE(bdl->ops->write(bdl, w, last_off, page_size) == ESP_OK); memset(r, 0, page_size); REQUIRE(bdl->ops->read(bdl, r, page_size, last_off, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); free(w); free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL 16 MiB full erase+program sweep then read-back all pages", "[spi_nand_flash][bdl][raw][sequential]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; const uint64_t disk_size = bdl->geometry.disk_size; const uint32_t num_blocks = (uint32_t)(disk_size / block_size); const uint32_t pages_per_block = block_size / page_size; for (uint32_t b = 0; b < num_blocks; b++) { const uint64_t ba = (uint64_t)b * block_size; REQUIRE(bdl->ops->erase(bdl, ba, block_size) == ESP_OK); for (uint32_t i = 0; i < pages_per_block; i++) { const uint32_t page = b * pages_per_block + i; uint8_t *w = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, w, (uint64_t)page * page_size, page_size) == ESP_OK); free(w); } } const uint32_t num_pages = (uint32_t)(disk_size / page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(r != nullptr); for (uint32_t page = 0; page < num_pages; page++) { memset(r, 0, page_size); REQUIRE(bdl->ops->read(bdl, r, page_size, (uint64_t)page * page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); } free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL overwrite same physical page many times", "[spi_nand_flash][bdl][raw][stress]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; REQUIRE(bdl->ops->erase(bdl, 0, block_size) == ESP_OK); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); constexpr int kRounds = 150; for (int i = 0; i < kRounds; i++) { spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, w, 0, page_size) == ESP_OK); } spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->read(bdl, r, page_size, 0, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); free(w); free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL multi-page write in one call", "[spi_nand_flash][bdl][raw]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; const uint32_t n = 4; REQUIRE(block_size >= n * page_size); REQUIRE(bdl->ops->erase(bdl, 0, block_size) == ESP_OK); const size_t total = (size_t)n * page_size; uint8_t *w = (uint8_t *)malloc(total); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); for (uint32_t p = 0; p < n; p++) { spi_nand_flash_fill_buffer(w + (size_t)p * page_size, page_size / sizeof(uint32_t)); } REQUIRE(bdl->ops->write(bdl, w, 0, total) == ESP_OK); for (uint32_t p = 0; p < n; p++) { memset(r, 0, page_size); REQUIRE(bdl->ops->read(bdl, r, page_size, (uint64_t)p * page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); } free(w); free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL sub-page read and misaligned write rejected", "[spi_nand_flash][bdl][raw]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; REQUIRE(bdl->ops->erase(bdl, 0, block_size) == ESP_OK); uint8_t *full = (uint8_t *)malloc(page_size); uint8_t *slice = (uint8_t *)malloc(page_size); REQUIRE(full != nullptr); REQUIRE(slice != nullptr); spi_nand_flash_fill_buffer(full, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, full, 0, page_size) == ESP_OK); const size_t off = 64; const size_t len = page_size - 128; REQUIRE(off + len <= page_size); memset(slice, 0, len); REQUIRE(bdl->ops->read(bdl, slice, len, off, len) == ESP_OK); REQUIRE(memcmp(slice, full + off, len) == 0); REQUIRE(bdl->ops->write(bdl, full, 1, page_size) == ESP_ERR_INVALID_SIZE); free(full); free(slice); bdl->ops->release(bdl); } TEST_CASE("Flash BDL COPY_PAGE same page is idempotent", "[spi_nand_flash][bdl][raw][copy]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; const uint32_t page = 3 * (block_size / page_size); REQUIRE(bdl->ops->erase(bdl, (page / (block_size / page_size)) * (uint64_t)block_size, block_size) == ESP_OK); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); const uint64_t addr = (uint64_t)page * page_size; REQUIRE(bdl->ops->write(bdl, w, addr, page_size) == ESP_OK); esp_blockdev_cmd_arg_copy_page_t copy_cmd = {.src_page = page, .dst_page = page}; REQUIRE(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd) == ESP_OK); memset(r, 0, page_size); REQUIRE(bdl->ops->read(bdl, r, page_size, addr, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); free(w); free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL COPY_PAGE does not alter source page", "[spi_nand_flash][bdl][raw][copy]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; const uint32_t ppb = block_size / page_size; const uint32_t src_page = 2u * ppb; const uint32_t dst_page = 2u * ppb + 1u; REQUIRE(bdl->ops->erase(bdl, 2u * (uint64_t)block_size, block_size) == ESP_OK); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, w, (uint64_t)src_page * page_size, page_size) == ESP_OK); esp_blockdev_cmd_arg_copy_page_t copy_cmd = {.src_page = src_page, .dst_page = dst_page}; REQUIRE(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd) == ESP_OK); memset(r, 0, page_size); REQUIRE(bdl->ops->read(bdl, r, page_size, (uint64_t)src_page * page_size, page_size) == ESP_OK); REQUIRE(memcmp(w, r, page_size) == 0); free(w); free(r); bdl->ops->release(bdl); } TEST_CASE("Flash BDL invalid write/read geometry returns error", "[spi_nand_flash][bdl][raw]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint64_t disk = bdl->geometry.disk_size; uint8_t *buf = (uint8_t *)malloc(page_size * 2); REQUIRE(buf != nullptr); spi_nand_flash_fill_buffer(buf, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, buf, page_size / 2u, page_size) == ESP_ERR_INVALID_SIZE); if (page_size >= 512u) { REQUIRE(bdl->ops->write(bdl, buf, 0, page_size / 2u) == ESP_ERR_INVALID_SIZE); } memset(buf, 0, page_size); REQUIRE(bdl->ops->read(bdl, buf, page_size, disk, page_size) == ESP_ERR_INVALID_SIZE); REQUIRE(bdl->ops->write(bdl, buf, disk, page_size) == ESP_ERR_INVALID_SIZE); free(buf); bdl->ops->release(bdl); } TEST_CASE("Flash BDL GET_PAGE_ECC_STATUS and GET_ECC_STATS (small image)", "[spi_nand_flash][bdl][raw]") { nand_file_mmap_emul_config_t conf = {"", 4 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &bdl) == ESP_OK); const uint32_t page_size = bdl->geometry.write_size; const uint32_t block_size = bdl->geometry.erase_size; REQUIRE(bdl->ops->erase(bdl, 0, block_size) == ESP_OK); uint8_t *buf = (uint8_t *)malloc(page_size); REQUIRE(buf != nullptr); spi_nand_flash_fill_buffer(buf, page_size / sizeof(uint32_t)); REQUIRE(bdl->ops->write(bdl, buf, 0, page_size) == ESP_OK); esp_blockdev_cmd_arg_ecc_status_t ecc_page = {}; ecc_page.page_num = 0; REQUIRE(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS, &ecc_page) == ESP_OK); esp_blockdev_cmd_arg_ecc_stats_t ecc_stats = {}; REQUIRE(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_ECC_STATS, &ecc_stats) == ESP_OK); free(buf); bdl->ops->release(bdl); } TEST_CASE("WL BDL sync after write preserves data", "[spi_nand_flash][bdl][wl]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); const uint32_t page_size = wl_bdl->geometry.write_size; uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, w, page_size, page_size) == ESP_OK); REQUIRE(wl_bdl->ops->sync(wl_bdl) == ESP_OK); memset(r, 0, page_size); REQUIRE(wl_bdl->ops->read(wl_bdl, r, page_size, page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); free(w); free(r); wl_bdl->ops->release(wl_bdl); } TEST_CASE("WL BDL MARK_DELETED then rewrite same logical page", "[spi_nand_flash][bdl][wl]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); const uint32_t page_size = wl_bdl->geometry.write_size; const uint32_t page_index = 5; const uint64_t off = (uint64_t)page_index * page_size; uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); memset(w, 0x22, page_size); REQUIRE(wl_bdl->ops->write(wl_bdl, w, off, page_size) == ESP_OK); esp_blockdev_cmd_arg_erase_t trim_arg = {.start_addr = off, .erase_len = page_size}; REQUIRE(wl_bdl->ops->ioctl(wl_bdl, ESP_BLOCKDEV_CMD_MARK_DELETED, &trim_arg) == ESP_OK); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, w, off, page_size) == ESP_OK); memset(r, 0, page_size); REQUIRE(wl_bdl->ops->read(wl_bdl, r, page_size, off, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); free(w); free(r); wl_bdl->ops->release(wl_bdl); } TEST_CASE("WL BDL MARK_DELETED misaligned range returns ESP_ERR_INVALID_SIZE", "[spi_nand_flash][bdl][wl]") { nand_file_mmap_emul_config_t conf = {"", 20 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); const uint32_t page_size = wl_bdl->geometry.write_size; esp_blockdev_cmd_arg_erase_t bad = {.start_addr = 1, .erase_len = page_size}; REQUIRE(wl_bdl->ops->ioctl(wl_bdl, ESP_BLOCKDEV_CMD_MARK_DELETED, &bad) == ESP_ERR_INVALID_SIZE); wl_bdl->ops->release(wl_bdl); } TEST_CASE("WL BDL 16 MiB sequential logical page fill and read-back", "[spi_nand_flash][bdl][wl][sequential]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); const uint32_t page_size = wl_bdl->geometry.write_size; const uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); REQUIRE(wl_bdl->geometry.disk_size == (uint64_t)num_pages * page_size); uint8_t *w = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); for (uint32_t p = 0; p < num_pages; p++) { spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, w, (uint64_t)p * page_size, page_size) == ESP_OK); } REQUIRE(wl_bdl->ops->sync(wl_bdl) == ESP_OK); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(r != nullptr); for (uint32_t p = 0; p < num_pages; p++) { memset(r, 0, page_size); REQUIRE(wl_bdl->ops->read(wl_bdl, r, page_size, (uint64_t)p * page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); } free(w); free(r); wl_bdl->ops->release(wl_bdl); } TEST_CASE("WL BDL hot-set random writes then read-back", "[spi_nand_flash][bdl][wl][stress]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); constexpr uint32_t kHotSetSize = 30u; constexpr uint32_t kTotalWrites = 1200u; const uint32_t page_size = wl_bdl->geometry.write_size; const uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); REQUIRE(num_pages >= kHotSetSize); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); bool written[kHotSetSize] = {}; std::srand(0xDEADBEEFu); for (uint32_t op = 0; op < kTotalWrites; op++) { const uint32_t lp = (uint32_t)((unsigned)std::rand() % kHotSetSize); spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, w, (uint64_t)lp * page_size, page_size) == ESP_OK); written[lp] = true; } REQUIRE(wl_bdl->ops->sync(wl_bdl) == ESP_OK); for (uint32_t s = 0; s < kHotSetSize; s++) { if (!written[s]) { continue; } REQUIRE(wl_bdl->ops->read(wl_bdl, r, page_size, (uint64_t)s * page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); } free(w); free(r); wl_bdl->ops->release(wl_bdl); } TEST_CASE("WL BDL hot-set with interleaved MARK_DELETED", "[spi_nand_flash][bdl][wl][stress][trim]") { nand_file_mmap_emul_config_t conf = {"", 16 * 1024 * 1024, false}; spi_nand_flash_config_t nand_flash_config = {&conf, 0, SPI_NAND_IO_MODE_SIO, 0}; esp_blockdev_handle_t flash_bdl = nullptr; REQUIRE(nand_flash_get_blockdev(&nand_flash_config, &flash_bdl) == ESP_OK); esp_blockdev_handle_t wl_bdl = nullptr; REQUIRE(spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl) == ESP_OK); constexpr uint32_t kHotSetSize = 30u; constexpr uint32_t kTotalWrites = 1200u; const uint32_t page_size = wl_bdl->geometry.write_size; const uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); REQUIRE(num_pages >= kHotSetSize); uint8_t *w = (uint8_t *)malloc(page_size); uint8_t *r = (uint8_t *)malloc(page_size); REQUIRE(w != nullptr); REQUIRE(r != nullptr); bool trimmed[kHotSetSize] = {}; bool written[kHotSetSize] = {}; std::srand(0xCAFEBABEu); for (uint32_t op = 0; op < kTotalWrites; op++) { const uint32_t lp = (uint32_t)((unsigned)std::rand() % kHotSetSize); if (op % 100u == 99u) { const uint32_t t = (uint32_t)((unsigned)std::rand() % kHotSetSize); esp_blockdev_cmd_arg_erase_t trim_arg = { .start_addr = (uint64_t)t * page_size, .erase_len = page_size, }; if (wl_bdl->ops->ioctl(wl_bdl, ESP_BLOCKDEV_CMD_MARK_DELETED, &trim_arg) == ESP_OK) { trimmed[t] = true; } } spi_nand_flash_fill_buffer(w, page_size / sizeof(uint32_t)); REQUIRE(wl_bdl->ops->write(wl_bdl, w, (uint64_t)lp * page_size, page_size) == ESP_OK); trimmed[lp] = false; written[lp] = true; } REQUIRE(wl_bdl->ops->sync(wl_bdl) == ESP_OK); for (uint32_t s = 0; s < kHotSetSize; s++) { if (trimmed[s] || !written[s]) { continue; } REQUIRE(wl_bdl->ops->read(wl_bdl, r, page_size, (uint64_t)s * page_size, page_size) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer(r, page_size / sizeof(uint32_t)) == 0); } free(w); free(r); wl_bdl->ops->release(wl_bdl); } ================================================ FILE: spi_nand_flash/host_test/main/test_nand_flash_ftl.cpp ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ /* * FTL-level host tests for spi_nand_flash. * * All tests exercise the public logical-sector API exclusively: * spi_nand_flash_write_sector / read_sector / copy_sector / trim / * sync / get_capacity / get_sector_size / get_block_size / * get_block_num / spi_nand_erase_chip * * Raw NAND page operations (nand_wrap_*) are intentionally NOT used here. * See test_nand_flash.cpp for raw-level coverage. */ #include #include #include #include #include "spi_nand_flash.h" #include "spi_nand_flash_test_helpers.h" #include "nand_linux_mmap_emul.h" #include /* ------------------------------------------------------------------------- * Fixture helpers * ---------------------------------------------------------------------- */ /** Default flash size used by most tests (16 MiB — fast init / GC pressure). */ #define FTL_TEST_FLASH_SIZE ((size_t)16u * 1024u * 1024u) /** Larger flash used for the full-capacity sequential sweep. */ #define FTL_TEST_FLASH_LARGE ((size_t)32u * 1024u * 1024u) /** * Open a fresh emulated NAND device backed by an anonymous temp file. * gc_factor == 0 selects the driver default. */ static spi_nand_flash_device_t *make_ftl_dev(size_t flash_size = FTL_TEST_FLASH_SIZE, uint8_t gc_factor = 0) { nand_file_mmap_emul_config_t emul = {"", flash_size, /*keep_dump=*/false}; spi_nand_flash_config_t cfg = {&emul, gc_factor, SPI_NAND_IO_MODE_SIO, 0}; spi_nand_flash_device_t *dev = nullptr; REQUIRE(spi_nand_flash_init_device(&cfg, &dev) == ESP_OK); REQUIRE(dev != nullptr); return dev; } static void destroy_ftl_dev(spi_nand_flash_device_t *dev) { REQUIRE(spi_nand_flash_deinit_device(dev) == ESP_OK); } /* ------------------------------------------------------------------------- * Group 1: Device info / capacity API * ---------------------------------------------------------------------- */ TEST_CASE("FTL get_capacity returns non-zero sector count", "[ftl][info]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(sectors > 0); destroy_ftl_dev(dev); } TEST_CASE("FTL get_sector_size returns a power-of-two >= 512", "[ftl][info]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); REQUIRE(sz >= 512u); /* Must be a power of two */ REQUIRE((sz & (sz - 1u)) == 0u); destroy_ftl_dev(dev); } TEST_CASE("FTL get_block_size is a multiple of sector_size", "[ftl][info]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sector_size = 0, block_size = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, §or_size) == ESP_OK); REQUIRE(spi_nand_flash_get_block_size(dev, &block_size) == ESP_OK); REQUIRE(block_size > 0); REQUIRE(block_size % sector_size == 0); destroy_ftl_dev(dev); } TEST_CASE("FTL get_block_num is consistent with capacity and block/sector sizes", "[ftl][info]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sector_size = 0, block_size = 0, blocks = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, §or_size) == ESP_OK); REQUIRE(spi_nand_flash_get_block_size(dev, &block_size) == ESP_OK); REQUIRE(spi_nand_flash_get_block_num(dev, &blocks) == ESP_OK); REQUIRE(blocks > 0); /* Total logical flash bytes derived from blocks must match capacity-derived bytes. * Dhara reserves some blocks for GC, so the logical sector count is <= physical. */ uint32_t pages_per_block = block_size / sector_size; REQUIRE(blocks * pages_per_block >= sectors); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 2: Single-sector write / read round-trip * ---------------------------------------------------------------------- */ TEST_CASE("FTL write then read back sector 0 produces correct data", "[ftl][rw]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 0) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 0) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL write then read back last valid sector", "[ftl][rw]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); uint32_t last = sectors - 1u; spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, last) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, last) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL overwrite same sector multiple times, each read-back is correct", "[ftl][rw]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); const uint32_t TARGET_SECTOR = 7; const int OVERWRITE_ROUNDS = 20; for (int i = 0; i < OVERWRITE_ROUNDS; i++) { /* Round-varying seed so each write is distinct; *0x1000 spreads seeds for remap checks. */ spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), (uint32_t)i * 0x1000u); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, TARGET_SECTOR) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, TARGET_SECTOR) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL multiple sectors hold independent data after interleaved writes", "[ftl][rw]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); const uint32_t N = 8; uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); /* Write each sector with the shared test pattern */ for (uint32_t s = 0; s < N; s++) { spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), s); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, s) == ESP_OK); } /* Read them all back and verify the pattern */ for (uint32_t s = 0; s < N; s++) { REQUIRE(spi_nand_flash_read_sector(dev, rbuf, s) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), s) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 3: sync * ---------------------------------------------------------------------- */ TEST_CASE("FTL sync returns ESP_OK on a freshly initialised device", "[ftl][sync]") { spi_nand_flash_device_t *dev = make_ftl_dev(); REQUIRE(spi_nand_flash_sync(dev) == ESP_OK); destroy_ftl_dev(dev); } TEST_CASE("FTL sync after writes returns ESP_OK and data survives", "[ftl][sync]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 3) == ESP_OK); REQUIRE(spi_nand_flash_sync(dev) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 3) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 4: copy_sector * ---------------------------------------------------------------------- */ TEST_CASE("FTL copy_sector duplicates data to a different logical sector", "[ftl][copy]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 10) == ESP_OK); REQUIRE(spi_nand_flash_copy_sector(dev, 10, 20) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 20) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL copy_sector to same sector id is idempotent", "[ftl][copy]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 5) == ESP_OK); /* Copying a sector onto itself should succeed (or at least not corrupt) */ esp_err_t rc = spi_nand_flash_copy_sector(dev, 5, 5); /* Behaviour is either ESP_OK or a graceful error — must not crash */ if (rc == ESP_OK) { REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 5) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); } /* If it returned an error that is also acceptable — just must not corrupt data */ free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL copy does not alter source sector data", "[ftl][copy]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 11) == ESP_OK); REQUIRE(spi_nand_flash_copy_sector(dev, 11, 22) == ESP_OK); /* Source must be unchanged */ REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 11) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 5: trim * ---------------------------------------------------------------------- */ TEST_CASE("FTL trim returns ESP_OK on a written sector", "[ftl][trim]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)malloc(sz); REQUIRE(buf != nullptr); memset(buf, 0xAB, sz); REQUIRE(spi_nand_flash_write_sector(dev, buf, 4) == ESP_OK); REQUIRE(spi_nand_flash_trim(dev, 4) == ESP_OK); free(buf); destroy_ftl_dev(dev); } TEST_CASE("FTL trim then write to the same sector succeeds", "[ftl][trim]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); memset(wbuf, 0x11, sz); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 6) == ESP_OK); REQUIRE(spi_nand_flash_trim(dev, 6) == ESP_OK); spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 6) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 6) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL trim on unwritten sector returns ESP_OK", "[ftl][trim]") { spi_nand_flash_device_t *dev = make_ftl_dev(); /* Sector 9 has never been written — trim should still succeed gracefully */ REQUIRE(spi_nand_flash_trim(dev, 9) == ESP_OK); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 6: erase_chip * ---------------------------------------------------------------------- */ TEST_CASE("FTL erase_chip succeeds and write/read works afterwards", "[ftl][erase-chip]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); /* Write something before the chip erase */ spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 0) == ESP_OK); REQUIRE(spi_nand_erase_chip(dev) == ESP_OK); /* After erase the FTL must accept new writes */ spi_nand_flash_fill_buffer(wbuf, sz / sizeof(uint32_t)); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 0) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 0) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 7: Logical sector IDs beyond capacity * * NOTE: Dhara does NOT bounds-check logical sector IDs at the FTL level. * Writing or reading beyond the reported capacity succeeds (returns ESP_OK) * rather than returning an error. These tests document that observed * behaviour so that any future change in Dhara that adds bounds-checking * will be caught explicitly. * ---------------------------------------------------------------------- */ TEST_CASE("FTL write to sector_id == capacity succeeds", "[ftl][bounds]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)calloc(1, sz); REQUIRE(buf != nullptr); /* Dhara does not validate the sector ID — expect success, not an error. */ esp_err_t rc = spi_nand_flash_write_sector(dev, buf, sectors); REQUIRE(rc == ESP_OK); free(buf); destroy_ftl_dev(dev); } TEST_CASE("FTL read from sector_id == capacity succeeds", "[ftl][bounds]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)calloc(1, sz); REQUIRE(buf != nullptr); /* Dhara does not validate the sector ID — expect success, not an error. */ esp_err_t rc = spi_nand_flash_read_sector(dev, buf, sectors); REQUIRE(rc == ESP_OK); free(buf); destroy_ftl_dev(dev); } TEST_CASE("FTL write to UINT32_MAX sector_id succeeds", "[ftl][bounds]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)calloc(1, sz); REQUIRE(buf != nullptr); /* Dhara does not validate the sector ID — expect success, not an error. */ esp_err_t rc = spi_nand_flash_write_sector(dev, buf, UINT32_MAX); REQUIRE(rc == ESP_OK); free(buf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 8: Sequential full-capacity write sweep * ---------------------------------------------------------------------- */ TEST_CASE("FTL sequential write to every logical sector, then read-back all", "[ftl][sequential]") { /* Use larger flash so we have a meaningful number of logical sectors */ spi_nand_flash_device_t *dev = make_ftl_dev(FTL_TEST_FLASH_LARGE); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); /* Write pass */ for (uint32_t s = 0; s < sectors; s++) { spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), s); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, s) == ESP_OK); } REQUIRE(spi_nand_flash_sync(dev) == ESP_OK); /* Verify capacity has not changed */ uint32_t sectors_after = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors_after) == ESP_OK); REQUIRE(sectors_after == sectors); /* Read-back pass */ for (uint32_t s = 0; s < sectors; s++) { REQUIRE(spi_nand_flash_read_sector(dev, rbuf, s) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), s) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 9: GC stability — hot-set repeated overwrites * * Write to a small set of logical sectors (HOT_SET_SIZE) many times * (TOTAL_WRITES). This forces Dhara to perform garbage collection * repeatedly. Asserts: * 1. Every write returns ESP_OK. * 2. After the run, get_capacity() still returns the original value. * 3. Each sector in the hot-set reads back the last value written. * 4. spi_nand_flash_sync() returns ESP_OK. * ---------------------------------------------------------------------- */ #define HOT_SET_SIZE 50u #define TOTAL_WRITES 5000u TEST_CASE("FTL GC stability: 50-sector hot-set written 5000 times", "[ftl][gc][stability]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); /* The hot-set must fit within the device's logical address space */ REQUIRE(sectors >= HOT_SET_SIZE); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); bool written[HOT_SET_SIZE] = {}; uint32_t last_seed[HOT_SET_SIZE] = {}; srand(0xDEADBEEFu); /* reproducible */ for (uint32_t op = 0; op < TOTAL_WRITES; op++) { uint32_t lsector = (uint32_t)((unsigned)rand() % HOT_SET_SIZE); spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), op); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, lsector) == ESP_OK); written[lsector] = true; last_seed[lsector] = op; } /* Capacity must be unchanged — the Dhara map must not have grown OOB */ uint32_t sectors_after = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors_after) == ESP_OK); REQUIRE(sectors_after == sectors); /* Flush any in-memory state */ REQUIRE(spi_nand_flash_sync(dev) == ESP_OK); /* Every hot sector must read back the last write's pattern */ for (uint32_t s = 0; s < HOT_SET_SIZE; s++) { if (!written[s]) { continue; /* rand() never selected this logical sector */ } REQUIRE(spi_nand_flash_read_sector(dev, rbuf, s) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), last_seed[s]) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL GC stability with trim: hot-set with interleaved trims", "[ftl][gc][trim][stability]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sectors = 0, sz = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors) == ESP_OK); REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); REQUIRE(sectors >= HOT_SET_SIZE); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); bool trimmed[HOT_SET_SIZE] = {}; bool written[HOT_SET_SIZE] = {}; uint32_t last_seed[HOT_SET_SIZE] = {}; srand(0xCAFEBABEu); for (uint32_t op = 0; op < TOTAL_WRITES; op++) { uint32_t lsector = (uint32_t)((unsigned)rand() % HOT_SET_SIZE); /* Every 100 ops trim a random sector in the hot-set */ if (op % 100 == 99) { uint32_t t = (uint32_t)((unsigned)rand() % HOT_SET_SIZE); if (spi_nand_flash_trim(dev, t) == ESP_OK) { trimmed[t] = true; } } spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), op); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, lsector) == ESP_OK); trimmed[lsector] = false; written[lsector] = true; last_seed[lsector] = op; } uint32_t sectors_after = 0; REQUIRE(spi_nand_flash_get_capacity(dev, §ors_after) == ESP_OK); REQUIRE(sectors_after == sectors); REQUIRE(spi_nand_flash_sync(dev) == ESP_OK); /* Verify untrimmed sectors */ for (uint32_t s = 0; s < HOT_SET_SIZE; s++) { if (trimmed[s] || !written[s]) { continue; /* skip trimmed or never-written */ } REQUIRE(spi_nand_flash_read_sector(dev, rbuf, s) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), last_seed[s]) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 10: Single-sector hammer — one sector written thousands of times * ---------------------------------------------------------------------- */ TEST_CASE("FTL single sector written 2000 times stays readable and correct", "[ftl][gc][hammer]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); const uint32_t TARGET = 0; const uint32_t ROUNDS = 2000; for (uint32_t i = 0; i < ROUNDS; i++) { spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), i); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, TARGET) == ESP_OK); } /* Final read must reflect the last write (seed ROUNDS - 1). */ spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), ROUNDS - 1u); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, TARGET) == ESP_OK); REQUIRE(memcmp(wbuf, rbuf, sz) == 0); free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 11: Alternating write / read pattern * ---------------------------------------------------------------------- */ TEST_CASE("FTL alternating write-read on two sectors never corrupts either", "[ftl][rw][alternating]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *wbuf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(wbuf != nullptr); REQUIRE(rbuf != nullptr); const uint32_t ITERS = 500; for (uint32_t i = 0; i < ITERS; i++) { /* Write A, then read B, then write B, then read A */ spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), i); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 1) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 2) == ESP_OK); if (i > 0) { REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), i - 1u) == 0); } spi_nand_flash_fill_buffer_seeded(wbuf, sz / sizeof(uint32_t), i); REQUIRE(spi_nand_flash_write_sector(dev, wbuf, 2) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 1) == ESP_OK); REQUIRE(spi_nand_flash_check_buffer_seeded(rbuf, sz / sizeof(uint32_t), i) == 0); } free(wbuf); free(rbuf); destroy_ftl_dev(dev); } /* ------------------------------------------------------------------------- * Group 12: All-zeros and all-ones patterns (edge-case data values) * ---------------------------------------------------------------------- */ TEST_CASE("FTL write/read all-zeros pattern", "[ftl][rw][patterns]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)calloc(1, sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(buf != nullptr); REQUIRE(rbuf != nullptr); REQUIRE(spi_nand_flash_write_sector(dev, buf, 0) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 0) == ESP_OK); REQUIRE(memcmp(buf, rbuf, sz) == 0); free(buf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL write/read all-0xFF pattern", "[ftl][rw][patterns]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(buf != nullptr); REQUIRE(rbuf != nullptr); memset(buf, 0xFF, sz); REQUIRE(spi_nand_flash_write_sector(dev, buf, 1) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 1) == ESP_OK); REQUIRE(memcmp(buf, rbuf, sz) == 0); free(buf); free(rbuf); destroy_ftl_dev(dev); } TEST_CASE("FTL write/read alternating 0xAA/0x55 pattern", "[ftl][rw][patterns]") { spi_nand_flash_device_t *dev = make_ftl_dev(); uint32_t sz = 0; REQUIRE(spi_nand_flash_get_sector_size(dev, &sz) == ESP_OK); uint8_t *buf = (uint8_t *)malloc(sz); uint8_t *rbuf = (uint8_t *)malloc(sz); REQUIRE(buf != nullptr); REQUIRE(rbuf != nullptr); for (size_t i = 0; i < sz; i++) { buf[i] = (i & 1u) ? 0x55u : 0xAAu; } REQUIRE(spi_nand_flash_write_sector(dev, buf, 2) == ESP_OK); REQUIRE(spi_nand_flash_read_sector(dev, rbuf, 2) == ESP_OK); REQUIRE(memcmp(buf, rbuf, sz) == 0); free(buf); free(rbuf); destroy_ftl_dev(dev); } ================================================ FILE: spi_nand_flash/host_test/pytest_nand_flash_linux.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.host_test @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['linux'], indirect=['target']) def test_nand_flash_linux(dut: Dut) -> None: dut.expect_exact('All tests passed', timeout=120) ================================================ FILE: spi_nand_flash/host_test/sdkconfig.defaults ================================================ CONFIG_IDF_TARGET="linux" CONFIG_COMPILER_CXX_EXCEPTIONS=y CONFIG_MMU_PAGE_SIZE=0X10000 CONFIG_NAND_ENABLE_STATS=y ================================================ FILE: spi_nand_flash/idf_component.yml ================================================ version: "1.0.2" description: Driver for accessing SPI NAND Flash url: https://github.com/espressif/idf-extra-components/tree/master/spi_nand_flash issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git documentation: https://github.com/espressif/idf-extra-components/tree/main/spi_nand_flash/README.md dependencies: idf: version: ">=5.0" require: public espressif/dhara: version: "0.1.*" override_path: "../dhara" require: public ================================================ FILE: spi_nand_flash/include/esp_nand_blockdev.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ /** * @file esp_nand_blockdev.h * @brief NAND Flash Block Device Interface * * This header provides the block device interface for SPI NAND Flash, including: * - Flash Block Device Layer (raw NAND flash access) * - Wear-Leveling Block Device Layer (logical sector access with wear leveling) * - NAND-specific ioctl commands and structures * * @note All block devices created by this interface use the standard esp_blockdev_t * interface from ESP-IDF, making them compatible with filesystems and other * block device consumers. */ #pragma once #include #include "esp_err.h" #include "spi_nand_flash.h" #include "nand_device_types.h" #ifdef CONFIG_NAND_FLASH_ENABLE_BDL #include "esp_blockdev.h" #ifdef __cplusplus extern "C" { #endif //============================================================================= // NAND-SPECIFIC IOCTL COMMANDS //============================================================================= #define ESP_BLOCKDEV_CMD_NAND_BASE (ESP_BLOCKDEV_CMD_SYSTEM_BASE + 10) /*!< Base for NAND-specific ioctl codes (10 above @ref ESP_BLOCKDEV_CMD_SYSTEM_BASE) */ /** * @defgroup esp_blockdev_nand_ioctl NAND flash block device ioctl commands * @brief NAND-specific ioctl commands for @ref esp_blockdev_ops_t::ioctl * * These extend the block device ioctl interface with bad-block handling, ECC status, * and other NAND-specific operations. Intended for raw NAND or FTL-backed block devices * that implement the corresponding cases in their ioctl handlers. * * @{ */ /** @brief Check if a block is marked as bad * * @code{c} * esp_blockdev_cmd_arg_status_t cmd = { .num = block_num }; * esp_err_t ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &cmd); * bool is_bad = cmd.status; * @endcode */ #define ESP_BLOCKDEV_CMD_IS_BAD_BLOCK (ESP_BLOCKDEV_CMD_NAND_BASE + 0) /** @brief Mark a block as bad * * @code{c} * uint32_t block = test_block_num; * esp_err_t ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK, &block); * @endcode */ #define ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK (ESP_BLOCKDEV_CMD_NAND_BASE + 1) /** @brief Check if a page is free * * @code{c} * esp_blockdev_cmd_arg_status_t cmd = { .num = page_num }; * esp_err_t ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &cmd); * bool is_free = cmd.status; * @endcode */ #define ESP_BLOCKDEV_CMD_IS_FREE_PAGE (ESP_BLOCKDEV_CMD_NAND_BASE + 2) /** @brief Get ECC status for a specific page * * @code{c} * esp_blockdev_cmd_arg_ecc_status_t cmd = { .page_num = page_num }; * esp_err_t ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS, &cmd); * nand_ecc_status_t ecc_status = cmd.ecc_status; * @endcode */ #define ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS (ESP_BLOCKDEV_CMD_NAND_BASE + 3) /** @brief Get the number of bad blocks in the flash * * @code{c} * uint32_t bad_block_count; * esp_err_t ret = flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT, &bad_block_count); * @endcode */ #define ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT (ESP_BLOCKDEV_CMD_NAND_BASE + 4) /** @brief Get ECC error statistics * * Scans the device for ECC status; can be slow on large devices. Intended for * diagnostics and debugging (for example flash health and degradation checks), not for use * in the normal I/O path. Do not call from an ISR. On large parts a full scan * may run long enough to risk the task watchdog; call from a suitable task * context or adjust WDT settings if needed. * * @code{c} * esp_blockdev_cmd_arg_ecc_stats_t ecc_stats; * esp_err_t ret = flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_ECC_STATS, &ecc_stats); * @endcode */ #define ESP_BLOCKDEV_CMD_GET_ECC_STATS (ESP_BLOCKDEV_CMD_NAND_BASE + 5) /** @brief Get complete NAND flash information (device ID and geometry) * * @code{c} * esp_blockdev_cmd_arg_nand_flash_info_t flash_info; * esp_err_t ret = flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, &flash_info); * printf("Manufacturer: 0x%02X, Device: 0x%04X\n", * flash_info.device_info.manufacturer_id, * flash_info.device_info.device_id); * @endcode */ #define ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO (ESP_BLOCKDEV_CMD_NAND_BASE + 6) /** @brief Copy a page from source to destination (raw flash block device) * * Performs a hardware-level page copy where supported, preserving copy optimizations * available on the NAND device. Often used internally by wear-leveling layers. * * @code{c} * esp_blockdev_cmd_arg_copy_page_t copy_cmd = { .src_page = 10, .dst_page = 20 }; * esp_err_t ret = flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd); * @endcode */ #define ESP_BLOCKDEV_CMD_COPY_PAGE (ESP_BLOCKDEV_CMD_NAND_BASE + 7) /** @} */ //============================================================================= // IOCTL COMMAND ARGUMENT STRUCTURES //============================================================================= /** * @brief Argument structure for block/page status commands * * Used with @ref ESP_BLOCKDEV_CMD_IS_BAD_BLOCK and @ref ESP_BLOCKDEV_CMD_IS_FREE_PAGE. */ typedef struct { uint32_t num; /*!< IN: block or page number */ bool status; /*!< OUT: bad-block status (true if bad) or page-free status (true if free) */ } esp_blockdev_cmd_arg_status_t; typedef esp_blockdev_cmd_arg_status_t esp_blockdev_cmd_arg_is_bad_block_t; typedef esp_blockdev_cmd_arg_status_t esp_blockdev_cmd_arg_is_free_page_t; /** * @brief Argument structure for ECC status query * * Used with @ref ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS. */ typedef struct { uint32_t page_num; /*!< IN: page number to check */ nand_ecc_status_t ecc_status; /*!< OUT: ECC status (@ref nand_ecc_status_t) */ } esp_blockdev_cmd_arg_ecc_status_t; /** * @brief ECC error statistics * * Used with @ref ESP_BLOCKDEV_CMD_GET_ECC_STATS. */ typedef struct { uint8_t ecc_threshold; /*!< Current ECC correction threshold */ uint32_t ecc_total_err_count; /*!< Total number of ECC errors encountered */ uint32_t ecc_uncorrected_err_count; /*!< Number of uncorrectable ECC errors */ uint32_t ecc_exceeding_threshold_err_count; /*!< Number of errors exceeding threshold (data refresh recommended) */ } esp_blockdev_cmd_arg_ecc_stats_t; /** * @brief Complete NAND flash device information * * Used with @ref ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO. */ typedef struct { nand_device_info_t device_info; /*!< Device identification (manufacturer, device ID, chip name) */ nand_flash_geometry_t geometry; /*!< Flash geometry (page size, block size, timing, and so on) */ } esp_blockdev_cmd_arg_nand_flash_info_t; /** * @brief Argument structure for page copy command * * Used with @ref ESP_BLOCKDEV_CMD_COPY_PAGE. */ typedef struct { uint32_t src_page; /*!< IN: source page number */ uint32_t dst_page; /*!< IN: destination page number */ } esp_blockdev_cmd_arg_copy_page_t; //============================================================================= // BLOCK DEVICE CREATION FUNCTIONS //============================================================================= /** * @brief Create Flash Block Device Layer (raw NAND flash access) * * This function initializes the NAND flash device and creates a block device * interface for direct physical access to the flash. * * @param[in] config Configuration for the SPI NAND flash device * @param[out] out_bdl_handle_ptr Pointer to store the Flash Block Device Layer handle * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid configuration or NULL pointers * - ESP_ERR_NO_MEM: Insufficient memory for device structures * - ESP_ERR_NOT_FOUND: NAND device not detected on SPI bus * - ESP_FAIL: Other initialization failure * * @note The returned block device handle must be released with bdl->ops->release(bdl) * when no longer needed. * * @note This creates the FLASH layer. For filesystem use, you typically want * the WEAR-LEVELING layer instead (see spi_nand_flash_wl_get_blockdev). * * @warning Raw flash access bypasses the Dhara FTL. For general read/write * workloads and any standard filesystem use, prefer the wear-leveling * BDL (`spi_nand_flash_wl_get_blockdev` / `spi_nand_flash_init_with_layers`) * * @warning @c erase on this handle performs a physical block erase only; it does not skip * or validate bad blocks. If you must not erase blocks that are marked bad, * query status with @ref ESP_BLOCKDEV_CMD_IS_BAD_BLOCK before erasing, or use the * wear-leveling BDL, which manages bad blocks for you. */ esp_err_t nand_flash_get_blockdev(spi_nand_flash_config_t *config, esp_blockdev_handle_t *out_bdl_handle_ptr); /** * @brief Create Wear-Leveling Block Device Layer (logical sector access) * * This function creates a wear-leveling block device on top of a Flash Block * Device Layer. The WL layer provides: * - Logical-to-physical sector mapping * - Automatic wear leveling (via Dhara library) * - Bad block abstraction (bad blocks invisible to user) * - Garbage collection * - Filesystem-ready interface * * @param[in] nand_bdl Flash Block Device Layer handle (from nand_flash_get_blockdev) * @param[out] out_bdl_handle_ptr Pointer to store the Wear-Leveling Block Device Layer handle * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid flash BDL handle or NULL pointer * - ESP_ERR_NO_MEM: Insufficient memory for wear-leveling structures * - ESP_FAIL: Wear-leveling initialization failure * * @note The returned block device handle must be released with bdl->ops->release(bdl) * when no longer needed. * * @note This is the recommended layer for filesystem use. It provides wear leveling * and bad block management automatically. */ esp_err_t spi_nand_flash_wl_get_blockdev(esp_blockdev_handle_t nand_bdl, esp_blockdev_handle_t *out_bdl_handle_ptr); #ifdef __cplusplus } #endif #endif // CONFIG_NAND_FLASH_ENABLE_BDL ================================================ FILE: spi_nand_flash/include/nand_device_types.h ================================================ /* * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /** @brief NAND Flash ECC status enumeration */ typedef enum { NAND_ECC_OK = 0, /*!< No ECC errors detected */ NAND_ECC_1_TO_3_BITS_CORRECTED = 1, /*!< 1-3 bits corrected */ NAND_ECC_BITS_CORRECTED = NAND_ECC_1_TO_3_BITS_CORRECTED, NAND_ECC_NOT_CORRECTED = 2, /*!< ECC errors not correctable */ NAND_ECC_4_TO_6_BITS_CORRECTED = 3, /*!< 4-6 bits corrected */ NAND_ECC_MAX_BITS_CORRECTED = NAND_ECC_4_TO_6_BITS_CORRECTED, NAND_ECC_7_8_BITS_CORRECTED = 5, /*!< 7-8 bits corrected */ NAND_ECC_MAX } nand_ecc_status_t; /** @brief NAND Flash ECC configuration and status */ typedef struct { uint8_t ecc_status_reg_len_in_bits; /*!< Length of ECC status register in bits */ uint8_t ecc_data_refresh_threshold; /*!< ECC error threshold for data refresh */ nand_ecc_status_t ecc_corrected_bits_status; /*!< Current ECC correction status */ } nand_ecc_data_t; /** @brief NAND Flash chip geometry and characteristics */ typedef struct { uint8_t log2_page_size; /*!< Page size as power of 2 (e.g., 11 for 2048 bytes) */ uint8_t log2_ppb; /*!< Pages per block as power of 2 (e.g., 6 for 64 pages) */ uint32_t block_size; /*!< Block size in bytes */ uint32_t page_size; /*!< Page size in bytes */ uint32_t num_blocks; /*!< Total number of blocks */ uint32_t read_page_delay_us; /*!< Read page delay in microseconds */ uint32_t erase_block_delay_us; /*!< Erase block delay in microseconds */ uint32_t program_page_delay_us; /*!< Program page delay in microseconds */ uint32_t num_planes; /*!< Number of planes in the flash */ uint32_t flags; /*!< Chip-specific flags */ nand_ecc_data_t ecc_data; /*!< ECC configuration and status */ uint8_t has_quad_enable_bit; /*!< 1 if chip supports QIO/QOUT mode */ uint8_t quad_enable_bit_pos; /*!< Position of quad enable bit */ #ifdef CONFIG_IDF_TARGET_LINUX uint32_t emulated_page_size; /*!< Emulated page size for Linux */ uint32_t emulated_page_oob; /*!< Emulated OOB size for Linux */ #endif } nand_flash_geometry_t; /** @brief NAND Flash device identification information */ typedef struct { uint8_t manufacturer_id; /*!< Manufacturer ID */ uint16_t device_id; /*!< Device ID */ char chip_name[32]; /*!< Chip name string */ } nand_device_info_t; #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/include/nand_diag_api.h ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "spi_nand_flash.h" #ifdef __cplusplus extern "C" { #endif // These API used for diagnostic purpose of SPI NAND Flash /** @brief Get bad block statistics for the NAND Flash. * * This function scans all the blocks in the NAND Flash and returns the total count of bad blocks. * * @param flash The handle to the SPI nand flash chip. * @param[out] bad_block_count A pointer of where to put the return value * @return ESP_OK on success, or a flash error code if it fails to get bad block statistics. */ esp_err_t nand_get_bad_block_stats(spi_nand_flash_device_t *flash, uint32_t *bad_block_count); /** @brief Get ECC error statistics for the NAND Flash. * * This function displays the total ECC errors reported, ECC not corrected error count and ECC error count exceeding threshold. * * @param flash The handle to the SPI nand flash chip. * @return ESP_OK on success, or a flash error code if it failed to read the page. */ esp_err_t nand_get_ecc_stats(spi_nand_flash_device_t *flash); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/include/nand_linux_mmap_emul.h ================================================ /* * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif // Control structure for NAND emulation typedef struct { char flash_file_name[256]; /** * Size of the backing mmap file in bytes. Must be a multiple of the chip's * user-visible block size (page_size * pages_per_block). * * Note: the file on disk is slightly larger than the user-visible NAND capacity * because each page has OOB bytes interleaved after it. The actual number of * usable blocks is derived internally as: * num_blocks = flash_file_size / (pages_per_block * (page_size + oob_size)) * and the effective user-visible capacity is num_blocks * block_size, which * will be slightly less than flash_file_size due to the OOB overhead. * * Example: passing 8 MiB with 2 KiB pages, 64 B OOB, 64 pages/block gives * file_bytes_per_block = 64 * (2048 + 64) = 135168 B * num_blocks = 8388608 / 135168 = 62 blocks * effective capacity = 62 * 131072 = 7.75 MiB (user-visible) */ size_t flash_file_size; bool keep_dump; } nand_file_mmap_emul_config_t; // nand mmap emulator handle typedef struct { void *mem_file_buf; int mem_file_fd; nand_file_mmap_emul_config_t file_mmap_ctrl; #ifdef CONFIG_NAND_ENABLE_STATS struct { size_t read_ops; size_t write_ops; size_t erase_ops; size_t read_bytes; size_t write_bytes; } stats; #endif } nand_mmap_emul_handle_t; // Emulated nand mmap file size #define EMULATED_NAND_SIZE 128 * 1024 * 1024 #include "spi_nand_flash.h" /** * @brief Initialize NAND flash emulation * * @param handle spi_nand_flash_device_t handle for nand device * @param cfg mmap emulation configuration setting * @return ESP_OK on success * ESP_ERR_INVALID_STATE if already initialized * ESP_ERR_NOT_FOUND if backing file cannot be created or opened (named path or default temp file via mkstemp) * ESP_ERR_INVALID_SIZE if file size setting fails * ESP_ERR_NO_MEM if memory not available or mmap fails */ esp_err_t nand_emul_init(spi_nand_flash_device_t *handle, nand_file_mmap_emul_config_t *cfg); /** * @brief Clean up NAND flash emulation * * @param handle spi_nand_flash_device_t handle for nand device * @return ESP_OK on success, or if @p handle is NULL or emulation was never initialized / already deinitialized (idempotent) * ESP_ERR_INVALID_STATE if emulation handle exists but is not mapped (inconsistent state) * ESP_ERR_INVALID_RESPONSE if cleanup operations fail */ esp_err_t nand_emul_deinit(spi_nand_flash_device_t *handle); /** * @brief Read data from NAND flash * * @param handle spi_nand_flash_device_t handle for nand device * @param addr Source address in NAND flash * @param dst Destination buffer * @param size Number of bytes to read * @return ESP_OK on success * ESP_ERR_INVALID_STATE if there is no emulation handle or the backing mmap is not ready * ESP_ERR_INVALID_SIZE if read would exceed flash size */ esp_err_t nand_emul_read(spi_nand_flash_device_t *handle, size_t addr, void *dst, size_t size); /** * @brief Write data to NAND flash * * @param handle spi_nand_flash_device_t handle for nand device * @param addr Destination address in NAND flash * @param src Source data buffer * @param size Number of bytes to write * @return ESP_OK on success * ESP_ERR_INVALID_STATE if there is no emulation handle or the backing mmap is not ready * ESP_ERR_INVALID_SIZE if write would exceed flash size */ esp_err_t nand_emul_write(spi_nand_flash_device_t *handle, size_t addr, const void *src, size_t size); /** * @brief Erase a NAND block * * @param handle spi_nand_flash_device_t handle for nand device * @param offset Block Address offset to erase * @return ESP_OK on success * ESP_ERR_INVALID_STATE if there is no emulation handle or the backing mmap is not ready * ESP_ERR_INVALID_SIZE if erase range would exceed emulated flash size */ esp_err_t nand_emul_erase_block(spi_nand_flash_device_t *handle, size_t offset); #ifdef CONFIG_NAND_ENABLE_STATS /** * @brief Get NAND operation statistics * * @param handle spi_nand_flash_device_t handle for nand device * @param[out] read_ops Number of read operations * @param[out] write_ops Number of write operations * @param[out] erase_ops Number of erase operations * @param[out] read_bytes Total bytes read * @param[out] write_bytes Total bytes written */ void nand_emul_get_stats(spi_nand_flash_device_t *handle, size_t *read_ops, size_t *write_ops, size_t *erase_ops, size_t *read_bytes, size_t *write_bytes); /** * @brief Clear NAND operation statistics * @param handle spi_nand_flash_device_t handle for nand device */ void nand_emul_clear_stats(spi_nand_flash_device_t *handle); #endif /* CONFIG_NAND_ENABLE_STATS */ #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/include/nand_private/nand_impl_wrap.h ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "spi_nand_flash.h" #ifdef __cplusplus extern "C" { #endif // These APIs provide direct access to lower-level NAND functions, bypassing the Dhara library. // These functions differ from the similarly named `nand_*` functions in that they also take the mutex for the duration of the call. esp_err_t nand_wrap_is_bad(spi_nand_flash_device_t *handle, uint32_t b, bool *is_bad_status); esp_err_t nand_wrap_mark_bad(spi_nand_flash_device_t *handle, uint32_t b); esp_err_t nand_wrap_erase_chip(spi_nand_flash_device_t *handle); esp_err_t nand_wrap_erase_block(spi_nand_flash_device_t *handle, uint32_t b); esp_err_t nand_wrap_prog(spi_nand_flash_device_t *handle, uint32_t p, const uint8_t *data); esp_err_t nand_wrap_is_free(spi_nand_flash_device_t *handle, uint32_t p, bool *is_free_status); esp_err_t nand_wrap_read(spi_nand_flash_device_t *handle, uint32_t p, size_t offset, size_t length, uint8_t *data); esp_err_t nand_wrap_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst); esp_err_t nand_wrap_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/include/spi_nand_flash.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2025 Espressif Systems (Shanghai) CO LTD */ #pragma once #include #include "esp_err.h" #include "nand_device_types.h" #ifndef CONFIG_IDF_TARGET_LINUX #include "driver/spi_common.h" #include "driver/spi_master.h" #endif #ifdef CONFIG_NAND_FLASH_ENABLE_BDL #include "esp_blockdev.h" #endif #ifdef __cplusplus extern "C" { #endif typedef struct spi_nand_flash_device_t spi_nand_flash_device_t; #ifdef CONFIG_IDF_TARGET_LINUX #include "nand_linux_mmap_emul.h" #endif /** @brief SPI mode used for reading from SPI NAND Flash */ typedef enum { SPI_NAND_IO_MODE_SIO = 0, SPI_NAND_IO_MODE_DOUT, SPI_NAND_IO_MODE_DIO, SPI_NAND_IO_MODE_QOUT, SPI_NAND_IO_MODE_QIO, } spi_nand_flash_io_mode_t; /** @brief Structure to describe how to configure the nand access layer. @note For DIO and DOUT mode The spi_device_handle_t must be initialized with the flag SPI_DEVICE_HALFDUPLEX SIO mode can be initialized with half-duplex or full-duplex mode */ struct spi_nand_flash_config_t { #ifndef CONFIG_IDF_TARGET_LINUX spi_device_handle_t device_handle; ///< SPI Device for this nand chip. #else nand_file_mmap_emul_config_t *emul_conf; #endif uint8_t gc_factor; ///< The gc factor controls the number of blocks to spare block ratio. ///< Lower values will reduce the available space but increase performance spi_nand_flash_io_mode_t io_mode; ///< set io mode for SPI NAND communication uint8_t flags; ///< set flag with SPI_DEVICE_HALFDUPLEX for half duplex communication, 0 for full-duplex. ///< This flag value must match the flag value in the spi_device_interface_config_t structure. }; typedef struct spi_nand_flash_config_t spi_nand_flash_config_t; /** @brief Initialise SPI nand flash chip interface. * * This function must be called before calling any other API functions for the nand flash. * * @param config Pointer to SPI nand flash config structure. * @param[out] handle The handle to the SPI nand flash chip is returned in this variable. * @return ESP_OK on success, or a flash error code if the initialisation failed. * * @note When CONFIG_NAND_FLASH_ENABLE_BDL is enabled, this function returns ESP_ERR_NOT_SUPPORTED. * Use spi_nand_flash_init_with_layers() instead. */ esp_err_t spi_nand_flash_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle); //----------------------------------------------------------------------------- // Page API (preferred terminology; NAND flash is page-based) //----------------------------------------------------------------------------- /** @brief Read a page from the nand flash. * * @param handle The handle to the SPI nand flash chip. * @param[out] buffer The output buffer to put the read data into (must hold at least page_size bytes). * @param page_id Logical page index to read. * @return ESP_OK on success, or a flash error code if the read failed. */ esp_err_t spi_nand_flash_read_page(spi_nand_flash_device_t *handle, uint8_t *buffer, uint32_t page_id); /** @brief Write a page to the nand flash. * * @param handle The handle to the SPI nand flash chip. * @param buffer The input buffer containing the data to write (must hold at least page_size bytes). * @param page_id Logical page index to write. * @return ESP_OK on success, or a flash error code if the write failed. */ esp_err_t spi_nand_flash_write_page(spi_nand_flash_device_t *handle, const uint8_t *buffer, uint32_t page_id); /** @brief Copy a page to another page within the nand flash. * * @param handle The handle to the SPI nand flash chip. * @param src_page Source logical page index. * @param dst_page Destination logical page index. * @return ESP_OK on success, or a flash error code if the copy failed. */ esp_err_t spi_nand_flash_copy_page(spi_nand_flash_device_t *handle, uint32_t src_page, uint32_t dst_page); /** @brief Trim a page from the nand flash. * * Marks the specified logical page as free to optimize memory usage and support wear-leveling. * Typically invoked when files are deleted or resized. * * @param handle The handle to the SPI nand flash chip. * @param page_id Logical page index to trim. * @return ESP_OK on success, or a flash error code if the trim failed. */ esp_err_t spi_nand_flash_trim(spi_nand_flash_device_t *handle, uint32_t page_id); /** @brief Get the number of logical pages (capacity). * * @param handle The handle to the SPI nand flash chip. * @param[out] number_of_pages Pointer to store the total number of logical pages. * @return ESP_OK on success, or a flash error code if the operation failed. */ esp_err_t spi_nand_flash_get_page_count(spi_nand_flash_device_t *handle, uint32_t *number_of_pages); /** @brief Get the size of each logical page in bytes. * * @param handle The handle to the SPI nand flash chip. * @param[out] page_size Pointer to store the page size in bytes. * @return ESP_OK on success, or a flash error code if the operation failed. */ esp_err_t spi_nand_flash_get_page_size(spi_nand_flash_device_t *handle, uint32_t *page_size); //----------------------------------------------------------------------------- // Sector API (backward-compatible aliases; equivalent to page API) //----------------------------------------------------------------------------- /** @brief Read a sector (alias for spi_nand_flash_read_page). * @deprecated Use spi_nand_flash_read_page() for new code. Sector and page are equivalent in this API. */ esp_err_t spi_nand_flash_read_sector(spi_nand_flash_device_t *handle, uint8_t *buffer, uint32_t sector_id); /** @brief Copy a sector (alias for spi_nand_flash_copy_page). * @deprecated Use spi_nand_flash_copy_page() for new code. */ esp_err_t spi_nand_flash_copy_sector(spi_nand_flash_device_t *handle, uint32_t src_sec, uint32_t dst_sec); /** @brief Write a sector (alias for spi_nand_flash_write_page). * @deprecated Use spi_nand_flash_write_page() for new code. */ esp_err_t spi_nand_flash_write_sector(spi_nand_flash_device_t *handle, const uint8_t *buffer, uint32_t sector_id); /** @brief Get number of sectors (alias for spi_nand_flash_get_page_count). * @deprecated Use spi_nand_flash_get_page_count() for new code. */ esp_err_t spi_nand_flash_get_capacity(spi_nand_flash_device_t *handle, uint32_t *number_of_sectors); /** @brief Get sector size (alias for spi_nand_flash_get_page_size). * @deprecated Use spi_nand_flash_get_page_size() for new code. */ esp_err_t spi_nand_flash_get_sector_size(spi_nand_flash_device_t *handle, uint32_t *sector_size); /** @brief Synchronizes any cache to the device. * * After this method is called, the nand flash chip should be synchronized with the results of any previous read/writes. * * @param handle The handle to the SPI nand flash chip. * @return ESP_OK on success, or a flash error code if the synchronization failed. */ esp_err_t spi_nand_flash_sync(spi_nand_flash_device_t *handle); /** @brief Retrieve the size of each block. * * @param handle The handle to the SPI nand flash chip. * @param[out] block_size A pointer of where to put the return value * @return ESP_OK on success, or a flash error code if the operation failed. */ esp_err_t spi_nand_flash_get_block_size(spi_nand_flash_device_t *handle, uint32_t *block_size); /** @brief Erases the entire chip, invalidating any data on the chip. * * @param handle The handle to the SPI nand flash chip. * @return ESP_OK on success, or a flash error code if the erase failed. */ esp_err_t spi_nand_erase_chip(spi_nand_flash_device_t *handle); /** @brief Retrieve the number of blocks available. * * @param handle The handle to the SPI nand flash chip. * @param[out] number_of_blocks A pointer of where to put the return value * @return ESP_OK on success, or a flash error code if the operation failed. */ esp_err_t spi_nand_flash_get_block_num(spi_nand_flash_device_t *handle, uint32_t *number_of_blocks); /** @brief Perform explicit garbage collection step * * This function triggers one garbage collection step in the wear-leveling layer. * It reclaims blocks with garbage pages by copying valid data and erasing physical blocks. * * Note: Garbage collection happens automatically during write operations based on * the gc_factor setting. This function is useful when you want to proactively * reclaim space during idle time. * * @param handle The handle to the SPI nand flash chip. * @return ESP_OK on success, or a flash error code if the operation failed. */ esp_err_t spi_nand_flash_gc(spi_nand_flash_device_t *handle); /** @brief De-initialize the handle, releasing any resources reserved. * * @param handle The handle to the SPI nand flash chip. * @return ESP_OK on success, or a flash error code if the de-initialization failed. */ esp_err_t spi_nand_flash_deinit_device(spi_nand_flash_device_t *handle); //--------------------------------------------------------------------------------------------------------------------------------------------- // NEW LAYERED ARCHITECTURE API //--------------------------------------------------------------------------------------------------------------------------------------------- #ifdef CONFIG_NAND_FLASH_ENABLE_BDL /** @brief Initialize SPI NAND Flash with separate layer block devices * * This function provides direct access to the layered architecture, allowing * users to work with the flash and wear-leveling layers separately. * Both layers are exposed as standard esp_blockdev_t interfaces. * * @param config Configuration for the SPI NAND flash * @param[out] wl_bdl Pointer to store the Wear-Leveling Block Device Layer handle * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid configuration or NULL pointers * - ESP_ERR_NO_MEM: Insufficient memory * - ESP_ERR_NOT_FOUND: NAND device not detected */ esp_err_t spi_nand_flash_init_with_layers(spi_nand_flash_config_t *config, esp_blockdev_handle_t *wl_bdl); #endif // CONFIG_NAND_FLASH_ENABLE_BDL #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/include/spi_nand_flash_test_helpers.h ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #ifdef __cplusplus extern "C" { #endif /** Fill a buffer with a deterministic uint32_t pattern (for testing). */ void spi_nand_flash_fill_buffer(uint8_t *dst, size_t count); /** * @brief Fill buffer with a deterministic pattern using a caller-supplied seed. * * Pattern: word[i] = seed + i (uint32_t words). Use a different seed per write * round to produce distinct data across multiple overwrites of the same sector. * * @param dst Destination buffer (must be 4-byte aligned, count * 4 bytes) * @param count Number of uint32_t words to fill * @param seed Pattern seed value */ void spi_nand_flash_fill_buffer_seeded(uint8_t *dst, size_t count, uint32_t seed); /** * Check buffer against the same deterministic pattern. * @return 0 on match, 1-based index of first mismatch on failure. */ int spi_nand_flash_check_buffer(const uint8_t *src, size_t count); /** * Check buffer against spi_nand_flash_fill_buffer_seeded(@p seed). * @return 0 on match, 1-based index of first mismatch on failure. */ int spi_nand_flash_check_buffer_seeded(const uint8_t *src, size_t count, uint32_t seed); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/layered_architecture.md ================================================ # SPI NAND Flash Layered Architecture This document describes the layered architecture implemented in the spi_nand_flash component, designed to provide cleaner separation of concerns, better maintainability, and enhanced extensibility while maintaining full backward compatibility. ## Feature Configuration The component supports two modes of operation controlled by Kconfig: - **Legacy Mode** (default): Traditional API only, minimal memory footprint - **BDL Mode** (`CONFIG_NAND_FLASH_ENABLE_BDL=y`): Includes Block Device Layer support with advanced features ### Kconfig Options #### `CONFIG_NAND_FLASH_ENABLE_BDL` **Type:** `bool` **Default:** `n` **Description:** Enable Block Device Layer (BDL) support for SPI NAND Flash When enabled, provides: - Standard `esp_blockdev_t` interface for layered architecture - Flash Block Device Layer (raw NAND flash access) - Wear-Leveling Block Device Layer (logical page access with wear leveling) - Advanced layered API (`spi_nand_flash_init_with_layers`) When disabled, only the legacy API is available. **Enable via menuconfig:** ``` Component config → SPI NAND Flash configuration → [*] Enable Block Device Layer (BDL) support ``` **Note:** BDL support requires ESP-IDF 6.0 or later (for the `esp_blockdev` component). The option is not available on older IDF versions. ## Architecture Overview ### Layered Structure #### Legacy Mode (Default) ``` Application ↓ ┌────────────────────────────────────────┐ │ spi_nand_flash.h (Public API) │ ← Legacy Interface │ - spi_nand_flash_init_device() │ │ - spi_nand_flash_read_page() │ │ - spi_nand_flash_write_page() │ │ - spi_nand_flash_trim() │ │ - spi_nand_flash_sync() │ │ - spi_nand_flash_gc() │ │ (+ sector-named aliases for compat) │ └────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────┐ │ NAND Wear-Leveling Layer │ ← Logical Sector Management │ (dhara_glue.c) │ │ - Logical-to-physical mapping │ │ - Wear leveling (Dhara integration) │ │ - Bad block management │ │ - Garbage collection │ └────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────┐ │ NAND Flash Implementation │ ← Physical Flash Operations │ (nand_impl.c) │ │ - Physical page/block operations │ │ - Device-specific implementations │ │ - ECC error handling │ │ - Direct hardware access │ └────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────┐ │ SPI NAND Operations / Emulation │ ← Hardware Abstraction │ (spi_nand_oper.c / nand_linux_mmap_ │ │ emul.c) │ │ - SPI transaction handling (ESP) │ │ - Memory-mapped file emulation (Linux) │ │ - Command execution │ │ - Register access │ └────────────────────────────────────────┘ ``` #### BDL Mode (CONFIG_NAND_FLASH_ENABLE_BDL=y) ``` Application / Filesystem ↓ ┌────────────────────────────────────────┐ │ spi_nand_flash.h (Public API) │ ← Legacy + BDL Interface │ Legacy API + BDL API: │ │ - spi_nand_flash_init_with_layers() │ └────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────┐ │ Direct BDL Access │ ← esp_blockdev_t interface │ (Application / Filesystem) │ │ - Read/Write/Erase via │ │ esp_blockdev_t interface │ │ - nand_flash_get_blockdev() │ │ - spi_nand_flash_wl_get_blockdev() │ └────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────┐ │ NAND Wear-Leveling BDL (esp_blockdev_t interface) │ │ (dhara_glue.c, nand_wl_blockdev.c) │ │ - Logical-to-physical mapping │ │ - Wear leveling (Dhara integration) │ │ - Bad block management │ │ - Garbage collection │ │ - TRIM support │ └──────────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────┐ │ NAND Flash BDL (esp_blockdev_t interface) │ │ (nand_flash_blockdev.c, nand_impl.c) │ │ - Physical page/block operations │ │ - Device-specific implementations │ │ - ECC error handling and statistics │ │ - Bad block detection and marking │ │ - Direct hardware access │ │ - IOCTL commands for advanced operations │ └──────────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────┐ │ SPI NAND Operations / Emulation │ │ (spi_nand_oper.c / nand_linux_mmap_emul.c) │ │ - SPI transaction handling (ESP) │ │ - Memory-mapped file emulation (Linux) │ │ - Command execution │ │ - Register access │ └──────────────────────────────────────────────────────────────┘ ``` ### Key Improvements 1. **Clear Separation of Concerns** - **Wear-Leveling Layer**: Handles logical-to-physical address mapping, wear leveling, and high-level bad block management - **Flash Layer**: Manages physical flash operations, device detection, and low-level error handling - **SPI Layer**: Handles SPI communication and command execution 2. **Better Maintainability** - Each layer has well-defined interfaces - Reduced coupling between components - Easier to test individual layers (Linux emulation support) - Clear header organization 3. **Enhanced Extensibility** - Easy to add new wear-leveling algorithms - Support for different NAND flash types - Better error handling and recovery ## Header File Organization ### Public Headers (`include/`) - **`spi_nand_flash.h`** - Main public API - **Page API** (Always available, preferred): - `spi_nand_flash_init_device()` - Initialize NAND flash device - `spi_nand_flash_read_page()` - Read logical page - `spi_nand_flash_write_page()` - Write logical page - `spi_nand_flash_copy_page()` - Copy page - `spi_nand_flash_trim()` - Trim/discard logical page - `spi_nand_flash_get_page_count()` - Get number of logical pages - `spi_nand_flash_get_page_size()` - Get page size in bytes - `spi_nand_flash_sync()` - Synchronize cache to device - `spi_nand_flash_gc()` - Explicit garbage collection - `spi_nand_flash_get_block_size()` - Get block size - `spi_nand_flash_get_block_num()` - Get number of blocks - `spi_nand_erase_chip()` - Erase entire chip - `spi_nand_flash_deinit_device()` - De-initialize device - **Sector API** (Backward-compatible aliases; equivalent to page API): - `spi_nand_flash_read_sector()` → `spi_nand_flash_read_page()` - `spi_nand_flash_write_sector()` → `spi_nand_flash_write_page()` - `spi_nand_flash_copy_sector()` → `spi_nand_flash_copy_page()` - `spi_nand_flash_get_capacity()` → `spi_nand_flash_get_page_count()` - `spi_nand_flash_get_sector_size()` → `spi_nand_flash_get_page_size()` - **BDL API** (Conditional - requires `CONFIG_NAND_FLASH_ENABLE_BDL`): - `spi_nand_flash_init_with_layers()` - Initialize with BDL handles - **`nand_device_types.h`** - Common types and definitions - `nand_ecc_status_t` - ECC status enumeration - `nand_ecc_data_t` - ECC configuration and status - `nand_flash_geometry_t` - Flash geometry (page size, block size, timing, planes, ECC data) - `nand_device_info_t` - Device identification (manufacturer ID, device ID, chip name) - **`esp_nand_blockdev.h`** - Block device interface (Conditional - requires `CONFIG_NAND_FLASH_ENABLE_BDL`) - `nand_flash_get_blockdev()` - Create Flash BDL - `spi_nand_flash_wl_get_blockdev()` - Create Wear-Leveling BDL - NAND-specific IOCTL commands - Argument structures for IOCTL operations - **`nand_diag_api.h`** - Diagnostic and statistics API - `nand_get_bad_block_stats()` - Get bad block count across the flash - `nand_get_ecc_stats()` - Display ECC error statistics - **`nand_linux_mmap_emul.h`** - Linux emulation configuration - `nand_file_mmap_emul_config_t` - Configuration for memory-mapped file emulation - **`spi_nand_flash_test_helpers.h`** - Helpers for test applications (e.g. emulation config, test fixtures) - **`nand_private/nand_impl_wrap.h`** - Wrapper API for implementation operations ### Private Headers (`priv_include/`) - **`nand.h`** - Internal device structure and operations - `spi_nand_flash_device_t` - Main device handle - `nand_wl_attach_ops()` / `nand_wl_detach_ops()` - Dhara integration - **`nand_impl.h`** - Low-level flash operations - `nand_init_device()` - Internal device initialization - Page read/write/erase functions - Bad block management - ECC status handling - **`nand_flash_devices.h`** - Device identification and initialization - Manufacturer IDs and device IDs - Device-specific initialization functions - **`spi_nand_oper.h`** - SPI operations (ESP targets only) - SPI transaction functions - Register read/write operations ## Source File Organization ### Core Implementation (`src/`) ``` src/ ├── nand.c # Public API implementation (Always compiled) │ # - spi_nand_flash_init_device() │ # - spi_nand_flash_read_page() / write_page() / copy_page() │ # - spi_nand_flash_get_page_count() / get_page_size() │ # - spi_nand_flash_trim() / sync() / gc() │ # - Sector-named aliases (backward compatible) │ # - spi_nand_flash_init_with_layers() [BDL only] │ ├── dhara_glue.c # Wear-Leveling implementation (Always compiled) │ # - Dhara library integration │ # - nand_wl_attach_ops() / nand_wl_detach_ops() │ # - Logical-to-physical mapping │ # - Conditional BDL handle support │ ├── nand_impl_wrap.c # Wrapper for nand_impl operations (Always compiled) │ # - Mutex-protected wrappers │ ├── nand_impl.c # Flash layer implementation │ # - Device detection and initialization │ # - nand_read(), nand_prog(), nand_erase() │ # - nand_is_bad(), nand_mark_bad() │ # - nand_is_free() │ # - ECC error detection and handling │ # - Plane selection support │ ├── nand_impl_linux.c # Flash layer implementation (Linux target only) │ # - Memory-mapped file emulation backend │ ├── nand_linux_mmap_emul.c # Linux emulation (Linux target only) │ # - Memory-mapped file I/O │ ├── nand_flash_blockdev.c # Flash BDL adapter [BDL only] │ # - nand_flash_get_blockdev() │ # - esp_blockdev_t interface implementation │ # - IOCTL command handling │ # - Boundary checks for operations │ ├── nand_wl_blockdev.c # WL BDL adapter [BDL only] │ # - spi_nand_flash_wl_get_blockdev() │ # - esp_blockdev_t interface implementation │ # - Wear-leveling operations │ # - Page read/write/trim │ # - Function pointer validation │ ├── nand_diag_api.c # Diagnostic and statistics API │ # - Bad block statistics │ # - ECC error statistics │ ├── spi_nand_flash_test_helpers.c # Test helpers (shared by test_app and host_test) │ ├── spi_nand_oper.c # SPI operations (ESP targets only) │ # - SPI transaction handling │ # - Multi-mode support (SIO/DOUT/DIO/QOUT/QIO) │ └── devices/ # Device-specific implementations (ESP targets only) ├── nand_winbond.c # Winbond NAND flash support ├── nand_gigadevice.c # GigaDevice NAND flash support ├── nand_alliance.c # Alliance NAND flash support ├── nand_micron.c # Micron NAND flash support ├── nand_zetta.c # Zetta NAND flash support └── nand_xtx.c # XTX NAND flash support ``` ## API Usage ### Page API (Always Available, Preferred) The public API uses **page** terminology to align with NAND flash (logical pages with wear-leveling). This API is always available regardless of Kconfig settings. Sector-named functions remain available as backward-compatible aliases. ```c #include "spi_nand_flash.h" // Configure SPI NAND flash spi_nand_flash_config_t config = { .device_handle = spi_handle, .io_mode = SPI_NAND_IO_MODE_QIO, .flags = SPI_DEVICE_HALFDUPLEX, }; spi_nand_flash_device_t *handle; // Initialize device esp_err_t ret = spi_nand_flash_init_device(&config, &handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize NAND flash"); return ret; } // Read/write pages uint32_t page_size; spi_nand_flash_get_page_size(handle, &page_size); uint8_t *buffer = malloc(page_size); uint32_t page_id = 0; ret = spi_nand_flash_read_page(handle, buffer, page_id); ret = spi_nand_flash_write_page(handle, buffer, page_id); // TRIM (mark page as free for garbage collection) ret = spi_nand_flash_trim(handle, page_id); // Explicit garbage collection (optional - happens automatically) ret = spi_nand_flash_gc(handle); // Synchronize cache to device ret = spi_nand_flash_sync(handle); // Get capacity information uint32_t num_pages; spi_nand_flash_get_page_count(handle, &num_pages); spi_nand_flash_get_page_size(handle, &page_size); ESP_LOGI(TAG, "Capacity: %u pages of %u bytes", num_pages, page_size); // Cleanup spi_nand_flash_deinit_device(handle); ``` **Backward compatibility:** The sector-named API (`spi_nand_flash_read_sector`, `spi_nand_flash_write_sector`, `spi_nand_flash_get_capacity`, `spi_nand_flash_get_sector_size`, etc.) is still supported and behaves identically to the page API. ### BDL API (Requires CONFIG_NAND_FLASH_ENABLE_BDL=y) The BDL API provides direct access to block device layers, enabling advanced features like raw flash access, detailed ECC statistics, and custom filesystem integration. #### Method 1: Direct Layer Creation ```c #include "spi_nand_flash.h" #include "esp_nand_blockdev.h" // Configure SPI NAND flash spi_nand_flash_config_t config = { .device_handle = spi_handle, .io_mode = SPI_NAND_IO_MODE_QIO, .flags = SPI_DEVICE_HALFDUPLEX, }; esp_blockdev_handle_t flash_bdl; esp_blockdev_handle_t wl_bdl; // Step 1: Create Flash Block Device Layer (raw NAND access) esp_err_t ret = nand_flash_get_blockdev(&config, &flash_bdl); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create Flash BDL"); return ret; } // Optional: Access raw flash operations directly uint8_t page_buffer[2048]; uint32_t page_addr = 0; flash_bdl->ops->read(flash_bdl, page_buffer, sizeof(page_buffer), page_addr, sizeof(page_buffer)); // Check if a block is bad esp_blockdev_cmd_arg_status_t bad_block_cmd = { .num = 10 }; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &bad_block_cmd); if (bad_block_cmd.status) { ESP_LOGW(TAG, "Block 10 is marked as bad"); } // Get detailed flash information esp_blockdev_cmd_arg_nand_flash_info_t flash_info; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, &flash_info); ESP_LOGI(TAG, "Chip: %s, Manufacturer: 0x%02x, Device: 0x%04x", flash_info.device_info.chip_name, flash_info.device_info.manufacturer_id, flash_info.device_info.device_id); ESP_LOGI(TAG, "Geometry: %u blocks, %u bytes/page", flash_info.geometry.num_blocks, flash_info.geometry.page_size); // Get ECC statistics esp_blockdev_cmd_arg_ecc_stats_t ecc_stats; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_ECC_STATS, &ecc_stats); ESP_LOGI(TAG, "ECC: %u total errors, %u exceeding threshold, %u uncorrected", ecc_stats.ecc_total_err_count, ecc_stats.ecc_exceeding_threshold_err_count, ecc_stats.ecc_uncorrected_err_count); // Step 2: Create Wear-Leveling Block Device Layer ret = spi_nand_flash_wl_get_blockdev(flash_bdl, &wl_bdl); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create WL BDL"); flash_bdl->ops->release(flash_bdl); return ret; } // Use wear-leveling layer for page-based operations (BDL reports these as "sectors") uint8_t page_buffer[2048]; uint32_t page_id = 0; wl_bdl->ops->read(wl_bdl, page_buffer, sizeof(page_buffer), page_id, sizeof(page_buffer)); wl_bdl->ops->write(wl_bdl, page_buffer, page_id, sizeof(page_buffer)); // Cleanup (releases both WL and Flash layers) wl_bdl->ops->release(wl_bdl); ``` #### Method 2: Simplified Initialization ```c #include "spi_nand_flash.h" #include "esp_nand_blockdev.h" // Configure SPI NAND flash spi_nand_flash_config_t config = { .device_handle = spi_handle, .io_mode = SPI_NAND_IO_MODE_QIO, .flags = SPI_DEVICE_HALFDUPLEX, }; esp_blockdev_handle_t wl_bdl; // Initialize both layers at once (simplified) esp_err_t ret = spi_nand_flash_init_with_layers(&config, &wl_bdl); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize NAND flash with layers"); return ret; } // Use wear-leveling BDL for page-based operations uint8_t buffer[2048]; wl_bdl->ops->read(wl_bdl, buffer, sizeof(buffer), 0, sizeof(buffer)); wl_bdl->ops->write(wl_bdl, buffer, 0, sizeof(buffer)); // Cleanup wl_bdl->ops->release(wl_bdl); ``` ## Block Device IOCTL Commands IOCTL commands provide advanced operations and diagnostics for block devices. These commands are only available when using the BDL API (`CONFIG_NAND_FLASH_ENABLE_BDL=y`). ### Flash BDL Commands These commands operate on the Flash Block Device Layer for low-level hardware access: | Command | Description | Argument Type | Example | |---------|-------------|---------------|---------| | `ESP_BLOCKDEV_CMD_IS_BAD_BLOCK` | Check if a physical block is marked bad | `esp_blockdev_cmd_arg_status_t*` | Check block health before raw access | | `ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK` | Mark a physical block as bad | `uint32_t*` (block number) | Mark block after repeated failures | | `ESP_BLOCKDEV_CMD_IS_FREE_PAGE` | Check if a page is erased (0xFF) | `esp_blockdev_cmd_arg_status_t*` | Verify erase operation | | `ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS` | Get ECC correction status for a page | `esp_blockdev_cmd_arg_ecc_status_t*` | Monitor bit error rates | | `ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT` | Get total count of bad blocks | `uint32_t*` | Flash health monitoring | | `ESP_BLOCKDEV_CMD_GET_ECC_STATS` | Get comprehensive ECC statistics | `esp_blockdev_cmd_arg_ecc_stats_t*` | Detect flash degradation | | `ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO` | Get complete device info and geometry | `esp_blockdev_cmd_arg_nand_flash_info_t*` | Query chip details | | `ESP_BLOCKDEV_CMD_COPY_PAGE` | Copy a page from source to destination | `esp_blockdev_cmd_arg_copy_page_t*` | Hardware-level page copy | ### WL BDL Commands These commands operate on the Wear-Leveling Block Device Layer for logical page management (BDL API uses "sector" in command names; each unit is one logical page): | Command | Description | Argument Type | Example | |---------|-------------|---------------|---------| | `ESP_BLOCKDEV_CMD_MARK_DELETED` | Mark range as unused (TRIM/discard) | `esp_blockdev_cmd_arg_erase_t*` (start_addr, erase_len; aligned to page size) | Optimize after file deletion | ### Example: Using IOCTL Commands ```c esp_blockdev_handle_t flash_bdl; nand_flash_get_blockdev(&config, &flash_bdl); // Check if block 10 is bad esp_blockdev_cmd_arg_status_t status_cmd = { .num = 10 }; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &status_cmd); if (status_cmd.status) { ESP_LOGW(TAG, "Block 10 is bad"); } // Get ECC statistics esp_blockdev_cmd_arg_ecc_stats_t ecc_stats; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_ECC_STATS, &ecc_stats); ESP_LOGI(TAG, "Total ECC errors: %u", ecc_stats.ecc_total_err_count); ESP_LOGI(TAG, "ECC errors exceeding threshold: %u", ecc_stats.ecc_exceeding_threshold_err_count); ESP_LOGI(TAG, "Uncorrectable ECC errors: %u", ecc_stats.ecc_uncorrected_err_count); // Get flash information esp_blockdev_cmd_arg_nand_flash_info_t info; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, &info); ESP_LOGI(TAG, "Chip: %s", info.device_info.chip_name); ESP_LOGI(TAG, "Capacity: %u blocks × %u bytes/page", info.geometry.num_blocks, info.geometry.page_size); // Copy a page esp_blockdev_cmd_arg_copy_page_t copy_cmd = { .src_page = 10, .dst_page = 20 }; flash_bdl->ops->ioctl(flash_bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd); ``` ## Testing and Validation ### Target Testing The component includes a comprehensive test application in `test_app/`: ```bash cd test_app idf.py build flash monitor ``` Tests are automatically selected based on Kconfig (main entry is `test_app_main.c`): - **Legacy tests** (`test_spi_nand_flash.c`): Run when `CONFIG_NAND_FLASH_ENABLE_BDL=n` - **BDL tests** (`test_spi_nand_flash_bdl.c`): Run when `CONFIG_NAND_FLASH_ENABLE_BDL=y` ### Linux Host Testing The component supports host-based testing on Linux using memory-mapped file emulation: ```c #ifdef CONFIG_IDF_TARGET_LINUX #include "nand_linux_mmap_emul.h" // Configure emulation nand_file_mmap_emul_config_t emul_cfg = { .flash_file_name = "", // Empty = auto-generate temp file .flash_file_size = 50 * 1024 * 1024, // 50MB .keep_dump = false // Delete file on cleanup }; spi_nand_flash_config_t config = { .emul_conf = &emul_cfg, // ... other config ... }; // Use normally - will emulate NAND flash spi_nand_flash_device_t *handle; spi_nand_flash_init_device(&config, &handle); #endif ``` **Build and run host tests:** ```bash cd host_test idf.py --preview set-target linux idf.py build monitor ``` Host tests also conditionally compile based on Kconfig (main entry is `test_app_main.cpp`): - **Legacy tests** (`test_nand_flash.cpp`): Compiled when `CONFIG_NAND_FLASH_ENABLE_BDL=n` - **BDL tests** (`test_nand_flash_bdl.cpp`): Compiled when `CONFIG_NAND_FLASH_ENABLE_BDL=y` See `host_test/README.md` for more details. ## Safety Improvements The component includes comprehensive safety checks to prevent common programming errors: ### Boundary Checks All division and modulo operations include zero-divisor checks: ```c // Example from nand_wl_blockdev.c if (page_size == 0) { ESP_LOGE(TAG, "Invalid page size (0)"); return ESP_ERR_INVALID_SIZE; } uint32_t page_count = erase_len / page_size; ``` **Protected operations:** - Page size calculations - Block size calculations - Page count calculations - Plane selection (modulo operations) ### Alignment Validation Read, write, and erase operations validate address and length alignment: ```c // Example: Erase alignment check if ((start_addr % page_size) != 0) { ESP_LOGE(TAG, "Start address not aligned to page size"); return ESP_ERR_INVALID_ARG; } if ((erase_len % page_size) != 0) { ESP_LOGE(TAG, "Erase length not aligned to page size"); return ESP_ERR_INVALID_ARG; } ``` ### Function Pointer Validation BDL creation validates all required operations are available: ```c // Example from spi_nand_flash_wl_get_blockdev() ESP_RETURN_ON_FALSE(nand_bdl != NULL, ESP_ERR_INVALID_ARG, TAG, "nand_bdl cannot be NULL"); ESP_RETURN_ON_FALSE(nand_bdl->ops != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL ops cannot be NULL"); ESP_RETURN_ON_FALSE(nand_bdl->ops->read != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL read operation is required"); ESP_RETURN_ON_FALSE(nand_bdl->ops->write != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL write operation is required"); // ... more validation ``` ## Migration Guide (0.x → 1.0.0) ### FATFS Integration Separated FATFS support has been moved to a separate `spi_nand_flash_fatfs` component. If your project uses FATFS with NAND flash: 1. Add `spi_nand_flash_fatfs` as a dependency in your `idf_component.yml`. 2. Include headers from `spi_nand_flash_fatfs` instead of the old unified headers. 3. Use **`spi_nand_flash_init_device()`** and keep **`CONFIG_NAND_FLASH_ENABLE_BDL` disabled**. When BDL is enabled, `spi_nand_flash_init_device()` returns `ESP_ERR_NOT_SUPPORTED`. **This release does not provide FatFs on top of the wear-leveling BDL** (`esp_blockdev_t`); that will be added in a future component update. 4. Aside from the component split and the BDL constraint above, FatFs usage matches 0.x (same mount helpers and diskio behavior). ### For Existing Projects (Legacy API) The existing legacy API (`spi_nand_flash_init_device`, `read_sector`, `write_sector`, etc.) continues to work **as long as `CONFIG_NAND_FLASH_ENABLE_BDL` is not enabled**. > **Important:** When `CONFIG_NAND_FLASH_ENABLE_BDL` is enabled via Kconfig, > `spi_nand_flash_init_device()` returns `ESP_ERR_NOT_SUPPORTED`. > You must use `spi_nand_flash_init_with_layers()` instead. The "sector" API functions are now deprecated aliases for the equivalent "page" functions. Consider migrating to the page terminology (`read_page`, `write_page`, `get_page_count`, `get_page_size`) for clarity. ### For New Projects (BDL API) New projects are encouraged to use the Block Device Layer API, which provides: - Direct flash access (raw page/block operations) — see **`nand_flash_get_blockdev()`**; for most workloads prefer the **wear-leveling** BDL (Dhara FTL: wear leveling, bad-block management, logical sectors). Raw flash BDL is mainly for diagnostics, bring-up etc. - Advanced diagnostics operations (ECC stats, bad block tracking) - Integration with consumers of **`esp_blockdev_t`** (this release does **not** include FatFs-on-BDL for SPI NAND; use **`spi_nand_flash_fatfs`** with BDL off for FatFs) - Fine-grained control over layers ### Enabling BDL Support To enable BDL support in your project: 1. **Via menuconfig:** ``` idf.py menuconfig → Component config → SPI NAND Flash configuration → [*] Enable Block Device Layer (BDL) support ``` 2. Use `spi_nand_flash_init_with_layers()` to initialize. 3. Interact with the flash through `esp_blockdev_t` handles returned by initialization. ## Error Handling The layered architecture provides comprehensive error reporting at each level: ### Hardware Layer Errors - **SPI Communication Failures**: `ESP_ERR_TIMEOUT`, `ESP_FAIL` - **ECC Errors**: `ESP_ERR_FLASH_BASE + DHARA_E_ECC` - **Bad Block Detection**: Automatic detection and remapping ### Flash Layer Errors - **Bad Block**: `ESP_ERR_NOT_FINISHED` when programming to bad block - **ECC Failure**: `ESP_ERR_FLASH_BASE + DHARA_E_ECC` for uncorrectable errors - **Invalid Parameters**: `ESP_ERR_INVALID_ARG`, `ESP_ERR_INVALID_SIZE` - **Alignment Errors**: `ESP_ERR_INVALID_ARG` for misaligned addresses ### Wear-Leveling Layer Errors - **Out of Space**: `ESP_ERR_FLASH_BASE + DHARA_E_NAND_FAILED` - **Mapping Errors**: `ESP_ERR_FLASH_BASE + DHARA_E_MAP_FAILED` - **Garbage Collection Issues**: Automatically retried or reported - **Too Many Bad Blocks**: `ESP_ERR_NO_MEM` when insufficient spare blocks ## Build Configuration ### CMakeLists.txt Integration The component's `CMakeLists.txt` conditionally compiles sources based on target and Kconfig. BDL sources are only added when **ESP-IDF 6.0 or later** is in use (for `esp_blockdev`): ```cmake # Base sources (always compiled for all targets) set(srcs "src/nand.c" "src/dhara_glue.c" "src/nand_impl_wrap.c") # BDL support (IDF >= 6.0 only) if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" GREATER_EQUAL "6.0") list(APPEND reqs esp_blockdev) if(CONFIG_NAND_FLASH_ENABLE_BDL) list(APPEND srcs "src/nand_flash_blockdev.c" "src/nand_wl_blockdev.c") endif() endif() # Target-specific sources if(${target} STREQUAL "linux") list(APPEND srcs "src/nand_impl_linux.c" "src/nand_linux_mmap_emul.c" "src/spi_nand_flash_test_helpers.c") else() list(APPEND srcs "src/devices/nand_winbond.c" "src/devices/nand_gigadevice.c" "src/devices/nand_alliance.c" "src/devices/nand_micron.c" "src/devices/nand_zetta.c" "src/devices/nand_xtx.c" "src/nand_impl.c" "src/nand_diag_api.c" "src/spi_nand_flash_test_helpers.c" "src/spi_nand_oper.c") endif() ``` ## Supported Manufacturers The component supports NAND flash chips from multiple manufacturers: | Manufacturer | File | Example Chips | |--------------|------|---------------| | Winbond | `src/devices/nand_winbond.c` | W25N01GV, W25N02KV | | GigaDevice | `src/devices/nand_gigadevice.c` | GD5F1GQ5UExxG | | Alliance | `src/devices/nand_alliance.c` | AS5F31G04SND | | Micron | `src/devices/nand_micron.c` | MT29F1G01 | | Zetta | `src/devices/nand_zetta.c` | ZD35Q1GA | | XTX | `src/devices/nand_xtx.c` | XT26G01C | Device detection is automatic based on JEDEC manufacturer and device IDs. ## Summary This layered architecture provides: **Backward Compatibility**: Existing code works without changes **Conditional Compilation**: Enable BDL only when needed **Safety**: Comprehensive boundary checks and validation **Flexibility**: Choose between simple legacy API or advanced BDL API **Testability**: Linux host testing support **Extensibility**: Easy to add new features and devices **Maintainability**: Clear separation of concerns The modular design makes the component production-ready while remaining extensible for future development. ================================================ FILE: spi_nand_flash/priv_include/nand.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2024 Espressif Systems (Shanghai) CO LTD */ #pragma once #include #include #include "spi_nand_flash.h" #ifdef CONFIG_IDF_TARGET_LINUX #include "nand_linux_mmap_emul.h" #endif #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "nand_device_types.h" #ifdef CONFIG_NAND_FLASH_ENABLE_BDL #include "esp_blockdev.h" #endif #ifdef __cplusplus extern "C" { #endif #define INVALID_PAGE 0xFFFF #define NAND_FLAG_HAS_PROG_PLANE_SELECT BIT(0) #define NAND_FLAG_HAS_READ_PLANE_SELECT BIT(1) // Legacy typedef for compatibility - now uses nand_flash_geometry_t internally typedef nand_flash_geometry_t spi_nand_chip_t; typedef struct { esp_err_t (*init)(spi_nand_flash_device_t *handle, void *bdl_handle); //if CONFIG_NAND_FLASH_ENABLE_BDL disabled, bdl_handle should be NULL esp_err_t (*deinit)(spi_nand_flash_device_t *handle); esp_err_t (*read)(spi_nand_flash_device_t *handle, uint8_t *buffer, uint32_t sector_id); esp_err_t (*write)(spi_nand_flash_device_t *handle, const uint8_t *buffer, uint32_t sector_id); esp_err_t (*erase_chip)(spi_nand_flash_device_t *handle); esp_err_t (*erase_block)(spi_nand_flash_device_t *handle, uint32_t block); esp_err_t (*trim)(spi_nand_flash_device_t *handle, uint32_t sector_id); esp_err_t (*sync)(spi_nand_flash_device_t *handle); esp_err_t (*copy_sector)(spi_nand_flash_device_t *handle, uint32_t src_sec, uint32_t dst_sec); esp_err_t (*get_capacity)(spi_nand_flash_device_t *handle, uint32_t *number_of_sectors); esp_err_t (*gc)(spi_nand_flash_device_t *handle); } spi_nand_ops; struct spi_nand_flash_device_t { spi_nand_flash_config_t config; spi_nand_chip_t chip; // Geometry (legacy typedef for nand_flash_geometry_t) nand_device_info_t device_info; // Device identification (manufacturer, device ID, chip name) const spi_nand_ops *ops; void *ops_priv_data; uint8_t *work_buffer; uint8_t *read_buffer; uint8_t *temp_buffer; SemaphoreHandle_t mutex; #ifdef CONFIG_IDF_TARGET_LINUX nand_mmap_emul_handle_t *emul_handle; #endif }; /** @return true if corrected-bit ECC class meets or exceeds the data-refresh threshold */ static inline bool nand_ecc_exceeds_data_refresh_threshold(const spi_nand_flash_device_t *handle) { uint8_t min_bits_corrected = 0; if (handle->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_1_TO_3_BITS_CORRECTED) { min_bits_corrected = 1; } else if (handle->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_4_TO_6_BITS_CORRECTED) { min_bits_corrected = 4; } else if (handle->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_7_8_BITS_CORRECTED) { min_bits_corrected = 7; } return min_bits_corrected >= handle->chip.ecc_data.ecc_data_refresh_threshold; } /** * @brief Attach wear-leveling operations to NAND device (internal use only) * * This function attaches the Dhara wear-leveling operation callbacks to the * device, enabling wear-leveling functionality. * * @param[in] handle NAND device handle * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid handle */ esp_err_t nand_wl_attach_ops(spi_nand_flash_device_t *handle); /** * @brief Detach wear-leveling operations from NAND device (internal use only) * * This function detaches the wear-leveling operation callbacks and frees * associated private data. * * @param[in] handle NAND device handle * * @return * - ESP_OK: Success */ esp_err_t nand_wl_detach_ops(spi_nand_flash_device_t *handle); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/priv_include/nand_flash_devices.h ================================================ /* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include "esp_err.h" #include "spi_nand_flash.h" #ifdef __cplusplus extern "C" { #endif //============================================================================= // MANUFACTURER IDs //============================================================================= #define SPI_NAND_FLASH_GIGADEVICE_MI 0xC8 #define SPI_NAND_FLASH_ALLIANCE_MI 0x52 #define SPI_NAND_FLASH_WINBOND_MI 0xEF #define SPI_NAND_FLASH_MICRON_MI 0x2C #define SPI_NAND_FLASH_ZETTA_MI 0xBA #define SPI_NAND_FLASH_XTX_MI 0x0B //============================================================================= // DEVICE IDs //============================================================================= // GigaDevice #define GIGADEVICE_DI_51 0x51 #define GIGADEVICE_DI_41 0x41 #define GIGADEVICE_DI_31 0x31 #define GIGADEVICE_DI_21 0x21 #define GIGADEVICE_DI_52 0x52 #define GIGADEVICE_DI_42 0x42 #define GIGADEVICE_DI_32 0x32 #define GIGADEVICE_DI_22 0x22 #define GIGADEVICE_DI_55 0x55 #define GIGADEVICE_DI_45 0x45 #define GIGADEVICE_DI_35 0x35 #define GIGADEVICE_DI_25 0x25 #define GIGADEVICE_DI_95 0x95 #define GIGADEVICE_DI_85 0x85 #define GIGADEVICE_DI_92 0x92 #define GIGADEVICE_DI_82 0x82 #define GIGADEVICE_DI_91 0x91 #define GIGADEVICE_DI_81 0x81 // Alliance Memory #define ALLIANCE_DI_25 0x25 // AS5F31G04SND-08LIN #define ALLIANCE_DI_2E 0x2E // AS5F32G04SND-08LIN #define ALLIANCE_DI_8E 0x8E // AS5F12G04SND-10LIN #define ALLIANCE_DI_2F 0x2F // AS5F34G04SND-08LIN #define ALLIANCE_DI_8F 0x8F // AS5F14G04SND-10LIN #define ALLIANCE_DI_2D 0x2D // AS5F38G04SND-08LIN #define ALLIANCE_DI_8D 0x8D // AS5F18G04SND-10LIN // Winbond #define WINBOND_DI_AA20 0xAA20 #define WINBOND_DI_BA20 0xBA20 #define WINBOND_DI_AA21 0xAA21 #define WINBOND_DI_BA21 0xBA21 #define WINBOND_DI_BC21 0xBC21 #define WINBOND_DI_AA22 0xAA22 #define WINBOND_DI_AA23 0xAA23 // Micron #define MICRON_DI_34 0x34 #define MICRON_DI_14 0x14 #define MICRON_DI_15 0x15 #define MICRON_DI_24 0x24 // MT29F2G // Zetta #define ZETTA_DI_71 0x71 // XTX #define XTX_DI_37 0x37 //============================================================================= // DEVICE INITIALIZATION FUNCTIONS //============================================================================= /** * @brief Initialize GigaDevice NAND flash */ esp_err_t spi_nand_gigadevice_init(spi_nand_flash_device_t *dev); /** * @brief Initialize Alliance Memory NAND flash */ esp_err_t spi_nand_alliance_init(spi_nand_flash_device_t *dev); /** * @brief Initialize Winbond NAND flash */ esp_err_t spi_nand_winbond_init(spi_nand_flash_device_t *dev); /** * @brief Initialize Micron NAND flash */ esp_err_t spi_nand_micron_init(spi_nand_flash_device_t *dev); /** * @brief Initialize Zetta NAND flash */ esp_err_t spi_nand_zetta_init(spi_nand_flash_device_t *dev); /** * @brief Initialize XTX NAND flash */ esp_err_t spi_nand_xtx_init(spi_nand_flash_device_t *dev); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/priv_include/nand_impl.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #pragma once #include #include "esp_err.h" #include "nand.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Initialize NAND flash device (internal use only) * * This is an internal function that initializes the NAND flash hardware, * detects the chip, and creates the device structure. It does NOT create * any block device interface. * * Used by: * - nand_flash_get_blockdev() - to create Flash BDL * - spi_nand_flash_init_device() - for legacy API * * @param[in] config Configuration for the SPI NAND flash device * @param[out] handle Pointer to store the created NAND device handle * * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid configuration or NULL pointer * - ESP_ERR_NO_MEM: Insufficient memory * - ESP_ERR_NOT_FOUND: NAND device not detected * * @note This is INTERNAL API. Do not use directly in applications. */ esp_err_t nand_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle); esp_err_t nand_is_bad(spi_nand_flash_device_t *handle, uint32_t b, bool *is_bad_status); esp_err_t nand_mark_bad(spi_nand_flash_device_t *handle, uint32_t b); esp_err_t nand_erase_chip(spi_nand_flash_device_t *handle); esp_err_t nand_erase_block(spi_nand_flash_device_t *handle, uint32_t b); esp_err_t nand_prog(spi_nand_flash_device_t *handle, uint32_t p, const uint8_t *data); esp_err_t nand_is_free(spi_nand_flash_device_t *handle, uint32_t p, bool *is_free_status); esp_err_t nand_read(spi_nand_flash_device_t *handle, uint32_t p, size_t offset, size_t length, uint8_t *data); esp_err_t nand_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst); esp_err_t nand_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/priv_include/spi_nand_oper.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #pragma once #include #include #include #include "nand.h" #ifdef __cplusplus extern "C" { #endif struct spi_nand_transaction_t { uint8_t command; uint8_t address_bytes; uint32_t address; uint32_t mosi_len; const uint8_t *mosi_data; uint32_t miso_len; uint8_t *miso_data; uint32_t dummy_bits; uint32_t flags; }; typedef struct spi_nand_transaction_t spi_nand_transaction_t; #define CMD_SET_REGISTER 0x1F #define CMD_READ_REGISTER 0x0F #define CMD_WRITE_ENABLE 0x06 #define CMD_READ_ID 0x9F #define CMD_PAGE_READ 0x13 #define CMD_PROGRAM_EXECUTE 0x10 #define CMD_PROGRAM_LOAD 0x84 #define CMD_PROGRAM_LOAD_X4 0x34 #define CMD_READ_FAST 0x0B #define CMD_READ_X2 0x3B #define CMD_READ_X4 0x6B #define CMD_ERASE_BLOCK 0xD8 #define CMD_READ_DIO 0xBB #define CMD_READ_QIO 0xEB #define REG_PROTECT 0xA0 #define REG_CONFIG 0xB0 #define REG_STATUS 0xC0 #define STAT_BUSY 1 << 0 #define STAT_WRITE_ENABLED 1 << 1 #define STAT_ERASE_FAILED 1 << 2 #define STAT_PROGRAM_FAILED 1 << 3 #define STAT_ECC0 1 << 4 #define STAT_ECC1 1 << 5 #define STAT_ECC2 1 << 6 size_t spi_nand_get_dma_alignment(void); esp_err_t spi_nand_execute_transaction(spi_nand_flash_device_t *handle, spi_nand_transaction_t *transaction); esp_err_t spi_nand_read_manufacturer_id(spi_nand_flash_device_t *handle, uint8_t *manufacturer_id); esp_err_t spi_nand_read_device_id(spi_nand_flash_device_t *handle, uint8_t *device_id, uint8_t length); esp_err_t spi_nand_read_register(spi_nand_flash_device_t *handle, uint8_t reg, uint8_t *val); esp_err_t spi_nand_write_register(spi_nand_flash_device_t *handle, uint8_t reg, uint8_t val); esp_err_t spi_nand_write_enable(spi_nand_flash_device_t *handle); esp_err_t spi_nand_read_page(spi_nand_flash_device_t *handle, uint32_t page); esp_err_t spi_nand_read(spi_nand_flash_device_t *handle, uint8_t *data, uint16_t column, uint16_t length); esp_err_t spi_nand_program_execute(spi_nand_flash_device_t *handle, uint32_t page); esp_err_t spi_nand_program_load(spi_nand_flash_device_t *handle, const uint8_t *data, uint16_t column, uint16_t length); esp_err_t spi_nand_erase_block(spi_nand_flash_device_t *handle, uint32_t page); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash/src/devices/nand_alliance.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_alliance"; esp_err_t spi_nand_alliance_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint8_t device_id = 0; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, &device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "alliance-0x%02" PRIx8, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 1; dev->chip.quad_enable_bit_pos = 0; dev->chip.erase_block_delay_us = 3000; dev->chip.program_page_delay_us = 630; switch (device_id) { case ALLIANCE_DI_25: //AS5F31G04SND-08LIN dev->chip.num_blocks = 1024; dev->chip.read_page_delay_us = 60; break; case ALLIANCE_DI_2E: //AS5F32G04SND-08LIN case ALLIANCE_DI_8E: //AS5F12G04SND-10LIN dev->chip.num_blocks = 2048; dev->chip.read_page_delay_us = 60; break; case ALLIANCE_DI_2F: //AS5F34G04SND-08LIN case ALLIANCE_DI_8F: //AS5F14G04SND-10LIN dev->chip.num_blocks = 4096; dev->chip.read_page_delay_us = 60; break; case ALLIANCE_DI_2D: //AS5F38G04SND-08LIN case ALLIANCE_DI_8D: //AS5F18G04SND-10LIN dev->chip.log2_page_size = 12; // 4k pages dev->chip.num_blocks = 4096; dev->chip.read_page_delay_us = 130; // somewhat slower reads break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/devices/nand_gigadevice.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_gigadevice"; esp_err_t spi_nand_gigadevice_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint8_t device_id = 0; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, &device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "gigadevice-0x%02" PRIx8, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 1; dev->chip.quad_enable_bit_pos = 0; dev->chip.read_page_delay_us = 25; dev->chip.erase_block_delay_us = 3200; dev->chip.program_page_delay_us = 380; switch (device_id) { case GIGADEVICE_DI_51: case GIGADEVICE_DI_41: case GIGADEVICE_DI_31: case GIGADEVICE_DI_21: case GIGADEVICE_DI_81: case GIGADEVICE_DI_91: dev->chip.num_blocks = 1024; break; case GIGADEVICE_DI_52: case GIGADEVICE_DI_42: case GIGADEVICE_DI_32: case GIGADEVICE_DI_22: case GIGADEVICE_DI_92: case GIGADEVICE_DI_82: dev->chip.num_blocks = 2048; break; case GIGADEVICE_DI_55: case GIGADEVICE_DI_45: case GIGADEVICE_DI_35: case GIGADEVICE_DI_25: dev->chip.num_blocks = 4096; break; case GIGADEVICE_DI_95: case GIGADEVICE_DI_85: dev->chip.num_blocks = 4096; dev->chip.flags = NAND_FLAG_HAS_PROG_PLANE_SELECT | NAND_FLAG_HAS_READ_PLANE_SELECT; dev->chip.num_planes = 2; break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/devices/nand_micron.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_micron"; esp_err_t spi_nand_micron_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint8_t device_id = 0; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, &device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "micron-0x%02" PRIx8, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 0; dev->chip.quad_enable_bit_pos = 0; dev->chip.ecc_data.ecc_status_reg_len_in_bits = 3; dev->chip.erase_block_delay_us = 2000; switch (device_id) { case MICRON_DI_34: dev->chip.read_page_delay_us = 115; dev->chip.program_page_delay_us = 240; dev->chip.num_blocks = 2048; dev->chip.log2_ppb = 6; // 64 pages per block dev->chip.log2_page_size = 12; // 4096 bytes per page break; case MICRON_DI_14: case MICRON_DI_15: dev->chip.read_page_delay_us = 46; dev->chip.program_page_delay_us = 220; dev->chip.num_blocks = 1024; dev->chip.log2_ppb = 6; // 64 pages per block dev->chip.log2_page_size = 11; // 2048 bytes per page break; case MICRON_DI_24: dev->chip.read_page_delay_us = 55; dev->chip.program_page_delay_us = 220; dev->chip.num_blocks = 2048; dev->chip.log2_ppb = 6; // 64 pages per block dev->chip.log2_page_size = 11; // 2048 bytes per page dev->chip.flags = NAND_FLAG_HAS_PROG_PLANE_SELECT | NAND_FLAG_HAS_READ_PLANE_SELECT; dev->chip.num_planes = 2; break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/devices/nand_winbond.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_winbond"; #define SWAP_BYTES(x) (uint16_t)((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF)) esp_err_t spi_nand_winbond_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint16_t device_id; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, (uint8_t *)&device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); device_id = SWAP_BYTES(device_id); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "winbond-0x%04" PRIx16, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 0; dev->chip.quad_enable_bit_pos = 0; dev->chip.read_page_delay_us = 10; dev->chip.erase_block_delay_us = 2500; dev->chip.program_page_delay_us = 320; switch (device_id) { case WINBOND_DI_AA20: case WINBOND_DI_BA20: dev->chip.num_blocks = 512; break; case WINBOND_DI_AA21: case WINBOND_DI_BA21: case WINBOND_DI_BC21: dev->chip.num_blocks = 1024; break; case WINBOND_DI_AA22: dev->chip.num_blocks = 2048; break; case WINBOND_DI_AA23: dev->chip.num_blocks = 4096; break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/devices/nand_xtx.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_xtx"; esp_err_t spi_nand_xtx_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint8_t device_id = 0; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, &device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "xtx-0x%02" PRIx8, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 1; dev->chip.quad_enable_bit_pos = 0; dev->chip.erase_block_delay_us = 3500; dev->chip.program_page_delay_us = 650; dev->chip.read_page_delay_us = 50; switch (device_id) { case XTX_DI_37: //XT26G08D dev->chip.num_blocks = 4096; dev->chip.log2_ppb = 6; // 64 pages per block dev->chip.log2_page_size = 12; // 4096 bytes per page break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/devices/nand_zetta.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "nand.h" #include "spi_nand_oper.h" #include "nand_flash_devices.h" static const char *TAG = "nand_zetta"; esp_err_t spi_nand_zetta_init(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; uint8_t device_id = 0; ESP_RETURN_ON_ERROR(spi_nand_read_device_id(dev, &device_id, sizeof(device_id)), TAG, "%s, Failed to get the device ID %d", __func__, ret); dev->device_info.device_id = device_id; snprintf(dev->device_info.chip_name, sizeof(dev->device_info.chip_name), "zetta-0x%02" PRIx8, device_id); ESP_LOGD(TAG, "%s: device_id: %x\n", __func__, device_id); dev->chip.has_quad_enable_bit = 1; dev->chip.quad_enable_bit_pos = 0; dev->chip.erase_block_delay_us = 2000; dev->chip.program_page_delay_us = 400; switch (device_id) { case ZETTA_DI_71: dev->chip.num_blocks = 1024; dev->chip.read_page_delay_us = 250; break; default: return ESP_ERR_INVALID_RESPONSE; } return ESP_OK; } ================================================ FILE: spi_nand_flash/src/dhara_glue.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2024 Espressif Systems (Shanghai) CO LTD */ #include #include #include "dhara/nand.h" #include "dhara/map.h" #include "dhara/error.h" #include "esp_check.h" #include "esp_err.h" #ifndef CONFIG_IDF_TARGET_LINUX #include "spi_nand_oper.h" #endif #include "nand_impl.h" #include "nand.h" #include "nand_device_types.h" #ifdef CONFIG_NAND_FLASH_ENABLE_BDL #include "esp_nand_blockdev.h" #endif typedef struct { struct dhara_nand dhara_nand; struct dhara_map dhara_map; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL esp_blockdev_handle_t bdl_handle; #endif spi_nand_flash_device_t *parent_handle; } spi_nand_flash_dhara_priv_data_t; static esp_err_t dhara_init(spi_nand_flash_device_t *handle, void *bdl_handle) { // create a holder structure for dhara context spi_nand_flash_dhara_priv_data_t *dhara_priv_data = heap_caps_calloc(1, sizeof(spi_nand_flash_dhara_priv_data_t), MALLOC_CAP_DEFAULT); if (dhara_priv_data == NULL) { return ESP_ERR_NO_MEM; } handle->ops_priv_data = dhara_priv_data; // store the pointer back to device structure in the holder structure dhara_priv_data->parent_handle = handle; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL dhara_priv_data->bdl_handle = (esp_blockdev_handle_t)bdl_handle; #endif dhara_priv_data->dhara_nand.log2_page_size = handle->chip.log2_page_size; dhara_priv_data->dhara_nand.log2_ppb = handle->chip.log2_ppb; dhara_priv_data->dhara_nand.num_blocks = handle->chip.num_blocks; dhara_map_init(&dhara_priv_data->dhara_map, &dhara_priv_data->dhara_nand, handle->work_buffer, handle->config.gc_factor); dhara_error_t ignored; dhara_map_resume(&dhara_priv_data->dhara_map, &ignored); return ESP_OK; } static esp_err_t dhara_deinit(spi_nand_flash_device_t *handle) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; // clear dhara map dhara_map_init(&dhara_priv_data->dhara_map, &dhara_priv_data->dhara_nand, handle->work_buffer, handle->config.gc_factor); dhara_map_clear(&dhara_priv_data->dhara_map); return ESP_OK; } static esp_err_t dhara_read(spi_nand_flash_device_t *handle, uint8_t *buffer, dhara_sector_t sector_id) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_read(&dhara_priv_data->dhara_map, sector_id, handle->read_buffer, &err)) { return ESP_ERR_FLASH_BASE + err; } memcpy(buffer, handle->read_buffer, handle->chip.page_size); return ESP_OK; } static esp_err_t dhara_write(spi_nand_flash_device_t *handle, const uint8_t *buffer, dhara_sector_t sector_id) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_write(&dhara_priv_data->dhara_map, sector_id, buffer, &err)) { return ESP_ERR_FLASH_BASE + err; } return ESP_OK; } static esp_err_t dhara_copy_sector(spi_nand_flash_device_t *handle, dhara_sector_t src_sec, dhara_sector_t dst_sec) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_copy_sector(&dhara_priv_data->dhara_map, src_sec, dst_sec, &err)) { return ESP_ERR_FLASH_BASE + err; } return ESP_OK; } static esp_err_t dhara_trim(spi_nand_flash_device_t *handle, dhara_sector_t sector_id) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_trim(&dhara_priv_data->dhara_map, sector_id, &err)) { return ESP_ERR_FLASH_BASE + err; } return ESP_OK; } static esp_err_t dhara_sync(spi_nand_flash_device_t *handle) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_sync(&dhara_priv_data->dhara_map, &err)) { return ESP_ERR_FLASH_BASE + err; } return ESP_OK; } static esp_err_t dhara_get_capacity(spi_nand_flash_device_t *handle, dhara_sector_t *number_of_sectors) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; *number_of_sectors = dhara_map_capacity(&dhara_priv_data->dhara_map); return ESP_OK; } static esp_err_t dhara_gc(spi_nand_flash_device_t *handle) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = (spi_nand_flash_dhara_priv_data_t *)handle->ops_priv_data; dhara_error_t err; if (dhara_map_gc(&dhara_priv_data->dhara_map, &err)) { return ESP_ERR_FLASH_BASE + err; } return ESP_OK; } static esp_err_t dhara_erase_chip(spi_nand_flash_device_t *handle) { return nand_erase_chip(handle); } static esp_err_t dhara_erase_block(spi_nand_flash_device_t *handle, uint32_t block) { return nand_erase_block(handle, block); } const spi_nand_ops dhara_nand_ops = { .init = &dhara_init, .deinit = &dhara_deinit, .read = &dhara_read, .write = &dhara_write, .erase_chip = &dhara_erase_chip, .erase_block = &dhara_erase_block, .trim = &dhara_trim, .sync = &dhara_sync, .copy_sector = &dhara_copy_sector, .get_capacity = &dhara_get_capacity, .gc = &dhara_gc, }; esp_err_t nand_wl_attach_ops(spi_nand_flash_device_t *handle) { if (handle == NULL) { return ESP_ERR_INVALID_ARG; } handle->ops = &dhara_nand_ops; return ESP_OK; } esp_err_t nand_wl_detach_ops(spi_nand_flash_device_t *handle) { free(handle->ops_priv_data); handle->ops = NULL; return ESP_OK; } /*------------------------------------------------------------------------------------------------------*/ // The following APIs are implementations required by the Dhara library. // Please refer to the header file dhara/nand.h for details. int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); spi_nand_flash_device_t *dev_handle = NULL; esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; dev_handle = (spi_nand_flash_device_t *)bdl_handle->ctx; ret = bdl_handle->ops->read(bdl_handle, data, length, (p * bdl_handle->geometry.read_size) + offset, length); #else dev_handle = dhara_priv_data->parent_handle; ret = nand_read(dev_handle, p, offset, length, data); #endif if (ret != ESP_OK) { if (dev_handle->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_NOT_CORRECTED) { dhara_set_error(err, DHARA_E_ECC); } return -1; } return 0; } int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; ret = bdl_handle->ops->write(bdl_handle, data, (p * bdl_handle->geometry.write_size), bdl_handle->geometry.write_size); #else spi_nand_flash_device_t *dev_handle = dhara_priv_data->parent_handle; ret = nand_prog(dev_handle, p, data); #endif if (ret) { if (ret == ESP_ERR_NOT_FINISHED) { dhara_set_error(err, DHARA_E_BAD_BLOCK); } return -1; } return 0; } int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; ret = bdl_handle->ops->erase(bdl_handle, b * bdl_handle->geometry.erase_size, bdl_handle->geometry.erase_size); #else spi_nand_flash_device_t *dev_handle = dhara_priv_data->parent_handle; ret = nand_erase_block(dev_handle, b); #endif if (ret) { if (ret == ESP_ERR_NOT_FINISHED) { dhara_set_error(err, DHARA_E_BAD_BLOCK); } return -1; } return 0; } int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); bool is_bad_status = false; esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; esp_blockdev_cmd_arg_is_bad_block_t bad_block_status = {b, false}; ret = bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &bad_block_status); is_bad_status = bad_block_status.status; #else spi_nand_flash_device_t *dev_handle = dhara_priv_data->parent_handle; ret = nand_is_bad(dev_handle, b, &is_bad_status); #endif if (ret || is_bad_status == true) { return 1; } return 0; } void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; uint32_t block = b; bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK, &block); #else spi_nand_flash_device_t *dev_handle = dhara_priv_data->parent_handle; nand_mark_bad(dev_handle, b); #endif return; } int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); bool is_free_status = true; esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; esp_blockdev_cmd_arg_is_free_page_t page_free_status = {p, true}; ret = bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &page_free_status); is_free_status = page_free_status.status; #else spi_nand_flash_device_t *dev_handle = dhara_priv_data->parent_handle; ret = nand_is_free(dev_handle, p, &is_free_status); #endif if (ret != ESP_OK) { return 0; } if (is_free_status == true) { return 1; } return 0; } int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) { spi_nand_flash_dhara_priv_data_t *dhara_priv_data = __containerof(n, spi_nand_flash_dhara_priv_data_t, dhara_nand); spi_nand_flash_device_t *dev_handle = NULL; esp_err_t ret = ESP_OK; #ifdef CONFIG_NAND_FLASH_ENABLE_BDL assert(dhara_priv_data->bdl_handle != NULL); esp_blockdev_handle_t bdl_handle = dhara_priv_data->bdl_handle; dev_handle = (spi_nand_flash_device_t *)bdl_handle->ctx; esp_blockdev_cmd_arg_copy_page_t copy_arg = { .src_page = src, .dst_page = dst }; ret = dhara_priv_data->bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_arg); #else dev_handle = dhara_priv_data->parent_handle; ret = nand_copy(dev_handle, src, dst); #endif if (ret) { if (dev_handle->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_NOT_CORRECTED) { dhara_set_error(err, DHARA_E_ECC); } if (ret == ESP_ERR_NOT_FINISHED) { dhara_set_error(err, DHARA_E_BAD_BLOCK); } return -1; } return 0; } ================================================ FILE: spi_nand_flash/src/nand.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2024 Espressif Systems (Shanghai) CO LTD */ #include #include "esp_check.h" #include "spi_nand_flash.h" #include "nand.h" #include "nand_impl.h" #include "nand_device_types.h" #ifdef CONFIG_NAND_FLASH_ENABLE_BDL #include "esp_blockdev.h" #include "esp_nand_blockdev.h" #endif static const char *TAG = "nand_api"; esp_err_t spi_nand_flash_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle) { #ifdef CONFIG_NAND_FLASH_ENABLE_BDL ESP_LOGE(TAG, "spi_nand_flash_init_device() is not supported when BDL is enabled. " "Use spi_nand_flash_init_with_layers() instead"); return ESP_ERR_NOT_SUPPORTED; #else if (!config->gc_factor) { config->gc_factor = 45; } esp_err_t ret = nand_init_device(config, handle); if (ret != ESP_OK) { return ret; } ret = nand_wl_attach_ops(*handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to attach wear-leveling operations"); } if ((*handle)->ops->init == NULL) { ESP_LOGE(TAG, "Failed to initialize spi_nand_ops"); ret = ESP_FAIL; return ret; } (*handle)->ops->init(*handle, NULL); return ESP_OK; #endif // CONFIG_NAND_FLASH_ENABLE_BDL } esp_err_t spi_nand_erase_chip(spi_nand_flash_device_t *handle) { ESP_LOGW(TAG, "Entire chip is being erased"); esp_err_t ret = ESP_OK; if (handle->ops->erase_chip == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->erase_chip(handle); if (ret) { goto end; } handle->ops->deinit(handle); end: xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_read_page(spi_nand_flash_device_t *handle, uint8_t *buffer, uint32_t page_id) { esp_err_t ret = ESP_OK; if (handle->ops->read == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->read(handle, buffer, page_id); // After a successful read operation, check the ECC corrected bit status; if the read fails, return an error if (ret == ESP_OK && handle->chip.ecc_data.ecc_corrected_bits_status) { // This indicates a soft ECC error, we rewrite the page to recover if corrected bits are greater than refresh threshold if (nand_ecc_exceeds_data_refresh_threshold(handle)) { ret = handle->ops->write(handle, buffer, page_id); } } xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_read_sector(spi_nand_flash_device_t *handle, uint8_t *buffer, uint32_t sector_id) { return spi_nand_flash_read_page(handle, buffer, sector_id); } esp_err_t spi_nand_flash_copy_page(spi_nand_flash_device_t *handle, uint32_t src_page, uint32_t dst_page) { esp_err_t ret = ESP_OK; if (handle->ops->copy_sector == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->copy_sector(handle, src_page, dst_page); xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_copy_sector(spi_nand_flash_device_t *handle, uint32_t src_sec, uint32_t dst_sec) { return spi_nand_flash_copy_page(handle, src_sec, dst_sec); } esp_err_t spi_nand_flash_write_page(spi_nand_flash_device_t *handle, const uint8_t *buffer, uint32_t page_id) { esp_err_t ret = ESP_OK; if (handle->ops->write == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->write(handle, buffer, page_id); xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_write_sector(spi_nand_flash_device_t *handle, const uint8_t *buffer, uint32_t sector_id) { return spi_nand_flash_write_page(handle, buffer, sector_id); } esp_err_t spi_nand_flash_trim(spi_nand_flash_device_t *handle, uint32_t page_id) { esp_err_t ret = ESP_OK; if (handle->ops->trim == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->trim(handle, page_id); xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_sync(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; if (handle->ops->sync == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->sync(handle); xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_gc(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; if (handle->ops->gc == NULL) { return ESP_ERR_NOT_SUPPORTED; } xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = handle->ops->gc(handle); xSemaphoreGive(handle->mutex); return ret; } esp_err_t spi_nand_flash_get_page_count(spi_nand_flash_device_t *handle, uint32_t *number_of_pages) { if (handle->ops->get_capacity == NULL) { return ESP_ERR_NOT_SUPPORTED; } return handle->ops->get_capacity(handle, number_of_pages); } esp_err_t spi_nand_flash_get_capacity(spi_nand_flash_device_t *handle, uint32_t *number_of_sectors) { return spi_nand_flash_get_page_count(handle, number_of_sectors); } esp_err_t spi_nand_flash_get_page_size(spi_nand_flash_device_t *handle, uint32_t *page_size) { *page_size = handle->chip.page_size; return ESP_OK; } esp_err_t spi_nand_flash_get_sector_size(spi_nand_flash_device_t *handle, uint32_t *sector_size) { return spi_nand_flash_get_page_size(handle, sector_size); } esp_err_t spi_nand_flash_get_block_size(spi_nand_flash_device_t *handle, uint32_t *block_size) { *block_size = handle->chip.block_size; return ESP_OK; } esp_err_t spi_nand_flash_get_block_num(spi_nand_flash_device_t *handle, uint32_t *num_blocks) { *num_blocks = handle->chip.num_blocks; return ESP_OK; } esp_err_t spi_nand_flash_deinit_device(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; #ifdef CONFIG_IDF_TARGET_LINUX ret = nand_emul_deinit(handle); #endif nand_wl_detach_ops(handle); free(handle->work_buffer); free(handle->read_buffer); #ifndef CONFIG_IDF_TARGET_LINUX free(handle->temp_buffer); #endif if (handle->mutex) { vSemaphoreDelete(handle->mutex); } free(handle); return ret; } // NEW LAYERED ARCHITECTURE API IMPLEMENTATION //--------------------------------------------------------------------------------------------------------------------------------------------- #ifdef CONFIG_NAND_FLASH_ENABLE_BDL esp_err_t spi_nand_flash_init_with_layers(spi_nand_flash_config_t *config, esp_blockdev_handle_t *wl_bdl) { ESP_RETURN_ON_FALSE(config && wl_bdl, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); // Set default GC factor if not specified if (!config->gc_factor) { config->gc_factor = 45; } // Initialize device and create Flash BDL esp_blockdev_handle_t flash_bdl; esp_err_t ret = nand_flash_get_blockdev(config, &flash_bdl); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create Flash BDL"); // Create WL BDL on top of Flash BDL (already a block device layer!) ret = spi_nand_flash_wl_get_blockdev(flash_bdl, wl_bdl); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create WL BDL"); (flash_bdl)->ops->release(flash_bdl); flash_bdl = NULL; return ret; } ESP_LOGD(TAG, "SPI NAND Flash initialized with layered block device architecture"); return ESP_OK; } #endif // CONFIG_NAND_FLASH_ENABLE_BDL ================================================ FILE: spi_nand_flash/src/nand_diag_api.c ================================================ /* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "esp_system.h" #include "spi_nand_flash.h" #include "nand.h" #include "nand_private/nand_impl_wrap.h" #include "esp_log.h" #include "esp_check.h" #include "nand_device_types.h" static const char *TAG = "nand_diag"; esp_err_t nand_get_bad_block_stats(spi_nand_flash_device_t *flash, uint32_t *bad_block_count) { esp_err_t ret = ESP_OK; uint32_t bad_blocks = 0; uint32_t num_blocks; spi_nand_flash_get_block_num(flash, &num_blocks); for (uint32_t blk = 0; blk < num_blocks; blk++) { bool is_bad = false; ret = nand_wrap_is_bad(flash, blk, &is_bad); if (ret == ESP_OK && is_bad) { bad_blocks++; ESP_LOGD(TAG, "bad block num=%"PRIu32"", blk); } else if (ret) { ESP_LOGE(TAG, "Failed to get bad block status for blk=%"PRIu32"", blk); return ret; } } *bad_block_count = bad_blocks; return ret; } esp_err_t nand_get_ecc_stats(spi_nand_flash_device_t *flash) { esp_err_t ret = ESP_OK; uint32_t page_size, block_size, num_blocks; uint32_t ecc_err_total_count = 0; uint32_t ecc_err_exceeding_threshold_count = 0; uint32_t ecc_err_not_corrected_count = 0; spi_nand_flash_get_page_size(flash, &page_size); spi_nand_flash_get_block_size(flash, &block_size); spi_nand_flash_get_block_num(flash, &num_blocks); if (page_size == 0) { ESP_LOGE(TAG, "Invalid page size (0)"); return ESP_ERR_INVALID_SIZE; } uint32_t pages_per_block = block_size / page_size; uint32_t num_pages = num_blocks * pages_per_block; bool is_free = true; for (uint32_t page = 0; page < num_pages; page++) { ret = nand_wrap_is_free(flash, page, &is_free); if (!is_free) { ret = nand_wrap_get_ecc_status(flash, page); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read ecc error for page=%" PRIu32 "", page); return ret; } if (flash->chip.ecc_data.ecc_corrected_bits_status) { ecc_err_total_count++; if (flash->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_NOT_CORRECTED) { ecc_err_not_corrected_count++; ESP_LOGD(TAG, "ecc error not corrected for page=%" PRIu32 "", page); } else if (nand_ecc_exceeds_data_refresh_threshold(flash)) { ecc_err_exceeding_threshold_count++; } } } } ESP_LOGI(TAG, "\nTotal number of ECC errors: %"PRIu32"\nECC not corrected count: %"PRIu32"\nECC errors exceeding threshold (%d): %"PRIu32"\n", ecc_err_total_count, ecc_err_not_corrected_count, flash->chip.ecc_data.ecc_data_refresh_threshold, ecc_err_exceeding_threshold_count); return ret; } ================================================ FILE: spi_nand_flash/src/nand_flash_blockdev.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_check.h" #include "esp_err.h" #include "esp_log.h" #include "esp_heap_caps.h" #include "nand.h" #include "nand_impl.h" #include "nand_flash_devices.h" #include "esp_nand_blockdev.h" #include "nand_device_types.h" #ifndef CONFIG_IDF_TARGET_LINUX #include "spi_nand_oper.h" #endif static const char *TAG = "nand_flash_blockdev"; /************************************************************************************** ************************************************************************************** * Block Device Layer interface implementation ************************************************************************************** */ static esp_err_t nand_flash_blockdev_read(esp_blockdev_handle_t handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) { uint32_t page_size = (uint32_t)handle->geometry.read_size; if (page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if (dst_buf == NULL || dst_buf_size < data_read_len) { return ESP_ERR_INVALID_ARG; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)handle->ctx; uint32_t start_page = (uint32_t)(src_addr >> dev_handle->chip.log2_page_size); uint32_t page_count = (uint32_t)(data_read_len >> dev_handle->chip.log2_page_size); size_t offset = src_addr % page_size; /* Single read: unaligned start or length not a multiple of page size */ if (offset != 0 || (data_read_len % page_size) != 0) { if (offset + data_read_len > page_size) { ESP_LOGE(TAG, "Read crosses page boundary: offset=%zu + len=%zu > page_size=%" PRIu32, offset, data_read_len, page_size); return ESP_ERR_INVALID_ARG; } if (src_addr + data_read_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Read range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } return nand_read(dev_handle, start_page, offset, data_read_len, dst_buf); } /* Multi-page read: page-aligned address and length */ if (src_addr + data_read_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Read range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } esp_err_t res = ESP_OK; for (uint32_t page_id = start_page; page_id < start_page + page_count; page_id++) { res = nand_read(dev_handle, page_id, 0, page_size, dst_buf); if (res != ESP_OK) { ESP_LOGE(TAG, "Failed to read page %" PRIu32, page_id); return res; } dst_buf += page_size; } ESP_LOGV(TAG, "read - src_addr=0x%.16" PRIx64 ", size=0x%08zx, result=0x%08x", src_addr, data_read_len, res); return res; } static esp_err_t nand_flash_blockdev_write(esp_blockdev_handle_t handle, const uint8_t *src_buf, uint64_t dst_addr, size_t data_write_len) { uint32_t page_size = (uint32_t)handle->geometry.write_size; if (handle->device_flags.read_only || page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if (src_buf == NULL) { return ESP_ERR_INVALID_ARG; } if ((dst_addr % handle->geometry.write_size) != 0) { ESP_LOGE(TAG, "Write address 0x%" PRIx64 " not aligned to page size %" PRIu32, dst_addr, (uint32_t)handle->geometry.write_size); return ESP_ERR_INVALID_SIZE; } if ((data_write_len % page_size) != 0) { ESP_LOGE(TAG, "Write length %zu not aligned to page size %" PRIu32, data_write_len, page_size); return ESP_ERR_INVALID_SIZE; } if (dst_addr + data_write_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Write range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)handle->ctx; uint32_t start_page = (uint32_t)(dst_addr >> dev_handle->chip.log2_page_size); uint32_t page_count = (uint32_t)(data_write_len >> dev_handle->chip.log2_page_size); esp_err_t res = ESP_OK; for (uint32_t page_id = start_page; page_id < start_page + page_count; page_id++) { res = nand_prog(dev_handle, page_id, src_buf); if (res != ESP_OK) { ESP_LOGE(TAG, "Failed to write page %" PRIu32, page_id); return res; } src_buf += page_size; } ESP_LOGV(TAG, "write - dst_addr=0x%.16" PRIx64 ", size=0x%08zx, result=0x%08x", dst_addr, data_write_len, res); return res; } static esp_err_t nand_flash_blockdev_erase(esp_blockdev_handle_t handle, uint64_t start_addr, size_t erase_len) { uint32_t erase_size = (uint32_t)handle->geometry.erase_size; if (handle->device_flags.read_only || erase_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if ((start_addr % erase_size) != 0) { ESP_LOGE(TAG, "Erase address 0x%" PRIx64 " not aligned to block size %" PRIu32, start_addr, erase_size); return ESP_ERR_INVALID_SIZE; } if (erase_len == 0 || (erase_len % erase_size) != 0) { ESP_LOGE(TAG, "Erase length %zu must be non-zero and a multiple of block size %" PRIu32, erase_len, erase_size); return ESP_ERR_INVALID_SIZE; } if (start_addr + erase_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Erase range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)handle->ctx; uint8_t log2_block_size = dev_handle->chip.log2_page_size + dev_handle->chip.log2_ppb; uint32_t start_block = (uint32_t)(start_addr >> log2_block_size); uint32_t num_blocks = (uint32_t)(erase_len >> log2_block_size); esp_err_t ret = ESP_OK; for (uint32_t block = start_block; block < (start_block + num_blocks); block++) { ret = nand_erase_block(dev_handle, block); if (ret) { ESP_LOGE(TAG, "failed to erase block = %" PRIu32, block); break; } } ESP_LOGV(TAG, "erase - start_addr=0x%.16" PRIx64 ", size=0x%zx, result=0x%08x", start_addr, erase_len, ret); return ret; } static esp_err_t nand_flash_blockdev_sync_no_op(esp_blockdev_handle_t handle) { return ESP_OK; } static esp_err_t nand_flash_blockdev_ioctl(esp_blockdev_handle_t handle, const uint8_t cmd, void *args) { if (args == NULL) { return ESP_ERR_INVALID_ARG; } spi_nand_flash_device_t *dev = (spi_nand_flash_device_t *)handle->ctx; switch (cmd) { case ESP_BLOCKDEV_CMD_IS_BAD_BLOCK: { esp_blockdev_cmd_arg_is_bad_block_t *bad_block_status = (esp_blockdev_cmd_arg_is_bad_block_t *) args; esp_err_t ret = nand_is_bad(dev, bad_block_status->num, &bad_block_status->status); return ret; } case ESP_BLOCKDEV_CMD_MARK_BAD_BLOCK: { uint32_t *block = (uint32_t *) args; esp_err_t ret = nand_mark_bad(dev, *block); return ret; } case ESP_BLOCKDEV_CMD_IS_FREE_PAGE: { esp_blockdev_cmd_arg_is_free_page_t *page_free_status = (esp_blockdev_cmd_arg_is_free_page_t *) args; esp_err_t ret = nand_is_free(dev, page_free_status->num, &page_free_status->status); return ret; } case ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS: { esp_blockdev_cmd_arg_ecc_status_t *page_ecc_status = (esp_blockdev_cmd_arg_ecc_status_t *) args; esp_err_t ret = nand_get_ecc_status(dev, page_ecc_status->page_num); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get ECC status for page=%"PRIu32"", page_ecc_status->page_num); return ret; } page_ecc_status->ecc_status = dev->chip.ecc_data.ecc_corrected_bits_status; return ret; } case ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO: { esp_blockdev_cmd_arg_nand_flash_info_t *flash_info = (esp_blockdev_cmd_arg_nand_flash_info_t *)args; flash_info->device_info.manufacturer_id = dev->device_info.manufacturer_id; flash_info->device_info.device_id = dev->device_info.device_id; memcpy(flash_info->device_info.chip_name, dev->device_info.chip_name, sizeof(dev->device_info.chip_name)); memcpy(&flash_info->geometry, &dev->chip, sizeof(nand_flash_geometry_t)); return ESP_OK; } case ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT: { uint32_t *bad_block_count = (uint32_t *)args; uint32_t num_blocks = dev->chip.num_blocks; uint32_t bad_blocks = 0; esp_err_t ret = ESP_OK; for (uint32_t blk = 0; blk < num_blocks; blk++) { bool is_bad = false; ret = nand_is_bad(dev, blk, &is_bad); if (ret == ESP_OK && is_bad) { bad_blocks++; ESP_LOGD(TAG, "bad block num=%"PRIu32"", blk); } else if (ret) { ESP_LOGE(TAG, "Failed to get bad block status for blk=%"PRIu32"", blk); return ret; } } *bad_block_count = bad_blocks; return ret; } case ESP_BLOCKDEV_CMD_COPY_PAGE: { esp_blockdev_cmd_arg_copy_page_t *copy_cmd = (esp_blockdev_cmd_arg_copy_page_t *)args; esp_err_t ret = nand_copy(dev, copy_cmd->src_page, copy_cmd->dst_page); return ret; } /* Full device scan for diagnostics; can be slow on large devices. Intended for debug/health checks only. */ case ESP_BLOCKDEV_CMD_GET_ECC_STATS: { esp_blockdev_cmd_arg_ecc_stats_t *ecc_stats = (esp_blockdev_cmd_arg_ecc_stats_t *)args; if (handle->geometry.write_size == 0) { return ESP_ERR_NOT_SUPPORTED; } uint32_t num_pages = (uint32_t)(handle->geometry.disk_size / handle->geometry.write_size); bool is_free = true; uint32_t ecc_err_total_count = 0; uint32_t ecc_err_exceeding_threshold_count = 0; uint32_t ecc_err_not_corrected_count = 0; esp_err_t ret = ESP_OK; for (uint32_t page = 0; page < num_pages; page++) { ret = nand_is_free(dev, page, &is_free); if (ret == ESP_OK && !is_free) { ret = nand_get_ecc_status(dev, page); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read ecc error for page=%" PRIu32, page); return ret; } if (dev->chip.ecc_data.ecc_corrected_bits_status) { ecc_err_total_count++; if (dev->chip.ecc_data.ecc_corrected_bits_status == NAND_ECC_NOT_CORRECTED) { ecc_err_not_corrected_count++; ESP_LOGD(TAG, "ecc error not corrected for page=%" PRIu32, page); } else if (nand_ecc_exceeds_data_refresh_threshold(dev)) { ecc_err_exceeding_threshold_count++; } } } } ecc_stats->ecc_threshold = dev->chip.ecc_data.ecc_data_refresh_threshold; ecc_stats->ecc_total_err_count = ecc_err_total_count; ecc_stats->ecc_uncorrected_err_count = ecc_err_not_corrected_count; ecc_stats->ecc_exceeding_threshold_err_count = ecc_err_exceeding_threshold_count; return ret; } default: return ESP_ERR_NOT_SUPPORTED; } } static esp_err_t nand_flash_blockdev_release(esp_blockdev_handle_t handle) { esp_err_t res = ESP_OK; spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)handle->ctx; #ifdef CONFIG_IDF_TARGET_LINUX res = nand_emul_deinit(dev_handle); #endif free(dev_handle->work_buffer); free(dev_handle->read_buffer); free(dev_handle->temp_buffer); if (dev_handle->mutex) { vSemaphoreDelete(dev_handle->mutex); } free(dev_handle); free(handle); return res; } static const esp_blockdev_ops_t nand_flash_blockdev_ops = { .read = nand_flash_blockdev_read, .write = nand_flash_blockdev_write, .erase = nand_flash_blockdev_erase, .ioctl = nand_flash_blockdev_ioctl, .sync = nand_flash_blockdev_sync_no_op, .release = nand_flash_blockdev_release, }; esp_err_t nand_flash_get_blockdev(spi_nand_flash_config_t *config, esp_blockdev_handle_t *out_bdl_handle_ptr) { if (config == NULL || out_bdl_handle_ptr == NULL) { return ESP_ERR_INVALID_ARG; } spi_nand_flash_device_t *handle = NULL; esp_err_t ret = nand_init_device(config, &handle); if (ret != ESP_OK) { return ret; } esp_blockdev_t *blockdev = (esp_blockdev_t *) heap_caps_calloc(1, sizeof(esp_blockdev_t), MALLOC_CAP_DEFAULT); if (blockdev == NULL) { free(handle->work_buffer); free(handle->read_buffer); free(handle->temp_buffer); if (handle->mutex) { vSemaphoreDelete(handle->mutex); } free(handle); return ESP_ERR_NO_MEM; } blockdev->ctx = (void *)handle; *out_bdl_handle_ptr = blockdev; blockdev->ops = &nand_flash_blockdev_ops; blockdev->device_flags.read_only = 0; blockdev->device_flags.encrypted = 0; blockdev->device_flags.erase_before_write = 1; blockdev->device_flags.and_type_write = 1; blockdev->device_flags.default_val_after_erase = 1; blockdev->device_flags.reserved = 0; // Set up geometry information uint32_t page_size = handle->chip.page_size; uint32_t block_size = handle->chip.block_size; uint32_t num_blocks = handle->chip.num_blocks; blockdev->geometry.disk_size = num_blocks * block_size; blockdev->geometry.write_size = page_size; blockdev->geometry.read_size = page_size; blockdev->geometry.erase_size = block_size; blockdev->geometry.recommended_write_size = page_size; blockdev->geometry.recommended_read_size = page_size; blockdev->geometry.recommended_erase_size = block_size; return ESP_OK; } ================================================ FILE: spi_nand_flash/src/nand_impl.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2024 Espressif Systems (Shanghai) CO LTD */ #include #include "esp_check.h" #include "esp_err.h" #include "spi_nand_oper.h" #include "nand.h" #include "nand_flash_devices.h" #include "nand_device_types.h" #define ROM_WAIT_THRESHOLD_US 1000 static const char *TAG = "nand_hal"; static esp_err_t detect_chip(spi_nand_flash_device_t *dev) { uint8_t manufacturer_id = 0; esp_err_t ret = ESP_OK; ESP_RETURN_ON_ERROR(spi_nand_read_manufacturer_id(dev, &manufacturer_id), TAG, "%s, Failed to get the manufacturer ID %d", __func__, ret); ESP_LOGD(TAG, "%s: manufacturer_id: %x\n", __func__, manufacturer_id); dev->device_info.manufacturer_id = manufacturer_id; switch (manufacturer_id) { case SPI_NAND_FLASH_ALLIANCE_MI: // Alliance return spi_nand_alliance_init(dev); case SPI_NAND_FLASH_WINBOND_MI: // Winbond return spi_nand_winbond_init(dev); case SPI_NAND_FLASH_GIGADEVICE_MI: // GigaDevice return spi_nand_gigadevice_init(dev); case SPI_NAND_FLASH_MICRON_MI: // Micron return spi_nand_micron_init(dev); case SPI_NAND_FLASH_ZETTA_MI: // Zetta return spi_nand_zetta_init(dev); case SPI_NAND_FLASH_XTX_MI: // XTX return spi_nand_xtx_init(dev); default: return ESP_ERR_INVALID_RESPONSE; } } static esp_err_t enable_quad_io_mode(spi_nand_flash_device_t *dev) { uint8_t io_config; esp_err_t ret = spi_nand_read_register(dev, REG_CONFIG, &io_config); if (ret != ESP_OK) { return ret; } io_config |= (1 << dev->chip.quad_enable_bit_pos); ESP_LOGD(TAG, "%s: quad config register value: 0x%x", __func__, io_config); if (io_config != 0x00) { ret = spi_nand_write_register(dev, REG_CONFIG, io_config); } return ret; } static esp_err_t unprotect_chip(spi_nand_flash_device_t *dev) { uint8_t status; esp_err_t ret = spi_nand_read_register(dev, REG_PROTECT, &status); if (ret != ESP_OK) { return ret; } if (status != 0x00) { ret = spi_nand_write_register(dev, REG_PROTECT, 0); } return ret; } esp_err_t nand_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(config->device_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Spi device pointer can not be NULL"); *handle = heap_caps_calloc(1, sizeof(spi_nand_flash_device_t), MALLOC_CAP_DEFAULT); if (*handle == NULL) { return ESP_ERR_NO_MEM; } memcpy(&(*handle)->config, config, sizeof(spi_nand_flash_config_t)); (*handle)->chip.ecc_data.ecc_status_reg_len_in_bits = 2; (*handle)->chip.ecc_data.ecc_data_refresh_threshold = 4; (*handle)->chip.log2_ppb = 6; // 64 pages per block is standard (*handle)->chip.log2_page_size = 11; // 2048 bytes per page is fairly standard (*handle)->chip.num_planes = 1; (*handle)->chip.flags = 0; ESP_GOTO_ON_ERROR(detect_chip(*handle), fail, TAG, "Failed to detect nand chip"); ESP_GOTO_ON_ERROR(unprotect_chip(*handle), fail, TAG, "Failed to clear protection register"); if (((*handle)->config.io_mode == SPI_NAND_IO_MODE_QOUT || (*handle)->config.io_mode == SPI_NAND_IO_MODE_QIO) && (*handle)->chip.has_quad_enable_bit) { ESP_GOTO_ON_ERROR(enable_quad_io_mode(*handle), fail, TAG, "Failed to enable quad mode"); } (*handle)->chip.page_size = 1 << (*handle)->chip.log2_page_size; (*handle)->chip.block_size = (1 << (*handle)->chip.log2_ppb) * (*handle)->chip.page_size; size_t dma_alignment = spi_nand_get_dma_alignment(); (*handle)->work_buffer = heap_caps_aligned_alloc(dma_alignment, (*handle)->chip.page_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE((*handle)->work_buffer != NULL, ESP_ERR_NO_MEM, fail, TAG, "nomem"); (*handle)->read_buffer = heap_caps_aligned_alloc(dma_alignment, (*handle)->chip.page_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE((*handle)->read_buffer != NULL, ESP_ERR_NO_MEM, fail, TAG, "nomem"); (*handle)->temp_buffer = heap_caps_aligned_alloc(dma_alignment, (*handle)->chip.page_size + dma_alignment, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE((*handle)->temp_buffer != NULL, ESP_ERR_NO_MEM, fail, TAG, "nomem"); (*handle)->mutex = xSemaphoreCreateMutex(); if (!(*handle)->mutex) { ret = ESP_ERR_NO_MEM; goto fail; } return ret; fail: free((*handle)->work_buffer); free((*handle)->read_buffer); free((*handle)->temp_buffer); if ((*handle)->mutex) { vSemaphoreDelete((*handle)->mutex); } free(*handle); return ret; } /***************************************************************************************/ #if CONFIG_NAND_FLASH_VERIFY_WRITE static esp_err_t s_verify_write(spi_nand_flash_device_t *handle, const uint8_t *expected_buffer, uint16_t offset, uint16_t length) { uint8_t *temp_buf = NULL; temp_buf = heap_caps_malloc(length, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_RETURN_ON_FALSE(temp_buf != NULL, ESP_ERR_NO_MEM, TAG, "nomem"); if (spi_nand_read(handle, temp_buf, offset, length)) { ESP_LOGE(TAG, "%s: Failed to read nand flash to verify previous write", __func__); free(temp_buf); return ESP_FAIL; } if (memcmp(temp_buf, expected_buffer, length)) { ESP_LOGE(TAG, "%s: Data mismatch detected. The previously written buffer does not match the read buffer.", __func__); free(temp_buf); return ESP_FAIL; } free(temp_buf); return ESP_OK; } #endif //CONFIG_NAND_FLASH_VERIFY_WRITE static esp_err_t wait_for_ready(spi_nand_flash_device_t *dev, uint32_t expected_operation_time_us, uint8_t *status_out) { if (expected_operation_time_us < ROM_WAIT_THRESHOLD_US) { esp_rom_delay_us(expected_operation_time_us); } while (true) { uint8_t status; ESP_RETURN_ON_ERROR(spi_nand_read_register(dev, REG_STATUS, &status), TAG, ""); if ((status & STAT_BUSY) == 0) { if (status_out) { *status_out = status; } break; } if (expected_operation_time_us >= ROM_WAIT_THRESHOLD_US) { vTaskDelay(1); } } return ESP_OK; } static esp_err_t read_page_and_wait(spi_nand_flash_device_t *dev, uint32_t page, uint8_t *status_out) { ESP_RETURN_ON_ERROR(spi_nand_read_page(dev, page), TAG, ""); return wait_for_ready(dev, dev->chip.read_page_delay_us, status_out); } static esp_err_t program_execute_and_wait(spi_nand_flash_device_t *dev, uint32_t page, uint8_t *status_out) { ESP_RETURN_ON_ERROR(spi_nand_program_execute(dev, page), TAG, ""); return wait_for_ready(dev, dev->chip.program_page_delay_us, status_out); } static uint16_t get_column_address(spi_nand_flash_device_t *handle, uint32_t block, uint32_t offset) { uint16_t column_addr = offset; if ((handle->chip.flags & NAND_FLAG_HAS_READ_PLANE_SELECT) || (handle->chip.flags & NAND_FLAG_HAS_PROG_PLANE_SELECT)) { if (handle->chip.num_planes == 0) { ESP_LOGE(TAG, "Invalid number of planes (0)"); return column_addr; // Return offset without plane selection } uint32_t plane = block % handle->chip.num_planes; // The plane index is the bit following the most significant bit (MSB) of the address. // For a 2048-byte page (2^11), the plane select bit is the 12th bit, and // for a 4096-byte page (2^12), it is the 13th bit. column_addr += plane << (handle->chip.log2_page_size + 1); } return column_addr; } esp_err_t nand_is_bad(spi_nand_flash_device_t *handle, uint32_t block, bool *is_bad_status) { uint32_t first_block_page = block * (1 << handle->chip.log2_ppb); // Markers layout: [bad_block_marker (bytes 0-1)][page_used_marker (bytes 2-3)] uint8_t markers[4]; esp_err_t ret = ESP_OK; ESP_GOTO_ON_ERROR(read_page_and_wait(handle, first_block_page, NULL), fail, TAG, ""); uint16_t column_addr = get_column_address(handle, block, handle->chip.page_size); // Read 4 bytes to include both bad block marker and page status ESP_GOTO_ON_ERROR(spi_nand_read(handle, (uint8_t *) handle->read_buffer, column_addr, 4), fail, TAG, ""); memcpy(&markers, handle->read_buffer, sizeof(markers)); ESP_LOGD(TAG, "is_bad, block=%"PRIu32", page=%"PRIu32",indicator = %02x,%02x", block, first_block_page, markers[0], markers[1]); *is_bad_status = (markers[0] != 0xFF || markers[1] != 0xFF); return ret; fail: ESP_LOGE(TAG, "Error in nand_is_bad %d", ret); return ret; } esp_err_t nand_mark_bad(spi_nand_flash_device_t *handle, uint32_t block) { esp_err_t ret = ESP_OK; uint32_t first_block_page = block * (1 << handle->chip.log2_ppb); // Markers layout: [bad_block_marker (bytes 0-1)][page_used_marker (bytes 2-3)] const uint8_t markers[4] = { 0x00, 0x00, 0xFF, 0xFF }; //// 0x0000 (bad block), 0xFFFF (free) uint8_t status; ESP_LOGD(TAG, "mark_bad, block=%"PRIu32", page=%"PRIu32"", block, first_block_page); ESP_GOTO_ON_ERROR(read_page_and_wait(handle, first_block_page, NULL), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_erase_block(handle, first_block_page), fail, TAG, ""); ESP_GOTO_ON_ERROR(wait_for_ready(handle, handle->chip.erase_block_delay_us, &status), fail, TAG, ""); if ((status & STAT_ERASE_FAILED) != 0) { ret = ESP_ERR_NOT_FINISHED; goto fail; } ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); uint16_t column_addr = get_column_address(handle, block, handle->chip.page_size); // Write 4 bytes: bad block marker (0x0000) + page used marker (0xFFFF) ESP_GOTO_ON_ERROR(spi_nand_program_load(handle, (const uint8_t *) &markers, column_addr, 4), fail, TAG, ""); ESP_GOTO_ON_ERROR(program_execute_and_wait(handle, first_block_page, NULL), fail, TAG, ""); #if CONFIG_NAND_FLASH_VERIFY_WRITE ret = s_verify_write(handle, (uint8_t *)&markers, column_addr, 4); if (ret != ESP_OK) { ESP_LOGE(TAG, "%s: mark_bad write verification failed for block=%"PRIu32" and page=%"PRIu32"", __func__, block, first_block_page); } #endif //CONFIG_NAND_FLASH_VERIFY_WRITE return ret; fail: ESP_LOGE(TAG, "Error in nand_mark_bad %d", ret); return ret; } esp_err_t nand_erase_block(spi_nand_flash_device_t *handle, uint32_t block) { ESP_LOGD(TAG, "erase_block, block=%"PRIu32",", block); esp_err_t ret = ESP_OK; uint8_t status; uint32_t first_block_page = block * (1 << handle->chip.log2_ppb); ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_erase_block(handle, first_block_page), fail, TAG, ""); ESP_GOTO_ON_ERROR(wait_for_ready(handle, handle->chip.erase_block_delay_us, &status), fail, TAG, ""); if ((status & STAT_ERASE_FAILED) != 0) { ret = ESP_ERR_NOT_FINISHED; } return ret; fail: ESP_LOGE(TAG, "Error in nand_erase %d", ret); return ret; } static esp_err_t nand_erase_good_block(spi_nand_flash_device_t *handle, uint32_t block) { ESP_LOGD(TAG, "erase_block, block=%"PRIu32",", block); esp_err_t ret = ESP_OK; bool is_bad = false; ret = nand_is_bad(handle, block, &is_bad); if (ret != ESP_OK) { ESP_LOGE(TAG, "Error querying bad block status for block=%"PRIu32"", block); return ret; } if (is_bad) { ESP_LOGD(TAG, "skip erase of bad block=%"PRIu32"", block); return ESP_OK; } ret = nand_erase_block(handle, block); return ret; } esp_err_t nand_erase_chip(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; for (int i = 0; i < handle->chip.num_blocks; i++) { esp_err_t err = nand_erase_good_block(handle, (uint32_t)i); if (err == ESP_ERR_NOT_FINISHED) { ret = ESP_ERR_NOT_FINISHED; } else if (err != ESP_OK) { ESP_LOGE(TAG, "Error in nand_erase_chip %d", err); return err; } } return ret; } esp_err_t nand_prog(spi_nand_flash_device_t *handle, uint32_t page, const uint8_t *data) { ESP_LOGV(TAG, "prog, page=%"PRIu32",", page); esp_err_t ret = ESP_OK; // Markers layout: [bad_block_marker (bytes 0-1)][page_used_marker (bytes 2-3)] // For good block with used page: bad=0xFFFF, used=0x0000 uint8_t markers[4] = { 0xFF, 0xFF, 0x00, 0x00 }; uint8_t status; uint32_t block = page >> handle->chip.log2_ppb; uint16_t column_addr = get_column_address(handle, block, 0); ESP_GOTO_ON_ERROR(read_page_and_wait(handle, page, NULL), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_program_load(handle, data, column_addr, handle->chip.page_size), fail, TAG, ""); // Write 4 bytes: bad block marker (0xFFFF - good block) + page used marker (0x0000 - used) ESP_GOTO_ON_ERROR(spi_nand_program_load(handle, (uint8_t *)&markers, column_addr + handle->chip.page_size, 4), fail, TAG, ""); ESP_GOTO_ON_ERROR(program_execute_and_wait(handle, page, &status), fail, TAG, ""); if ((status & STAT_PROGRAM_FAILED) != 0) { ESP_LOGE(TAG, "prog failed, page=%"PRIu32", status=0x%02x", page, status); return ESP_ERR_NOT_FINISHED; } #if CONFIG_NAND_FLASH_VERIFY_WRITE ret = s_verify_write(handle, data, column_addr, handle->chip.page_size); if (ret != ESP_OK) { ESP_LOGE(TAG, "%s: prog page=%"PRIu32" write verification failed", __func__, page); } ret = s_verify_write(handle, (uint8_t *)&markers, column_addr + handle->chip.page_size, 4); if (ret != ESP_OK) { ESP_LOGE(TAG, "%s: prog page=%"PRIu32" markers write verification failed", __func__, page); } #endif //CONFIG_NAND_FLASH_VERIFY_WRITE return ret; fail: ESP_LOGE(TAG, "Error in nand_prog %d", ret); return ret; } esp_err_t nand_is_free(spi_nand_flash_device_t *handle, uint32_t page, bool *is_free_status) { esp_err_t ret = ESP_OK; // Markers layout: [bad_block_marker (bytes 0-1)][page_used_marker (bytes 2-3)] uint8_t markers[4]; ESP_GOTO_ON_ERROR(read_page_and_wait(handle, page, NULL), fail, TAG, ""); uint32_t block = page >> handle->chip.log2_ppb; uint16_t column_addr = get_column_address(handle, block, handle->chip.page_size); // Read 4 bytes to get both bad block marker and page used marker ESP_GOTO_ON_ERROR(spi_nand_read(handle, (uint8_t *)handle->read_buffer, column_addr, 4), fail, TAG, ""); memcpy(&markers, handle->read_buffer, sizeof(markers)); ESP_LOGD(TAG, "is free, page=%"PRIu32", used_marker=%02x,%02x,", page, markers[2], markers[3]); *is_free_status = (markers[2] == 0xFF && markers[3] == 0xFF); return ret; fail: ESP_LOGE(TAG, "Error in nand_is_free %d", ret); return ret; } #define PACK_2BITS_STATUS(status, bit1, bit0) ((((status) & (bit1)) << 1) | ((status) & (bit0))) #define PACK_3BITS_STATUS(status, bit2, bit1, bit0) ((((status) & (bit2)) << 2) | (((status) & (bit1)) << 1) | ((status) & (bit0))) static bool is_ecc_error(spi_nand_flash_device_t *dev, uint8_t status) { bool is_ecc_err = false; nand_ecc_status_t bits_corrected_status = NAND_ECC_OK; if (dev->chip.ecc_data.ecc_status_reg_len_in_bits == 2) { bits_corrected_status = PACK_2BITS_STATUS(status, STAT_ECC1, STAT_ECC0); } else if (dev->chip.ecc_data.ecc_status_reg_len_in_bits == 3) { bits_corrected_status = PACK_3BITS_STATUS(status, STAT_ECC2, STAT_ECC1, STAT_ECC0); } else { bits_corrected_status = NAND_ECC_MAX; } dev->chip.ecc_data.ecc_corrected_bits_status = bits_corrected_status; if (bits_corrected_status) { if (bits_corrected_status == NAND_ECC_MAX) { ESP_LOGE(TAG, "%s: Error while initializing value of ecc_status_reg_len_in_bits", __func__); is_ecc_err = true; } else if (bits_corrected_status == NAND_ECC_NOT_CORRECTED) { is_ecc_err = true; } } return is_ecc_err; } esp_err_t nand_read(spi_nand_flash_device_t *handle, uint32_t page, size_t offset, size_t length, uint8_t *data) { ESP_LOGV(TAG, "read, page=%"PRIu32", offset=%d, length=%d", page, offset, length); assert(page < handle->chip.num_blocks * (1 << handle->chip.log2_ppb)); esp_err_t ret = ESP_OK; uint8_t status; ESP_GOTO_ON_ERROR(read_page_and_wait(handle, page, &status), fail, TAG, ""); if (is_ecc_error(handle, status)) { ESP_LOGD(TAG, "read ecc error, page=%"PRIu32"", page); return ESP_FAIL; } uint32_t block = page >> handle->chip.log2_ppb; uint16_t column_addr = get_column_address(handle, block, offset); ESP_GOTO_ON_ERROR(spi_nand_read(handle, data, column_addr, length), fail, TAG, ""); return ret; fail: ESP_LOGE(TAG, "Error in nand_read %d", ret); return ret; } esp_err_t nand_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst) { ESP_LOGD(TAG, "copy, src=%"PRIu32", dst=%"PRIu32"", src, dst); esp_err_t ret = ESP_OK; #if CONFIG_NAND_FLASH_VERIFY_WRITE uint8_t *temp_buf = NULL; #endif //CONFIG_NAND_FLASH_VERIFY_WRITE uint8_t status; ESP_GOTO_ON_ERROR(read_page_and_wait(handle, src, &status), fail, TAG, ""); if (is_ecc_error(handle, status)) { ESP_LOGD(TAG, "copy, ecc error"); return ESP_FAIL; } ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); uint32_t src_block = src >> handle->chip.log2_ppb; uint32_t dst_block = dst >> handle->chip.log2_ppb; uint16_t src_column_addr = get_column_address(handle, src_block, 0); uint16_t dst_column_addr = get_column_address(handle, dst_block, 0); if (src_column_addr != dst_column_addr) { // In a 2 plane structure of the flash, if the pages are not on the same plane, the data must be copied through RAM. uint8_t *copy_buf = heap_caps_malloc(handle->chip.page_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE(copy_buf, ESP_ERR_NO_MEM, fail, TAG, "Failed to allocate copy buffer"); ESP_GOTO_ON_ERROR(spi_nand_read(handle, copy_buf, src_column_addr, handle->chip.page_size), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_write_enable(handle), fail, TAG, ""); ESP_GOTO_ON_ERROR(spi_nand_program_load(handle, copy_buf, dst_column_addr, handle->chip.page_size), fail, TAG, ""); // Write 4 bytes: bad block marker (0xFFFF - good block) + page used marker (0x0000 - used) uint8_t markers[4] = { 0xFF, 0xFF, 0x00, 0x00 }; ESP_GOTO_ON_ERROR(spi_nand_program_load(handle, (uint8_t *)&markers, dst_column_addr + handle->chip.page_size, 4), fail, TAG, ""); ESP_GOTO_ON_ERROR(program_execute_and_wait(handle, dst, &status), fail, TAG, ""); if ((status & STAT_PROGRAM_FAILED) != 0) { ESP_LOGD(TAG, "copy, prog failed"); return ESP_ERR_NOT_FINISHED; } free(copy_buf); } ESP_GOTO_ON_ERROR(program_execute_and_wait(handle, dst, &status), fail, TAG, ""); if ((status & STAT_PROGRAM_FAILED) != 0) { ESP_LOGD(TAG, "copy, prog failed"); return ESP_ERR_NOT_FINISHED; } #if CONFIG_NAND_FLASH_VERIFY_WRITE // First read src page data from cache to temp_buf if (src_column_addr != dst_column_addr) { // Then read src page data from nand memory array and load it in cache ESP_GOTO_ON_ERROR(read_page_and_wait(handle, src, &status), fail, TAG, ""); if (is_ecc_error(handle, status)) { ESP_LOGE(TAG, "%s: dst_page=%"PRIu32" read, ecc error", __func__, dst); goto fail; } } temp_buf = heap_caps_malloc(handle->chip.page_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); ESP_RETURN_ON_FALSE(temp_buf != NULL, ESP_ERR_NO_MEM, TAG, "nomem"); if (spi_nand_read(handle, temp_buf, src_column_addr, handle->chip.page_size)) { ESP_LOGE(TAG, "%s: Failed to read src_page=%"PRIu32"", __func__, src); goto fail; } // Then read dst page data from nand memory array and load it in cache ESP_GOTO_ON_ERROR(read_page_and_wait(handle, dst, &status), fail, TAG, ""); if (is_ecc_error(handle, status)) { ESP_LOGE(TAG, "%s: dst_page=%"PRIu32" read, ecc error", __func__, dst); goto fail; } // Check if the data in the src page matches the dst page ret = s_verify_write(handle, temp_buf, dst_column_addr, handle->chip.page_size); if (ret != ESP_OK) { ESP_LOGE(TAG, "%s: dst_page=%"PRIu32" write verification failed", __func__, dst); } free(temp_buf); #endif //CONFIG_NAND_FLASH_VERIFY_WRITE return ret; fail: #if CONFIG_NAND_FLASH_VERIFY_WRITE free(temp_buf); #endif //CONFIG_NAND_FLASH_VERIFY_WRITE ESP_LOGE(TAG, "Error in nand_copy %d", ret); return ret; } esp_err_t nand_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page) { esp_err_t ret = ESP_OK; uint8_t status; ESP_GOTO_ON_ERROR(read_page_and_wait(handle, page, &status), fail, TAG, ""); if (is_ecc_error(handle, status)) { ESP_LOGD(TAG, "read ecc error, page=%"PRIu32"", page); } return ret; fail: ESP_LOGE(TAG, "Error in nand_is_ecc_error %d", ret); return ret; } ================================================ FILE: spi_nand_flash/src/nand_impl_linux.c ================================================ /* * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "esp_check.h" #include "esp_err.h" #include "spi_nand_flash.h" #include "nand.h" #include "nand_linux_mmap_emul.h" static const char *TAG = "nand_linux"; /* OOB marker layout at page data offset `page_size` (matches nand_impl.c HW path): * bytes 0-1: bad-block marker (0xFFFF = good / erased, 0x0000 = bad) * bytes 2-3: page-used marker (0xFFFF = free, 0x0000 = used after program) */ static const uint8_t s_oob_used_page_markers[4] = { 0xFF, 0xFF, 0x00, 0x00 }; static const uint8_t s_oob_mark_bad_markers[4] = { 0x00, 0x00, 0xFF, 0xFF }; /* Start of erase block `block` in the mmap file: ppb slots of (data + OOB) per page. */ static esp_err_t linux_mmap_block_file_offset(const spi_nand_flash_device_t *handle, uint32_t block, size_t *out_offset) { ESP_RETURN_ON_FALSE(out_offset != NULL, ESP_ERR_INVALID_ARG, TAG, "out_offset is NULL"); ESP_RETURN_ON_FALSE(handle->chip.num_blocks > 0, ESP_ERR_INVALID_STATE, TAG, "num_blocks is 0"); ESP_RETURN_ON_FALSE(block < handle->chip.num_blocks, ESP_ERR_INVALID_ARG, TAG, "block index out of range"); const uint64_t ppb = 1ull << handle->chip.log2_ppb; const uint64_t bytes_per_block = ppb * (uint64_t)handle->chip.emulated_page_size; const uint64_t off = (uint64_t)block * bytes_per_block; ESP_RETURN_ON_FALSE(off <= (uint64_t)SIZE_MAX, ESP_ERR_INVALID_SIZE, TAG, "mmap block offset overflow"); *out_offset = (size_t)off; return ESP_OK; } static esp_err_t detect_chip(spi_nand_flash_device_t *dev) { esp_err_t ret = ESP_OK; spi_nand_flash_config_t *config = &dev->config; ESP_GOTO_ON_ERROR(nand_emul_init(dev, config->emul_conf), fail, TAG, ""); dev->chip.page_size = (1 << dev->chip.log2_page_size); dev->chip.emulated_page_oob = 64; // The default page size is 2048, so the OOB size is 64. if (dev->chip.page_size == 512) { dev->chip.emulated_page_oob = 16; } else if (dev->chip.page_size == 2048) { dev->chip.emulated_page_oob = 64; } else if (dev->chip.page_size == 4096) { dev->chip.emulated_page_oob = 128; } dev->chip.emulated_page_size = dev->chip.page_size + dev->chip.emulated_page_oob; /* chip.block_size: user-visible data per erase block (matches nand_impl.c / BDL). * File layout interleaves OOB after each page; use file_bytes_per_block for num_blocks * and linux_mmap_block_file_offset() for mmap byte addresses. */ dev->chip.block_size = (1u << dev->chip.log2_ppb) * dev->chip.page_size; const uint32_t file_bytes_per_block = (1u << dev->chip.log2_ppb) * dev->chip.emulated_page_size; if (dev->chip.block_size == 0 || file_bytes_per_block == 0) { ESP_LOGE(TAG, "Invalid block size (0)"); ret = ESP_ERR_INVALID_SIZE; goto fail; } if (config->emul_conf->flash_file_size % dev->chip.block_size != 0) { ESP_LOGE(TAG, "flash_file_size (0x%" PRIx64 ") is not a multiple of chip.block_size (0x%" PRIx32 ")", config->emul_conf->flash_file_size, dev->chip.block_size); ret = ESP_ERR_INVALID_SIZE; goto fail; } dev->chip.num_blocks = config->emul_conf->flash_file_size / file_bytes_per_block; dev->chip.erase_block_delay_us = 3000; dev->chip.program_page_delay_us = 630; dev->chip.read_page_delay_us = 60; /* Device info for GET_NAND_FLASH_INFO ioctl (host tests expect non-zero IDs and non-empty chip name) */ dev->device_info.manufacturer_id = 0xEF; /* Synthetic ID for Linux emulator */ dev->device_info.device_id = 0xE100; strncpy(dev->device_info.chip_name, "Linux NAND mmap emul", sizeof(dev->device_info.chip_name) - 1); dev->device_info.chip_name[sizeof(dev->device_info.chip_name) - 1] = '\0'; fail: return ret; } esp_err_t nand_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(config->emul_conf != NULL, ESP_ERR_INVALID_ARG, TAG, "Linux mmap emulation configuration pointer can not be NULL"); *handle = heap_caps_calloc(1, sizeof(spi_nand_flash_device_t), MALLOC_CAP_DEFAULT); if (*handle == NULL) { return ESP_ERR_NO_MEM; } memcpy(&(*handle)->config, config, sizeof(spi_nand_flash_config_t)); (*handle)->chip.ecc_data.ecc_status_reg_len_in_bits = 2; (*handle)->chip.ecc_data.ecc_data_refresh_threshold = 4; (*handle)->chip.log2_ppb = 6; // 64 pages per block is standard (*handle)->chip.log2_page_size = 11; // 2048 bytes per page is fairly standard (*handle)->chip.num_planes = 1; (*handle)->chip.flags = 0; (*handle)->chip.page_size = 1 << (*handle)->chip.log2_page_size; (*handle)->chip.block_size = (1 << (*handle)->chip.log2_ppb) * (*handle)->chip.page_size; ESP_GOTO_ON_ERROR(detect_chip(*handle), fail, TAG, "Failed to detect nand chip"); (*handle)->work_buffer = heap_caps_malloc((*handle)->chip.page_size, MALLOC_CAP_DEFAULT); ESP_GOTO_ON_FALSE((*handle)->work_buffer != NULL, ESP_ERR_NO_MEM, fail, TAG, "nomem"); (*handle)->read_buffer = heap_caps_malloc((*handle)->chip.page_size, MALLOC_CAP_DEFAULT); ESP_GOTO_ON_FALSE((*handle)->read_buffer != NULL, ESP_ERR_NO_MEM, fail, TAG, "nomem"); (*handle)->mutex = xSemaphoreCreateMutex(); if (!(*handle)->mutex) { ret = ESP_ERR_NO_MEM; goto fail; } return ret; fail: free((*handle)->work_buffer); free((*handle)->read_buffer); if ((*handle)->mutex) { vSemaphoreDelete((*handle)->mutex); } free(*handle); *handle = NULL; return ret; } esp_err_t nand_is_bad(spi_nand_flash_device_t *handle, uint32_t block, bool *is_bad_status) { uint8_t markers[4]; size_t block_offset = 0; ESP_RETURN_ON_ERROR(linux_mmap_block_file_offset(handle, block, &block_offset), TAG, "nand_is_bad: mmap block offset failed"); ESP_RETURN_ON_ERROR(nand_emul_read(handle, block_offset + handle->chip.page_size, markers, sizeof(markers)), TAG, "Error in nand_is_bad"); ESP_LOGD(TAG, "is_bad, block=%"PRIu32", file_off=%zu,indicator = %02x,%02x", block, block_offset, markers[0], markers[1]); *is_bad_status = (markers[0] != 0xFF || markers[1] != 0xFF); return ESP_OK; } esp_err_t nand_mark_bad(spi_nand_flash_device_t *handle, uint32_t block) { size_t block_base = 0; uint64_t first_block_page = (uint64_t)block * (1ull << handle->chip.log2_ppb); ESP_LOGD(TAG, "mark_bad, block=%"PRIu32", first_page=%"PRIu64"", block, first_block_page); ESP_RETURN_ON_ERROR(linux_mmap_block_file_offset(handle, block, &block_base), TAG, "nand_mark_bad: mmap block offset failed"); ESP_RETURN_ON_ERROR(nand_emul_erase_block(handle, block_base), TAG, "nand_mark_bad: erase failed"); ESP_RETURN_ON_ERROR(nand_emul_write(handle, block_base + handle->chip.page_size, s_oob_mark_bad_markers, sizeof(s_oob_mark_bad_markers)), TAG, "nand_mark_bad: OOB marker write failed"); return ESP_OK; } esp_err_t nand_erase_block(spi_nand_flash_device_t *handle, uint32_t block) { ESP_LOGD(TAG, "erase_block, block=%"PRIu32",", block); esp_err_t ret = ESP_OK; size_t address = 0; ESP_RETURN_ON_ERROR(linux_mmap_block_file_offset(handle, block, &address), TAG, "nand_erase_block: mmap block offset failed"); ESP_RETURN_ON_ERROR(nand_emul_erase_block(handle, address), TAG, "Error in nand_erase %x", ret); return ESP_OK; } static esp_err_t nand_erase_good_block(spi_nand_flash_device_t *handle, uint32_t block) { ESP_LOGD(TAG, "erase_block, block=%"PRIu32",", block); esp_err_t ret = ESP_OK; bool is_bad = false; ret = nand_is_bad(handle, block, &is_bad); if (ret != ESP_OK) { ESP_LOGE(TAG, "Error querying bad block status for block=%"PRIu32"", block); return ret; } if (is_bad) { ESP_LOGD(TAG, "skip erase of bad block=%"PRIu32"", block); return ESP_OK; } ret = nand_erase_block(handle, block); return ret; } esp_err_t nand_erase_chip(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; for (int i = 0; i < handle->chip.num_blocks; i++) { esp_err_t err = nand_erase_good_block(handle, (uint32_t)i); if (err == ESP_ERR_NOT_FINISHED) { ret = ESP_ERR_NOT_FINISHED; } else if (err != ESP_OK) { ESP_LOGE(TAG, "Error in nand_erase_chip %d", err); return err; } } return ret; } esp_err_t nand_prog(spi_nand_flash_device_t *handle, uint32_t page, const uint8_t *data) { ESP_LOGV(TAG, "prog, page=%"PRIu32",", page); esp_err_t ret = ESP_OK; uint32_t data_offset = page * handle->chip.emulated_page_size; ESP_RETURN_ON_ERROR(nand_emul_write(handle, data_offset, data, handle->chip.page_size), TAG, "Error in nand_prog %d", ret); ESP_RETURN_ON_ERROR(nand_emul_write(handle, data_offset + handle->chip.page_size, s_oob_used_page_markers, sizeof(s_oob_used_page_markers)), TAG, "Error in nand_prog %d", ret); return ret; } esp_err_t nand_is_free(spi_nand_flash_device_t *handle, uint32_t page, bool *is_free_status) { esp_err_t ret = ESP_OK; uint8_t markers[4]; ESP_RETURN_ON_ERROR(nand_emul_read(handle, page * handle->chip.emulated_page_size + handle->chip.page_size, markers, sizeof(markers)), TAG, "Error in nand_is_free %d", ret); ESP_LOGD(TAG, "is free, page=%"PRIu32", used_marker=%02x%02x,", page, markers[2], markers[3]); *is_free_status = (markers[2] == 0xFF && markers[3] == 0xFF); return ret; } esp_err_t nand_read(spi_nand_flash_device_t *handle, uint32_t page, size_t offset, size_t length, uint8_t *data) { ESP_LOGV(TAG, "read, page=%"PRIu32", offset=%ld, length=%ld", page, offset, length); assert(page < handle->chip.num_blocks * (1 << handle->chip.log2_ppb)); esp_err_t ret = ESP_OK; ESP_RETURN_ON_ERROR(nand_emul_read(handle, page * handle->chip.emulated_page_size + offset, data, length), TAG, "Error in nand_read %d", ret); return ret; } esp_err_t nand_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst) { ESP_LOGD(TAG, "copy, src=%"PRIu32", dst=%"PRIu32"", src, dst); esp_err_t ret = ESP_OK; uint32_t dst_offset = dst * handle->chip.emulated_page_size; uint32_t src_offset = src * handle->chip.emulated_page_size; ESP_RETURN_ON_ERROR(nand_emul_read(handle, (size_t)src_offset, (void *)handle->read_buffer, handle->chip.page_size), TAG, "Error in nand_copy %d", ret); ESP_RETURN_ON_ERROR(nand_emul_write(handle, (size_t)dst_offset, (void *)handle->read_buffer, handle->chip.page_size), TAG, "Error in nand_copy %d", ret); ESP_RETURN_ON_ERROR(nand_emul_write(handle, (size_t)dst_offset + handle->chip.page_size, s_oob_used_page_markers, sizeof(s_oob_used_page_markers)), TAG, "Error in nand_copy %d", ret); return ret; } esp_err_t nand_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page) { esp_err_t ret = ESP_OK; return ret; } ================================================ FILE: spi_nand_flash/src/nand_impl_wrap.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "esp_check.h" #include "esp_err.h" #include "spi_nand_flash.h" #include "nand.h" #include "nand_impl.h" esp_err_t nand_wrap_is_bad(spi_nand_flash_device_t *handle, uint32_t block, bool *is_bad_status) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_is_bad(handle, block, is_bad_status); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_mark_bad(spi_nand_flash_device_t *handle, uint32_t block) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_mark_bad(handle, block); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_erase_chip(spi_nand_flash_device_t *handle) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_erase_chip(handle); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_erase_block(spi_nand_flash_device_t *handle, uint32_t block) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_erase_block(handle, block); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_prog(spi_nand_flash_device_t *handle, uint32_t page, const uint8_t *data) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_prog(handle, page, data); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_is_free(spi_nand_flash_device_t *handle, uint32_t page, bool *is_free_status) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_is_free(handle, page, is_free_status); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_read(spi_nand_flash_device_t *handle, uint32_t page, size_t offset, size_t length, uint8_t *data) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_read(handle, page, offset, length, data); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_copy(handle, src, dst); xSemaphoreGive(handle->mutex); return ret; } esp_err_t nand_wrap_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page) { esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); ret = nand_get_ecc_status(handle, page); xSemaphoreGive(handle->mutex); return ret; } ================================================ FILE: spi_nand_flash/src/nand_linux_mmap_emul.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "esp_log.h" #include "nand_linux_mmap_emul.h" #include "nand.h" static const char *TAG = "nand_linux_emul"; // Initialize NAND flash Emulation // Exposes direct pointer to the memory mapped file created by nand_partition_mmap // No address alignment is performed static esp_err_t nand_emul_mmap_init(nand_mmap_emul_handle_t *emul_handle) { if (emul_handle->mem_file_buf != NULL) { ESP_LOGE(TAG, "NAND flash already initialized"); return ESP_ERR_INVALID_STATE; } // Open the backing file. If nand_emul_init already opened it via mkstemp(), // mem_file_fd is valid and we skip open(). if (emul_handle->mem_file_fd == -1) { emul_handle->mem_file_fd = open(emul_handle->file_mmap_ctrl.flash_file_name, O_RDWR | O_CREAT, 0600); } if (emul_handle->mem_file_fd == -1) { ESP_LOGE(TAG, "Failed to open NAND file %s: %s", emul_handle->file_mmap_ctrl.flash_file_name, strerror(errno)); return ESP_ERR_NOT_FOUND; } // Set file size if (ftruncate(emul_handle->mem_file_fd, emul_handle->file_mmap_ctrl.flash_file_size) != 0) { ESP_LOGE(TAG, "Failed to set NAND file size: %s", strerror(errno)); close(emul_handle->mem_file_fd); return ESP_ERR_INVALID_SIZE; } // Map file to memory emul_handle->mem_file_buf = mmap(NULL, emul_handle->file_mmap_ctrl.flash_file_size, PROT_READ | PROT_WRITE, MAP_SHARED, emul_handle->mem_file_fd, 0); if (emul_handle->mem_file_buf == MAP_FAILED) { ESP_LOGE(TAG, "Failed to mmap NAND file: %s", strerror(errno)); close(emul_handle->mem_file_fd); return ESP_ERR_NO_MEM; } // Initialize with 0xFF (erased state) memset(emul_handle->mem_file_buf, 0xFF, emul_handle->file_mmap_ctrl.flash_file_size); ESP_LOGI(TAG, "NAND flash emulation initialized: %s (size: %zu bytes)", emul_handle->file_mmap_ctrl.flash_file_name, emul_handle->file_mmap_ctrl.flash_file_size); return ESP_OK; } // Cleanup NAND flash emulation static esp_err_t nand_emul_mmap_deinit(nand_mmap_emul_handle_t *emul_handle) { if (emul_handle->mem_file_buf == NULL) { return ESP_ERR_INVALID_STATE; } // Unmap memory if (munmap(emul_handle->mem_file_buf, emul_handle->file_mmap_ctrl.flash_file_size) != 0) { ESP_LOGE(TAG, "Failed to munmap NAND file: %s", strerror(errno)); return ESP_ERR_INVALID_RESPONSE; } // Close file if (close(emul_handle->mem_file_fd) != 0) { ESP_LOGE(TAG, "Failed to close NAND file: %s", strerror(errno)); return ESP_ERR_INVALID_RESPONSE; } // Remove file if requested if (!emul_handle->file_mmap_ctrl.keep_dump) { if (remove(emul_handle->file_mmap_ctrl.flash_file_name) != 0) { ESP_LOGE(TAG, "Failed to remove NAND file: %s", strerror(errno)); return ESP_ERR_INVALID_RESPONSE; } } // Reset state emul_handle->mem_file_buf = NULL; emul_handle->mem_file_fd = -1; memset(&emul_handle->file_mmap_ctrl, 0, sizeof(emul_handle->file_mmap_ctrl)); return ESP_OK; } esp_err_t nand_emul_init(spi_nand_flash_device_t *handle, nand_file_mmap_emul_config_t *cfg) { nand_mmap_emul_handle_t *emul_handle = heap_caps_calloc(1, sizeof(nand_mmap_emul_handle_t), MALLOC_CAP_DEFAULT); if (emul_handle == NULL) { return ESP_ERR_NO_MEM; } emul_handle->mem_file_buf = NULL; emul_handle->mem_file_fd = -1; #ifdef CONFIG_NAND_ENABLE_STATS emul_handle->stats.read_ops = 0; emul_handle->stats.write_ops = 0; emul_handle->stats.erase_ops = 0; emul_handle->stats.read_bytes = 0; emul_handle->stats.write_bytes = 0; #endif //CONFIG_NAND_ENABLE_STATS handle->emul_handle = emul_handle; // Store configuration if (!cfg->flash_file_size) { cfg->flash_file_size = EMULATED_NAND_SIZE; } if (*(cfg->flash_file_name)) { // Named file: copy the caller-provided path strlcpy(emul_handle->file_mmap_ctrl.flash_file_name, cfg->flash_file_name, sizeof(emul_handle->file_mmap_ctrl.flash_file_name)); } else { // Temporary file: copy the template and let mkstemp resolve it now, // so that nand_emul_mmap_init sees the real path and an already-open fd. strlcpy(emul_handle->file_mmap_ctrl.flash_file_name, "/tmp/idf-nand-XXXXXX", sizeof(emul_handle->file_mmap_ctrl.flash_file_name)); emul_handle->mem_file_fd = mkstemp(emul_handle->file_mmap_ctrl.flash_file_name); if (emul_handle->mem_file_fd == -1) { ESP_LOGE(TAG, "Failed to create temporary NAND file: %s", strerror(errno)); free(emul_handle); handle->emul_handle = NULL; return ESP_ERR_NOT_FOUND; } } emul_handle->file_mmap_ctrl.flash_file_size = cfg->flash_file_size ? cfg->flash_file_size : EMULATED_NAND_SIZE; emul_handle->file_mmap_ctrl.keep_dump = cfg->keep_dump; esp_err_t err = nand_emul_mmap_init(emul_handle); if (err != ESP_OK) { free(emul_handle); handle->emul_handle = NULL; } return err; } esp_err_t nand_emul_deinit(spi_nand_flash_device_t *handle) { if (handle == NULL || handle->emul_handle == NULL) { return ESP_OK; } esp_err_t ret = nand_emul_mmap_deinit(handle->emul_handle); free(handle->emul_handle); handle->emul_handle = NULL; return ret; } // Read from NAND esp_err_t nand_emul_read(spi_nand_flash_device_t *handle, size_t addr, void *dst, size_t size) { nand_mmap_emul_handle_t *emul_handle = handle->emul_handle; if (emul_handle == NULL) { return ESP_ERR_INVALID_STATE; } if (emul_handle->mem_file_buf == NULL) { return ESP_ERR_INVALID_STATE; } size_t limit = emul_handle->file_mmap_ctrl.flash_file_size; /* Avoid computing addr + size for the bounds test: with unsigned size_t that sum can * wrap, so addr + size > limit could be false even when the access runs past the end. * If size > limit the range is invalid. Otherwise size <= limit makes (limit - size) * well defined, and addr > limit - size is equivalent to addr + size > limit. */ if (size > limit || addr > limit - size) { return ESP_ERR_INVALID_SIZE; } void *src_addr = emul_handle->mem_file_buf + addr; memcpy(dst, src_addr, size); #ifdef CONFIG_NAND_ENABLE_STATS emul_handle->stats.read_ops++; emul_handle->stats.read_bytes += size; #endif return ESP_OK; } // Write to NAND esp_err_t nand_emul_write(spi_nand_flash_device_t *handle, size_t addr, const void *src, size_t size) { nand_mmap_emul_handle_t *emul_handle = handle->emul_handle; if (emul_handle == NULL) { return ESP_ERR_INVALID_STATE; } if (emul_handle->mem_file_buf == NULL) { return ESP_ERR_INVALID_STATE; } size_t limit = emul_handle->file_mmap_ctrl.flash_file_size; /* Overflow-safe range check; see comment in nand_emul_read. */ if (size > limit || addr > limit - size) { return ESP_ERR_INVALID_SIZE; } void *dst_addr = emul_handle->mem_file_buf + addr; // Emulate NAND behavior: can only change 1->0 for (size_t i = 0; i < size; i++) { ((uint8_t *)dst_addr)[i] &= ((uint8_t *)src)[i]; } #ifdef CONFIG_NAND_ENABLE_STATS emul_handle->stats.write_ops++; emul_handle->stats.write_bytes += size; #endif return ESP_OK; } // Erase NAND memory range esp_err_t nand_emul_erase_block(spi_nand_flash_device_t *handle, size_t offset) { nand_mmap_emul_handle_t *emul_handle = handle->emul_handle; if (emul_handle == NULL) { return ESP_ERR_INVALID_STATE; } if (emul_handle->mem_file_buf == NULL) { return ESP_ERR_INVALID_STATE; } /* Erase one physical block in the mmap file: ppb pages × (data + OOB) each. * chip.block_size is data-only (BDL); nbytes is the on-disk stride for this layer. */ const size_t nbytes = (size_t)(1u << handle->chip.log2_ppb) * (size_t)handle->chip.emulated_page_size; size_t limit = emul_handle->file_mmap_ctrl.flash_file_size; /* Same principle as read/write: do not test offset + nbytes (unsigned wrap). */ if (nbytes > limit || offset > limit - nbytes) { return ESP_ERR_INVALID_SIZE; } void *dst_addr = emul_handle->mem_file_buf + offset; memset(dst_addr, 0xFF, nbytes); #ifdef CONFIG_NAND_ENABLE_STATS emul_handle->stats.erase_ops++; #endif return ESP_OK; } #ifdef CONFIG_NAND_ENABLE_STATS // Clear statistics void nand_emul_clear_stats(spi_nand_flash_device_t *handle) { nand_mmap_emul_handle_t *emul_handle = handle->emul_handle; if (emul_handle == NULL) { return; } emul_handle->stats.read_ops = 0; emul_handle->stats.write_ops = 0; emul_handle->stats.erase_ops = 0; emul_handle->stats.read_bytes = 0; emul_handle->stats.write_bytes = 0; } #endif ================================================ FILE: spi_nand_flash/src/nand_wl_blockdev.c ================================================ /* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "esp_check.h" #include "esp_log.h" #include "nand.h" #include "esp_blockdev.h" #include "esp_nand_blockdev.h" #include "nand_device_types.h" static const char *TAG = "nand_wl_blockdev"; /************************************************************************************** ************************************************************************************** * Block Device Layer interface implementation ************************************************************************************** */ static esp_err_t spi_nand_flash_wl_blockdev_read(esp_blockdev_handle_t handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) { uint32_t page_size = (uint32_t)handle->geometry.read_size; if (page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if (dst_buf == NULL || dst_buf_size < data_read_len) { return ESP_ERR_INVALID_ARG; } if ((src_addr % page_size) != 0 || (data_read_len % page_size) != 0) { ESP_LOGE(TAG, "Source address 0x%.16" PRIx64 " or Read length 0x%08" PRIx32 " is not aligned to Page size %" PRIu32, src_addr, (uint32_t)data_read_len, page_size); return ESP_ERR_INVALID_SIZE; } if (src_addr + data_read_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Read range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)((esp_blockdev_handle_t)handle->ctx)->ctx; uint32_t start_page_id = (uint32_t)(src_addr >> dev_handle->chip.log2_page_size); uint32_t page_count = (uint32_t)(data_read_len >> dev_handle->chip.log2_page_size); esp_err_t ret = ESP_OK; for (uint32_t page_id = start_page_id; page_id < (start_page_id + page_count); page_id++) { ret = spi_nand_flash_read_page(dev_handle, dst_buf, page_id); if (ret) { ESP_LOGE(TAG, "%s, Failed to read the page, result=0x%08x", __func__, ret); return ret; } dst_buf += page_size; } ESP_LOGV(TAG, "read - src_addr=0x%.16" PRIx64 ", size=0x%08" PRIx32 ", result=0x%08x", src_addr, (uint32_t)data_read_len, ret); return ret; } static esp_err_t spi_nand_flash_wl_blockdev_write(esp_blockdev_handle_t handle, const uint8_t *src_buf, uint64_t dst_addr, size_t data_write_len) { uint32_t page_size = (uint32_t)handle->geometry.write_size; if (handle->device_flags.read_only || page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if (src_buf == NULL) { return ESP_ERR_INVALID_ARG; } if ((dst_addr % page_size) != 0 || (data_write_len % page_size) != 0) { ESP_LOGE(TAG, "Write address 0x%" PRIx64 " not aligned to page size %" PRIu32, dst_addr, page_size); return ESP_ERR_INVALID_SIZE; } if (dst_addr + data_write_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Write range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)((esp_blockdev_handle_t)handle->ctx)->ctx; uint32_t start_page_id = (uint32_t)(dst_addr >> dev_handle->chip.log2_page_size); uint32_t page_count = (uint32_t)(data_write_len >> dev_handle->chip.log2_page_size); esp_err_t ret = ESP_OK; for (uint32_t page_id = start_page_id; page_id < (start_page_id + page_count); page_id++) { ret = spi_nand_flash_write_page(dev_handle, src_buf, page_id); if (ret) { ESP_LOGE(TAG, "%s, Failed to write the page", __func__); return ret; } src_buf += page_size; } ESP_LOGV(TAG, "write - dst_addr=0x%.16" PRIx64 ", size=0x%08" PRIx32 ", result=0x%08x", dst_addr, (uint32_t)data_write_len, ret); return ret; } static esp_err_t spi_nand_flash_wl_blockdev_erase(esp_blockdev_handle_t handle, uint64_t start_addr, size_t erase_len) { uint32_t page_size = (uint32_t)handle->geometry.write_size; uint32_t erase_size = (uint32_t)handle->geometry.erase_size; if (handle->device_flags.read_only || erase_size == 0 || page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if ((start_addr % erase_size) != 0 || (erase_len % erase_size) != 0 || (erase_size % page_size != 0)) { ESP_LOGE(TAG, "Start address 0x%" PRIx64 " or Erase length %zu not aligned to Erase size %" PRIu32, start_addr, erase_len, erase_size); return ESP_ERR_INVALID_SIZE; } if (start_addr + erase_len > handle->geometry.disk_size) { ESP_LOGE(TAG, "Erase range exceeds device bounds"); return ESP_ERR_INVALID_SIZE; } spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)((esp_blockdev_handle_t)handle->ctx)->ctx; uint32_t page_count = (uint32_t)(erase_len >> dev_handle->chip.log2_page_size); uint32_t start_page_id = (uint32_t)(start_addr >> dev_handle->chip.log2_page_size); esp_err_t ret = ESP_OK; for (uint32_t page_id = start_page_id; page_id < (start_page_id + page_count); page_id++) { ret = spi_nand_flash_trim(dev_handle, page_id); if (ret != ESP_OK) { ESP_LOGE(TAG, "%s, Failed to trim the page", __func__); return ret; } } ret = spi_nand_flash_gc(dev_handle); ESP_LOGV(TAG, "erase - start_addr=0x%.16" PRIx64 ", size=0x%zx, result=0x%08x", start_addr, erase_len, ret); return ret; } static esp_err_t spi_nand_flash_wl_blockdev_sync(esp_blockdev_handle_t handle) { spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)((esp_blockdev_handle_t)handle->ctx)->ctx; return spi_nand_flash_sync(dev_handle); } static esp_err_t spi_nand_flash_wl_blockdev_ioctl(esp_blockdev_handle_t handle, const uint8_t cmd, void *args) { if (args == NULL) { return ESP_ERR_INVALID_ARG; } esp_err_t ret = ESP_OK; esp_blockdev_handle_t nand_bdl = (esp_blockdev_handle_t)handle->ctx; spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)nand_bdl->ctx; switch (cmd) { case ESP_BLOCKDEV_CMD_MARK_DELETED: { esp_blockdev_cmd_arg_erase_t *trim_arg = (esp_blockdev_cmd_arg_erase_t *)args; uint32_t page_size = (uint32_t)handle->geometry.write_size; if (page_size == 0) { return ESP_ERR_NOT_SUPPORTED; } if ((trim_arg->start_addr % page_size) != 0 || (trim_arg->erase_len % page_size != 0)) { ESP_LOGE(TAG, "Start address/length 0x%" PRIx64 "/%zu not aligned to page size %" PRIu32, trim_arg->start_addr, trim_arg->erase_len, page_size); return ESP_ERR_INVALID_SIZE; } uint32_t page_count = (uint32_t)(trim_arg->erase_len >> dev_handle->chip.log2_page_size); uint32_t start_page_id = (uint32_t)(trim_arg->start_addr >> dev_handle->chip.log2_page_size); uint32_t total_pages = (uint32_t)(handle->geometry.disk_size / page_size); if ((start_page_id + page_count) > total_pages) { ESP_LOGE(TAG, "TRIM range exceeds device bounds: start_page_id=%"PRIu32" count=%"PRIu32" total=%"PRIu32"", start_page_id, page_count, total_pages); return ESP_ERR_INVALID_ARG; } for (uint32_t page_id = start_page_id; page_id < (start_page_id + page_count); page_id++) { ret = spi_nand_flash_trim(dev_handle, page_id); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to trim page %"PRIu32"", page_id); return ret; } } } break; case ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO: case ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT: case ESP_BLOCKDEV_CMD_GET_ECC_STATS: { ret = nand_bdl->ops->ioctl(nand_bdl, cmd, args); } break; default: return ESP_ERR_NOT_SUPPORTED; } return ret; } static esp_err_t spi_nand_flash_wl_blockdev_release(esp_blockdev_handle_t handle) { esp_blockdev_handle_t nand_handle = (esp_blockdev_handle_t)handle->ctx; spi_nand_flash_device_t *dev_handle = (spi_nand_flash_device_t *)nand_handle->ctx; nand_wl_detach_ops(dev_handle); esp_err_t ret = nand_handle->ops->release(nand_handle); free(handle); return ret; } static const esp_blockdev_ops_t spi_nand_flash_wl_blockdev_ops = { .read = spi_nand_flash_wl_blockdev_read, .write = spi_nand_flash_wl_blockdev_write, .erase = spi_nand_flash_wl_blockdev_erase, .ioctl = spi_nand_flash_wl_blockdev_ioctl, .sync = spi_nand_flash_wl_blockdev_sync, .release = spi_nand_flash_wl_blockdev_release, }; esp_err_t spi_nand_flash_wl_get_blockdev(esp_blockdev_handle_t nand_bdl, esp_blockdev_handle_t *out_bdl_handle_ptr) { // Validate input parameters ESP_RETURN_ON_FALSE(nand_bdl != NULL, ESP_ERR_INVALID_ARG, TAG, "nand_bdl cannot be NULL"); ESP_RETURN_ON_FALSE(out_bdl_handle_ptr != NULL, ESP_ERR_INVALID_ARG, TAG, "out_bdl_handle_ptr cannot be NULL"); spi_nand_flash_device_t *dev = (spi_nand_flash_device_t *)nand_bdl->ctx; ESP_RETURN_ON_FALSE(dev != NULL, ESP_ERR_INVALID_ARG, TAG, "spi_nand_flash_device_t pointer cannot be NULL"); // Validate that Flash BDL operations are available ESP_RETURN_ON_FALSE(nand_bdl->ops != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL ops cannot be NULL"); ESP_RETURN_ON_FALSE(nand_bdl->ops->read != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL read operation is required"); ESP_RETURN_ON_FALSE(nand_bdl->ops->write != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL write operation is required"); ESP_RETURN_ON_FALSE(nand_bdl->ops->erase != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL erase operation is required"); ESP_RETURN_ON_FALSE(nand_bdl->ops->ioctl != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL ioctl operation is required"); ESP_RETURN_ON_FALSE(nand_bdl->ops->release != NULL, ESP_ERR_INVALID_STATE, TAG, "Flash BDL release operation is required"); // Initialize ops with dhara operations esp_err_t ret = nand_wl_attach_ops(dev); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to attach wear-leveling operations"); return ret; } // Initialize dhara lib if (dev->ops->init == NULL) { ESP_LOGE(TAG, "WL init operation is NULL"); nand_wl_detach_ops(dev); return ESP_FAIL; } ret = dev->ops->init(dev, nand_bdl); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize wear-leveling layer"); nand_wl_detach_ops(dev); return ret; } esp_blockdev_t *blockdev = (esp_blockdev_t *) heap_caps_calloc(1, sizeof(esp_blockdev_t), MALLOC_CAP_DEFAULT); if (blockdev == NULL) { dev->ops->deinit(dev); nand_wl_detach_ops(dev); return ESP_ERR_NO_MEM; } blockdev->ctx = (void *)nand_bdl; blockdev->device_flags.read_only = 0; blockdev->device_flags.encrypted = 0; blockdev->device_flags.erase_before_write = 0; blockdev->device_flags.and_type_write = 0; blockdev->device_flags.default_val_after_erase = 1; blockdev->device_flags.reserved = 0; blockdev->ops = &spi_nand_flash_wl_blockdev_ops; // Set up geometry information (BDL exposes logical pages as "sectors" per esp_blockdev API) uint32_t num_pages; spi_nand_flash_get_page_count(dev, &num_pages); uint32_t page_size = dev->chip.page_size; blockdev->geometry.disk_size = (uint64_t)num_pages * page_size; blockdev->geometry.write_size = page_size; blockdev->geometry.read_size = page_size; blockdev->geometry.erase_size = page_size; blockdev->geometry.recommended_write_size = page_size; blockdev->geometry.recommended_read_size = page_size; blockdev->geometry.recommended_erase_size = page_size; *out_bdl_handle_ptr = blockdev; return ESP_OK; } ================================================ FILE: spi_nand_flash/src/spi_nand_flash_test_helpers.c ================================================ /* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "spi_nand_flash_test_helpers.h" #define SPI_NAND_FLASH_PATTERN_SEED 0x12345678U void spi_nand_flash_fill_buffer(uint8_t *dst, size_t count) { uint32_t *p = (uint32_t *)dst; for (size_t i = 0; i < count; ++i) { p[i] = SPI_NAND_FLASH_PATTERN_SEED + (uint32_t)i; } } void spi_nand_flash_fill_buffer_seeded(uint8_t *dst, size_t count, uint32_t seed) { uint32_t *p = (uint32_t *)dst; for (size_t i = 0; i < count; ++i) { p[i] = seed + (uint32_t)i; } } int spi_nand_flash_check_buffer(const uint8_t *src, size_t count) { const uint32_t *p = (const uint32_t *)src; for (size_t i = 0; i < count; ++i) { if (p[i] != SPI_NAND_FLASH_PATTERN_SEED + (uint32_t)i) { return (int)(i + 1); } } return 0; } int spi_nand_flash_check_buffer_seeded(const uint8_t *src, size_t count, uint32_t seed) { const uint32_t *p = (const uint32_t *)src; for (size_t i = 0; i < count; ++i) { if (p[i] != seed + (uint32_t)i) { return (int)(i + 1); } } return 0; } ================================================ FILE: spi_nand_flash/src/spi_nand_oper.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #include #include "spi_nand_oper.h" #include "driver/spi_master.h" #include "esp_memory_utils.h" #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE == 1 #include "esp_private/esp_cache_private.h" #endif esp_err_t spi_nand_execute_transaction(spi_nand_flash_device_t *handle, spi_nand_transaction_t *transaction) { uint8_t half_duplex = handle->config.flags & SPI_DEVICE_HALFDUPLEX; if (!half_duplex) { uint32_t len = transaction->miso_len > transaction->mosi_len ? transaction->miso_len : transaction->mosi_len; transaction->miso_len = len; transaction->mosi_len = len; } spi_transaction_ext_t e = { .base = { .flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_DUMMY | transaction->flags, .rxlength = transaction->miso_len * 8, .rx_buffer = transaction->miso_data, .length = transaction->mosi_len * 8, .tx_buffer = transaction->mosi_data, .addr = transaction->address, .cmd = transaction->command }, .address_bits = transaction->address_bytes * 8, .command_bits = 8, .dummy_bits = transaction->dummy_bits }; if (transaction->flags & SPI_TRANS_USE_TXDATA) { assert(transaction->mosi_len <= 4 && "SPI_TRANS_USE_TXDATA used for a long transaction"); memcpy(e.base.tx_data, transaction->mosi_data, transaction->mosi_len); } if (transaction->flags & SPI_TRANS_USE_RXDATA) { assert(transaction->miso_len <= 4 && "SPI_TRANS_USE_RXDATA used for a long transaction"); } esp_err_t ret = spi_device_transmit(handle->config.device_handle, (spi_transaction_t *) &e); if (ret == ESP_OK) { if (transaction->flags == SPI_TRANS_USE_RXDATA) { memcpy(transaction->miso_data, e.base.rx_data, transaction->miso_len); } } return ret; } esp_err_t spi_nand_read_manufacturer_id(spi_nand_flash_device_t *handle, uint8_t *manufacturer_id) { esp_err_t ret = ESP_OK; spi_nand_transaction_t t = { .command = CMD_READ_ID, .address = 0, // This normally selects the manufacturer id. Some chips ignores it, but still expects 8 dummy bits here .address_bytes = 1, .miso_len = 1, .miso_data = manufacturer_id, .flags = SPI_TRANS_USE_RXDATA, }; ret = spi_nand_execute_transaction(handle, &t); return ret; } esp_err_t spi_nand_read_device_id(spi_nand_flash_device_t *handle, uint8_t *device_id, uint8_t length) { esp_err_t ret = ESP_OK; spi_nand_transaction_t t = { .command = CMD_READ_ID, .address = 0, .address_bytes = 2, .miso_len = length, .miso_data = device_id, .flags = SPI_TRANS_USE_RXDATA, }; ret = spi_nand_execute_transaction(handle, &t); return ret; } esp_err_t spi_nand_read_register(spi_nand_flash_device_t *handle, uint8_t reg, uint8_t *val) { spi_nand_transaction_t t = { .command = CMD_READ_REGISTER, .address_bytes = 1, .address = reg, .miso_len = 1, .miso_data = val, .flags = SPI_TRANS_USE_RXDATA, }; return spi_nand_execute_transaction(handle, &t); } esp_err_t spi_nand_write_register(spi_nand_flash_device_t *handle, uint8_t reg, uint8_t val) { spi_nand_transaction_t t = { .command = CMD_SET_REGISTER, .address_bytes = 1, .address = reg, .mosi_len = 1, .mosi_data = &val, .flags = SPI_TRANS_USE_TXDATA, }; return spi_nand_execute_transaction(handle, &t); } esp_err_t spi_nand_write_enable(spi_nand_flash_device_t *handle) { spi_nand_transaction_t t = { .command = CMD_WRITE_ENABLE }; return spi_nand_execute_transaction(handle, &t); } esp_err_t spi_nand_read_page(spi_nand_flash_device_t *handle, uint32_t page) { spi_nand_transaction_t t = { .command = CMD_PAGE_READ, .address_bytes = 3, .address = page }; return spi_nand_execute_transaction(handle, &t); } size_t spi_nand_get_dma_alignment(void) { size_t alignment; #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE == 1 esp_cache_get_alignment(MALLOC_CAP_DMA, &alignment); #else // For non-L1CACHE targets, use DMA alignment of 4 bytes alignment = 4; #endif return alignment; } static uint16_t check_length_alignment(spi_nand_flash_device_t *handle, uint16_t length, size_t alignment) { uint16_t data_len = length; bool is_length_unaligned = (length & (alignment - 1)) ? true : false; if (is_length_unaligned) { if (length < alignment) { data_len = ((length + alignment) & ~(alignment - 1)); } else { data_len = ((length + (alignment - 1)) & ~(alignment - 1)); } } if (!(handle->config.flags & SPI_DEVICE_HALFDUPLEX)) { data_len = data_len + alignment; } return data_len; } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) static bool spi_nand_buf_dma_aligned(const void *buf, size_t alignment) { return esp_ptr_dma_capable(buf) && (((uintptr_t)buf % alignment) == 0); } /** * Prepare the TX buffer for a NAND program-load SPI transaction. * * When @p length is DMA-aligned but @p user_buf is not DMA-capable or not properly * aligned (ESP-IDF >= 5.2), data is copied into handle->temp_buffer and * @p *out_manual_dma is set so the caller sets SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL. * * When @p length is not DMA-aligned we cannot pad the write (extra bytes would be * programmed into the NAND page); @p *out_manual_dma stays false and the SPI driver * handles alignment internally. */ static void spi_nand_tx_prepare_write_buffers(spi_nand_flash_device_t *handle, const uint8_t *user_buf, uint16_t length, const uint8_t **out_data_write, bool *out_manual_dma) { *out_data_write = user_buf; *out_manual_dma = false; size_t alignment = spi_nand_get_dma_alignment(); uint16_t aligned_len = check_length_alignment(handle, length, alignment); if (aligned_len != length) { return; } if (!spi_nand_buf_dma_aligned(user_buf, alignment)) { memcpy(handle->temp_buffer, user_buf, length); *out_data_write = handle->temp_buffer; } *out_manual_dma = true; } #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) /** * Prepare the RX buffer for a NAND read SPI transaction. * * Decides whether the caller's buffer can be used directly for DMA, or whether the * receive must be bounced through handle->temp_buffer. * * Bounce is needed when: * 1. @p length is not DMA-aligned — the padded amount is read into temp_buffer * (*out_data_read_len > length). * 2. @p length is DMA-aligned but @p user_buf is not DMA-capable or not properly * aligned (ESP-IDF >= 5.2) — the original length is read into temp_buffer * (*out_data_read_len == length). * * When *out_data_read != @p user_buf after a successful transaction, copy the useful * data back into @p user_buf. The copy is not always from offset 0: in full-duplex * fast read, byte 0 of the receive buffer is a dummy clocked during the command / * address phase and must be skipped (see spi_nand_fast_read). */ static void spi_nand_rx_prepare_read_buffers(spi_nand_flash_device_t *handle, uint8_t *user_buf, uint16_t length, uint8_t **out_data_read, uint16_t *out_data_read_len) { size_t alignment = spi_nand_get_dma_alignment(); uint16_t aligned_len = check_length_alignment(handle, length, alignment); if (aligned_len != length) { *out_data_read = handle->temp_buffer; *out_data_read_len = aligned_len; return; } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) if (!spi_nand_buf_dma_aligned(user_buf, alignment)) { *out_data_read = handle->temp_buffer; *out_data_read_len = length; return; } #endif *out_data_read = user_buf; *out_data_read_len = length; } static esp_err_t spi_nand_quad_read(spi_nand_flash_device_t *handle, uint8_t *data, uint16_t column, uint16_t length) { uint32_t spi_flags = SPI_TRANS_MODE_QIO; uint8_t cmd = CMD_READ_X4; uint8_t dummy_bits = 8; uint8_t *data_read; uint16_t data_read_len; spi_nand_rx_prepare_read_buffers(handle, data, length, &data_read, &data_read_len); if (handle->config.io_mode == SPI_NAND_IO_MODE_QIO) { spi_flags |= SPI_TRANS_MULTILINE_ADDR; cmd = CMD_READ_QIO; dummy_bits = 4; } spi_nand_transaction_t t = { .command = cmd, .address_bytes = 2, .address = column, .miso_len = data_read_len, .miso_data = data_read, .dummy_bits = dummy_bits, .flags = spi_flags, }; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) t.flags |= SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL; #endif esp_err_t ret = spi_nand_execute_transaction(handle, &t); if (ret == ESP_OK && (data_read != data)) { memcpy(data, data_read, length); } return ret; } static esp_err_t spi_nand_dual_read(spi_nand_flash_device_t *handle, uint8_t *data, uint16_t column, uint16_t length) { uint32_t spi_flags = SPI_TRANS_MODE_DIO; uint8_t cmd = CMD_READ_X2; uint8_t dummy_bits = 8; uint8_t *data_read; uint16_t data_read_len; spi_nand_rx_prepare_read_buffers(handle, data, length, &data_read, &data_read_len); if (handle->config.io_mode == SPI_NAND_IO_MODE_DIO) { spi_flags |= SPI_TRANS_MULTILINE_ADDR; cmd = CMD_READ_DIO; dummy_bits = 4; } spi_nand_transaction_t t = { .command = cmd, .address_bytes = 2, .address = column, .miso_len = data_read_len, .miso_data = data_read, .dummy_bits = dummy_bits, .flags = spi_flags, }; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) t.flags |= SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL; #endif esp_err_t ret = spi_nand_execute_transaction(handle, &t); if (ret == ESP_OK && (data_read != data)) { memcpy(data, data_read, length); } return ret; } static esp_err_t spi_nand_fast_read(spi_nand_flash_device_t *handle, uint8_t *data, uint16_t column, uint16_t length) { uint8_t half_duplex = handle->config.flags & SPI_DEVICE_HALFDUPLEX; uint8_t *data_read; uint16_t data_read_len; spi_nand_rx_prepare_read_buffers(handle, data, length, &data_read, &data_read_len); spi_nand_transaction_t t = { .command = CMD_READ_FAST, .address_bytes = 2, .address = column, .miso_len = data_read_len, .miso_data = data_read, }; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) t.flags = SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL; #endif if (half_duplex) { t.dummy_bits = 8; } esp_err_t ret = spi_nand_execute_transaction(handle, &t); if (ret != ESP_OK) { goto fail; } if (data_read != data) { if (!half_duplex) { /* In full-duplex fast read, byte 0 in the receive buffer is a dummy clocked in during the command/address phase — skip it. check_length_alignment() guarantees data_read_len > length for full-duplex (it unconditionally pads by 'alignment'), so the +1 offset is always within bounds.*/ assert(data_read_len > length); memcpy(data, data_read + 1, length); } else { memcpy(data, data_read, length); } } fail: return ret; } esp_err_t spi_nand_read(spi_nand_flash_device_t *handle, uint8_t *data, uint16_t column, uint16_t length) { if (handle->config.io_mode == SPI_NAND_IO_MODE_DOUT || handle->config.io_mode == SPI_NAND_IO_MODE_DIO) { return spi_nand_dual_read(handle, data, column, length); } else if (handle->config.io_mode == SPI_NAND_IO_MODE_QOUT || handle->config.io_mode == SPI_NAND_IO_MODE_QIO) { return spi_nand_quad_read(handle, data, column, length); } return spi_nand_fast_read(handle, data, column, length); } esp_err_t spi_nand_program_execute(spi_nand_flash_device_t *handle, uint32_t page) { spi_nand_transaction_t t = { .command = CMD_PROGRAM_EXECUTE, .address_bytes = 3, .address = page }; return spi_nand_execute_transaction(handle, &t); } esp_err_t spi_nand_program_load(spi_nand_flash_device_t *handle, const uint8_t *data, uint16_t column, uint16_t length) { uint8_t cmd = CMD_PROGRAM_LOAD; uint32_t spi_flags = 0; if (handle->config.io_mode == SPI_NAND_IO_MODE_QOUT || handle->config.io_mode == SPI_NAND_IO_MODE_QIO) { cmd = CMD_PROGRAM_LOAD_X4; spi_flags = SPI_TRANS_MODE_QIO; } const uint8_t *data_write = data; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) bool manual_dma = false; spi_nand_tx_prepare_write_buffers(handle, data, length, &data_write, &manual_dma); if (manual_dma) { spi_flags |= SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL; } #endif spi_nand_transaction_t t = { .command = cmd, .address_bytes = 2, .address = column, .mosi_len = length, .mosi_data = data_write, .flags = spi_flags, }; return spi_nand_execute_transaction(handle, &t); } esp_err_t spi_nand_erase_block(spi_nand_flash_device_t *handle, uint32_t page) { spi_nand_transaction_t t = { .command = CMD_ERASE_BLOCK, .address_bytes = 3, .address = page }; return spi_nand_execute_transaction(handle, &t); } ================================================ FILE: spi_nand_flash/test_app/CMakeLists.txt ================================================ # This is the project CMakeLists.txt file for the test subproject cmake_minimum_required(VERSION 3.5) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(spi_nand_flash_test) ================================================ FILE: spi_nand_flash/test_app/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | ================================================ FILE: spi_nand_flash/test_app/main/CMakeLists.txt ================================================ set(src "test_app_main.c") if(CONFIG_NAND_FLASH_ENABLE_BDL) list(APPEND src "test_spi_nand_flash_bdl.c") else() list(APPEND src "test_spi_nand_flash.c") endif() set(priv_reqs unity esp_timer) if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3") list(APPEND priv_reqs esp_driver_spi) else() list(APPEND priv_reqs driver) endif() idf_component_register(SRCS ${src} PRIV_REQUIRES ${priv_reqs} WHOLE_ARCHIVE ) ================================================ FILE: spi_nand_flash/test_app/main/idf_component.yml ================================================ dependencies: espressif/spi_nand_flash: version: '*' override_path: '../../' ================================================ FILE: spi_nand_flash/test_app/main/test_app_main.c ================================================ /* * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "unity.h" #include "unity_test_utils.h" #include "esp_heap_caps.h" #include "esp_newlib.h" #include "unity_test_utils_memory.h" void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { esp_reent_cleanup(); //clean up some of the newlib's lazy allocations unity_utils_evaluate_leaks_direct(32); } void app_main(void) { printf("Running spi_nand_flash component tests\n"); unity_run_menu(); } ================================================ FILE: spi_nand_flash/test_app/main/test_spi_nand_flash.c ================================================ /* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include #include "esp_attr.h" #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/spi_master.h" #include "spi_nand_flash.h" #include "nand_private/nand_impl_wrap.h" #include "unity.h" #include "soc/spi_pins.h" #include "sdkconfig.h" #include "spi_nand_flash_test_helpers.h" // Pin mapping // ESP32 (VSPI) #ifdef CONFIG_IDF_TARGET_ESP32 #define HOST_ID SPI3_HOST #define PIN_MOSI SPI3_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI3_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI3_IOMUX_PIN_NUM_CLK #define PIN_CS SPI3_IOMUX_PIN_NUM_CS #define PIN_WP SPI3_IOMUX_PIN_NUM_WP #define PIN_HD SPI3_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #else // Other chips (SPI2/HSPI) #define HOST_ID SPI2_HOST #define PIN_MOSI SPI2_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI2_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI2_IOMUX_PIN_NUM_CLK #define PIN_CS SPI2_IOMUX_PIN_NUM_CS #define PIN_WP SPI2_IOMUX_PIN_NUM_WP #define PIN_HD SPI2_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #endif static void do_single_write_test(spi_nand_flash_device_t *flash, uint32_t start_page, uint16_t page_count); static void setup_bus(spi_host_device_t host_id) { spi_bus_config_t spi_bus_cfg = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadhd_io_num = PIN_HD, .quadwp_io_num = PIN_WP, .max_transfer_sz = 64, }; esp_err_t ret = spi_bus_initialize(host_id, &spi_bus_cfg, SPI_DMA_CHAN); TEST_ESP_OK(ret); } static void setup_chip(spi_device_handle_t *spi, uint8_t flags) { setup_bus(HOST_ID); spi_device_interface_config_t devcfg = { .clock_speed_hz = 40 * 1000 * 1000, .mode = 0, .spics_io_num = PIN_CS, .queue_size = 10, .flags = flags, }; TEST_ESP_OK(spi_bus_add_device(HOST_ID, &devcfg, spi)); } static void setup_nand_flash(spi_nand_flash_device_t **out_handle, spi_device_handle_t *spi_handle, spi_nand_flash_io_mode_t mode, uint8_t flags) { spi_device_handle_t spi; setup_chip(&spi, flags); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = flags, .io_mode = mode, }; spi_nand_flash_device_t *device_handle; TEST_ESP_OK(spi_nand_flash_init_device(&nand_flash_config, &device_handle)); *out_handle = device_handle; *spi_handle = spi; } static void deinit_nand_flash(spi_nand_flash_device_t *flash, spi_device_handle_t spi) { spi_nand_flash_deinit_device(flash); spi_bus_remove_device(spi); spi_bus_free(HOST_ID); } TEST_CASE("erase nand flash", "[spi_nand_flash]") { spi_nand_flash_device_t *nand_flash_device_handle; spi_device_handle_t spi; setup_nand_flash(&nand_flash_device_handle, &spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); TEST_ESP_OK(spi_nand_erase_chip(nand_flash_device_handle)); do_single_write_test(nand_flash_device_handle, 1, 1); deinit_nand_flash(nand_flash_device_handle, spi); } static void do_single_write_test(spi_nand_flash_device_t *flash, uint32_t start_page, uint16_t page_count) { uint8_t *temp_buf = NULL; uint8_t *pattern_buf = NULL; uint32_t page_size, num_pages; TEST_ESP_OK(spi_nand_flash_get_page_count(flash, &num_pages)); TEST_ESP_OK(spi_nand_flash_get_page_size(flash, &page_size)); TEST_ESP_OK((start_page + page_count) > num_pages); pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); temp_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(temp_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); int64_t read_time = 0; int64_t write_time = 0; for (uint32_t i = start_page; i < (start_page + page_count); i++) { int64_t start = esp_timer_get_time(); TEST_ESP_OK(spi_nand_flash_write_page(flash, pattern_buf, i)); write_time += esp_timer_get_time() - start; memset((void *)temp_buf, 0x00, page_size); start = esp_timer_get_time(); TEST_ESP_OK(spi_nand_flash_read_page(flash, temp_buf, i)); read_time += esp_timer_get_time() - start; TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(temp_buf, page_size / sizeof(uint32_t))); } free(pattern_buf); free(temp_buf); printf("Wrote %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, write_time, (float)page_size * page_count / write_time * 1000); printf("Read %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, read_time, (float)page_size * page_count / read_time * 1000); } static void test_write_nand_flash_pages(spi_nand_flash_io_mode_t mode, uint8_t flags) { uint32_t num_pages, page_size; spi_nand_flash_device_t *nand_flash_device_handle; spi_device_handle_t spi; setup_nand_flash(&nand_flash_device_handle, &spi, mode, flags); TEST_ESP_OK(spi_nand_flash_get_page_count(nand_flash_device_handle, &num_pages)); TEST_ESP_OK(spi_nand_flash_get_page_size(nand_flash_device_handle, &page_size)); printf("Number of pages: %" PRIu32 ", Page size: %" PRIu32 "\n", num_pages, page_size); do_single_write_test(nand_flash_device_handle, 1, 16); do_single_write_test(nand_flash_device_handle, 16, 32); do_single_write_test(nand_flash_device_handle, 32, 64); do_single_write_test(nand_flash_device_handle, 64, 128); do_single_write_test(nand_flash_device_handle, num_pages / 2, 32); do_single_write_test(nand_flash_device_handle, num_pages / 2, 256); do_single_write_test(nand_flash_device_handle, num_pages - 20, 16); deinit_nand_flash(nand_flash_device_handle, spi); } TEST_CASE("read and write nand flash pages (sio half-duplex)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("read and write nand flash pages (sio full-duplex)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_SIO, 0); //set flags 0 for full-duplex mode } TEST_CASE("read and write nand flash pages (dio)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_DIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("read and write nand flash pages (dout)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_DOUT, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("read and write nand flash pages (qio)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_QIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("read and write nand flash pages (qout)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_QOUT, SPI_DEVICE_HALFDUPLEX); } static void test_copy_nand_flash_pages(spi_nand_flash_io_mode_t mode, uint8_t flags) { spi_nand_flash_device_t *nand_flash_device_handle; spi_device_handle_t spi; setup_nand_flash(&nand_flash_device_handle, &spi, mode, flags); uint32_t num_pages, page_size; uint32_t src_page = 10; uint32_t dst_page = 11; TEST_ESP_OK(spi_nand_flash_get_page_count(nand_flash_device_handle, &num_pages)); TEST_ESP_OK(spi_nand_flash_get_page_size(nand_flash_device_handle, &page_size)); printf("Number of pages: %" PRIu32 ", Page size: %" PRIu32 "\n", num_pages, page_size); if (src_page < num_pages && dst_page < num_pages) { do_single_write_test(nand_flash_device_handle, src_page, 1); TEST_ESP_OK(spi_nand_flash_copy_page(nand_flash_device_handle, src_page, dst_page)); } deinit_nand_flash(nand_flash_device_handle, spi); } TEST_CASE("copy nand flash pages (sio half-duplex)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("copy nand flash pages (sio full-duplex)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_SIO, 0); } TEST_CASE("copy nand flash pages (dio)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_DIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("copy nand flash pages (dout)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_DOUT, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("copy nand flash pages (qio)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_QIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("copy nand flash pages (qout)", "[spi_nand_flash]") { test_copy_nand_flash_pages(SPI_NAND_IO_MODE_QOUT, SPI_DEVICE_HALFDUPLEX); } static void test_nand_operations(spi_nand_flash_io_mode_t mode, uint8_t flags) { spi_nand_flash_device_t *nand_flash_device_handle; spi_device_handle_t spi; uint32_t num_pages, page_size, block_size; setup_nand_flash(&nand_flash_device_handle, &spi, mode, flags); TEST_ESP_OK(spi_nand_flash_get_page_count(nand_flash_device_handle, &num_pages)); TEST_ESP_OK(spi_nand_flash_get_page_size(nand_flash_device_handle, &page_size)); TEST_ESP_OK(spi_nand_flash_get_block_size(nand_flash_device_handle, &block_size)); printf("Number of pages: %" PRIu32 ", Page size: %" PRIu32 "\n", num_pages, page_size); uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DMA); TEST_ASSERT_NOT_NULL(pattern_buf); uint8_t *temp_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DMA); TEST_ASSERT_NOT_NULL(temp_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); bool is_page_free = true; uint32_t src_block = 20; uint32_t dst_block = 21; uint32_t test_page = src_block * (block_size / page_size); // pages_per_block uint32_t dst_page = dst_block * (block_size / page_size); TEST_ESP_OK(nand_wrap_erase_block(nand_flash_device_handle, src_block)); TEST_ESP_OK(nand_wrap_erase_block(nand_flash_device_handle, dst_block)); TEST_ASSERT_TRUE(test_page < num_pages); // Verify if test_page is free TEST_ESP_OK(nand_wrap_is_free(nand_flash_device_handle, test_page, &is_page_free)); TEST_ASSERT_TRUE(is_page_free == true); // Write/program test_page TEST_ESP_OK(nand_wrap_prog(nand_flash_device_handle, test_page, pattern_buf)); // Verify if test_page is used/programmed TEST_ESP_OK(nand_wrap_is_free(nand_flash_device_handle, test_page, &is_page_free)); TEST_ASSERT_TRUE(is_page_free == false); // read test_page and verify with pattern_buf TEST_ESP_OK(nand_wrap_read(nand_flash_device_handle, test_page, 0, page_size, temp_buf)); TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(temp_buf, page_size / sizeof(uint32_t))); // Copy test_page to dst_page TEST_ESP_OK(nand_wrap_copy(nand_flash_device_handle, test_page, dst_page)); // read dst_page and verify with pattern_buf TEST_ESP_OK(nand_wrap_read(nand_flash_device_handle, dst_page, 0, page_size, temp_buf)); TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(temp_buf, page_size / sizeof(uint32_t))); free(pattern_buf); free(temp_buf); deinit_nand_flash(nand_flash_device_handle, spi); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (sio half-duplex)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (sio full-duplex)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_SIO, 0); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (dio)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_DIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (dout)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_DOUT, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (qio)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_QIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works (bypassing dhara) (qout)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_QOUT, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("Fail safe test if chip is not detected", "[spi_nand_flash]") { spi_device_handle_t spi; spi_bus_config_t spi_bus_cfg = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_MISO, // Initialize with wrong pin .max_transfer_sz = 64, }; esp_err_t ret = spi_bus_initialize(HOST_ID, &spi_bus_cfg, SPI_DMA_CHAN); TEST_ESP_OK(ret); spi_device_interface_config_t devcfg = { .clock_speed_hz = 40 * 1000 * 1000, .mode = 0, .spics_io_num = PIN_CS, .queue_size = 10, .flags = SPI_DEVICE_HALFDUPLEX, }; TEST_ESP_OK(spi_bus_add_device(HOST_ID, &devcfg, &spi)); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .io_mode = SPI_NAND_IO_MODE_SIO, }; spi_nand_flash_device_t *device_handle; esp_err_t err = spi_nand_flash_init_device(&nand_flash_config, &device_handle); TEST_ASSERT_TRUE(err != ESP_OK); spi_bus_remove_device(spi); spi_bus_free(HOST_ID); } ================================================ FILE: spi_nand_flash/test_app/main/test_spi_nand_flash_bdl.c ================================================ /* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include #include "esp_attr.h" #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/spi_master.h" #include "spi_nand_flash.h" #include "nand_private/nand_impl_wrap.h" #include "unity.h" #include "soc/spi_pins.h" #include "sdkconfig.h" #include "esp_blockdev.h" #include "esp_nand_blockdev.h" #include "spi_nand_flash_test_helpers.h" // Pin mapping // ESP32 (VSPI) #ifdef CONFIG_IDF_TARGET_ESP32 #define HOST_ID SPI3_HOST #define PIN_MOSI SPI3_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI3_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI3_IOMUX_PIN_NUM_CLK #define PIN_CS SPI3_IOMUX_PIN_NUM_CS #define PIN_WP SPI3_IOMUX_PIN_NUM_WP #define PIN_HD SPI3_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #else // Other chips (SPI2/HSPI) #define HOST_ID SPI2_HOST #define PIN_MOSI SPI2_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI2_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI2_IOMUX_PIN_NUM_CLK #define PIN_CS SPI2_IOMUX_PIN_NUM_CS #define PIN_WP SPI2_IOMUX_PIN_NUM_WP #define PIN_HD SPI2_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #endif static void do_single_write_test(esp_blockdev_handle_t bdl, uint32_t start_page, uint16_t page_count); static void setup_bus(spi_host_device_t host_id) { spi_bus_config_t spi_bus_cfg = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadhd_io_num = PIN_HD, .quadwp_io_num = PIN_WP, .max_transfer_sz = 64, }; esp_err_t ret = spi_bus_initialize(host_id, &spi_bus_cfg, SPI_DMA_CHAN); TEST_ESP_OK(ret); } static void setup_chip(spi_device_handle_t *spi, uint8_t flags) { setup_bus(HOST_ID); spi_device_interface_config_t devcfg = { .clock_speed_hz = 40 * 1000 * 1000, .mode = 0, .spics_io_num = PIN_CS, .queue_size = 10, .flags = flags, }; TEST_ESP_OK(spi_bus_add_device(HOST_ID, &devcfg, spi)); } static void setup_nand_flash(spi_device_handle_t *spi_handle, spi_nand_flash_io_mode_t mode, uint8_t flags, esp_blockdev_handle_t *bdl_handle) { spi_device_handle_t spi; setup_chip(&spi, flags); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = flags, .io_mode = mode, }; esp_blockdev_handle_t wl_bdl; TEST_ESP_OK(spi_nand_flash_init_with_layers(&nand_flash_config, &wl_bdl)); *spi_handle = spi; *bdl_handle = wl_bdl; } static void deinit_nand_flash(spi_device_handle_t spi, esp_blockdev_handle_t bdl_handle) { bdl_handle->ops->release(bdl_handle); spi_bus_remove_device(spi); spi_bus_free(HOST_ID); } TEST_CASE("erase nand flash using block device interface [via dhara]", "[spi_nand_flash]") { spi_device_handle_t spi; esp_blockdev_handle_t bdl_handle; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &bdl_handle); /* Erase length must be aligned to erase_size (block size) */ size_t erase_size = (size_t)bdl_handle->geometry.erase_size; size_t erase_len = (size_t)((bdl_handle->geometry.disk_size / erase_size) * erase_size); TEST_ESP_OK(bdl_handle->ops->erase(bdl_handle, 0, erase_len)); do_single_write_test(bdl_handle, 1, 1); deinit_nand_flash(spi, bdl_handle); } static void do_single_write_test(esp_blockdev_handle_t bdl, uint32_t start_page, uint16_t page_count) { uint8_t *temp_buf = NULL; uint8_t *pattern_buf = NULL; uint32_t page_size = bdl->geometry.write_size; TEST_ASSERT_TRUE(page_size > 0); uint32_t num_pages = (uint32_t)(bdl->geometry.disk_size / page_size); TEST_ASSERT_TRUE((start_page + page_count) <= num_pages); pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); temp_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(temp_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); int64_t read_time = 0; int64_t write_time = 0; for (uint32_t i = start_page; i < (start_page + page_count); i++) { int64_t start = esp_timer_get_time(); bdl->ops->write(bdl, pattern_buf, i * page_size, page_size); write_time += esp_timer_get_time() - start; memset((void *)temp_buf, 0x00, page_size); start = esp_timer_get_time(); bdl->ops->read(bdl, temp_buf, page_size, i * page_size, page_size); read_time += esp_timer_get_time() - start; TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(temp_buf, page_size / sizeof(uint32_t))); } free(pattern_buf); free(temp_buf); printf("Wrote %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, write_time, (float)page_size * page_count / write_time * 1000); printf("Read %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, read_time, (float)page_size * page_count / read_time * 1000); } /* Returns 0 on success, non-zero on failure. Frees buffers so caller can always run cleanup. */ static int do_multiple_page_write_test(esp_blockdev_handle_t bdl, uint32_t start_page, uint16_t page_count) { uint8_t *temp_buf = NULL; uint8_t *pattern_buf = NULL; uint32_t page_size = bdl->geometry.write_size; uint32_t num_pages = bdl->geometry.disk_size / page_size; int ret = -1; if ((start_page + page_count) > num_pages) { return -1; } pattern_buf = (uint8_t *)heap_caps_malloc(page_size * page_count, MALLOC_CAP_DEFAULT); if (pattern_buf == NULL) { return -1; } temp_buf = (uint8_t *)heap_caps_malloc(page_size * page_count, MALLOC_CAP_DEFAULT); if (temp_buf == NULL) { free(pattern_buf); return -1; } spi_nand_flash_fill_buffer(pattern_buf, page_size * page_count / sizeof(uint32_t)); int64_t read_time = 0; int64_t write_time = 0; int64_t start = esp_timer_get_time(); if (bdl->ops->write(bdl, pattern_buf, start_page * page_size, page_count * page_size) != ESP_OK) { free(pattern_buf); free(temp_buf); return -1; } write_time += esp_timer_get_time() - start; memset((void *)temp_buf, 0x00, page_count * page_size); start = esp_timer_get_time(); if (bdl->ops->read(bdl, temp_buf, page_count * page_size, start_page * page_size, page_count * page_size) != ESP_OK) { free(pattern_buf); free(temp_buf); return -1; } read_time += esp_timer_get_time() - start; ret = spi_nand_flash_check_buffer(temp_buf, page_size * page_count / sizeof(uint32_t)); free(pattern_buf); free(temp_buf); printf("Wrote %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, write_time, (float)page_size * page_count / write_time * 1000); printf("Read %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, read_time, (float)page_size * page_count / read_time * 1000); return ret; } static void test_write_nand_flash_pages(spi_nand_flash_io_mode_t mode, uint8_t flags) { spi_device_handle_t spi; esp_blockdev_handle_t bdl_handle; setup_nand_flash(&spi, mode, flags, &bdl_handle); uint32_t page_size = bdl_handle->geometry.write_size; TEST_ASSERT_TRUE(page_size > 0); uint32_t num_pages = (uint32_t)(bdl_handle->geometry.disk_size / page_size); printf("Number of pages: %" PRIu32 ", Page size: %" PRIu32 "\n", num_pages, page_size); int ret = do_multiple_page_write_test(bdl_handle, 1, 2); do_single_write_test(bdl_handle, 16, 32); deinit_nand_flash(spi, bdl_handle); TEST_ASSERT_EQUAL(0, ret); } TEST_CASE("read and write nand flash pages using block device interface (via dhara) (sio half-duplex)", "[spi_nand_flash]") { test_write_nand_flash_pages(SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); } /** * For raw Flash BDL: query IS_BAD_BLOCK, then erase only if the block is good. * @return true if erase was issued, false if block was bad (erase skipped). */ static bool flash_bdl_erase_block_if_good(esp_blockdev_handle_t bdl, uint32_t block) { esp_blockdev_cmd_arg_is_bad_block_t arg = {.num = block, .status = false}; TEST_ESP_OK(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_IS_BAD_BLOCK, &arg)); if (arg.status) { return false; } uint32_t block_size = (uint32_t)bdl->geometry.erase_size; TEST_ESP_OK(bdl->ops->erase(bdl, (uint64_t)block * block_size, (size_t)block_size)); return true; } static void test_nand_operations(spi_nand_flash_io_mode_t mode, uint8_t flags) { spi_device_handle_t spi; setup_chip(&spi, flags); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = flags, .io_mode = mode, }; esp_blockdev_handle_t bdl_handle; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl_handle)); uint32_t page_size = bdl_handle->geometry.write_size; uint32_t block_size = bdl_handle->geometry.erase_size; uint32_t num_pages = bdl_handle->geometry.disk_size / bdl_handle->geometry.read_size; printf("Number of pages: %" PRIu32 ", Page size: %" PRIu32 "\n", num_pages, page_size); uint32_t num_blocks = (uint32_t)(bdl_handle->geometry.disk_size / block_size); uint32_t src_block = 20; if (src_block >= num_blocks) { src_block = 0; } if (!flash_bdl_erase_block_if_good(bdl_handle, src_block)) { src_block = UINT32_MAX; for (uint32_t b = 0; b < num_blocks; b++) { if (flash_bdl_erase_block_if_good(bdl_handle, b)) { src_block = b; break; } } TEST_ASSERT_NOT_EQUAL(UINT32_MAX, src_block); } uint32_t test_page = src_block * (block_size / page_size); // pages_per_block uint32_t page_count = 2; TEST_ASSERT_TRUE(test_page < num_pages); // Verify if test_page is free for (uint32_t page = test_page; page < (test_page + page_count); page++) { esp_blockdev_cmd_arg_is_free_page_t page_free_status = {page, false}; TEST_ESP_OK(bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &page_free_status)); TEST_ASSERT_TRUE(page_free_status.status == true); } TEST_ASSERT_EQUAL(0, do_multiple_page_write_test(bdl_handle, test_page, page_count)); // Verify if test_page is free for (uint32_t page = test_page; page < (test_page + page_count); page++) { esp_blockdev_cmd_arg_is_free_page_t page_free_status = {page, true}; TEST_ESP_OK(bdl_handle->ops->ioctl(bdl_handle, ESP_BLOCKDEV_CMD_IS_FREE_PAGE, &page_free_status)); TEST_ASSERT_TRUE(page_free_status.status == false); } deinit_nand_flash(spi, bdl_handle); } TEST_CASE("verify nand_prog, nand_read, nand_is_free works (bypassing dhara) using block device interface (sio half-duplex)", "[spi_nand_flash]") { test_nand_operations(SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX); } TEST_CASE("verify ioctl (bad blocks and ecc stats) works with bdl interface", "[spi_nand_flash]") { spi_nand_flash_io_mode_t mode = SPI_NAND_IO_MODE_SIO; uint8_t flags = SPI_DEVICE_HALFDUPLEX; spi_device_handle_t spi; setup_chip(&spi, flags); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = flags, .io_mode = mode, }; esp_blockdev_handle_t nand_bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &nand_bdl)); TEST_ASSERT_TRUE(nand_bdl != NULL); uint32_t bad_block_count = 0xFFFF; TEST_ESP_OK(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT, &bad_block_count)); TEST_ASSERT_TRUE(bad_block_count != 0xFFFF); esp_blockdev_cmd_arg_ecc_stats_t ecc_stats; memset(&ecc_stats, 0xFF, sizeof(ecc_stats)); TEST_ESP_OK(nand_bdl->ops->ioctl(nand_bdl, ESP_BLOCKDEV_CMD_GET_ECC_STATS, &ecc_stats)); TEST_ASSERT_TRUE(ecc_stats.ecc_threshold != 0xFF); deinit_nand_flash(spi, nand_bdl); } TEST_CASE("Flash BDL geometry and device_flags after nand_flash_get_blockdev", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t block_size = bdl->geometry.erase_size; uint32_t num_blocks = (uint32_t)(bdl->geometry.disk_size / block_size); TEST_ASSERT_EQUAL_UINT32(num_blocks * block_size, (uint32_t)bdl->geometry.disk_size); TEST_ASSERT_EQUAL(bdl->geometry.read_size, bdl->geometry.write_size); TEST_ASSERT_TRUE(bdl->geometry.read_size > 0); TEST_ASSERT_TRUE(bdl->geometry.erase_size > 0); TEST_ASSERT_TRUE(bdl->device_flags.erase_before_write); TEST_ASSERT_TRUE(bdl->device_flags.and_type_write); TEST_ASSERT_TRUE(bdl->device_flags.default_val_after_erase); TEST_ASSERT_NOT_NULL(bdl->ops); TEST_ASSERT_NOT_NULL(bdl->ops->read); TEST_ASSERT_NOT_NULL(bdl->ops->write); TEST_ASSERT_NOT_NULL(bdl->ops->erase); TEST_ASSERT_NOT_NULL(bdl->ops->ioctl); TEST_ASSERT_NOT_NULL(bdl->ops->release); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL multi-block erase honours erase_len", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t block_size = bdl->geometry.erase_size; uint32_t page_size = bdl->geometry.write_size; const uint32_t num_blocks_to_erase = 3; const uint32_t start_block = 30; uint64_t start_addr = (uint64_t)start_block * block_size; bool erased[3]; TEST_ASSERT_EQUAL(3u, num_blocks_to_erase); for (uint32_t i = 0; i < num_blocks_to_erase; i++) { erased[i] = flash_bdl_erase_block_if_good(bdl, start_block + i); } uint8_t *read_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(read_buf); for (uint32_t blk = 0; blk < num_blocks_to_erase; blk++) { if (!erased[blk]) { continue; } uint32_t pages_per_block = block_size / page_size; for (uint32_t p = 0; p < pages_per_block; p++) { uint64_t addr = start_addr + (uint64_t)(blk * block_size + p * page_size); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, page_size, addr, page_size)); for (size_t i = 0; i < page_size; i++) { TEST_ASSERT_EQUAL_HEX8(0xFF, read_buf[i]); } } } free(read_buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL GET_NAND_FLASH_INFO ioctl", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); esp_blockdev_cmd_arg_nand_flash_info_t flash_info; memset(&flash_info, 0, sizeof(flash_info)); TEST_ESP_OK(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, &flash_info)); TEST_ASSERT_TRUE(flash_info.device_info.manufacturer_id != 0 || flash_info.device_info.device_id != 0); TEST_ASSERT_TRUE(strnlen((char *)flash_info.device_info.chip_name, sizeof(flash_info.device_info.chip_name)) > 0); TEST_ASSERT_EQUAL(bdl->geometry.read_size, flash_info.geometry.page_size); TEST_ASSERT_EQUAL(bdl->geometry.erase_size, flash_info.geometry.block_size); TEST_ASSERT_EQUAL((uint32_t)(bdl->geometry.disk_size / bdl->geometry.erase_size), flash_info.geometry.num_blocks); deinit_nand_flash(spi, bdl); } TEST_CASE("nand_flash_get_blockdev and spi_nand_flash_wl_get_blockdev error paths", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t out = NULL; TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, nand_flash_get_blockdev(NULL, &out)); TEST_ASSERT_NULL(out); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, nand_flash_get_blockdev(&config, NULL)); esp_blockdev_handle_t flash_bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&config, &flash_bdl)); TEST_ASSERT_NOT_NULL(flash_bdl); out = NULL; TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spi_nand_flash_wl_get_blockdev(NULL, &out)); TEST_ASSERT_NULL(out); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spi_nand_flash_wl_get_blockdev(flash_bdl, NULL)); deinit_nand_flash(spi, flash_bdl); } TEST_CASE("Flash BDL erase invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t block_size = bdl->geometry.erase_size; esp_err_t ret = bdl->ops->erase(bdl, block_size / 2, block_size); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); ret = bdl->ops->erase(bdl, 0, block_size / 2); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); ret = bdl->ops->erase(bdl, 0, 0); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL unsupported ioctl returns ESP_ERR_NOT_SUPPORTED", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t dummy = 0; esp_err_t ret = bdl->ops->ioctl(bdl, (uint8_t)0xFF, &dummy); TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, ret); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL COPY_PAGE ioctl", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint32_t block_size = bdl->geometry.erase_size; uint32_t num_pages = (uint32_t)(bdl->geometry.disk_size / page_size); uint32_t pages_per_block = block_size / page_size; uint32_t src_block = 5; uint32_t dst_block = 6; uint32_t src_page = src_block * pages_per_block; uint32_t dst_page = dst_block * pages_per_block; TEST_ASSERT_TRUE(dst_page < num_pages); TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, src_block)); TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, dst_block)); uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); TEST_ESP_OK(bdl->ops->write(bdl, pattern_buf, src_page * (uint64_t)page_size, page_size)); esp_blockdev_cmd_arg_copy_page_t copy_cmd = { .src_page = src_page, .dst_page = dst_page, }; TEST_ESP_OK(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_COPY_PAGE, ©_cmd)); memset(read_buf, 0, page_size); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, page_size, dst_page * (uint64_t)page_size, page_size)); TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(read_buf, page_size / sizeof(uint32_t))); free(pattern_buf); free(read_buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL GET_PAGE_ECC_STATUS ioctl", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint32_t block_size = bdl->geometry.erase_size; uint32_t test_page = (bdl->geometry.disk_size / page_size) / 2; uint32_t pages_per_block = block_size / page_size; uint32_t ecc_block = test_page / pages_per_block; TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, ecc_block)); uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); TEST_ESP_OK(bdl->ops->write(bdl, pattern_buf, test_page * (uint64_t)page_size, page_size)); free(pattern_buf); esp_blockdev_cmd_arg_ecc_status_t page_ecc_status = { .page_num = test_page, .ecc_status = 0xFF, }; TEST_ESP_OK(bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_PAGE_ECC_STATUS, &page_ecc_status)); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL read invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.read_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); esp_err_t ret = bdl->ops->read(bdl, NULL, page_size, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = bdl->ops->read(bdl, buf, page_size - 1, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); uint64_t oob_addr = bdl->geometry.disk_size; if (oob_addr >= page_size) { ret = bdl->ops->read(bdl, buf, page_size, oob_addr - page_size / 2, page_size); TEST_ASSERT_TRUE(ret != ESP_OK); } /* Read that crosses page boundary returns error */ size_t bad_len = page_size + 1; ret = bdl->ops->read(bdl, buf, bad_len, 0, bad_len); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); free(buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL write invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); memset(buf, 0x55, page_size); esp_err_t ret = bdl->ops->write(bdl, NULL, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = bdl->ops->write(bdl, buf, 1, page_size); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); ret = bdl->ops->write(bdl, buf, 0, page_size - 1); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); uint64_t oob = bdl->geometry.disk_size; if (oob >= page_size) { ret = bdl->ops->write(bdl, buf, oob - page_size + 1, page_size); TEST_ASSERT_TRUE(ret != ESP_OK); } free(buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL multi-page read and write", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint32_t block_size = bdl->geometry.erase_size; const uint32_t num_pages = 3; uint32_t start_page = (uint32_t)(bdl->geometry.disk_size / page_size) / 2; if (start_page + num_pages > bdl->geometry.disk_size / page_size) { start_page = 0; } uint32_t start_block = start_page / (block_size / page_size); TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, start_block)); size_t total_len = num_pages * page_size; uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(total_len, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(total_len, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, total_len / sizeof(uint32_t)); TEST_ESP_OK(bdl->ops->write(bdl, pattern_buf, start_page * (uint64_t)page_size, total_len)); memset(read_buf, 0, total_len); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, total_len, start_page * (uint64_t)page_size, total_len)); TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(read_buf, total_len / sizeof(uint32_t))); /* Read back in single-page chunks */ uint8_t *chunk_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(chunk_buf); for (uint32_t i = 0; i < num_pages; i++) { memset(chunk_buf, 0, page_size); TEST_ESP_OK(bdl->ops->read(bdl, chunk_buf, page_size, start_page * (uint64_t)page_size + i * (uint64_t)page_size, page_size)); TEST_ASSERT_EQUAL_MEMORY(pattern_buf + i * page_size, chunk_buf, page_size); } free(chunk_buf); free(pattern_buf); free(read_buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL release then create again (no use-after-free)", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); bdl->ops->release(bdl); bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); memset(buf, 0xAA, page_size); TEST_ESP_OK(bdl->ops->write(bdl, buf, 0, page_size)); memset(buf, 0, page_size); TEST_ESP_OK(bdl->ops->read(bdl, buf, page_size, 0, page_size)); free(buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL sync returns success", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); esp_err_t ret = bdl->ops->sync(bdl); TEST_ASSERT_EQUAL(ESP_OK, ret); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL ioctl with NULL args returns error", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); esp_err_t ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_NAND_FLASH_INFO, NULL); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = bdl->ops->ioctl(bdl, ESP_BLOCKDEV_CMD_GET_BAD_BLOCKS_COUNT, NULL); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL zero-length read and write", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint8_t buf = 0; esp_err_t ret = bdl->ops->read(bdl, &buf, 1, 0, 0); TEST_ASSERT_EQUAL(ESP_OK, ret); ret = bdl->ops->write(bdl, &buf, 0, 0); TEST_ASSERT_EQUAL(ESP_OK, ret); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL sub-page (partial page) read", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint32_t block_size = bdl->geometry.erase_size; size_t partial_len = (page_size >= 256) ? 256 : (page_size / 2); if (partial_len == 0) { partial_len = 1; } uint32_t test_page = (uint32_t)(bdl->geometry.disk_size / page_size) / 2; uint64_t addr = test_page * (uint64_t)page_size; uint32_t sub_block = (uint32_t)(addr / block_size); TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, sub_block)); uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(partial_len, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); TEST_ESP_OK(bdl->ops->write(bdl, pattern_buf, addr, page_size)); memset(read_buf, 0, partial_len); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, partial_len, addr, partial_len)); TEST_ASSERT_EQUAL_MEMORY(pattern_buf, read_buf, partial_len); size_t offset = (page_size >= 128) ? 128 : 1; size_t len = (page_size > offset + 64) ? 64 : (page_size - offset); if (len > 0) { memset(read_buf, 0, len); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, len, addr + offset, len)); TEST_ASSERT_EQUAL_MEMORY((uint8_t *)pattern_buf + offset, read_buf, len); } free(pattern_buf); free(read_buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL multi-page spanning block boundary", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint32_t page_size = bdl->geometry.write_size; uint32_t block_size = bdl->geometry.erase_size; uint32_t pages_per_block = block_size / page_size; uint32_t num_pages = (uint32_t)(bdl->geometry.disk_size / page_size); if (pages_per_block < 2 || num_pages < pages_per_block + 2) { deinit_nand_flash(spi, bdl); return; } uint32_t last_page_of_block0 = pages_per_block - 1; uint32_t page_count = 2; uint64_t start_addr = last_page_of_block0 * (uint64_t)page_size; TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, 0)); TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, 1)); size_t total_len = page_count * page_size; uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(total_len, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(total_len, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, total_len / sizeof(uint32_t)); TEST_ESP_OK(bdl->ops->write(bdl, pattern_buf, start_addr, total_len)); memset(read_buf, 0, total_len); TEST_ESP_OK(bdl->ops->read(bdl, read_buf, total_len, start_addr, total_len)); TEST_ASSERT_EQUAL_MEMORY(pattern_buf, read_buf, total_len); free(pattern_buf); free(read_buf); deinit_nand_flash(spi, bdl); } TEST_CASE("Flash BDL read at end of device (last byte)", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; setup_chip(&spi, SPI_DEVICE_HALFDUPLEX); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .flags = SPI_DEVICE_HALFDUPLEX, .io_mode = SPI_NAND_IO_MODE_SIO, }; esp_blockdev_handle_t bdl = NULL; TEST_ESP_OK(nand_flash_get_blockdev(&nand_flash_config, &bdl)); TEST_ASSERT_NOT_NULL(bdl); uint64_t disk_size = bdl->geometry.disk_size; uint32_t block_size = (uint32_t)bdl->geometry.erase_size; uint32_t last_block = (uint32_t)(disk_size / block_size) - 1; TEST_ASSERT_TRUE(flash_bdl_erase_block_if_good(bdl, last_block)); uint8_t buf[1]; esp_err_t ret = bdl->ops->read(bdl, buf, sizeof(buf), disk_size - 1, 1); TEST_ASSERT_EQUAL(ESP_OK, ret); ret = bdl->ops->read(bdl, buf, sizeof(buf), disk_size, 1); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, ret); deinit_nand_flash(spi, bdl); } /* --- WL BDL tests (grouped together) --- */ TEST_CASE("WL BDL sync after write", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.write_size; uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); TEST_ESP_OK(wl_bdl->ops->write(wl_bdl, pattern_buf, 0, page_size)); TEST_ESP_OK(wl_bdl->ops->sync(wl_bdl)); memset(read_buf, 0, page_size); TEST_ESP_OK(wl_bdl->ops->read(wl_bdl, read_buf, page_size, 0, page_size)); TEST_ASSERT_EQUAL(0, spi_nand_flash_check_buffer(read_buf, page_size / sizeof(uint32_t))); free(pattern_buf); free(read_buf); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL ESP_BLOCKDEV_CMD_MARK_DELETED (TRIM)", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.write_size; TEST_ASSERT_TRUE(page_size > 0); uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); TEST_ASSERT_TRUE(num_pages >= 4); uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); const uint32_t start_page = 1; const uint32_t page_count = 3; for (uint32_t i = start_page; i < start_page + page_count; i++) { TEST_ESP_OK(wl_bdl->ops->write(wl_bdl, pattern_buf, (uint64_t)i * page_size, page_size)); } esp_blockdev_cmd_arg_erase_t trim_arg = { .start_addr = (uint64_t)start_page * page_size, .erase_len = (size_t)page_count * page_size, }; TEST_ESP_OK(wl_bdl->ops->ioctl(wl_bdl, ESP_BLOCKDEV_CMD_MARK_DELETED, &trim_arg)); free(pattern_buf); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL read invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.read_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); esp_err_t ret = wl_bdl->ops->read(wl_bdl, NULL, page_size, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = wl_bdl->ops->read(wl_bdl, buf, page_size - 1, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = wl_bdl->ops->read(wl_bdl, buf, page_size, wl_bdl->geometry.disk_size, page_size); TEST_ASSERT_TRUE(ret != ESP_OK); free(buf); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL write invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.write_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); memset(buf, 0x55, page_size); esp_err_t ret = wl_bdl->ops->write(wl_bdl, NULL, 0, page_size); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); ret = wl_bdl->ops->write(wl_bdl, buf, 1, page_size); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); ret = wl_bdl->ops->write(wl_bdl, buf, wl_bdl->geometry.disk_size, page_size); TEST_ASSERT_TRUE(ret != ESP_OK); free(buf); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL erase invalid args", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t erase_size = (uint32_t)wl_bdl->geometry.erase_size; esp_err_t ret = wl_bdl->ops->erase(wl_bdl, erase_size / 2, erase_size); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); ret = wl_bdl->ops->erase(wl_bdl, wl_bdl->geometry.disk_size, erase_size); TEST_ASSERT_TRUE(ret != ESP_OK); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL zero-length read and write", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint8_t buf = 0; esp_err_t ret = wl_bdl->ops->read(wl_bdl, &buf, 1, 0, 0); TEST_ASSERT_EQUAL(ESP_OK, ret); ret = wl_bdl->ops->write(wl_bdl, &buf, 0, 0); TEST_ASSERT_EQUAL(ESP_OK, ret); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL geometry page count stable after full erase", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.write_size; TEST_ASSERT_TRUE(page_size > 0); uint32_t num_before = (uint32_t)(wl_bdl->geometry.disk_size / page_size); /* Erase length must be aligned to erase_size */ size_t erase_size = (size_t)wl_bdl->geometry.erase_size; size_t erase_len = (size_t)((wl_bdl->geometry.disk_size / erase_size) * erase_size); TEST_ESP_OK(wl_bdl->ops->erase(wl_bdl, 0, erase_len)); uint32_t num_after = (uint32_t)(wl_bdl->geometry.disk_size / page_size); TEST_ASSERT_EQUAL_UINT32(num_before, num_after); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL write N pages read back in different chunk sizes", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.write_size; TEST_ASSERT_TRUE(page_size > 0); uint32_t num_pages = (uint32_t)(wl_bdl->geometry.disk_size / page_size); TEST_ASSERT_TRUE(num_pages >= 3); const uint32_t n = 3; size_t total_len = n * page_size; uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(total_len, MALLOC_CAP_DEFAULT); uint8_t *read_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(pattern_buf); TEST_ASSERT_NOT_NULL(read_buf); spi_nand_flash_fill_buffer(pattern_buf, total_len / sizeof(uint32_t)); TEST_ESP_OK(wl_bdl->ops->write(wl_bdl, pattern_buf, 0, total_len)); for (uint32_t i = 0; i < n; i++) { memset(read_buf, 0, page_size); TEST_ESP_OK(wl_bdl->ops->read(wl_bdl, read_buf, page_size, i * (uint64_t)page_size, page_size)); TEST_ASSERT_EQUAL_MEMORY(pattern_buf + i * page_size, read_buf, page_size); } free(pattern_buf); free(read_buf); deinit_nand_flash(spi, wl_bdl); } TEST_CASE("WL BDL unaligned read and write length returns error", "[spi_nand_flash][bdl]") { spi_device_handle_t spi; esp_blockdev_handle_t wl_bdl; setup_nand_flash(&spi, SPI_NAND_IO_MODE_SIO, SPI_DEVICE_HALFDUPLEX, &wl_bdl); uint32_t page_size = wl_bdl->geometry.read_size; uint8_t *buf = (uint8_t *)heap_caps_malloc(page_size + 1, MALLOC_CAP_DEFAULT); TEST_ASSERT_NOT_NULL(buf); esp_err_t ret = wl_bdl->ops->read(wl_bdl, buf, page_size + 1, 0, page_size + 1); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); memset(buf, 0x55, page_size + 1); ret = wl_bdl->ops->write(wl_bdl, buf, 0, page_size + 1); TEST_ASSERT_TRUE(ret == ESP_ERR_INVALID_ARG || ret == ESP_ERR_INVALID_SIZE); free(buf); deinit_nand_flash(spi, wl_bdl); } ================================================ FILE: spi_nand_flash/test_app/pytest_spi_nand_flash.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.spi_nand_flash @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @pytest.mark.parametrize( 'config', [ 'default', ], indirect=True, ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_spi_nand_flash(dut: Dut, config: str) -> None: dut.run_all_single_board_cases() @pytest.mark.spi_nand_flash @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @pytest.mark.parametrize( 'config', [ 'bdl', ], indirect=True, ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_spi_nand_flash_bdl(dut: Dut, config: str) -> None: dut.run_all_single_board_cases() ================================================ FILE: spi_nand_flash/test_app/sdkconfig.ci.bdl ================================================ CONFIG_NAND_FLASH_ENABLE_BDL=y ================================================ FILE: spi_nand_flash/test_app/sdkconfig.ci.default ================================================ ================================================ FILE: spi_nand_flash/test_app/sdkconfig.defaults ================================================ # Enable Unity fixture support CONFIG_UNITY_ENABLE_FIXTURE=n CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y CONFIG_ESP_TASK_WDT_INIT=n ================================================ FILE: spi_nand_flash_fatfs/CHANGELOG.md ================================================ ## [1.0.0] ### Breaking Changes - FATFS integration for SPI NAND Flash now lives in this component. Projects that previously relied on FATFS support bundled inside `spi_nand_flash` must add `spi_nand_flash_fatfs` as a dependency and include its headers. **Migration:** See **Migration Guide (0.x → 1.0.0)** in [`spi_nand_flash/layered_architecture.md`](../spi_nand_flash/layered_architecture.md) (FATFS split, legacy init with BDL disabled, and related driver changes). ================================================ FILE: spi_nand_flash_fatfs/CMakeLists.txt ================================================ set(srcs "src/diskio_nand.c" "src/vfs_fat_spinandflash.c") idf_component_register( SRCS ${srcs} INCLUDE_DIRS "include" REQUIRES spi_nand_flash fatfs PRIV_REQUIRES vfs ) ================================================ FILE: spi_nand_flash_fatfs/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: spi_nand_flash_fatfs/README.md ================================================ # SPI NAND Flash FatFS Integration FatFS integration layer for the SPI NAND Flash driver. ## Requirements (read first) **`spi_nand_flash_fatfs` only supports the legacy driver handle** (`spi_nand_flash_device_t` from `spi_nand_flash_init_device()`). - Keep **`CONFIG_NAND_FLASH_ENABLE_BDL` disabled** in menuconfig. When BDL is enabled, `spi_nand_flash_init_device()` returns `ESP_ERR_NOT_SUPPORTED`, so `esp_vfs_fat_nand_mount()` / diskio cannot be used. - **FatFs on top of the wear-leveling block device (`esp_blockdev_t`) is not supported in this release.** A future component integration will add it; until then use the legacy path above for FAT. **Migration from 0.x:** See the SPI NAND component’s [layered_architecture.md](../spi_nand_flash/layered_architecture.md) — **Migration Guide (0.x → 1.0.0)** (FATFS split, BDL vs legacy init). ## Features - FATFS diskio adapter and VFS mount helpers using `spi_nand_flash_device_t` - Same usage as before the `spi_nand_flash` / `spi_nand_flash_fatfs` split (legacy init + mount) ## Dependencies - `spi_nand_flash` component (driver) - ESP-IDF `fatfs` component - ESP-IDF `vfs` component ## Usage ```c #include "spi_nand_flash.h" #include "esp_vfs_fat_nand.h" // Initialize device (CONFIG_NAND_FLASH_ENABLE_BDL must be off) spi_nand_flash_device_t *nand_device; spi_nand_flash_init_device(&config, &nand_device); // Mount FATFS esp_vfs_fat_mount_config_t mount_config = { .max_files = 4, .format_if_mount_failed = true, }; esp_vfs_fat_nand_mount("/nand", nand_device, &mount_config); // Use filesystem... FILE *f = fopen("/nand/test.txt", "w"); // ... // Unmount esp_vfs_fat_nand_unmount("/nand", nand_device); spi_nand_flash_deinit_device(nand_device); ``` ## Examples | Example | Description | `CONFIG_NAND_FLASH_ENABLE_BDL` | IDF | |---------|-------------|--------------------------------|-----| | `examples/nand_flash` | FATFS on NAND (`spi_nand_flash_init_device` + `esp_vfs_fat_nand_mount`) | **Must be off** | 5.0+ | | `examples/nand_flash_debug_app` | Diagnostics (bad blocks, ECC stats, throughput); `spi_nand_flash` only, no VFS | **Must be off** | 5.0+ | See each example’s `README.md` for hardware and usage. ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(nand_flash) ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | # SPI NAND Flash Example This example demonstrates how to use the SPI NAND Flash driver with FAT filesystem in ESP-IDF. ## Hardware Required * Any ESP board from the supported targets list above * An external SPI NAND Flash chip connected to the following pins: * For ESP32 (SPI3): - MOSI - SPI3_IOMUX_PIN_NUM_MOSI (23) - MISO - SPI3_IOMUX_PIN_NUM_MISO (19) - CLK - SPI3_IOMUX_PIN_NUM_CLK (18) - CS - SPI3_IOMUX_PIN_NUM_CS (5) - WP - SPI3_IOMUX_PIN_NUM_WP (22) - HD - SPI3_IOMUX_PIN_NUM_HD (21) * For other ESP chips (SPI2): - MOSI - SPI2_IOMUX_PIN_NUM_MOSI (13) - MISO - SPI2_IOMUX_PIN_NUM_MISO (12) - CLK - SPI2_IOMUX_PIN_NUM_CLK (14) - CS - SPI2_IOMUX_PIN_NUM_CS (15) - WP - SPI2_IOMUX_PIN_NUM_WP (2) - HD - SPI2_IOMUX_PIN_NUM_HD (4) ## Configuration The example can be configured to format the filesystem if mounting fails. This can be enabled using the `CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED` configuration option. Keep **`CONFIG_NAND_FLASH_ENABLE_BDL` disabled** (Component config → SPI NAND Flash). This example uses `spi_nand_flash_init_device()`, which is not available when BDL is enabled. ## How to Use Example Build the project and flash it to the board, then run monitor tool to view serial output: ```bash idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output The example: 1. Initializes the SPI bus and NAND Flash device 2. Mounts a FAT filesystem on the NAND Flash 3. Creates and writes to a file named "hello.txt" 4. Reads back and displays the contents of the file 5. Displays filesystem space information 6. Unmounts the filesystem and deinitializes the NAND Flash Here is the example's console output: ``` ... I (315) main_task: Calling app_main() I (315) example: DMA CHANNEL: 3 W (355) vfs_fat_nand: f_mount failed (13) I (355) vfs_fat_nand: Formatting FATFS partition, allocation unit size=16384 I (6635) vfs_fat_nand: Mounting again I (6655) example: FAT FS: 117024 kB total, 117024 kB free I (6655) example: Opening file I (6685) example: File written I (6685) example: Reading file I (6685) example: Read from file: 'Written using ESP-IDF v5.5-dev-2627-g2cbfce97768' I (6685) example: FAT FS: 117024 kB total, 117008 kB free I (6695) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6705) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6705) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6715) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6725) gpio: GPIO[22]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6735) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (6745) main_task: Returned from app_main() ... ``` ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/main/CMakeLists.txt ================================================ idf_component_register(SRCS "spi_nand_flash_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES spi_nand_flash_fatfs ) ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/main/Kconfig.projbuild ================================================ menu "SPI Example Configuration" config EXAMPLE_FORMAT_IF_MOUNT_FAILED bool "Format the flash if mount failed" default n help If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if the mount has failed. endmenu ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/main/idf_component.yml ================================================ dependencies: espressif/spi_nand_flash_fatfs: version: '*' override_path: '../../../' ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/main/spi_nand_flash_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "esp_system.h" #include "soc/spi_pins.h" #include "esp_vfs_fat_nand.h" #define EXAMPLE_FLASH_FREQ_KHZ 40000 static const char *TAG = "example"; // Pin mapping // ESP32 (VSPI) #ifdef CONFIG_IDF_TARGET_ESP32 #define HOST_ID SPI3_HOST #define PIN_MOSI SPI3_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI3_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI3_IOMUX_PIN_NUM_CLK #define PIN_CS SPI3_IOMUX_PIN_NUM_CS #define PIN_WP SPI3_IOMUX_PIN_NUM_WP #define PIN_HD SPI3_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #else // Other chips (SPI2/HSPI) #define HOST_ID SPI2_HOST #define PIN_MOSI SPI2_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI2_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI2_IOMUX_PIN_NUM_CLK #define PIN_CS SPI2_IOMUX_PIN_NUM_CS #define PIN_WP SPI2_IOMUX_PIN_NUM_WP #define PIN_HD SPI2_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #endif // Mount path for the partition const char *base_path = "/nandflash"; static void example_init_nand_flash(spi_nand_flash_device_t **out_handle, spi_device_handle_t *spi_handle) { const spi_bus_config_t bus_config = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadhd_io_num = PIN_HD, .quadwp_io_num = PIN_WP, .max_transfer_sz = 4096 * 2, }; // Initialize the SPI bus ESP_LOGI(TAG, "DMA CHANNEL: %d", SPI_DMA_CHAN); ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_config, SPI_DMA_CHAN)); // spi_flags = SPI_DEVICE_HALFDUPLEX -> half duplex // spi_flags = 0 -> full_duplex const uint32_t spi_flags = SPI_DEVICE_HALFDUPLEX; spi_device_interface_config_t devcfg = { .clock_speed_hz = EXAMPLE_FLASH_FREQ_KHZ * 1000, .mode = 0, .spics_io_num = PIN_CS, .queue_size = 10, .flags = spi_flags, }; spi_device_handle_t spi; ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &devcfg, &spi)); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .io_mode = SPI_NAND_IO_MODE_SIO, .flags = spi_flags, }; assert(devcfg.flags == nand_flash_config.flags); spi_nand_flash_device_t *nand_flash_device_handle; ESP_ERROR_CHECK(spi_nand_flash_init_device(&nand_flash_config, &nand_flash_device_handle)); *out_handle = nand_flash_device_handle; *spi_handle = spi; } static void example_deinit_nand_flash(spi_nand_flash_device_t *flash, spi_device_handle_t spi) { ESP_ERROR_CHECK(spi_nand_flash_deinit_device(flash)); ESP_ERROR_CHECK(spi_bus_remove_device(spi)); ESP_ERROR_CHECK(spi_bus_free(HOST_ID)); } void app_main(void) { esp_err_t ret; // Set up SPI bus and initialize the external SPI Flash chip spi_device_handle_t spi; spi_nand_flash_device_t *flash; example_init_nand_flash(&flash, &spi); if (flash == NULL) { return; } esp_vfs_fat_mount_config_t config = { .max_files = 4, #ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED .format_if_mount_failed = true, #else .format_if_mount_failed = false, #endif .allocation_unit_size = 16 * 1024 }; ret = esp_vfs_fat_nand_mount(base_path, flash, &config); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount filesystem. " "If you want the flash memory to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); } return; } // Print FAT FS size information uint64_t bytes_total, bytes_free; esp_vfs_fat_info(base_path, &bytes_total, &bytes_free); ESP_LOGI(TAG, "FAT FS: %" PRIu64 " kB total, %" PRIu64 " kB free", bytes_total / 1024, bytes_free / 1024); // Create a file in FAT FS ESP_LOGI(TAG, "Opening file"); FILE *f = fopen("/nandflash/hello.txt", "wb"); if (f == NULL) { ESP_LOGE(TAG, "Failed to open file for writing"); return; } fprintf(f, "Written using ESP-IDF %s\n", esp_get_idf_version()); fclose(f); ESP_LOGI(TAG, "File written"); // Open file for reading ESP_LOGI(TAG, "Reading file"); f = fopen("/nandflash/hello.txt", "rb"); if (f == NULL) { ESP_LOGE(TAG, "Failed to open file for reading"); return; } char line[128]; fgets(line, sizeof(line), f); fclose(f); // strip newline char *pos = strchr(line, '\n'); if (pos) { *pos = '\0'; } ESP_LOGI(TAG, "Read from file: '%s'", line); esp_vfs_fat_info(base_path, &bytes_total, &bytes_free); ESP_LOGI(TAG, "FAT FS: %" PRIu64 " kB total, %" PRIu64 " kB free", bytes_total / 1024, bytes_free / 1024); esp_vfs_fat_nand_unmount(base_path, flash); example_deinit_nand_flash(flash, spi); } ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/pytest_nand_flash_example.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.spi_nand_flash @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_nand_flash_example(dut) -> None: dut.expect_exact("Opening file") dut.expect_exact("File written") dut.expect_exact("Reading file") dut.expect_exact("Read from file:") dut.expect_exact("Returned from app_main") ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash/sdkconfig.ci ================================================ CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED=y ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(nand_flash_debug_app) ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/README.md ================================================ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | # SPI NAND Flash debug example This example is designed to gather diagnostic statistics for NAND flash, as outlined below: 1. Bad block statistics. 2. ECC error statistics. 3. Read-write NAND page throughput (both at the lower level and through the Dhara library). 4. Verification of NAND write operations using the Kconfig option `CONFIG_NAND_FLASH_VERIFY_WRITE`. ## How to use example To run the example, type the following command: ```CMake # CMake idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example output Here is the example's console output: ``` ... I (353) debug_app: Get bad block statistics: I (533) nand_diag: Total number of Blocks: 1024 Bad Blocks: 1 Valid Blocks: 1023 I (533) debug_app: Read-Write Throughput via Dhara: I (3423) debug_app: Wrote 2048000 bytes in 2269005 us, avg 902.60 kB/s I (3423) debug_app: Read 2048000 bytes in 617570 us, avg 3316.22 kB/s I (3423) debug_app: Read-Write Throughput at lower level (bypassing Dhara): I (5913) debug_app: Wrote 2048000 bytes in 1883853 us, avg 1087.13 kB/s I (5913) debug_app: Read 2048000 bytes in 585556 us, avg 3497.53 kB/s I (5913) debug_app: ECC errors statistics: I (17173) nand_diag: Total number of ECC errors: 42 ECC not corrected count: 0 ECC errors exceeding threshold (4): 0 ... ``` ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/main/CMakeLists.txt ================================================ idf_component_register(SRCS "spi_nand_flash_debug_app_main.c" INCLUDE_DIRS "." PRIV_REQUIRES spi_nand_flash esp_timer ) ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/main/idf_component.yml ================================================ dependencies: espressif/spi_nand_flash: version: '*' override_path: '../../../../spi_nand_flash/' espressif/spi_nand_flash_fatfs: version: '*' override_path: '../../../' ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/main/spi_nand_flash_debug_app_main.c ================================================ /* * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include "esp_system.h" #include "soc/spi_pins.h" #include "spi_nand_flash.h" #include "spi_nand_flash_test_helpers.h" #include "nand_diag_api.h" #include "nand_private/nand_impl_wrap.h" #include "esp_log.h" #include "esp_check.h" #include "esp_timer.h" #include "esp_heap_caps.h" #define EXAMPLE_FLASH_FREQ_KHZ 40000 static const char *TAG = "debug_app"; // Pin mapping // ESP32 (VSPI) #ifdef CONFIG_IDF_TARGET_ESP32 #define HOST_ID SPI3_HOST #define PIN_MOSI SPI3_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI3_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI3_IOMUX_PIN_NUM_CLK #define PIN_CS SPI3_IOMUX_PIN_NUM_CS #define PIN_WP SPI3_IOMUX_PIN_NUM_WP #define PIN_HD SPI3_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #else // Other chips (SPI2/HSPI) #define HOST_ID SPI2_HOST #define PIN_MOSI SPI2_IOMUX_PIN_NUM_MOSI #define PIN_MISO SPI2_IOMUX_PIN_NUM_MISO #define PIN_CLK SPI2_IOMUX_PIN_NUM_CLK #define PIN_CS SPI2_IOMUX_PIN_NUM_CS #define PIN_WP SPI2_IOMUX_PIN_NUM_WP #define PIN_HD SPI2_IOMUX_PIN_NUM_HD #define SPI_DMA_CHAN SPI_DMA_CH_AUTO #endif static void example_init_nand_flash(spi_nand_flash_device_t **out_handle, spi_device_handle_t *spi_handle) { const spi_bus_config_t bus_config = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadhd_io_num = PIN_HD, .quadwp_io_num = PIN_WP, .max_transfer_sz = 4096 * 2, }; // Initialize the SPI bus ESP_LOGI(TAG, "DMA CHANNEL: %d", SPI_DMA_CHAN); ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_config, SPI_DMA_CHAN)); // spi_flags = SPI_DEVICE_HALFDUPLEX -> half duplex // spi_flags = 0 -> full_duplex const uint32_t spi_flags = SPI_DEVICE_HALFDUPLEX; spi_device_interface_config_t devcfg = { .clock_speed_hz = EXAMPLE_FLASH_FREQ_KHZ * 1000, .mode = 0, .spics_io_num = PIN_CS, .queue_size = 10, .flags = spi_flags, }; spi_device_handle_t spi; ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &devcfg, &spi)); spi_nand_flash_config_t nand_flash_config = { .device_handle = spi, .io_mode = SPI_NAND_IO_MODE_SIO, .flags = spi_flags }; spi_nand_flash_device_t *nand_flash_device_handle; ESP_ERROR_CHECK(spi_nand_flash_init_device(&nand_flash_config, &nand_flash_device_handle)); *out_handle = nand_flash_device_handle; *spi_handle = spi; } static void example_deinit_nand_flash(spi_nand_flash_device_t *flash, spi_device_handle_t spi) { ESP_ERROR_CHECK(spi_nand_flash_deinit_device(flash)); ESP_ERROR_CHECK(spi_bus_remove_device(spi)); ESP_ERROR_CHECK(spi_bus_free(HOST_ID)); } static esp_err_t read_write_pages_tp(spi_nand_flash_device_t *flash, uint32_t start_page, uint16_t page_count, bool get_raw_tp) { esp_err_t ret = ESP_OK; uint8_t *temp_buf = NULL; uint8_t *pattern_buf = NULL; uint32_t page_size, num_pages; ESP_ERROR_CHECK(spi_nand_flash_get_page_count(flash, &num_pages)); ESP_ERROR_CHECK(spi_nand_flash_get_page_size(flash, &page_size)); ESP_RETURN_ON_FALSE((start_page + page_count) < num_pages, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); pattern_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DMA); ESP_RETURN_ON_FALSE(pattern_buf != NULL, ESP_ERR_NO_MEM, TAG, "nomem"); temp_buf = (uint8_t *)heap_caps_malloc(page_size, MALLOC_CAP_DMA); ESP_RETURN_ON_FALSE(temp_buf != NULL, ESP_ERR_NO_MEM, TAG, "nomem"); spi_nand_flash_fill_buffer(pattern_buf, page_size / sizeof(uint32_t)); int64_t read_time = 0; int64_t write_time = 0; for (uint32_t i = start_page; i < (start_page + page_count); i++) { int64_t start = esp_timer_get_time(); if (get_raw_tp) { ESP_ERROR_CHECK(nand_wrap_prog(flash, i, pattern_buf)); } else { ESP_ERROR_CHECK(spi_nand_flash_write_page(flash, pattern_buf, i)); } write_time += esp_timer_get_time() - start; memset((void *)temp_buf, 0x00, page_size); start = esp_timer_get_time(); if (get_raw_tp) { ESP_ERROR_CHECK(nand_wrap_read(flash, i, 0, page_size, temp_buf)); } else { ESP_ERROR_CHECK(spi_nand_flash_read_page(flash, temp_buf, i)); } read_time += esp_timer_get_time() - start; } free(pattern_buf); free(temp_buf); ESP_LOGI(TAG, "Wrote %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s", page_size * page_count, write_time, (float)page_size * page_count / write_time * 1000); ESP_LOGI(TAG, "Read %" PRIu32 " bytes in %" PRId64 " us, avg %.2f kB/s\n", page_size * page_count, read_time, (float)page_size * page_count / read_time * 1000); return ret; } void app_main(void) { // Set up SPI bus and initialize the external SPI Flash chip spi_device_handle_t spi; spi_nand_flash_device_t *flash; example_init_nand_flash(&flash, &spi); if (flash == NULL) { return; } uint32_t num_blocks; ESP_ERROR_CHECK(spi_nand_flash_get_block_num(flash, &num_blocks)); // Get bad block statistics uint32_t bad_block_count; ESP_LOGI(TAG, "Get bad block statistics:"); ESP_ERROR_CHECK(nand_get_bad_block_stats(flash, &bad_block_count)); ESP_LOGI(TAG, "\nTotal number of Blocks: %"PRIu32"\nBad Blocks: %"PRIu32"\nValid Blocks: %"PRIu32"\n", num_blocks, bad_block_count, num_blocks - bad_block_count); // Calculate read and write throughput via Dhara uint32_t start_page = 1; uint16_t page_count = 1000; bool get_raw_tp = false; ESP_LOGI(TAG, "Read-Write Throughput via Dhara:"); ESP_ERROR_CHECK(read_write_pages_tp(flash, start_page, page_count, get_raw_tp)); // Calculate read and write throughput at lower level (bypassing Dhara) start_page = 1001; page_count = 1000; get_raw_tp = true; ESP_LOGI(TAG, "Read-Write Throughput at lower level (bypassing Dhara):"); ESP_ERROR_CHECK(read_write_pages_tp(flash, start_page, page_count, get_raw_tp)); // Get ECC error statistics ESP_LOGI(TAG, "ECC errors statistics:"); ESP_ERROR_CHECK(nand_get_ecc_stats(flash)); example_deinit_nand_flash(flash, spi); } ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/pytest_nand_flash_debug_example.py ================================================ # SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize import glob from pathlib import Path @pytest.mark.spi_nand_flash @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_nand_flash_debug_example(dut) -> None: dut.expect_exact("Get bad block statistics:") dut.expect_exact("Read-Write Throughput via Dhara:") dut.expect_exact("Read-Write Throughput at lower level (bypassing Dhara):") dut.expect_exact("ECC errors statistics:") dut.expect_exact("Returned from app_main") ================================================ FILE: spi_nand_flash_fatfs/examples/nand_flash_debug_app/sdkconfig.defaults ================================================ CONFIG_NAND_FLASH_VERIFY_WRITE=y CONFIG_ESP_TASK_WDT_EN=n ================================================ FILE: spi_nand_flash_fatfs/idf_component.yml ================================================ version: "1.0.0" description: "FATFS integration for SPI NAND Flash" url: https://github.com/espressif/idf-extra-components/tree/master/spi_nand_flash_fatfs issues: https://github.com/espressif/idf-extra-components/issues repository: https://github.com/espressif/idf-extra-components.git documentation: https://github.com/espressif/idf-extra-components/tree/master/spi_nand_flash_fatfs/README.md dependencies: idf: version: ">=5.0" espressif/spi_nand_flash: version: "*" override_path: "../spi_nand_flash" ================================================ FILE: spi_nand_flash_fatfs/include/diskio_nand.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #pragma once #include "spi_nand_flash.h" #ifdef __cplusplus extern "C" { #endif /** * Register NAND flash diskio driver * * @param pdrv drive number * @param device pointer to a nand flash device structure; device should be initialized before calling f_mount. */ esp_err_t ff_diskio_register_nand(BYTE pdrv, spi_nand_flash_device_t *device); /** * @brief Get the driver number corresponding to a device * * @param device The device for which to return its driver * @return Driver number of the device */ BYTE ff_diskio_get_pdrv_nand(const spi_nand_flash_device_t *device); /** * @brief Clear a registered nand driver, so it can be reused * * @param device The device for which to clear its registration */ void ff_diskio_clear_pdrv_nand(const spi_nand_flash_device_t *dev); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash_fatfs/include/esp_vfs_fat_nand.h ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #pragma once #include #include "spi_nand_flash.h" #include "esp_err.h" #include "esp_vfs_fat.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Convenience function to initialize FAT filesystem in SPI nand flash and register it in VFS * * This is an all-in-one function which does the following: * * - mounts FAT partition using FATFS library on top of nand flash * - registers FATFS library with VFS, with prefix given by base_prefix variable * * @param base_path path where FATFS partition should be mounted (e.g. "/nandflash") * @param nand_device nand device handle returned by spi_nand_flash_init_device * @param mount_config pointer to structure with extra parameters for mounting FATFS * @return * - ESP_OK on success * - ESP_ERR_NOT_FOUND if there are no more free fatfs slots * - ESP_ERR_INVALID_STATE if esp_vfs_fat_nand_mount was already called * - ESP_ERR_NO_MEM if memory can not be allocated * - other error codes from nand driver, SPI flash driver, or FATFS drivers */ esp_err_t esp_vfs_fat_nand_mount(const char *base_path, spi_nand_flash_device_t *nand_device, const esp_vfs_fat_mount_config_t *mount_config); /** * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_nand_mount * * @param base_path path where nand flash is mounted * @param nand_device nand device handle used in mount * * @return * - ESP_OK on success * - ESP_ERR_INVALID_STATE if esp_vfs_fat_nand_mount hasn't been called */ esp_err_t esp_vfs_fat_nand_unmount(const char *base_path, spi_nand_flash_device_t *nand_device); #ifdef __cplusplus } #endif ================================================ FILE: spi_nand_flash_fatfs/src/diskio_nand.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #include "diskio.h" #include "esp_log.h" #include "esp_check.h" #include "diskio_nand.h" #include "spi_nand_flash.h" #include "diskio_impl.h" static const char *TAG = "diskio_nand"; static spi_nand_flash_device_t *ff_nand_handles[FF_VOLUMES] = {NULL}; DSTATUS ff_nand_initialize(BYTE pdrv) { return 0; } DSTATUS ff_nand_status(BYTE pdrv) { return 0; } DRESULT ff_nand_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { ESP_LOGV(TAG, "ff_nand_read - pdrv=%i, sector=%lu, count=%u", (unsigned int) pdrv, (unsigned long) sector, (unsigned int) count); esp_err_t ret; uint32_t page_size; spi_nand_flash_device_t *dev = ff_nand_handles[pdrv]; assert(dev); ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_size(dev, &page_size), fail, TAG, ""); for (uint32_t i = 0; i < count; i++) { ESP_GOTO_ON_ERROR(spi_nand_flash_read_page(dev, buff + i * page_size, sector + i), fail, TAG, "spi_nand_flash_read_page failed"); } return RES_OK; fail: ESP_LOGE(TAG, "ff_nand_read failed with error 0x%X", ret); return RES_ERROR; } DRESULT ff_nand_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { ESP_LOGV(TAG, "ff_nand_write - pdrv=%i, sector=%lu, count=%u", (unsigned int) pdrv, (unsigned long) sector, (unsigned int) count); esp_err_t ret; uint32_t page_size; spi_nand_flash_device_t *dev = ff_nand_handles[pdrv]; assert(dev); ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_size(dev, &page_size), fail, TAG, ""); for (uint32_t i = 0; i < count; i++) { ESP_GOTO_ON_ERROR(spi_nand_flash_write_page(dev, buff + i * page_size, sector + i), fail, TAG, "spi_nand_flash_write_page failed"); } return RES_OK; fail: ESP_LOGE(TAG, "ff_nand_write failed with error 0x%X", ret); return RES_ERROR; } #if FF_USE_TRIM DRESULT ff_nand_trim(BYTE pdrv, DWORD start_sector, DWORD sector_count) { esp_err_t ret; spi_nand_flash_device_t *dev = ff_nand_handles[pdrv]; assert(dev); uint32_t num_pages; ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_count(dev, &num_pages), fail, TAG, "get_page_count failed"); if ((start_sector > num_pages) || ((start_sector + sector_count) > num_pages)) { return RES_PARERR; } for (uint32_t i = 0; i < sector_count; i++) { ESP_GOTO_ON_ERROR(spi_nand_flash_trim(dev, start_sector + i), fail, TAG, "spi_nand_flash_trim failed"); } return RES_OK; fail: ESP_LOGE(TAG, "ff_nand_trim failed with error 0x%X", ret); return RES_ERROR; } #endif //FF_USE_TRIM DRESULT ff_nand_ioctl(BYTE pdrv, BYTE cmd, void *buff) { spi_nand_flash_device_t *dev = ff_nand_handles[pdrv]; assert(dev); ESP_LOGV(TAG, "ff_nand_ioctl: cmd=%i", cmd); esp_err_t ret = ESP_OK; switch (cmd) { case CTRL_SYNC: ESP_GOTO_ON_ERROR(spi_nand_flash_sync(dev), fail, TAG, "sync failed"); break; case GET_SECTOR_COUNT: { uint32_t num_pages; ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_count(dev, &num_pages), fail, TAG, "get_page_count failed"); *((DWORD *)buff) = num_pages; ESP_LOGV(TAG, "capacity=%"PRIu32" pages", num_pages); break; } case GET_SECTOR_SIZE: { uint32_t page_size; ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_size(dev, &page_size), fail, TAG, "get_page_size failed"); *((WORD *)buff) = (WORD)page_size; ESP_LOGV(TAG, "page size=%u", (unsigned)page_size); break; } #if FF_USE_TRIM case CTRL_TRIM: { DWORD start_sector = *((DWORD *)buff); DWORD end_sector = *((DWORD *)buff + 1) + 1; DWORD sector_count = end_sector - start_sector; return ff_nand_trim(pdrv, start_sector, sector_count); } #endif //FF_USE_TRIM default: return RES_ERROR; } return RES_OK; fail: ESP_LOGE(TAG, "ff_nand_ioctl cmd=%i, failed with error=0x%X", cmd, ret); return RES_ERROR; } esp_err_t ff_diskio_register_nand(BYTE pdrv, spi_nand_flash_device_t *device) { if (pdrv >= FF_VOLUMES) { return ESP_ERR_INVALID_ARG; } static const ff_diskio_impl_t nand_impl = { .init = &ff_nand_initialize, .status = &ff_nand_status, .read = &ff_nand_read, .write = &ff_nand_write, .ioctl = &ff_nand_ioctl }; ff_nand_handles[pdrv] = device; ff_diskio_register(pdrv, &nand_impl); return ESP_OK; } BYTE ff_diskio_get_pdrv_nand(const spi_nand_flash_device_t *dev) { for (int i = 0; i < FF_VOLUMES; i++) { if (dev == ff_nand_handles[i]) { return i; } } return 0xff; } void ff_diskio_clear_pdrv_nand(const spi_nand_flash_device_t *dev) { for (int i = 0; i < FF_VOLUMES; i++) { if (dev == ff_nand_handles[i]) { ff_nand_handles[i] = NULL; } } } ================================================ FILE: spi_nand_flash_fatfs/src/vfs_fat_spinandflash.c ================================================ /* * SPDX-FileCopyrightText: 2022 mikkeldamsgaard project * * SPDX-License-Identifier: Apache-2.0 * * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD */ #include #include #include #include "esp_log.h" #include "esp_vfs.h" #include "esp_vfs_fat.h" #include "spi_nand_flash.h" #include "vfs_fat_internal.h" #include "diskio_impl.h" #include "diskio_nand.h" static const char *TAG = "vfs_fat_nand"; esp_err_t esp_vfs_fat_nand_mount(const char *base_path, spi_nand_flash_device_t *nand_device, const esp_vfs_fat_mount_config_t *mount_config) { esp_err_t ret = ESP_OK; void *workbuf = NULL; FATFS *fs = NULL; uint32_t page_size; const size_t workbuf_size = 4096; // connect driver to FATFS BYTE pdrv = 0xFF; ESP_GOTO_ON_ERROR(ff_diskio_get_drive(&pdrv), fail, TAG, "the maximum count of volumes is already mounted"); ESP_LOGD(TAG, "using pdrv=%i", pdrv); char drv[3] = {(char)('0' + pdrv), ':', 0}; ESP_GOTO_ON_ERROR(ff_diskio_register_nand(pdrv, nand_device), fail, TAG, "ff_diskio_register_nand failed drv=%i", pdrv); ESP_GOTO_ON_ERROR(spi_nand_flash_get_page_size(nand_device, &page_size), fail, TAG, ""); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) esp_vfs_fat_conf_t conf = { .base_path = base_path, .fat_drive = drv, .max_files = mount_config->max_files, }; ESP_GOTO_ON_ERROR(esp_vfs_fat_register_cfg(&conf, &fs), fail, TAG, "esp_vfs_fat_register failed"); #else ESP_GOTO_ON_ERROR(esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs), fail, TAG, "esp_vfs_fat_register failed"); #endif // Try to mount partition FRESULT fresult = f_mount(fs, drv, 1); if (fresult != FR_OK) { ESP_LOGW(TAG, "f_mount failed (%d)", fresult); if (!((fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR) && mount_config->format_if_mount_failed)) { ret = ESP_FAIL; goto fail; } workbuf = ff_memalloc(workbuf_size); if (workbuf == NULL) { ret = ESP_ERR_NO_MEM; goto fail; } size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size( page_size, mount_config->allocation_unit_size); ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size); const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size}; fresult = f_mkfs(drv, &opt, workbuf, workbuf_size); if (fresult != FR_OK) { ret = ESP_FAIL; ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult); goto fail; } free(workbuf); workbuf = NULL; ESP_LOGI(TAG, "Mounting again"); fresult = f_mount(fs, drv, 0); if (fresult != FR_OK) { ret = ESP_FAIL; ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult); goto fail; } } return ESP_OK; fail: if (workbuf) { free(workbuf); } if (fs) { esp_vfs_fat_unregister_path(base_path); } ff_diskio_unregister(pdrv); return ret; } esp_err_t esp_vfs_fat_nand_unmount(const char *base_path, spi_nand_flash_device_t *nand_device) { BYTE pdrv = ff_diskio_get_pdrv_nand(nand_device); if (pdrv == 0xff) { return ESP_ERR_INVALID_STATE; } char drv[3] = {(char)('0' + pdrv), ':', 0}; f_mount(NULL, drv, 0); ff_diskio_unregister(pdrv); ff_diskio_clear_pdrv_nand(nand_device); esp_err_t err = esp_vfs_fat_unregister_path(base_path); return err; } ================================================ FILE: supertinycron/CMakeLists.txt ================================================ idf_component_register(SRCS "supertinycron/ccronexpr.c" INCLUDE_DIRS "supertinycron/") target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DCRON_USE_LOCAL_TIME") if(CONFIG_CRON_DISABLE_YEARS) target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DCRON_DISABLE_YEARS") endif() target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-char-subscripts) ================================================ FILE: supertinycron/Kconfig ================================================ menu "CRON Expression Parser" config CRON_DISABLE_YEARS bool "Disable support for parsing years" default n help When this option is selected, year parsing support will be disabled, reducing memory footprint for cron_expr by 29 bytes. The library will still accept the year field. However, the field will not be validated and the cron event will be triggered every year. endmenu ================================================ FILE: supertinycron/LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2015, staticlibs.net Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: supertinycron/README.md ================================================ ## Cron expression parsing in ANSI C Given a cron expression and a date, you can get the next date which satisfies the cron expression. The more details please refer to [README.md](https://github.com/espressif/idf-extra-components/blob/master/supertinycron/supertinycron/README.md) ## Usage Example Refer to [cron_example](https://github.com/espressif/idf-extra-components/blob/master/supertinycron/examples/cron_example) ## API Reference To learn more about how to use this component, please check API Documentation from header file [ccronexpr.h](https://github.com/espressif/idf-extra-components/blob/master/supertinycron/supertinycron/ccronexpr.h) ## License This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). ================================================ FILE: supertinycron/examples/cron_example/CMakeLists.txt ================================================ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # The following five lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(cron_example) ================================================ FILE: supertinycron/examples/cron_example/README.md ================================================ # Cron expression parsing example This example can be used to run cron expression parsing on an Espressif chip. The example doesn't require any special hardware and can run on any development board. ## Building and running Run the application as usual for an ESP-IDF project. For example, for ESP32-C3: ``` idf.py set-target esp32c3 idf.py -p PORT flash monitor ``` After launching, the benchmark takes a few seconds to run, please be patient. ================================================ FILE: supertinycron/examples/cron_example/main/CMakeLists.txt ================================================ idf_component_register(SRCS cron_example_main.c PRIV_REQUIRES supertinycron) ================================================ FILE: supertinycron/examples/cron_example/main/cron_example_main.c ================================================ /* * Copyright 2015, alex at staticlibs.net * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * File: CronExprParser_test.cpp * Author: alex * * Created on February 24, 2015, 9:36 AM */ #include #include #include #include #include #include "ccronexpr.h" #include "sdkconfig.h" #define MAX_SECONDS 62 #define CRON_MAX_MINUTES 60 #define CRON_MAX_HOURS 24 #define CRON_MAX_DAYS_OF_WEEK 8 #define CRON_MAX_DAYS_OF_MONTH 32 #define CRON_MAX_MONTHS 12 #define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" #ifndef ARRAY_LEN #define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) #endif /* declared in cronexpr.c */ time_t cron_mktime(struct tm *tm); /** * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers */ uint8_t cron_get_bit(const uint8_t *rbyte, int idx); void cron_set_bit(uint8_t *rbyte, int idx); void cron_del_bit(uint8_t *rbyte, int idx); static int crons_equal(cron_expr *cr1, cron_expr *cr2) { unsigned int i; for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { if (cr1->seconds[i] != cr2->seconds[i]) { printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); return 0; } } for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { if (cr1->minutes[i] != cr2->minutes[i]) { printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); return 0; } } for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { if (cr1->hours[i] != cr2->hours[i]) { printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); return 0; } } for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { if (cr1->days_of_week[i] != cr2->days_of_week[i]) { printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); return 0; } } for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { if (cr1->days_of_month[i] != cr2->days_of_month[i]) { printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); return 0; } } for (i = 0; i < ARRAY_LEN(cr1->months); i++) { if (cr1->months[i] != cr2->months[i]) { printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); return 0; } } return 1; } int one_dec_num(const char ch) { switch (ch) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; default: return -1; } } int extract_digits(const char *str, int *digit_count) { int num = 0; if (!str || !digit_count) { return 0; } while (*str) { (*digit_count)++; if (*str >= '0' && *str <= '9') { num = num * 10 + (*str - '0'); } else { break; } str++; } return num; } /* strptime is not available in msvc */ /* 2012-07-01_09:53:50 */ /* 0123456789012345678 */ void poors_mans_strptime(const char *str, struct tm *cal) { int count = 0; assert(cal != NULL); memset(cal, 0, sizeof(struct tm)); cal->tm_year = extract_digits(str, &count) - 1900; cal->tm_mon = extract_digits(str + count, &count) - 1; cal->tm_mday = extract_digits(str + count, &count); cal->tm_wday = 0; cal->tm_yday = 0; cal->tm_hour = extract_digits(str + count, &count); cal->tm_min = extract_digits(str + count, &count); cal->tm_sec = extract_digits(str + count, &count); cal->tm_isdst = -1; } typedef time_t (*cron_find_fn)(cron_expr *, time_t); int count_fields(const char *str, char del) { size_t count = 0; if (!str) { return -1; } while ((str = strchr(str, del)) != NULL) { count++; do { str++; } while (del == *str); } return (int)count + 1; } #define check_fn(fn_fn,pattern,initial,expected) check_fn_line(fn_fn, pattern, initial, expected, __LINE__) void check_fn_line(cron_find_fn fn, const char *pattern, const char *initial, const char *expected, int line) { const char *err = NULL; const int len = count_fields(pattern, ' '); cron_expr parsed1, parsed2; /*printf("Pattern: %s\n", pattern);**/ cron_parse_expr(pattern, &parsed1, &err); struct tm calinit; poors_mans_strptime(initial, &calinit); time_t dateinit = cron_mktime(&calinit); assert(-1 != dateinit); time_t datenext = fn(&parsed1, dateinit); #ifdef CRON_USE_LOCAL_TIME struct tm *calnext = localtime(&datenext); #else struct tm *calnext = gmtime(&datenext); #endif assert(calnext); char *buffer = (char *) malloc(512); memset(buffer, 0, 512); strftime(buffer, 512, DATE_FORMAT, calnext); printf("parsed: %s\n", pattern); if (0 != strcmp(expected, buffer + (buffer[0] == '+' ? 1 : 0))) { printf("Line: %d\n", line); printf("Pattern: %s\n", pattern); printf("Initial: %s\n", initial); printf("Expected: %s\n", expected); printf("Actual: %s\n", buffer); assert(0); } assert(cron_generate_expr(&parsed1, buffer, 512, len, &err) > 0); if (0 != strcmp(pattern, buffer)) { printf("Line: %d\n", line); printf("Pattern: %s\n", pattern); printf("Actual: %s\n", buffer); } cron_parse_expr(buffer, &parsed2, &err); assert(crons_equal(&parsed1, &parsed2)); free(buffer); } void check_same(const char *expr1, const char *expr2) { cron_expr parsed1; cron_parse_expr(expr1, &parsed1, NULL); cron_expr parsed2; cron_parse_expr(expr2, &parsed2, NULL); printf("parsed1: %s\n", expr1); printf("parsed2: %s\n", expr2); assert(crons_equal(&parsed1, &parsed2)); } void check_calc_invalid(void) { cron_expr parsed; cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); struct tm calinit; poors_mans_strptime("2012-07-01_09:53:50", &calinit); time_t dateinit = cron_mktime(&calinit); time_t res = cron_next(&parsed, dateinit); assert(CRON_INVALID_INSTANT == res); } void check_expr_invalid(const char *pattern) { const char *err = NULL; cron_expr parsed1; cron_parse_expr(pattern, &parsed1, &err); printf("parsed1: %s\n", pattern); if (err) { printf("check_expr_invalid: %s\n", err); } assert(err); } #define check_expr_valid(pattern) check_expr_valid_line(pattern, __LINE__) void check_expr_valid_line(const char *pattern, int line) { const char *err = NULL; const int len = count_fields(pattern, ' '); cron_expr parsed1, parsed2; cron_parse_expr(pattern, &parsed1, &err); printf("parsed1: %s\n", pattern); if (err) { printf("check_expr_invalid: %s\n", err); } char *buffer = (char *) malloc(512); memset(buffer, 0, 512); assert(cron_generate_expr(&parsed1, buffer, 512, len, &err) > 0); if (0 != strcmp(pattern, buffer)) { printf("Line: %d\n", line); printf("Pattern: %s\n", pattern); printf("Actual: %s\n", buffer); } cron_parse_expr(buffer, &parsed2, &err); assert(crons_equal(&parsed1, &parsed2)); assert(!err); } void test_expr(void) { char *tz = getenv("TZ"); /*Test leap seconds - nejsou nastavené hodnoty, co se kontrolují */ /*Test leap seconds check_fn(cron_next, "60 0 0 * * *", "2015-01-01_15:12:42", "2015-06-30_00:00:00");*/ check_fn(cron_next, "* * * * * *", "2100-01-01_15:12:42", "2100-01-01_15:12:43"); check_fn(cron_next, "* * * * * *", "2198-01-01_15:12:42", "2198-01-01_15:12:43"); check_fn(cron_next, "* * * * * *", "2199-01-01_15:12:42", "2199-01-01_15:12:43"); if (tz && !strcmp("right/UTC", tz)) { check_fn(cron_next, "L59 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:50:59"); check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:59:60"); check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:59:59", "2016-12-31_23:59:60"); } check_fn(cron_next, "* * * 1 1 * *", "1970-01-01_15:12:42", "1970-01-01_15:12:43"); #ifndef CONFIG_CRON_DISABLE_YEARS check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "1970-01-01_15:12:42", "1970-01-01_15:12:43"); /*check_fn(cron_next, "* * * 1 1 * 1969,2100,2193,2199", "1969-01-01_15:12:42", "1969-01-01_15:12:43");*/ check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "1971-01-01_15:12:42", "2100-01-01_00:00:00"); check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "2195-01-01_15:12:42", "2199-01-01_00:00:00"); /*check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2200", "2195-01-01_15:12:42", "2200-01-01_00:00:00");*/ check_fn(cron_next, "* * * 1 1 * 2020", "2011-01-01_15:12:42", "2020-01-01_00:00:00"); #endif check_fn(cron_next, "* * * 1W * *", "2011-01-01_15:12:42", "2011-01-03_00:00:00"); check_fn(cron_next, "* * * 31W * *", "2010-01-01_15:12:42", "2010-01-29_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-01-01_15:12:42", "2010-01-29_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-02-03_15:12:42", "2010-02-26_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-03-06_15:12:42", "2010-03-31_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-04-09_15:12:42", "2010-04-30_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-05-12_15:12:42", "2010-05-31_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-06-15_15:12:42", "2010-06-30_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-07-18_15:12:42", "2010-07-30_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-08-21_15:12:42", "2010-08-31_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-09-24_15:12:42", "2010-09-30_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-10-27_15:12:42", "2010-10-29_00:00:00"); check_fn(cron_next, "* * * LW * *", "2010-11-30_15:12:42", "2010-11-30_15:12:43"); check_fn(cron_next, "* * * LW * *", "2010-12-31_15:12:42", "2010-12-31_15:12:43"); check_fn(cron_next, "* * * 15W * *", "2010-01-01_15:12:42", "2010-01-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-02-03_15:12:42", "2010-02-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-03-06_15:12:42", "2010-03-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-04-09_15:12:42", "2010-04-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-05-12_15:12:42", "2010-05-14_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-06-15_15:12:42", "2010-06-15_15:12:43"); check_fn(cron_next, "* * * 15W * *", "2010-07-18_15:12:42", "2010-08-16_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-08-21_15:12:42", "2010-09-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-09-24_15:12:42", "2010-10-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-10-27_15:12:42", "2010-11-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-11-30_15:12:42", "2010-12-15_00:00:00"); check_fn(cron_next, "* * * 15W * *", "2010-12-31_15:12:42", "2011-01-14_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-01-01_15:12:42", "2010-01-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-02-03_15:12:42", "2010-02-27_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-03-06_15:12:42", "2010-03-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-04-09_15:12:42", "2010-04-29_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-05-12_15:12:42", "2010-05-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-06-15_15:12:42", "2010-06-29_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-07-18_15:12:42", "2010-07-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-08-21_15:12:42", "2010-08-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-09-24_15:12:42", "2010-09-29_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-10-27_15:12:42", "2010-10-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-11-30_15:12:42", "2010-12-30_00:00:00"); check_fn(cron_next, "* * * L-1 * *", "2010-12-31_15:12:42", "2011-01-30_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-01-01_15:12:42", "2010-01-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-02-03_15:12:42", "2010-02-28_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-03-06_15:12:42", "2010-03-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-04-09_15:12:42", "2010-04-30_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-05-12_15:12:42", "2010-05-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-06-15_15:12:42", "2010-06-30_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-07-18_15:12:42", "2010-07-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-08-21_15:12:42", "2010-08-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-09-24_15:12:42", "2010-09-30_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * L * *", "2010-11-30_15:12:42", "2010-11-30_15:12:43"); check_fn(cron_next, "* * * L * *", "2010-12-31_15:12:42", "2010-12-31_15:12:43"); check_fn(cron_next, "* * * * * 1#-5", "2010-09-03_15:12:42", "2010-11-01_00:00:00"); check_fn(cron_next, "* * * * * 1#-4", "2010-09-03_15:12:42", "2010-09-06_00:00:00"); check_fn(cron_next, "* * * * * 1#-3", "2010-09-03_15:12:42", "2010-09-13_00:00:00"); check_fn(cron_next, "* * * * * 1#-2", "2010-09-03_15:12:42", "2010-09-20_00:00:00"); check_fn(cron_next, "* * * * * 1#1", "2010-09-03_15:12:42", "2010-09-06_00:00:00"); check_fn(cron_next, "* * * * * 1#2", "2010-09-03_15:12:42", "2010-09-13_00:00:00"); check_fn(cron_next, "* * * * * 1#3", "2010-09-03_15:12:42", "2010-09-20_00:00:00"); check_fn(cron_next, "* * * * * 1#4", "2010-09-03_15:12:42", "2010-09-27_00:00:00"); check_fn(cron_next, "* * * * * 1#5", "2010-09-03_15:12:42", "2010-11-29_00:00:00"); check_fn(cron_next, "* * * * * 2#-5", "2010-09-03_15:12:42", "2010-11-02_00:00:00"); check_fn(cron_next, "* * * * * 2#-4", "2010-09-03_15:12:42", "2010-09-07_00:00:00"); check_fn(cron_next, "* * * * * 2#-3", "2010-09-03_15:12:42", "2010-09-14_00:00:00"); check_fn(cron_next, "* * * * * 2#-2", "2010-09-03_15:12:42", "2010-09-21_00:00:00"); check_fn(cron_next, "* * * * * 2#1", "2010-09-03_15:12:42", "2010-09-07_00:00:00"); check_fn(cron_next, "* * * * * 2#2", "2010-09-03_15:12:42", "2010-09-14_00:00:00"); check_fn(cron_next, "* * * * * 2#3", "2010-09-03_15:12:42", "2010-09-21_00:00:00"); check_fn(cron_next, "* * * * * 2#4", "2010-09-03_15:12:42", "2010-09-28_00:00:00"); check_fn(cron_next, "* * * * * 2#5", "2010-09-03_15:12:42", "2010-11-30_00:00:00"); check_fn(cron_next, "* * * * * 3#-5", "2010-09-03_15:12:42", "2010-12-01_00:00:00"); check_fn(cron_next, "* * * * * 3#-4", "2010-09-03_15:12:42", "2010-09-08_00:00:00"); check_fn(cron_next, "* * * * * 3#-3", "2010-09-03_15:12:42", "2010-09-15_00:00:00"); check_fn(cron_next, "* * * * * 3#-2", "2010-09-03_15:12:42", "2010-09-22_00:00:00"); check_fn(cron_next, "* * * * * 3#1", "2010-09-03_15:12:42", "2010-10-06_00:00:00"); check_fn(cron_next, "* * * * * 3#2", "2010-09-03_15:12:42", "2010-09-08_00:00:00"); check_fn(cron_next, "* * * * * 3#3", "2010-09-03_15:12:42", "2010-09-15_00:00:00"); check_fn(cron_next, "* * * * * 3#4", "2010-09-03_15:12:42", "2010-09-22_00:00:00"); check_fn(cron_next, "* * * * * 3#5", "2010-09-03_15:12:42", "2010-09-29_00:00:00"); check_fn(cron_next, "* * * * * 4#-5", "2010-09-03_15:12:42", "2010-12-02_00:00:00"); check_fn(cron_next, "* * * * * 4#-4", "2010-09-03_15:12:42", "2010-09-09_00:00:00"); check_fn(cron_next, "* * * * * 4#-3", "2010-09-03_15:12:42", "2010-09-16_00:00:00"); check_fn(cron_next, "* * * * * 4#-2", "2010-09-03_15:12:42", "2010-09-23_00:00:00"); check_fn(cron_next, "* * * * * 4#1", "2010-09-03_15:12:42", "2010-10-07_00:00:00"); check_fn(cron_next, "* * * * * 4#2", "2010-09-03_15:12:42", "2010-09-09_00:00:00"); check_fn(cron_next, "* * * * * 4#3", "2010-09-03_15:12:42", "2010-09-16_00:00:00"); check_fn(cron_next, "* * * * * 4#4", "2010-09-03_15:12:42", "2010-09-23_00:00:00"); check_fn(cron_next, "* * * * * 4#5", "2010-09-03_15:12:42", "2010-09-30_00:00:00"); check_fn(cron_next, "* * * * * 5#-5", "2010-09-03_15:12:42", "2010-10-01_00:00:00"); check_fn(cron_next, "* * * * * 5#-4", "2010-09-03_15:12:42", "2010-09-03_15:12:43"); check_fn(cron_next, "* * * * * 5#-3", "2010-09-03_15:12:42", "2010-09-10_00:00:00"); check_fn(cron_next, "* * * * * 5#-2", "2010-09-03_15:12:42", "2010-09-17_00:00:00"); check_fn(cron_next, "* * * * * 5#1", "2010-09-03_15:12:42", "2010-09-03_15:12:43"); check_fn(cron_next, "* * * * * 5#2", "2010-09-03_15:12:42", "2010-09-10_00:00:00"); check_fn(cron_next, "* * * * * 5#3", "2010-09-03_15:12:42", "2010-09-17_00:00:00"); check_fn(cron_next, "* * * * * 5#4", "2010-09-03_15:12:42", "2010-09-24_00:00:00"); check_fn(cron_next, "* * * * * 5#5", "2010-09-03_15:12:42", "2010-10-29_00:00:00"); check_fn(cron_next, "* * * * * 6#-5", "2010-09-03_15:12:42", "2010-10-02_00:00:00"); check_fn(cron_next, "* * * * * 6#-4", "2010-09-03_15:12:42", "2010-09-04_00:00:00"); check_fn(cron_next, "* * * * * 6#-3", "2010-09-03_15:12:42", "2010-09-11_00:00:00"); check_fn(cron_next, "* * * * * 6#-2", "2010-09-03_15:12:42", "2010-09-18_00:00:00"); check_fn(cron_next, "* * * * * 6#1", "2010-09-03_15:12:42", "2010-09-04_00:00:00"); check_fn(cron_next, "* * * * * 6#2", "2010-09-03_15:12:42", "2010-09-11_00:00:00"); check_fn(cron_next, "* * * * * 6#3", "2010-09-03_15:12:42", "2010-09-18_00:00:00"); check_fn(cron_next, "* * * * * 6#4", "2010-09-03_15:12:42", "2010-09-25_00:00:00"); check_fn(cron_next, "* * * * * 6#5", "2010-09-03_15:12:42", "2010-10-30_00:00:00"); check_fn(cron_next, "* * * * * 7#-5", "2010-09-03_15:12:42", "2010-10-03_00:00:00"); check_fn(cron_next, "* * * * * 7#-4", "2010-09-03_15:12:42", "2010-09-05_00:00:00"); check_fn(cron_next, "* * * * * 7#-3", "2010-09-03_15:12:42", "2010-09-12_00:00:00"); check_fn(cron_next, "* * * * * 7#-2", "2010-09-03_15:12:42", "2010-09-19_00:00:00"); check_fn(cron_next, "* * * * * 7#1", "2010-09-03_15:12:42", "2010-09-05_00:00:00"); check_fn(cron_next, "* * * * * 7#2", "2010-09-03_15:12:42", "2010-09-12_00:00:00"); check_fn(cron_next, "* * * * * 7#3", "2010-09-03_15:12:42", "2010-09-19_00:00:00"); check_fn(cron_next, "* * * * * 7#4", "2010-09-03_15:12:42", "2010-09-26_00:00:00"); check_fn(cron_next, "* * * * * 7#5", "2010-09-03_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * * * 1L", "2010-09-30_15:12:42", "2010-10-25_00:00:00"); check_fn(cron_next, "* * * * * 2L", "2010-09-30_15:12:42", "2010-10-26_00:00:00"); check_fn(cron_next, "* * * * * 3L", "2010-09-30_15:12:42", "2010-10-27_00:00:00"); check_fn(cron_next, "* * * * * 4L", "2010-09-30_15:12:42", "2010-09-30_15:12:43"); check_fn(cron_next, "* * * * * 5L", "2010-09-30_15:12:42", "2010-10-29_00:00:00"); check_fn(cron_next, "* * * * * 6L", "2010-09-30_15:12:42", "2010-10-30_00:00:00"); check_fn(cron_next, "* * * * * 7L", "2010-09-30_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * * * 1L", "2010-10-27_15:12:42", "2010-11-29_00:00:00"); check_fn(cron_next, "* * * * * 2L", "2010-10-27_15:12:42", "2010-11-30_00:00:00"); check_fn(cron_next, "* * * * * 3L", "2010-10-27_15:12:42", "2010-10-27_15:12:43"); check_fn(cron_next, "* * * * * 4L", "2010-10-27_15:12:42", "2010-10-28_00:00:00"); check_fn(cron_next, "* * * * * 5L", "2010-10-27_15:12:42", "2010-10-29_00:00:00"); check_fn(cron_next, "* * * * * 6L", "2010-10-27_15:12:42", "2010-10-30_00:00:00"); check_fn(cron_next, "* * * * * 7L", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * * * 1L", "2010-10-30_15:12:42", "2010-11-29_00:00:00"); check_fn(cron_next, "* * * * * 2L", "2010-10-30_15:12:42", "2010-11-30_00:00:00"); check_fn(cron_next, "* * * * * 3L", "2010-10-30_15:12:42", "2010-11-24_00:00:00"); check_fn(cron_next, "* * * * * 4L", "2010-10-30_15:12:42", "2010-11-25_00:00:00"); check_fn(cron_next, "* * * * * 5L", "2010-10-30_15:12:42", "2010-11-26_00:00:00"); check_fn(cron_next, "* * * * * 6L", "2010-10-30_15:12:42", "2010-10-30_15:12:43"); check_fn(cron_next, "* * * * * 7L", "2010-10-30_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * * * 1L", "2010-11-27_15:12:42", "2010-11-29_00:00:00"); check_fn(cron_next, "* * * * * 2L", "2010-11-27_15:12:42", "2010-11-30_00:00:00"); check_fn(cron_next, "* * * * * 3L", "2010-11-27_15:12:42", "2010-12-29_00:00:00"); check_fn(cron_next, "* * * * * 4L", "2010-11-27_15:12:42", "2010-12-30_00:00:00"); check_fn(cron_next, "* * * * * 5L", "2010-11-27_15:12:42", "2010-12-31_00:00:00"); check_fn(cron_next, "* * * * * 6L", "2010-11-27_15:12:42", "2010-11-27_15:12:43"); check_fn(cron_next, "* * * * * 7L", "2010-11-27_15:12:42", "2010-11-28_00:00:00"); check_fn(cron_next, "0 0 7 W * *", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); check_fn(cron_next, "0 0 7 W * *", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); check_fn(cron_next, "* * * * * L", "2010-10-25_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* * * * * L", "2010-10-20_15:12:42", "2010-10-24_00:00:00"); check_fn(cron_next, "* * * * * L", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); check_fn(cron_next, "* 15 11 * * *", "2019-03-09_11:43:00", "2019-03-10_11:15:00"); check_fn(cron_next, "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); check_fn(cron_next, "*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); check_fn(cron_next, "0,15,30,45 * 1,2,3,4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); check_fn(cron_next, "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); check_fn(cron_next, "0 */2 * * * *", "2012-07-01_09:00:00", "2012-07-01_09:02:00"); check_fn(cron_next, "0 */2 * * * *", "2013-07-01_09:00:00", "2013-07-01_09:02:00"); check_fn(cron_next, "0 */2 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:26:00"); check_fn(cron_next, "0 */2 * * * *", "2018-09-14_14:25:00", "2018-09-14_14:26:00"); check_fn(cron_next, "0 */20 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:40:00"); check_fn(cron_next, "* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); check_fn(cron_next, "* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); check_fn(cron_next, "10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); check_fn(cron_next, "11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); check_fn(cron_next, "10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); check_fn(cron_next, "10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); check_fn(cron_next, "10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); check_fn(cron_next, "0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); check_fn(cron_next, "0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); check_fn(cron_next, "0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); check_fn(cron_next, "0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); check_fn(cron_next, "0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); check_fn(cron_next, "0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); check_fn(cron_next, "0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); check_fn(cron_next, "0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); check_fn(cron_next, "* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); check_fn(cron_next, "* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); check_fn(cron_next, "0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); check_fn(cron_next, "0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); check_fn(cron_next, "0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); check_fn(cron_next, "0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); check_fn(cron_next, "0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); check_fn(cron_next, "0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); check_fn(cron_next, "* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); check_fn(cron_next, "* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); check_fn(cron_next, "* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); check_fn(cron_next, "55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); check_fn(cron_next, "55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); check_fn(cron_next, "55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); check_fn(cron_next, "55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); check_fn(cron_next, "* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); check_fn(cron_next, "* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); check_fn(cron_next, "55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); check_fn(cron_next, "55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); check_fn(cron_next, "* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); check_fn(cron_next, "* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); check_fn(cron_next, "0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); check_fn(cron_next, "0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); check_fn(cron_next, "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); check_fn(cron_next, "0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); check_fn(cron_next, "0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); check_fn(cron_next, "0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); check_fn(cron_next, "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); check_fn(cron_next, "* * * * * *", "2020-12-31_23:59:59", "2021-01-01_00:00:00"); check_fn(cron_next, "0 0 * * * *", "2020-02-28_23:00:00", "2020-02-29_00:00:00"); check_fn(cron_next, "0 0 0 * * *", "2020-02-29_01:02:03", "2020-03-01_00:00:00"); check_fn(cron_prev, "* 15 11 * * *", "2019-03-09_11:43:00", "2019-03-09_11:15:59"); check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-01_04:59:45"); check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_01:00:14", "2012-07-01_01:00:00"); check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_01:00:00", "2012-06-30_04:59:45"); check_fn(cron_prev, "* * * * * *", "2012-07-01_09:00:00", "2012-07-01_08:59:59"); check_fn(cron_prev, "* * * * * *", "2021-01-01_00:00:00", "2020-12-31_23:59:59"); check_fn(cron_prev, "0 0 * * * *", "2020-02-29_00:00:00", "2020-02-28_23:00:00"); check_fn(cron_prev, "0 0 0 * * *", "2020-03-01_00:00:00", "2020-02-29_00:00:00"); check_fn(cron_prev, "0 0 * * * *", "2020-03-01_00:00:00", "2020-02-29_23:00:00"); check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-05-31_00:00:00", "2022-11-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-07-31_00:00:00", "2022-11-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-08-31_00:00:00", "2022-11-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-10-31_00:00:00", "2022-11-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 6-7 *", "2022-05-31_00:00:00", "2022-06-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 8-9 *", "2022-07-31_00:00:00", "2022-08-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 9-10 *", "2022-08-31_00:00:00", "2022-09-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 2-3 *", "2022-01-31_00:00:00", "2022-02-01_00:00:00"); check_fn(cron_next, "0 0 0 ? 4-5 *", "2022-03-31_00:00:00", "2022-04-01_00:00:00"); check_fn(cron_next, "* * * 29 2 *", "2021-12-07_12:00:00", "2024-02-29_00:00:00"); check_fn(cron_prev, "* * * 29 2 *", "2021-12-07_12:00:00", "2020-02-29_23:59:59"); check_fn(cron_prev, "* * * 1 2 *", "2023-11-01_00:00:00", "2023-02-01_23:59:59"); check_fn(cron_prev, "* * * 2 2 *", "2023-11-01_00:00:00", "2023-02-02_23:59:59"); check_fn(cron_prev, "* * * 3 2 *", "2023-11-01_00:00:00", "2023-02-03_23:59:59"); check_fn(cron_prev, "* * * 4 2 *", "2023-11-01_00:00:00", "2023-02-04_23:59:59"); check_fn(cron_prev, "* * * 1 4 *", "2023-10-01_00:00:00", "2023-04-01_23:59:59"); check_fn(cron_prev, "* * * 2 4 *", "2023-10-01_00:00:00", "2023-04-02_23:59:59"); check_fn(cron_prev, "* * * 3 4 *", "2023-10-01_00:00:00", "2023-04-03_23:59:59"); check_fn(cron_prev, "* * * 4 4 *", "2023-10-01_00:00:00", "2023-04-04_23:59:59"); check_fn(cron_prev, "0 0 20 1 2 *", "2022-12-30_08:10:23", "2022-02-01_20:00:00"); check_fn(cron_prev, "0 0 20 2 2 *", "2022-12-30_08:10:23", "2022-02-02_20:00:00"); check_fn(cron_prev, "0 0 20 3 2 *", "2022-12-30_08:10:23", "2022-02-03_20:00:00"); check_fn(cron_prev, "0 0 20 1 2 *", "2022-12-31_08:10:23", "2022-02-01_20:00:00"); check_fn(cron_prev, "0 0 20 2 2 *", "2022-12-31_08:10:23", "2022-02-02_20:00:00"); check_fn(cron_prev, "0 0 20 3 2 *", "2022-12-31_08:10:23", "2022-02-03_20:00:00"); check_fn(cron_prev, "0 0 20 1 2 *", "2023-01-01_08:10:23", "2022-02-01_20:00:00"); check_fn(cron_prev, "0 0 20 2 2 *", "2023-01-01_08:10:23", "2022-02-02_20:00:00"); check_fn(cron_prev, "0 0 20 3 2 *", "2023-01-01_08:10:23", "2022-02-03_20:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-08-31_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-01_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-02_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-03_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-04_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-05_18:00:00", "2023-02-28_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_17:00:00", "2023-03-01_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_17:00:01", "2023-03-02_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_18:00:00", "2023-03-02_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-03_18:00:00", "2023-03-03_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-04_18:00:00", "2023-03-03_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-05_18:00:00", "2023-03-03_17:00:00"); check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-06_18:00:00", "2023-03-06_17:00:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-05_18:00:00", "2023-04-29_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:29:59", "2023-04-29_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:30:00", "2023-04-29_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:30:01", "2024-04-06_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_18:00:00", "2024-04-06_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-07_18:00:00", "2024-04-06_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-26_18:00:00", "2024-04-20_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-27_18:00:00", "2024-04-27_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-28_18:00:00", "2024-04-27_09:30:00"); check_fn(cron_prev, "0 30 9 * 4 6", "2024-05-01_18:00:00", "2024-04-27_09:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-02-27_10:00:00", "2020-02-22_11:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-02-28_10:00:00", "2020-02-22_11:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-02-29_10:00:00", "2020-02-22_11:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-02-29_11:31:00", "2020-02-29_11:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-03-01_10:00:00", "2020-02-29_11:30:00"); check_fn(cron_prev, "0 30 11 * * 6", "2020-03-01_12:00:00", "2020-02-29_11:30:00"); check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_09:59:59", "2020-02-28_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_10:00:00", "2020-02-28_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_10:00:01", "2020-02-29_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_09:59:59", "2022-02-27_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_10:00:00", "2022-02-27_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_10:00:01", "2022-02-28_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_09:59:59", "2022-12-30_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_10:00:00", "2022-12-30_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_10:00:01", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_23:59:59", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_00:00:00", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_00:00:01", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_09:59:59", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_10:00:00", "2022-12-31_10:00:00"); check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_10:00:01", "2023-01-01_10:00:00"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-01_12:34:56", "2023-06-22_23:50:30"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-19_12:34:56", "2023-06-22_23:50:30"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-20_12:34:56", "2023-06-22_23:50:30"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-21_12:34:56", "2023-07-20_23:50:30"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-22_12:34:56", "2023-07-21_23:50:30"); check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-23_12:34:56", "2023-07-22_23:50:30"); } void test_parse(void) { check_same("* * * W * *", "* * * * * 1-5"); check_same("* * * * * L", "* * * * * 0"); check_same("* * * * * 6#-1", "* * * * * 6L"); check_same("0 0 0 * * *", "0 0 * * *"); check_same("0 0 0 * * *", "0 0 0 * * * *"); check_same("* * * * * *", "* * * * * * *"); check_same("@annually", "0 0 0 1 1 * *"); check_same("@yearly", "0 0 0 1 1 * *"); check_same("@monthly", "0 0 0 1 * * *"); check_same("@weekly", "0 0 0 * * 0 *"); check_same("@daily", "0 0 0 * * * *"); check_same("@midnight", "0 0 0 * * * *"); check_same("@hourly", "0 0 * * * * *"); check_same("@minutely", "0 * * * * * *"); check_same("@secondly", "* * * * * * *"); check_same("* * * 2 * *", "* * * 2 * ?"); check_same("* * * 2 * *", "* * * 2 * ?"); check_same("57,59 * * * * *", "57/2 * * * * *"); check_same("L57,59,61 * * * * *", "L57/2 * * * * *"); check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); check_same("* * * * * 0", "* * * * * SUN"); check_same("* * * * * 0", "* * * * * 7"); check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); check_same("* * * * 2 *", "* * * * Feb *"); check_same("* * * * 1 *", "* * * * 1 *"); check_expr_invalid("Z * * * * *"); check_expr_invalid("* * * * * 1#-6"); check_expr_invalid("* * * * * 1#6"); check_expr_invalid("77 * * * * *"); check_expr_invalid("44-77 * * * * *"); check_expr_invalid("* 77 * * * *"); check_expr_invalid("* 44-77 * * * *"); check_expr_invalid("* * 27 * * *"); check_expr_invalid("* * 23-28 * * *"); check_expr_invalid("* * * 45 * *"); check_expr_invalid("* * * 28-45 * *"); check_expr_invalid("0 0 0 25 13 ?"); check_expr_invalid("0 0 0 25 0 ?"); check_expr_invalid("0 0 0 32 12 ?"); check_expr_invalid("* * * * 11-13 *"); check_expr_invalid("-5 * * * * *"); check_expr_invalid("3-2 */5 * * * *"); check_expr_invalid("/5 * * * * *"); check_expr_invalid("*/0 * * * * *"); check_expr_invalid("*/-0 * * * * *"); check_expr_invalid("* 1 1 0 * *"); /* Source: https://www.freeformatter.com/cron-expression-generator-quartz.html */ check_expr_valid("* * * ? * *"); /* Every second */ check_expr_valid("0 * * ? * *"); /* Every minute */ check_expr_valid("0 */2 * ? * *"); /* Every even minute */ check_expr_valid("0 1/2 * ? * *"); /* Every uneven minute */ check_expr_valid("0 */2 * ? * *"); /* Every 2 minutes */ check_expr_valid("0 */3 * ? * *"); /* Every 3 minutes */ check_expr_valid("0 */4 * ? * *"); /* Every 4 minutes */ check_expr_valid("0 */5 * ? * *"); /* Every 5 minutes */ check_expr_valid("0 */10 * ? * *"); /* Every 10 minutes */ check_expr_valid("0 */15 * ? * *"); /* Every 15 minutes */ check_expr_valid("0 */30 * ? * *"); /* Every 30 minutes */ check_expr_valid("0 15,30,45 * ? * *"); /* Every hour at minutes 15, 30 and 45 */ check_expr_valid("0 0 * ? * *"); /* Every hour */ check_expr_valid("0 0 */2 ? * *"); /* Every hour */ check_expr_valid("0 0 0/2 ? * *"); /* Every even hour */ check_expr_valid("0 0 1/2 ? * *"); /* Every uneven hour */ check_expr_valid("0 0 */3 ? * *"); /* Every three hours */ check_expr_valid("0 0 */4 ? * *"); /* Every four hours */ check_expr_valid("0 0 */6 ? * *"); /* Every six hours */ check_expr_valid("0 0 */8 ? * *"); /* Every eight hours */ check_expr_valid("0 0 */12 ? * *"); /* Every twelve hours */ check_expr_valid("0 0 0 * * ?"); /* Every day at midnight - 12am */ check_expr_valid("0 0 1 * * ?"); /* Every day at 1am */ check_expr_valid("0 0 6 * * ?"); /* Every day at 6am */ check_expr_valid("0 0 12 * * ?"); /* Every day at noon - 12pm */ check_expr_valid("0 0 12 * * ?"); /* Every day at noon - 12pm */ check_expr_valid("0 0 12 ? * SUN"); /* Every Sunday at noon */ check_expr_valid("0 0 12 ? * MON"); /* Every Monday at noon */ check_expr_valid("0 0 12 ? * TUE"); /* Every Tuesday at noon */ check_expr_valid("0 0 12 ? * WED"); /* Every Wednesday at noon */ check_expr_valid("0 0 12 ? * THU"); /* Every Thursday at noon */ check_expr_valid("0 0 12 ? * FRI"); /* Every Friday at noon */ check_expr_valid("0 0 12 ? * SAT"); /* Every Saturday at noon */ check_expr_valid("0 0 12 ? * MON-FRI"); /* Every Weekday at noon */ check_expr_valid("0 0 12 ? * SUN,SAT"); /* Every Saturday and Sunday at noon */ check_expr_valid("0 0 12 */7 * ?"); /* Every 7 days at noon */ check_expr_valid("0 0 12 1 * ?"); /* Every month on the 1st, at noon */ check_expr_valid("0 0 12 2 * ?"); /* Every month on the 2nd, at noon */ check_expr_valid("0 0 12 15 * ?"); /* Every month on the 15th, at noon */ check_expr_valid("0 0 12 1/2 * ?"); /* Every 2 days starting on the 1st of the month, at noon */ check_expr_valid("0 0 12 1/4 * ?"); /* Every 4 days staring on the 1st of the month, at noon */ check_expr_valid("0 0 12 L * ?"); /* Every month on the last day of the month, at noon */ check_expr_valid("0 0 12 L-2 * ?"); /* Every month on the second to last day of the month, at noon */ check_expr_valid("0 0 12 LW * ?"); /* Every month on the last weekday, at noon */ /*check_expr_valid("0 0 12 1L * ?"); */ /* Every month on the last Sunday, at noon */ /*check_expr_valid("0 0 12 2L * ?"); */ /* Every month on the last Monday, at noon */ /*check_expr_valid("0 0 12 6L * ?"); */ /* Every month on the last Friday, at noon */ check_expr_valid("0 0 12 ? * 1L"); /* Every month on the last Sunday, at noon */ check_expr_valid("0 0 12 ? * 2L"); /* Every month on the last Monday, at noon */ check_expr_valid("0 0 12 ? * 6L"); /* Every month on the last Friday, at noon */ check_expr_valid("0 0 12 1W * ?"); /* Every month on the nearest Weekday to the 1st of the month, at noon */ check_expr_valid("0 0 12 15W * ?"); /* Every month on the nearest Weekday to the 15th of the month, at noon */ check_expr_valid("0 0 12 ? * 2#1"); /* Every month on the first Monday of the Month, at noon */ check_expr_valid("0 0 12 ? * 6#1"); /* Every month on the first Friday of the Month, at noon */ check_expr_valid("0 0 12 ? * 2#2"); /* Every month on the second Monday of the Month, at noon */ check_expr_valid("0 0 12 ? * 5#3"); /* Every month on the third Thursday of the Month, at noon - 12pm */ check_expr_valid("0 0 12 ? JAN *"); /* Every day at noon in January only */ check_expr_valid("0 0 12 ? JUN *"); /* Every day at noon in June only */ check_expr_valid("0 0 12 ? JAN,JUN *"); /* Every day at noon in January and June */ check_expr_valid("0 0 12 ? DEC *"); /* Every day at noon in December only */ check_expr_valid("0 0 12 ? JAN,FEB,MAR,APR *"); /* Every day at noon in January, February, March and April */ check_expr_valid("0 0 12 ? 9-12 *"); /* Every day at noon between September and December */ /* ChatGPT generated inputs for further testing. */ check_expr_valid("0 0 12 * * ?"); /* Every day at 12 PM (noon). */ check_expr_valid("0 15 10 ? * *"); /* Every day at 10:15 AM. */ check_expr_valid("0 15 10 * * ?"); /* Every day at 10:15 AM. */ check_expr_valid("0 15 10 * * ? *"); /* Every day at 10:15 AM. */ check_expr_valid("0 15 10 * * ? 2023"); /* Every day at 10:15 AM during the year 2023. */ check_expr_valid("0 * 14 * * ?"); /* Every minute starting at 2 PM and ending at 2:59 PM, every day. */ check_expr_valid("0 0/5 14 * * ?"); /* Every 5 minutes starting at 2 PM and ending at 2:55 PM, every day. */ check_expr_valid("0 0/5 14,18 * * ?"); /* Every 5 minutes starting at 2 PM and ending at 2:55 PM, AND every 5 minutes starting at 6 PM and ending at 6:55 PM, every day. */ check_expr_valid("0 0-5 14 * * ?"); /* Every minute starting at 2 PM and ending at 2:05 PM, every day. */ check_expr_valid("0 10,44 14 ? 3 WED"); /* Every Wednesday in March at 2:10 PM and 2:44 PM. */ check_expr_valid("0 15 10 ? * MON-FRI"); /* Every weekday at 10:15 AM. */ check_expr_valid("0 15 10 15 * ?"); /* Every 15th day of the month at 10:15 AM. */ check_expr_valid("0 15 10 L * ?"); /* Last day of every month at 10:15 AM. */ check_expr_valid("0 15 10 ? * 6L"); /* Last Friday of every month at 10:15 AM. */ check_expr_valid("0 15 10 ? * 6L 2022-2025"); /* Last Friday of every month during the years 2022 through 2025 at 10:15 AM. */ check_expr_valid("0 15 10 ? * 6#3"); /* Third Friday of every month at 10:15 AM. */ check_expr_valid("0 15 10 ? * 2-6"); /* Every weekday (Monday to Friday) at 10:15 AM. */ check_expr_valid("0 0/5 14-18 * * ?"); /* Every 5 minutes from 2 PM to 6:55 PM every day. */ check_expr_valid("0 0 12 1/5 * ?"); /* Every 5 days at 12 PM. */ check_expr_valid("0 11 11 11 11 ?"); /* Every November 11th at 11:11 AM. */ check_expr_valid("0 0 12 ? * SUN"); /* Every Sunday at noon. */ check_expr_valid("0 0 12 ? * MON"); /* Every Monday at noon. */ check_expr_valid("0 0 12 ? * TUE"); /* Every Tuesday at noon. */ check_expr_valid("0 0 12 ? * WED"); /* Every Wednesday at noon. */ check_expr_valid("0 0 12 ? * THU"); /* Every Thursday at noon. */ check_expr_valid("0 0 12 ? * FRI"); /* Every Friday at noon. */ check_expr_valid("0 0 12 ? * SAT"); /* Every Saturday at noon. */ check_expr_valid("0 0/30 8-9 1 * ?"); /* Every 30 minutes between 8-9 AM on the 1st of the month. */ check_expr_valid("0 0 0 1 1 ?"); /* Every New Year's Day at midnight. */ check_expr_valid("0 0 0 25 12 ?"); /* Every Christmas at midnight. */ check_expr_valid("0 0 6,18 * * ?"); /* Every day at 6 AM and 6 PM. */ check_expr_valid("0 0 8-10 ? * 2-6"); /* Every weekday between 8-10 AM. */ check_expr_valid("0 0/15 * ? * *"); /* Every 15 minutes. */ check_expr_valid("0 0 12 LW * ?"); /* Last weekday of the month at noon. */ check_expr_valid("0 0 12 1W * ?"); /* Nearest weekday to the 1st of the month at noon. */ check_expr_valid("0 0 12 W * ?"); /* Every weekday at noon. */ check_expr_valid("0 0 12 ? JAN,DEC *"); /* Every day at noon in January and December. */ check_expr_valid("0 0 12 ? * 1#2"); /* Second Sunday of every month at noon. */ check_expr_valid("0 0 12 ? * 2#2"); /* Second Monday of every month at noon. */ check_expr_valid("0 0 12 ? * 6#1"); /* First Friday of every month at noon. */ check_expr_valid("0 0 8,20 ? * *"); /* Every day at 8 AM and 8 PM. */ check_expr_valid("0 30 10-13 ? * WED,FRI"); /* Every Wednesday and Friday between 10:30 AM and 1:30 PM. */ check_expr_valid("0 0/10 * * * ?"); /* Every 10 minutes. */ check_expr_valid("0 0 12 L-2 * ?"); /* Two days before the end of the month at noon. */ check_expr_valid("0 0 12 15W * ?"); /* Nearest weekday to the 15th of every month at noon. */ check_expr_valid("0 0 0 * * ?"); /* Every day at midnight (beginning of the day). */ check_expr_valid("0 0 23 * * ?"); /* Every day at 11 PM (end of the day). */ check_expr_valid("0 30 10 ? * 2-6"); /* Every weekday at 10:30 AM. */ check_expr_valid("0 0/10 8-17 ? * MON-FRI"); /* Every 10 minutes during working hours (8 AM - 5 PM) on weekdays. */ /*check_expr_valid("0 0 12 1L * ?"); */ /* Last day of the month at noon. */ check_expr_valid("0 0 12 ? * SUN,MON"); /* Every Sunday and Monday at noon. */ check_expr_valid("0 0/5 9-16 * * ?"); /* Every 5 minutes from 9 AM to 4:55 PM every day. */ check_expr_valid("0 0 12 10-15 * ?"); /* Every day from the 10th to the 15th at noon. */ check_expr_valid("0 0 0 1 JAN-JUN ?"); /* First day of the month from January to June at midnight. */ check_expr_valid("0 0 0 ? * 2L"); /* Last Monday of the month at midnight. */ check_expr_valid("0 0 0 ? * 6#4"); /* Fourth Friday of the month at midnight. */ check_expr_valid("0 0 12 1/2 * ?"); /* Every other day at noon. */ check_expr_valid("0 0 12 ? * 2/2"); /* Every other Monday at noon. */ check_expr_valid("0 0 0 29 FEB ?"); /* Every 29th of February at midnight (Leap Year). */ check_expr_valid("0 0 12 1,15 * ?"); /* 1st and 15th of the month at noon. */ check_expr_valid("0 0 0 1 * ? 2024"); /* 1st of every month in 2024 at midnight. */ check_expr_valid("0 30 6 ? * 1-5"); /* Weekdays at 6:30 AM. */ check_expr_valid("0 0 0/2 * * ?"); /* Every 2 hours. */ check_expr_valid("0 0 12/3 ? * *"); /* Every 3 hours starting at noon. */ check_expr_valid("0 15,45 * ? * *"); /* Every hour at 15 and 45 minutes past. */ check_expr_valid("0 0 0 ? * SAT,SUN"); /* Every weekend at midnight. */ check_expr_valid("0 0 8-10,14-16 * * ?"); /* Every day from 8-10 AM and 2-4 PM. */ check_expr_valid("0 0 12 ? 1-6,9-12 *"); /* Every day at noon excluding July and August. */ check_expr_valid("0 0 12/4 * * ?"); /* Every 4 hours starting at noon. */ check_expr_valid("0 0 9-17 * * ?"); /* Every hour from 9 AM to 5 PM. */ check_expr_valid("0 0/20 9-16 * * ?"); /* Every 20 minutes from 9 AM to 4:40 PM. */ check_expr_valid("0 0 12 ? * 2-6"); /* Every weekday at noon. */ check_expr_valid("0 0 8-11,13-16 * * ?"); /* Every day from 8-11 AM and 1-4 PM. */ check_expr_valid("0 0/30 6-18 * * ?"); /* Every 30 minutes from 6 AM to 6:30 PM. */ check_expr_valid("0 0 12 ? JAN,MAR,MAY,JUL,SEP,NOV *"); /* Every alternate month at noon. */ check_expr_valid("0 15 9 ? * MON,TUE,WED,THU,FRI"); /* Every weekday at 9:15 AM. */ check_expr_valid("0 0/40 * ? * *"); /* Every 40 minutes. */ check_expr_valid("0 30 10-14 ? * ?"); /* Every day from 10:30 AM to 2:30 PM. */ check_expr_valid("0 0 12 * JAN-DEC ?"); /* Every day of the year at noon. */ check_expr_valid("0 0 0 25 12 ? *"); /* Every Christmas at midnight. */ check_expr_valid("0 0 0/3 * * ?"); /* Every 3 hours. */ check_expr_valid("0 0 12 * * ? 2025"); /* Every day at noon in the year 2025. */ check_expr_valid("0 0 6 ? * 2-6"); /* Every weekday at 6 AM. */ check_expr_valid("0 0 18 ? * 1-5"); /* Every weekday at 6 PM. */ check_expr_valid("0 0 0 ? * * 2026"); /* Every day at midnight in 2026. */ check_expr_valid("0 0/45 10-14 * * ?"); /* Every 45 minutes between 10 AM and 2:45 PM. */ check_expr_valid("0 0 12 1/3 * ?"); /* Every 3 days at noon. */ check_expr_valid("0 0 0 1 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC ?"); /* Start of every month at midnight. */ check_expr_valid("0 0 12 10,20,30 * ?"); /* Every 10th, 20th, and 30th of the month at noon. */ check_expr_valid("0 0/50 8-16 * * ?"); /* Every 50 minutes from 8 AM to 4:50 PM. */ check_expr_invalid("0 0 12 ? * 1#1,3#3,5#5"); /* First Sunday, third Wednesday, and fifth Friday of every month at noon. */ check_expr_valid("0 0 0/4 * * ?"); /* Every 4 hours. */ check_expr_valid("0 15,30,45 * * * ?"); /* Every hour at 15, 30, and 45 minutes past. */ check_expr_valid("0 0 12 ? * 2L"); /* Last Monday of every month at noon. */ check_expr_valid("0 0 12 ? * 5L"); /* Last Thursday of every month at noon. */ check_expr_valid("0 0 0/6 * * ?"); /* Every 6 hours. */ check_expr_valid("0 0 12/2 ? * *"); /* Every 2 hours starting at noon. */ check_expr_valid("0 0 12 ? 1,3,5,7,9,11 *"); /* Every odd month at noon. */ check_expr_valid("0 0 0 ? 2,4,6,8,10,12 *"); /* Every even month at midnight. */ check_expr_valid("0 0 12 * * ? 2027"); /* Every day at noon in the year 2027. */ } void test_bits(void) { uint8_t testbyte[8]; memset(testbyte, 0, 8); int err = 0; int i; for (i = 0; i <= 63; i++) { cron_set_bit(testbyte, i); if (!cron_get_bit(testbyte, i)) { printf("Bit set error! Bit: %d!\n", i); err = 1; } cron_del_bit(testbyte, i); if (cron_get_bit(testbyte, i)) { printf("Bit clear error! Bit: %d!\n", i); err = 1; } assert(!err); } for (i = 0; i < 12; i++) { cron_set_bit(testbyte, i); } if (testbyte[0] != 0xff) { err = 1; } if (testbyte[1] != 0x0f) { err = 1; } assert(!err); } void app_main(void) { test_bits(); test_expr(); test_parse(); check_calc_invalid(); printf("\nAll OK!\n"); } ================================================ FILE: supertinycron/examples/cron_example/main/idf_component.yml ================================================ description: Cron expression parsing in ANSI C. dependencies: espressif/supertinycron: version: "*" override_path: '../../../' ================================================ FILE: supertinycron/idf_component.yml ================================================ version: "2.0.0~2" description: Cron expression parsing in ANSI C. url: https://github.com/espressif/idf-extra-components/tree/master/supertinycron dependencies: idf: ">=4.1" ================================================ FILE: supertinycron/sbom_supertinycron.yml ================================================ name: supertinycron version: 2.0.0 cpe: cpe:2.3:a:supertinycron:supertinycron:{}:*:*:*:*:*:*:* supplier: 'Organization: supertinycron' description: Cron expression parsing in ANSI C with tinycron tool url: https://github.com/exander77/supertinycron hash: 30b9e373234c3c16c279e5a05f65dc07f5be9161 ================================================ FILE: thorvg/.build-test-rules.yml ================================================ thorvg/examples/thorvg_lottie: disable: - if: IDF_TARGET not in ["esp32s3", "esp32p4"] reason: Running lottie animation requires a large PSRAM - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 reason: test version >= 5.3 is sufficient ================================================ FILE: thorvg/CMakeLists.txt ================================================ # Set a name for libthorvg library set(TVG_LIB libthorvg) set(TVG_INC_DIR thorvg/src/bindings/capi) set(TVG_SUBDIR "${CMAKE_CURRENT_SOURCE_DIR}/thorvg") set(TVG_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/thorvg_build") set(TVG_CROSS_CFG "${CMAKE_CURRENT_BINARY_DIR}/thorvg_build/cross_file.txt") set(TVG_STATIC_LIB "${TVG_BUILD_DIR}/src/libthorvg-1.a") set(TVG_LOADERS_OPTION "") if(CONFIG_THORVG_LOTTIE_LOADER_SUPPORT) list(APPEND TVG_LOADERS_OPTION "lottie") endif() if(CONFIG_THORVG_SVG_LOADER_SUPPORT) list(APPEND TVG_LOADERS_OPTION "svg") endif() if(CONFIG_THORVG_PNG_LOADER_SUPPORT) list(APPEND TVG_LOADERS_OPTION "png") endif() if(CONFIG_THORVG_JPEG_LOADER_SUPPORT) list(APPEND TVG_LOADERS_OPTION "jpg") endif() if(CONFIG_THORVG_WEBP_LOADER_SUPPORT) list(APPEND TVG_LOADERS_OPTION "webp") endif() list(JOIN TVG_LOADERS_OPTION "," TVG_LOADERS_OPTION) if(CONFIG_THORVG_LOG_ENABLED) set(TVG_LOG "true") else() set(TVG_LOG "false") endif() if(CONFIG_THORVG_THREAD_ENABLED) set(TVG_THREADS "true") else() set(TVG_THREADS "false") endif() idf_component_register( SRCS dummy.c INCLUDE_DIRS "${TVG_INC_DIR}" PRIV_REQUIRES pthread) target_compile_options(${COMPONENT_LIB} INTERFACE $<$:-Wno-ignored-qualifiers> ) # Expands CMake flags that may contain response files (@"file" syntax) into a list. # ESP-IDF v6 uses @"file" syntax for response files, but Meson doesn't support this. # We need to expand the response file contents because: # 1. CMake expands @"file" internally, but Meson passes it literally to GCC # 2. GCC only supports @file (without quotes), not @"file" # 3. We can't just remove quotes because paths may contain spaces (especially on Windows) function(expand_response_file input_flags output_var) set(flags ${input_flags}) if(flags MATCHES "@\"(.+)\"") set(response_file "${CMAKE_MATCH_1}") if(EXISTS "${response_file}") file(STRINGS "${response_file}" flags) else() set(flags "") endif() else() separate_arguments(flags) endif() list(FILTER flags EXCLUDE REGEX "^$") # Strip surrounding double quotes from each flag e.g. "-specs=path" -> -specs=path) # Response files from ESP-IDF v6 may contain quoted flags which cause issues with Meson list(TRANSFORM flags REPLACE "^\"(.*)\"$" "\\1") set(${output_var} ${flags} PARENT_SCOPE) endfunction() expand_response_file("${CMAKE_CXX_FLAGS}" compiler_args) expand_response_file("${CMAKE_EXE_LINKER_FLAGS}" linker_args) message(STATUS "CMAKE_CXX_FLAGS: ${compiler_args}") message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${linker_args}") # Add pthread library path to the linker flags if threads are enabled if(TVG_THREADS STREQUAL "true") idf_component_get_property(pthread_lib "pthread" COMPONENT_LIB GENERATOR_EXPRESSION) set(pthread_lib_path_expr "$") list(APPEND linker_args "-L${pthread_lib_path_expr}") set(TVG_REQUIRES pthread) endif() # Convert the list of arguments (arg;arg;arg) to a string "'arg', 'arg', 'arg'" list(JOIN compiler_args "', '" MESON_CXX_FLAGS) set(MESON_CXX_FLAGS "'${MESON_CXX_FLAGS}'") list(JOIN linker_args "', '" MESON_LINKER_FLAGS) set(MESON_LINKER_FLAGS "'${MESON_LINKER_FLAGS}'") # Please note, xtensa is not recognized by Meson, but "riscv32" is a safe generic option # Always use "riscv32" as cpu_family in cross_file.txt to avoid Meson warnings set(cpu_family "riscv32") # Two-step generation of cross_file.txt to expand CMake variables... configure_file(${CMAKE_CURRENT_LIST_DIR}/cross_file.txt.in ${TVG_CROSS_CFG}.tmp) # ...and then expand generator expressions file(GENERATE OUTPUT ${TVG_CROSS_CFG} INPUT ${TVG_CROSS_CFG}.tmp) include(ExternalProject) ExternalProject_Add(${TVG_LIB}_target PREFIX ${TVG_BUILD_DIR} SOURCE_DIR ${TVG_SUBDIR} BINARY_DIR ${TVG_BUILD_DIR} CONFIGURE_COMMAND meson setup ${TVG_BUILD_DIR} ${TVG_SUBDIR} --cross-file ${TVG_CROSS_CFG} -Dbindings=capi -Dextra= -Ddefault_library=static # build static library -Db_staticpic=false # no -fPIC -Dthreads=${TVG_THREADS} -Dlog=${TVG_LOG} # allow log output -Dloaders=${TVG_LOADERS_OPTION} # Pass the loaders option to Meson BUILD_COMMAND ninja -C ${TVG_BUILD_DIR} INSTALL_COMMAND "" BUILD_BYPRODUCTS ${TVG_STATIC_LIB} ) add_prebuilt_library(${TVG_LIB} ${TVG_STATIC_LIB} PRIV_REQUIRES ${TVG_REQUIRES}) add_dependencies(${TVG_LIB} ${TVG_LIB}_target) if(TVG_THREADS STREQUAL "true") add_dependencies(${TVG_LIB}_target idf::pthread) endif() target_link_libraries(${COMPONENT_LIB} INTERFACE ${TVG_LIB}) ================================================ FILE: thorvg/Kconfig ================================================ menu "ThorVG Support Options" config THORVG_LOG_ENABLED bool "Enable ThorVG log" default n help Enable ThorVG log. config THORVG_THREAD_ENABLED bool "Enable ThorVG threading support" default y help Enable ThorVG threading support. This option can be disabled if ThorVG is only used from one FreeRTOS task. menu "Loaders Support" config THORVG_LOTTIE_LOADER_SUPPORT bool "Enable Lottie loader support" default y config THORVG_SVG_LOADER_SUPPORT bool "Enable svg loader support" default y config THORVG_PNG_LOADER_SUPPORT bool "Enable png loader support" default n config THORVG_JPEG_LOADER_SUPPORT bool "Enable jpeg loader support" default n config THORVG_WEBP_LOADER_SUPPORT bool "Enable webp loader support" default n endmenu endmenu ================================================ FILE: thorvg/LICENSE ================================================ Copyright (c) 2020 - 2024 notice for the ThorVG Project (see AUTHORS) 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: thorvg/README.md ================================================ # ThorVG component [![Component Registry](https://components.espressif.com/components/espressif/thorvg/badge.svg)](https://components.espressif.com/components/espressif/thorvg) ThorVG is an open-source graphics library designed for creating vector-based scenes and animations. Embracing the philosophy of "Simpler is better," the ThorVG project offers intuitive and user-friendly interfaces, all the while maintaining a compact size and minimal software complexity. This component is based on [ThorVG](https://github.com/thorvg/thorvg). To learn more about how to use this component, please check [README.md](https://github.com/thorvg/thorvg/blob/main/README.md). ================================================ FILE: thorvg/cross_file.txt.in ================================================ [binaries] cpp = '${CMAKE_CXX_COMPILER}' ar = '${CMAKE_CXX_COMPILER_AR}' as = '${CMAKE_ASM_COMPILER}' ranlib = '${CMAKE_CXX_COMPILER_RANLIB}' ld = '${CMAKE_LINKER}' strip = '${CMAKE_STRIP}' [built-in options] cpp_args = ['-D_GNU_SOURCE','-D__linux__','-Wno-format','-Wno-stringop-truncation',${MESON_CXX_FLAGS}] cpp_link_args = [${MESON_LINKER_FLAGS}] [host_machine] system = 'esp-idf' cpu_family = '${cpu_family}' cpu = 'esp' endian = 'little' ================================================ FILE: thorvg/dummy.c ================================================ ================================================ FILE: thorvg/examples/thorvg_lottie/CMakeLists.txt ================================================ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # The following five lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(thorvg-example) ================================================ FILE: thorvg/examples/thorvg_lottie/README.md ================================================ # ThorVG Lottie Animation Example This is a minimalistic display + ThorVG graphics library example that demonstrates how to render Lottie animations on an ESP device. With just a few function calls, it sets up the display and shows Lottie animations using the ThorVG vector graphics engine. ## Overview This example demonstrates: - Setting up an SPI LCD display (SH8601 controller) - Initializing the ThorVG vector graphics engine - Loading and rendering a Lottie animation from a JSON file - Converting graphics buffers from ARGB8888 to RGB565 format for display ## Hardware Required - An ESP32 device with PSRAM support (necessary for the graphic buffers) - An SPI LCD display (the example uses SH8601 controller by default) ## Building and Running 1. Prepare your project's partition table to include a LittleFS partition named "storage" 2. Place your Lottie animation file (in JSON format) in the project's "storage" directory with the name "emoji-animation.json" 3. Build and flash the application as usual for an ESP-IDF project: ``` idf.py set-target esp32s3 idf.py -p PORT flash monitor ``` ## Customizing the Example ### Using a Different LCD Display If you are using a different LCD controller, you'll need to: 1. Replace the SH8601-specific initialization code in the `main/thorvg_example_main.c` file: - Update the LCD pin configurations - Replace the `esp_lcd_new_panel_sh8601()` call with the appropriate function for your LCD - Modify the LCD initialization commands as required by your display ### Using a Different Lottie File To use your own Lottie animation: 1. Ensure your animation file is in JSON format 2. Update the `EXAMPLE_LOTTIE_FILENAME` macro in the code to match your file path 3. Adjust the `EXAMPLE_LOTTIE_SIZE_HOR` and `EXAMPLE_LOTTIE_SIZE_VER` macros if your animation has different dimensions ## Memory Usage This example allocates graphic buffers in PSRAM: - One ARGB8888 buffer (4 bytes per pixel) for ThorVG rendering - One RGB565 buffer (2 bytes per pixel) for LCD output Ensure your device has sufficient PSRAM for these buffers, especially when increasing animation dimensions. ================================================ FILE: thorvg/examples/thorvg_lottie/lottie_files/emoji-animation.json ================================================ { "v": "5.5.7", "fr": 24, "ip": 0, "op": 48, "w": 1024, "h": 1024, "nm": "party_face", "ddd": 0, "assets": [ { "id": "comp_0", "layers": [ { "ddd": 0, "ind": 1, "ty": 3, "nm": "Face_CTRL", "parent": 2, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.333, "y": 0 }, "t": 0, "s": [0, -286, 0], "to": [0.092, 3.435, 0], "ti": [0.627, 6.25, 0] }, { "i": { "x": 0, "y": 1 }, "o": { "x": 0.167, "y": 0.167 }, "t": 4, "s": [0.552, -265.39, 0], "to": [-0.627, -6.25, 0], "ti": [0.092, 3.435, 0] }, { "i": { "x": 0, "y": 1 }, "o": { "x": 0.333, "y": 0 }, "t": 18, "s": [-3.763, -323.5, 0], "to": [-0.092, -3.435, 0], "ti": [-0.627, -6.25, 0] }, { "t": 26, "s": [0, -286, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "ef": [ { "ty": 5, "nm": "Controller", "np": 13, "mn": "Pseudo/DUIK controller", "ix": 1, "en": 1, "ef": [ { "ty": 6, "nm": "Icon", "mn": "Pseudo/DUIK controller-0001", "ix": 1, "v": 0 }, { "ty": 2, "nm": "Color", "mn": "Pseudo/DUIK controller-0002", "ix": 2, "v": { "a": 0, "k": [ 0.92549020052, 0.0941176489, 0.0941176489, 1 ], "ix": 2 } }, { "ty": 3, "nm": "Position", "mn": "Pseudo/DUIK controller-0003", "ix": 3, "v": { "a": 0, "k": [0, 0], "ix": 3 } }, { "ty": 0, "nm": "Size", "mn": "Pseudo/DUIK controller-0004", "ix": 4, "v": { "a": 0, "k": 100, "ix": 4 } }, { "ty": 0, "nm": "Orientation", "mn": "Pseudo/DUIK controller-0005", "ix": 5, "v": { "a": 0, "k": 0, "ix": 5 } }, { "ty": 0, "nm": "Opacity", "mn": "Pseudo/DUIK controller-0006", "ix": 6, "v": { "a": 0, "k": 100, "ix": 6 } }, { "ty": 6, "nm": "", "mn": "Pseudo/DUIK controller-0007", "ix": 7, "v": 0 }, { "ty": 6, "nm": "Anchor", "mn": "Pseudo/DUIK controller-0008", "ix": 8, "v": 0 }, { "ty": 2, "nm": "Color", "mn": "Pseudo/DUIK controller-0009", "ix": 9, "v": { "a": 0, "k": [0, 0, 0, 1], "ix": 9 } }, { "ty": 0, "nm": "Size", "mn": "Pseudo/DUIK controller-0010", "ix": 10, "v": { "a": 0, "k": 100, "ix": 10 } }, { "ty": 6, "nm": "", "mn": "Pseudo/DUIK controller-0011", "ix": 11, "v": 0 } ] } ], "ip": 0, "op": 48, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 2, "ty": 3, "nm": "Head_CTRL", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 0, "s": [0] }, { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 18, "s": [4] }, { "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 26, "s": [-1] }, { "t": 48, "s": [0] } ], "ix": 10 }, "p": { "a": 0, "k": [517.24, 850.396, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, "s": { "a": 1, "k": [ { "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 0, "s": [100, 100, 100] }, { "i": { "x": [0, 0, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0] }, "t": 4, "s": [103, 97, 100] }, { "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 18, "s": [97, 103, 100] }, { "i": { "x": [0, 0.051, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 26, "s": [103, 97, 100] }, { "t": 48, "s": [100, 100, 100] } ], "ix": 6 } }, "ao": 0, "ef": [ { "ty": 5, "nm": "Controller", "np": 13, "mn": "Pseudo/DUIK controller", "ix": 1, "en": 1, "ef": [ { "ty": 6, "nm": "Icon", "mn": "Pseudo/DUIK controller-0001", "ix": 1, "v": 0 }, { "ty": 2, "nm": "Color", "mn": "Pseudo/DUIK controller-0002", "ix": 2, "v": { "a": 0, "k": [ 0.92549020052, 0.0941176489, 0.0941176489, 1 ], "ix": 2 } }, { "ty": 3, "nm": "Position", "mn": "Pseudo/DUIK controller-0003", "ix": 3, "v": { "a": 0, "k": [0, 0], "ix": 3 } }, { "ty": 0, "nm": "Size", "mn": "Pseudo/DUIK controller-0004", "ix": 4, "v": { "a": 0, "k": 100, "ix": 4 } }, { "ty": 0, "nm": "Orientation", "mn": "Pseudo/DUIK controller-0005", "ix": 5, "v": { "a": 0, "k": 0, "ix": 5 } }, { "ty": 0, "nm": "Opacity", "mn": "Pseudo/DUIK controller-0006", "ix": 6, "v": { "a": 0, "k": 100, "ix": 6 } }, { "ty": 6, "nm": "", "mn": "Pseudo/DUIK controller-0007", "ix": 7, "v": 0 }, { "ty": 6, "nm": "Anchor", "mn": "Pseudo/DUIK controller-0008", "ix": 8, "v": 0 }, { "ty": 2, "nm": "Color", "mn": "Pseudo/DUIK controller-0009", "ix": 9, "v": { "a": 0, "k": [0, 0, 0, 1], "ix": 9 } }, { "ty": 0, "nm": "Size", "mn": "Pseudo/DUIK controller-0010", "ix": 10, "v": { "a": 0, "k": 100, "ix": 10 } }, { "ty": 6, "nm": "", "mn": "Pseudo/DUIK controller-0011", "ix": 11, "v": 0 } ] } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 3, "ty": 4, "nm": "HatStripe", "parent": 6, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [0, 0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0], [4.631, 7.095] ], "o": [ [3.818, 5.331], [0, 0], [0, 0], [0, 0] ], "v": [ [9.463, -26.924], [16.938, -19.632], [17.467, -14.328], [6.154, -25.348] ], "c": true }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 9, "k": { "a": 0, "k": [ 0.176, 0.91, 0.302, 0.533, 0.267, 0.9, 0.292, 0.524, 0.359, 0.89, 0.282, 0.514, 0.474, 0.863, 0.251, 0.484, 0.588, 0.835, 0.22, 0.455, 0.715, 0.792, 0.171, 0.406, 0.842, 0.749, 0.122, 0.357, 0.921, 0.714, 0.082, 0.32, 1, 0.678, 0.043, 0.282 ], "ix": 9 } }, "s": { "a": 0, "k": [10.307, -30.112], "ix": 5 }, "e": { "a": 0, "k": [20.858, -18.045], "ix": 6 }, "t": 1, "nm": "Stripegradienta", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "HatStripe", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 4, "ty": 4, "nm": "HatStripe1", "parent": 6, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [0, 0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0], [3.103, 1.552], [5.313, 8.386] ], "o": [ [6.646, 8.84], [0, 0], [-0.334, 0.638], [0, 0], [0, 0] ], "v": [ [2.846, -23.771], [17.989, -9.078], [18.085, -8.12], [12.218, -9.919], [-0.462, -22.195] ], "c": true }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 9, "k": { "a": 0, "k": [ 0.176, 0.91, 0.302, 0.533, 0.267, 0.9, 0.292, 0.524, 0.359, 0.89, 0.282, 0.514, 0.474, 0.863, 0.251, 0.484, 0.588, 0.835, 0.22, 0.455, 0.715, 0.792, 0.171, 0.406, 0.842, 0.749, 0.122, 0.357, 0.921, 0.714, 0.082, 0.32, 1, 0.678, 0.043, 0.282 ], "ix": 9 } }, "s": { "a": 0, "k": [10.307, -30.112], "ix": 5 }, "e": { "a": 0, "k": [23.622, -15.706], "ix": 6 }, "t": 1, "nm": "Stripegradientb", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "HatStripe1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 5, "ty": 4, "nm": "HatStripe2", "parent": 6, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [0, 0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [1.385, 2.573] ], "o": [ [0, 0], [0, 0], [0, 0] ], "v": [ [15.906, -29.994], [16.41, -24.931], [12.732, -28.48] ], "c": true }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 9, "k": { "a": 0, "k": [ 0.176, 0.91, 0.302, 0.533, 0.267, 0.9, 0.292, 0.524, 0.359, 0.89, 0.282, 0.514, 0.474, 0.863, 0.251, 0.484, 0.588, 0.835, 0.22, 0.455, 0.715, 0.792, 0.171, 0.406, 0.842, 0.749, 0.122, 0.357, 0.921, 0.714, 0.082, 0.32, 1, 0.678, 0.043, 0.282 ], "ix": 9 } }, "s": { "a": 0, "k": [10.307, -30.112], "ix": 5 }, "e": { "a": 0, "k": [18.414, -21.744], "ix": 6 }, "t": 1, "nm": "Stripegradientc", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "HatStripe2", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 6, "ty": 4, "nm": "Hat", "parent": 21, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 2, "ix": 10 }, "p": { "a": 0, "k": [-230.213, 207.025, 0], "ix": 2 }, "a": { "a": 0, "k": [9.386, -12.953, 0], "ix": 1 }, "s": { "a": 0, "k": [115.214, 105.229, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [-1.048, 1.975] ], "o": [ [0, 0], [-1.045, 1.979], [0, 0] ], "v": [ [15.906, -29.995], [18.084, -8.12], [-3.275, -20.853] ], "c": true }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 11, "k": { "a": 0, "k": [ 0.115, 0.459, 0.839, 1, 0.198, 0.451, 0.831, 0.992, 0.282, 0.443, 0.824, 0.984, 0.372, 0.418, 0.798, 0.959, 0.461, 0.392, 0.773, 0.933, 0.555, 0.349, 0.731, 0.894, 0.648, 0.306, 0.69, 0.855, 0.743, 0.247, 0.633, 0.798, 0.838, 0.188, 0.576, 0.741, 0.919, 0.125, 0.516, 0.68, 1, 0.063, 0.455, 0.62 ], "ix": 9 } }, "s": { "a": 0, "k": [-3.016, -26.005], "ix": 5 }, "e": { "a": 0, "k": [13.937, -7.054], "ix": 6 }, "t": 1, "nm": "Hatgradient", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Hat", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 7, "ty": 4, "nm": "Confetti11", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": -74, "ix": 10 }, "p": { "a": 0, "k": [135.111, 329.463, 0], "ix": 2 }, "a": { "a": 0, "k": [16.786, 25.814, 0], "ix": 1 }, "s": { "a": 0, "k": [1381.996, 1381.996, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [-1.175, -0.325], [-0.525, 0.4], [-0.469, -0.175], [-0.66, 1.01], [-1.025, -0.249], [-0.1, -0.725] ], "o": [ [0, 0], [1.298, 0.359], [0.525, -0.4], [1.275, 0.475], [0.85, -1.3], [1.75, 0.425], [0.269, 1.224] ], "v": [ [8.8, 29.45], [12.675, 25.025], [14.575, 27.625], [16.675, 23.625], [19.2, 26.775], [22.125, 23.425], [24.325, 26.75] ], "c": false }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "tm", "s": { "a": 1, "k": [ { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 19, "s": [100] }, { "t": 40, "s": [0] } ], "ix": 1 }, "e": { "a": 1, "k": [ { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 15, "s": [100] }, { "t": 36, "s": [0] } ], "ix": 2 }, "o": { "a": 0, "k": 0, "ix": 3 }, "m": 1, "ix": 2, "nm": "Raccorder les tracés 1", "mn": "ADBE Vector Filter - Trim", "hd": false }, { "ty": "st", "c": { "a": 0, "k": [ 0.650980392157, 0.901960844152, 0.223529426724, 1 ], "ix": 3 }, "o": { "a": 0, "k": 100, "ix": 4 }, "w": { "a": 0, "k": 2, "ix": 5 }, "lc": 1, "lj": 1, "ml": 4, "bm": 0, "nm": "green", "mn": "ADBE Vector Graphic - Stroke", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Forme 1", "np": 3, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 15, "op": 42, "st": 15, "bm": 0 }, { "ddd": 0, "ind": 8, "ty": 4, "nm": "Confetti10", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [873, 522.696, 0], "ix": 2 }, "a": { "a": 0, "k": [25.11, 6.511, 0], "ix": 1 }, "s": { "a": 0, "k": [1381.996, 1381.996, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-3.116, 1.363], [0.225, 0.8], [-0.35, 1.125], [-0.05, 0.35], [0.3, 0.675], [-0.425, 0.475], [1.275, 0] ], "o": [ [1.6, -0.7], [-0.225, -0.8], [0.35, -1.125], [0.05, -0.35], [-0.3, -0.675], [0.425, -0.475], [-1.275, 0] ], "v": [ [24.6, 13.6], [28.575, 11.15], [24.25, 8.375], [26.2, 5.125], [21.65, 4.95], [24.55, 0.35], [22.6, -0.675] ], "c": false }, "ix": 2 }, "nm": "Tracé 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "tm", "s": { "a": 1, "k": [ { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 4, "s": [100] }, { "t": 25, "s": [0] } ], "ix": 1 }, "e": { "a": 1, "k": [ { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 0, "s": [100] }, { "t": 21, "s": [0] } ], "ix": 2 }, "o": { "a": 0, "k": 0, "ix": 3 }, "m": 1, "ix": 2, "nm": "Raccorder les tracés 1", "mn": "ADBE Vector Filter - Trim", "hd": false }, { "ty": "st", "c": { "a": 0, "k": [ 0.909803981407, 0.109803929048, 0.152941176471, 1 ], "ix": 3 }, "o": { "a": 0, "k": 100, "ix": 4 }, "w": { "a": 0, "k": 2, "ix": 5 }, "lc": 1, "lj": 1, "ml": 4, "bm": 0, "nm": "Contour 1", "mn": "ADBE Vector Graphic - Stroke", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Forme 1", "np": 3, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 30, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 9, "ty": 4, "nm": "Layer 10", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 1, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 3, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 18, "s": [100] }, { "t": 20, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 1, "s": [0] }, { "t": 19, "s": [149] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 1, "s": [194.929, 367.888, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 20, "s": [194.929, 647.888, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-270.075, 219.877, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, -3.567], [0, 3.569] ], "o": [ [0, 3.569], [0, -3.567] ], "v": [ [-267.399, 219.876], [-272.751, 219.876] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.058823529631, 0.705882370472, 0.831372559071, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 1, "op": 20, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 10, "ty": 4, "nm": "Layer 9", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 21, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 23, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 38, "s": [100] }, { "t": 40, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 21, "s": [0] }, { "t": 39, "s": [-238] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 21, "s": [870.597, 174.763, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 40, "s": [870.597, 454.763, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-216.535, 204.574, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, -3.568], [0, 3.569] ], "o": [ [0, 3.569], [0, -3.568] ], "v": [ [-213.859, 204.573], [-219.211, 204.573] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.65098041296, 0.901960790157, 0.223529413342, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 21, "op": 40, "st": 20, "bm": 0 }, { "ddd": 0, "ind": 11, "ty": 4, "nm": "Layer 8", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 10, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 12, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 27, "s": [100] }, { "t": 29, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 10, "s": [0] }, { "t": 28, "s": [-49] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 10, "s": [790.435, 694.021, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 29, "s": [790.435, 974.021, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-222.887, 245.719, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "o": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "v": [ [-219.674, 244.714], [-221.718, 249.449], [-226.101, 246.73], [-224.056, 241.989] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.901960790157, 0.207843139768, 0.870588243008, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 10, "op": 29, "st": 9, "bm": 0 }, { "ddd": 0, "ind": 12, "ty": 4, "nm": "Layer 7", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 0, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 2, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 17, "s": [100] }, { "t": 19, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 0, "s": [0] }, { "t": 18, "s": [35] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 0, "s": [423.853, 174.764, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 19, "s": [423.853, 454.764, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-251.935, 204.574, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "o": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "v": [ [-249.335, 205.209], [-252.875, 208.444], [-254.535, 203.943], [-250.991, 200.703] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.058823529631, 0.705882370472, 0.831372559071, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 19, "st": -1, "bm": 0 }, { "ddd": 0, "ind": 13, "ty": 4, "nm": "Layer 6", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 29, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 31, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 46, "s": [100] }, { "t": 48, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 29, "s": [0] }, { "t": 47, "s": [-115] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 29, "s": [194.479, 190.293, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 48, "s": [194.479, 470.293, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-270.111, 205.804, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "o": [ [0, 0], [0, 0], [0, 0], [0, 0] ], "v": [ [-266.857, 204.993], [-269.272, 209.164], [-273.364, 206.613], [-270.943, 202.444] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.909803926945, 0.109803922474, 0.152941182256, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 29, "op": 48, "st": 28, "bm": 0 }, { "ddd": 0, "ind": 14, "ty": 4, "nm": "Layer 5", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 16, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 18, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 33, "s": [100] }, { "t": 35, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 16, "s": [0] }, { "t": 34, "s": [29] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 16, "s": [781.475, 542.889, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 35, "s": [781.475, 822.889, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-223.597, 233.744, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0] ], "o": [ [0, 0], [0, 0], [0, 0] ], "v": [ [-221.09, 235.787], [-226.105, 236.067], [-223.558, 231.42] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.65098041296, 0.901960790157, 0.223529413342, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 16, "op": 35, "st": 15, "bm": 0 }, { "ddd": 0, "ind": 15, "ty": 4, "nm": "Layer 4", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 5, "s": [4] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 7, "s": [100] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 22, "s": [100] }, { "t": 24, "s": [0] } ], "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 5, "s": [0] }, { "t": 23, "s": [-78] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 5, "s": [289.011, 51.812, 0], "to": [0, 46.667, 0], "ti": [0, -46.667, 0] }, { "t": 24, "s": [289.011, 331.812, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-262.62, 194.831, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [0, 0], [0, 0] ], "o": [ [0, 0], [0, 0], [0, 0] ], "v": [ [-259.364, 196.93], [-265.876, 198.066], [-263.263, 191.596] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.901960790157, 0.207843139768, 0.870588243008, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 5, "op": 24, "st": 4, "bm": 0 }, { "ddd": 0, "ind": 16, "ty": 4, "nm": "Mouth", "parent": 1, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [1.1, 101.975, 0], "ix": 2 }, "a": { "a": 0, "k": [-244.448, 237.823, 0], "ix": 1 }, "s": { "a": 1, "k": [ { "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 0, "s": [1262, 1262, 100] }, { "i": { "x": [0, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 4, "s": [1299.86, 1262, 100] }, { "i": { "x": [0, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 18, "s": [1173.66, 1262, 100] }, { "i": { "x": [0, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 30, "s": [1375.58, 1262, 100] }, { "t": 41, "s": [1262, 1262, 100] } ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-0.296, 0.419], [1.111, 0.249], [-1.006, 1.535], [-1.835, 3.294], [0.211, -1.095], [-0.41, -1.177], [-1.08, -0.51], [-1.838, -3.404], [1.972, 0.537] ], "o": [ [0.925, -1.314], [-0.841, -0.189], [1.132, -1.727], [0.507, 3.837], [-0.222, 1.148], [0.296, 0.85], [1.012, 0.479], [-3.419, -1.598], [-2.499, -0.679] ], "v": [ [-250.347, 241.811], [-251.329, 238.417], [-251.856, 235.16], [-245.719, 230.892], [-249.789, 236.446], [-248.06, 239.115], [-247.821, 242.498], [-241.295, 244.546], [-248.775, 244.67] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.101960785687, 0.086274512112, 0.149019613862, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 17, "ty": 4, "nm": "Wistle 2", "parent": 18, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0, "y": 0.97 }, "o": { "x": 0.167, "y": 0.167 }, "t": 19, "s": [-269.13, 249.354, 0], "to": [-2.391, 0.777, 0], "ti": [0, 0, 0] }, { "i": { "x": 0, "y": 0.97 }, "o": { "x": 0.333, "y": 0 }, "t": 30, "s": [-283.475, 254.013, 0], "to": [0, 0, 0], "ti": [-2.391, 0.777, 0] }, { "t": 41, "s": [-269.13, 249.354, 0] } ], "ix": 2 }, "a": { "a": 0, "k": [-269.13, 249.354, 0], "ix": 1 }, "s": { "a": 1, "k": [ { "i": { "x": [0.085, 0.085, 0.582], "y": [0.783, 0.783, 0.263] }, "o": { "x": [0.24, 0.24, 0.18], "y": [0.147, 0.147, 0.294] }, "t": 19, "s": [100, 100, 100] }, { "i": { "x": [0.727, 0.727, 0.667], "y": [1.024, 1.024, 1.031] }, "o": { "x": [0.261, 0.261, 0.347], "y": [0.023, 0.023, 0.153] }, "t": 23, "s": [40.348, 40.348, 100] }, { "i": { "x": [0.727, 0.727, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.223, 0.223, 0.333], "y": [5.639, 5.639, -0.218] }, "t": 24, "s": [0, 0, 100] }, { "i": { "x": [0, 0, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.223, 0.223, 0.333], "y": [0, 0, 0] }, "t": 31, "s": [0, 0, 100] }, { "i": { "x": [0.017, 0.017, 0.667], "y": [0.994, 0.994, 1] }, "o": { "x": [0.401, 0.401, 0.333], "y": [0, 0, 0] }, "t": 32, "s": [36, 36, 100] }, { "t": 41, "s": [100, 100, 100] } ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [1.349, 0.972], [-1.201, -1.202] ], "o": [ [-1.227, -0.883], [0.93, 0.929] ], "v": [ [-270.784, 247.568], [-268.41, 245.193] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.160784319043, 0.443137258291, 0.141176477075, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [2.157, 1.555], [-1.921, -1.922] ], "o": [ [-1.961, -1.409], [1.489, 1.489] ], "v": [ [-271.896, 248.061], [-268.099, 244.26] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.321568638086, 0.57647061348, 0.1254902035, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 2, "cix": 2, "bm": 0, "ix": 2, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [2.901, 2.09], [-2.585, -2.583] ], "o": [ [-2.641, -1.899], [2.003, 2.003] ], "v": [ [-273.022, 248.652], [-267.914, 243.537] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.478431373835, 0.709803938866, 0.113725490868, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 3", "np": 2, "cix": 2, "bm": 0, "ix": 3, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-1.166, 2.21], [2.033, 2.806], [-1.296, -3.921], [-4.548, 2.075], [-0.061, 0.344] ], "o": [ [0, 0], [-1.537, -2.119], [0.744, 2.244], [-0.055, -0.962], [0.194, -1.095] ], "v": [ [-267.526, 244.127], [-271.283, 238.679], [-276.315, 243.625], [-268.976, 249.505], [-269.135, 245.865] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.65098041296, 0.901960790157, 0.223529413342, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 9", "np": 2, "cix": 2, "bm": 0, "ix": 4, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 4, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 18, "ty": 4, "nm": "Wistle", "parent": 16, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 0, "s": [0] }, { "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 5, "s": [2] }, { "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 18, "s": [4] }, { "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 30, "s": [-16] }, { "t": 41, "s": [0] } ], "ix": 10 }, "p": { "a": 0, "k": [-249.704, 239.133, 0], "ix": 2 }, "a": { "a": 0, "k": [-249.704, 239.133, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 1, "k": [ { "i": { "x": 0, "y": 1 }, "o": { "x": 0.167, "y": 0.167 }, "t": 19, "s": [ { "i": [ [ -0.27, 0.114 ], [ 0.522, -0.162 ], [0, 0], [0, 0] ], "o": [ [ 0.486, -0.201 ], [ -0.349, 0.109 ], [0, 0], [ -0.001, 0.002 ] ], "v": [ [ -265.416, 247.908 ], [ -266.411, 243.219 ], [ -269.252, 244.125 ], [ -269.065, 249.449 ] ], "c": true } ] }, { "i": { "x": 0, "y": 1 }, "o": { "x": 0.333, "y": 0 }, "t": 30, "s": [ { "i": [ [ -0.27, 0.114 ], [ 0.522, -0.162 ], [0, 0], [0, 0] ], "o": [ [ 0.486, -0.201 ], [ -0.349, 0.109 ], [0, 0], [ -0.001, 0.002 ] ], "v": [ [ -265.416, 247.908 ], [ -266.411, 243.219 ], [ -284.873, 249.317 ], [ -284.686, 254.641 ] ], "c": true } ] }, { "t": 41, "s": [ { "i": [ [ -0.27, 0.114 ], [ 0.522, -0.162 ], [0, 0], [0, 0] ], "o": [ [ 0.486, -0.201 ], [ -0.349, 0.109 ], [0, 0], [ -0.001, 0.002 ] ], "v": [ [ -265.416, 247.908 ], [ -266.411, 243.219 ], [ -269.252, 244.125 ], [ -269.065, 249.449 ] ], "c": true } ] } ], "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.478431373835, 0.709803938866, 0.113725490868, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 4", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [-0.441, 0.168], [0, 0], [0.451, -0.16] ], "o": [ [0.445, -0.171], [0, 0], [-0.462, 0.162], [0, 0] ], "v": [ [-261.763, 246.347], [-259.498, 245.484], [-263.556, 242.416], [-265.742, 243.183] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.058823529631, 0.705882370472, 0.831372559071, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 50, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 5", "np": 2, "cix": 2, "bm": 0, "ix": 2, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [-0.717, 0.273], [0, 0], [0.794, -0.273] ], "o": [ [0.799, -0.307], [0, 0], [-0.764, 0.265], [0, 0] ], "v": [ [-256.817, 244.455], [-254.535, 243.584], [-258.435, 240.64], [-260.781, 241.459] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.058823529631, 0.705882370472, 0.831372559071, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 50, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 6", "np": 2, "cix": 2, "bm": 0, "ix": 3, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-1.271, -0.967], [-0.407, 0.148], [1.258, 0.945], [0.803, -0.283] ], "o": [ [0.988, -0.374], [-1.257, -0.951], [-0.66, 0.232], [1.279, 0.953] ], "v": [ [-251.812, 242.543], [-249.663, 241.733], [-253.431, 238.887], [-255.637, 239.659] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.058823529631, 0.705882370472, 0.831372559071, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 50, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 7", "np": 2, "cix": 2, "bm": 0, "ix": 4, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [0, 0], [-0.988, 0.359], [0.92, -0.309], [6.715, -2.361] ], "o": [ [6.81, -2.612], [0.59, -0.214], [-1.177, 0.393], [0, 0] ], "v": [ [-266.05, 248.005], [-249.399, 241.634], [-250.421, 237.841], [-267.078, 243.648] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.458823531866, 0.839215695858, 1, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 8", "np": 2, "cix": 2, "bm": 0, "ix": 5, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 5, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 19, "ty": 4, "nm": "Blush", "parent": 1, "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 21, "s": [0] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 27, "s": [97] }, { "i": { "x": [0.833], "y": [0.833] }, "o": { "x": [0.167], "y": [0.167] }, "t": 34, "s": [100] }, { "t": 46, "s": [0] } ], "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [-1.651, 60.272, 0], "ix": 2 }, "a": { "a": 0, "k": [-244.666, 234.519, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-5.559, 0], [0, -5.557], [5.557, 0], [0, 5.561] ], "o": [ [5.557, 0], [0, 5.561], [-5.559, 0], [0, -5.557] ], "v": [ [-229.286, 224.454], [-219.221, 234.519], [-229.286, 244.583], [-239.35, 234.519] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 3, "k": { "a": 0, "k": [ 0.1, 1, 0.388, 0.6, 0.73, 1, 0.649, 0.351, 1, 1, 0.91, 0.102, 0.1, 1, 0.73, 0.5, 1, 0 ], "ix": 9 } }, "s": { "a": 0, "k": [-230, 234], "ix": 5 }, "e": { "a": 0, "k": [-219.936, 234], "ix": 6 }, "t": 2, "h": { "a": 0, "k": 0, "ix": 7 }, "a": { "a": 0, "k": 0, "ix": 8 }, "nm": "B1G", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 60, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [-5.559, 0], [0, -5.557], [5.557, 0], [0, 5.561] ], "o": [ [5.557, 0], [0, 5.561], [-5.559, 0], [0, -5.557] ], "v": [ [-260.046, 224.454], [-249.983, 234.519], [-260.046, 244.583], [-270.111, 234.519] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 3, "k": { "a": 0, "k": [ 0.1, 1, 0.388, 0.6, 0.73, 1, 0.649, 0.351, 1, 1, 0.91, 0.102, 0.1, 1, 0.73, 0.5, 1, 0 ], "ix": 9 } }, "s": { "a": 0, "k": [-261, 234], "ix": 5 }, "e": { "a": 0, "k": [-250.936, 234], "ix": 6 }, "t": 2, "h": { "a": 0, "k": 0, "ix": 7 }, "a": { "a": 0, "k": 0, "ix": 8 }, "nm": "B2G", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 60, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 2, "cix": 2, "bm": 0, "ix": 2, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 3", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 20, "ty": 4, "nm": "Eyes", "parent": 1, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [0, -52.396, 0], "ix": 2 }, "a": { "a": 0, "k": [-244.535, 225.591, 0], "ix": 1 }, "s": { "a": 1, "k": [ { "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 0, "s": [1262, 1262, 100] }, { "i": { "x": [0, 0, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 5, "s": [1262, 1009.6, 100] }, { "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 18, "s": [1262, 1388.2, 100] }, { "i": { "x": [0, 0, 0.667], "y": [1, 1, 1] }, "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, "t": 26, "s": [1262, 1009.6, 100] }, { "t": 44, "s": [1262, 1262, 100] } ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 0.605, 0.779 ], [ 3.988, -5.154 ], [ -0.258, 0.756 ], [ -3.385, -9.963 ] ], "o": [ [ -3.989, -5.154 ], [ -0.605, 0.779 ], [ 3.385, -9.963 ], [ 0.258, 0.756 ] ], "v": [ [ -248.954, 223.965 ], [ -259.716, 223.965 ], [ -261.3, 223.408 ], [ -247.37, 223.408 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.101960785687, 0.086274512112, 0.149019613862, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 0, 0 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 0.605, 0.779 ], [ 3.989, -5.154 ], [ -0.258, 0.756 ], [ -3.387, -9.963 ] ], "o": [ [ -3.989, -5.154 ], [ -0.605, 0.779 ], [ 3.385, -9.963 ], [ 0.256, 0.756 ] ], "v": [ [ -229.355, 223.965 ], [ -240.117, 223.965 ], [ -241.7, 223.408 ], [ -227.769, 223.408 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.101960785687, 0.086274512112, 0.149019613862, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 0, 0 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 2, "cix": 2, "bm": 0, "ix": 2, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 1, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 1, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 4", "np": 1, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 21, "ty": 4, "nm": "Face", "parent": 2, "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [0, -338.396, 0], "ix": 2 }, "a": { "a": 0, "k": [-244.535, 225.591, 0], "ix": 1 }, "s": { "a": 0, "k": [1262, 1262, 100], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [15.464, 0], [0, -15.467], [-15.464, 0], [-0.001, 15.459] ], "o": [ [-15.464, 0], [0, 15.459], [15.464, 0], [0.001, -15.467] ], "v": [ [-244.535, 197.592], [-272.535, 225.592], [-244.535, 253.59], [-216.535, 225.592] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "gf", "o": { "a": 0, "k": 100, "ix": 10 }, "r": 1, "bm": 0, "g": { "p": 3, "k": { "a": 0, "k": [ 0, 1, 0.584, 0, 0.4, 1, 0.747, 0.051, 1, 1, 0.91, 0.102 ], "ix": 9 } }, "s": { "a": 0, "k": [-231.07, 253], "ix": 5 }, "e": { "a": 0, "k": [-231.07, 198.011], "ix": 6 }, "t": 1, "nm": "party_face_grad", "mn": "ADBE Vector Graphic - G-Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [0, 0], "ix": 2 }, "a": { "a": 0, "k": [0, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 4", "np": 1, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 49, "st": 0, "bm": 0 } ] } ], "layers": [ { "ddd": 0, "ind": 1, "ty": 0, "nm": "party_face", "refId": "comp_0", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [512, 512, 0], "ix": 2 }, "a": { "a": 0, "k": [512, 512, 0], "ix": 1 }, "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } }, "ao": 0, "w": 1024, "h": 1024, "ip": 0, "op": 48, "st": 0, "bm": 0 } ], "markers": [] } ================================================ FILE: thorvg/examples/thorvg_lottie/main/CMakeLists.txt ================================================ idf_component_register( SRCS "thorvg_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES esp_lcd pthread) littlefs_create_partition_image(storage ../lottie_files FLASH_IN_PROJECT) ================================================ FILE: thorvg/examples/thorvg_lottie/main/idf_component.yml ================================================ dependencies: espressif/esp_lcd_sh8601: "^2.0.0" joltwallet/littlefs: "^1.20.0" espressif/thorvg: version: "*" override_path: "../../.." ================================================ FILE: thorvg/examples/thorvg_lottie/main/thorvg_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "esp_log.h" #include "esp_err.h" #include "esp_check.h" #include "esp_pthread.h" #include "esp_heap_caps.h" #include "esp_littlefs.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_sh8601.h" #include "driver/spi_master.h" #include "thorvg_capi.h" static const char *TAG = "example"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define EXAMPLE_PIN_NUM_LCD_CS 12 #define EXAMPLE_PIN_NUM_LCD_PCLK 11 #define EXAMPLE_PIN_NUM_LCD_DATA0 4 #define EXAMPLE_PIN_NUM_LCD_DATA1 5 #define EXAMPLE_PIN_NUM_LCD_DATA2 6 #define EXAMPLE_PIN_NUM_LCD_DATA3 7 #define EXAMPLE_LCD_PCLK_HZ (20 * 1000 * 1000) #define EXAMPLE_LCD_BIT_PER_PIXEL 16 // RGB565 #define EXAMPLE_LCD_SPI_HOST SPI2_HOST #define EXAMPLE_FS_MOUNT_POINT "/storage" #define EXAMPLE_LOTTIE_FILENAME EXAMPLE_FS_MOUNT_POINT"/emoji-animation.json" #define EXAMPLE_LOTTIE_SIZE_HOR 368 #define EXAMPLE_LOTTIE_SIZE_VER 448 static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = { {0x11, (uint8_t[]){0x00}, 0, 120}, {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0}, {0x35, (uint8_t[]){0x00}, 1, 0}, {0x53, (uint8_t[]){0x20}, 1, 10}, {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0}, {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0}, {0x51, (uint8_t[]){0x00}, 1, 10}, {0x29, (uint8_t[]){0x00}, 0, 10}, {0x51, (uint8_t[]){0xFF}, 1, 0}, }; static void argb888_to_rgb565(const uint32_t *in, uint16_t *out, size_t num_pixels) { for (size_t i = 0; i < num_pixels; ++i) { uint32_t argb = in[i]; uint8_t r = (argb >> 16) & 0xFF; uint8_t g = (argb >> 8) & 0xFF; uint8_t b = argb & 0xFF; // Convert to RGB565 uint16_t rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); rgb565 = (rgb565 >> 8) | (rgb565 << 8); out[i] = rgb565; } } static void play_lottie(esp_lcd_panel_handle_t lcd_panel, uint32_t *canvas_buf_argb888, uint16_t *canvas_buf_rgb565, SemaphoreHandle_t flush_done_sem) { // Initialize ThorVG engine if (tvg_engine_init(0) != TVG_RESULT_SUCCESS) { printf("Failed to initialize ThorVG engine\n"); abort(); } // Create a software canvas backed by an ARGB8888 buffer. // ThorVG renders into this buffer first, then we convert it to panel RGB565. Tvg_Canvas canvas = tvg_swcanvas_create(TVG_ENGINE_OPTION_DEFAULT); assert(canvas); tvg_swcanvas_set_target(canvas, canvas_buf_argb888, EXAMPLE_LOTTIE_SIZE_HOR, EXAMPLE_LOTTIE_SIZE_HOR, EXAMPLE_LOTTIE_SIZE_VER, TVG_COLORSPACE_ARGB8888); // Flush the background with black. esp_lcd_panel_draw_bitmap(lcd_panel, 0, 0, EXAMPLE_LOTTIE_SIZE_HOR, EXAMPLE_LOTTIE_SIZE_VER, canvas_buf_rgb565); // Create an animation object Tvg_Animation animation = tvg_lottie_animation_new(); // Get the picture object from animation Tvg_Paint picture = tvg_animation_get_picture(animation); // Load the Lottie file (JSON) if (tvg_picture_load(picture, EXAMPLE_LOTTIE_FILENAME) != TVG_RESULT_SUCCESS) { printf("Problem with loading a lottie file\n"); abort(); } // Resize the picture tvg_picture_set_size(picture, EXAMPLE_LOTTIE_SIZE_HOR, EXAMPLE_LOTTIE_SIZE_VER); // add the picture to the canvas tvg_canvas_add(canvas, picture); // Play the animation frame by frame. float total_frames; tvg_animation_get_total_frame(animation, &total_frames); for (float frame = 0.0f; frame < total_frames; frame += 1.0f) { tvg_animation_set_frame(animation, frame); tvg_canvas_update(canvas); // Draw the canvas (renders to the buffer) tvg_canvas_draw(canvas, false); // Sync to ensure drawing is completed tvg_canvas_sync(canvas); // Wait until the previous panel transfer is completed before reusing buffers. xSemaphoreTake(flush_done_sem, portMAX_DELAY); // Convert ARGB8888 to RGB565 and flush one full frame to the panel. argb888_to_rgb565(canvas_buf_argb888, canvas_buf_rgb565, EXAMPLE_LOTTIE_SIZE_HOR * EXAMPLE_LOTTIE_SIZE_VER); esp_lcd_panel_draw_bitmap(lcd_panel, 0, 0, EXAMPLE_LOTTIE_SIZE_HOR, EXAMPLE_LOTTIE_SIZE_VER, canvas_buf_rgb565); } // Cleanup tvg_animation_del(animation); tvg_canvas_destroy(canvas); tvg_engine_term(); } typedef struct { esp_lcd_panel_handle_t lcd_panel; uint32_t *canvas_buf_argb888; uint16_t *canvas_buf_rgb565; SemaphoreHandle_t flush_done_sem; } lottie_render_ctx_t; static void *lottie_render_thread(void *arg) { lottie_render_ctx_t *ctx = (lottie_render_ctx_t *)arg; uint32_t *canvas_buf_argb888 = ctx->canvas_buf_argb888; uint16_t *canvas_buf_rgb565 = ctx->canvas_buf_rgb565; esp_lcd_panel_handle_t lcd_panel = ctx->lcd_panel; SemaphoreHandle_t flush_done_sem = ctx->flush_done_sem; while (1) { play_lottie(lcd_panel, canvas_buf_argb888, canvas_buf_rgb565, flush_done_sem); vTaskDelay(pdMS_TO_TICKS(100)); } return NULL; } static esp_err_t example_init_fs(void) { esp_vfs_littlefs_conf_t conf = { .base_path = EXAMPLE_FS_MOUNT_POINT, .partition_label = "storage", .format_if_mount_failed = true, }; esp_err_t ret = esp_vfs_littlefs_register(&conf); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount or format filesystem"); } else if (ret == ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "Failed to find LittleFS partition"); } else { ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret)); } return ESP_FAIL; } size_t total = 0, used = 0; ret = esp_littlefs_info(conf.partition_label, &total, &used); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret)); esp_littlefs_format(conf.partition_label); } else { ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); } return ESP_OK; } static bool example_on_color_trans_done(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { SemaphoreHandle_t flush_done_sem = (SemaphoreHandle_t)user_ctx; BaseType_t high_task_wakeup = pdFALSE; xSemaphoreGiveFromISR(flush_done_sem, &high_task_wakeup); return high_task_wakeup == pdTRUE; } void app_main(void) { // allocate the canvas buffer(s) from PSRAM uint32_t *canvas_buf_argb888 = heap_caps_calloc(EXAMPLE_LOTTIE_SIZE_HOR * EXAMPLE_LOTTIE_SIZE_VER, sizeof(uint32_t), MALLOC_CAP_SPIRAM); uint16_t *canvas_buf_rgb565 = heap_caps_calloc(EXAMPLE_LOTTIE_SIZE_HOR * EXAMPLE_LOTTIE_SIZE_VER, sizeof(uint16_t), MALLOC_CAP_SPIRAM); assert(canvas_buf_argb888 && canvas_buf_rgb565); // lottie files are saved in the filesystem, we need to initialize the file system first ESP_ERROR_CHECK(example_init_fs()); spi_bus_config_t buscfg = { .sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK, .data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0, .data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1, .data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2, .data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3, .max_transfer_sz = EXAMPLE_LOTTIE_SIZE_HOR * EXAMPLE_LOTTIE_SIZE_VER * 3, }; ESP_ERROR_CHECK(spi_bus_initialize(EXAMPLE_LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); esp_lcd_panel_io_handle_t io_handle = NULL; esp_lcd_panel_io_spi_config_t io_config = { .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS, .dc_gpio_num = -1, // no D/C pin for SH8601 .spi_mode = 0, .pclk_hz = EXAMPLE_LCD_PCLK_HZ, .trans_queue_depth = 20, .lcd_cmd_bits = 32, // according to SH8601 spec .lcd_param_bits = 8, // according to SH8601 spec .flags = { .quad_mode = true, // QSPI mode }, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(EXAMPLE_LCD_SPI_HOST, &io_config, &io_handle)); esp_lcd_panel_handle_t lcd_panel = NULL; sh8601_vendor_config_t vendor_config = { .init_cmds = lcd_init_cmds, .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), .flags = { .use_qspi_interface = 1, // SH8601 support many interfaces, we select QSPI here }, }; const esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = -1, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, .bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL, .vendor_config = &vendor_config, }; ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &lcd_panel)); esp_lcd_panel_reset(lcd_panel); esp_lcd_panel_init(lcd_panel); esp_lcd_panel_disp_on_off(lcd_panel, true); lottie_render_ctx_t render_ctx = { .lcd_panel = lcd_panel, .canvas_buf_argb888 = canvas_buf_argb888, .canvas_buf_rgb565 = canvas_buf_rgb565, .flush_done_sem = xSemaphoreCreateBinary(), }; assert(render_ctx.flush_done_sem); esp_lcd_panel_io_callbacks_t cbs = { .on_color_trans_done = example_on_color_trans_done, }; // Pass the semaphore as callback user data for frame-complete notification. esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, render_ctx.flush_done_sem); esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); cfg.thread_name = "lottie_render"; cfg.inherit_cfg = false; cfg.stack_size = 30 * 1024; ESP_ERROR_CHECK(esp_pthread_set_cfg(&cfg)); pthread_t thread; int ret = pthread_create(&thread, NULL, lottie_render_thread, &render_ctx); if (ret != 0) { ESP_LOGE(TAG, "Failed to create render thread: %d", ret); abort(); } pthread_detach(thread); } ================================================ FILE: thorvg/examples/thorvg_lottie/partitions.csv ================================================ # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1M, storage, data, spiffs, , 1M, ================================================ FILE: thorvg/examples/thorvg_lottie/sdkconfig.defaults ================================================ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_COMPILER_OPTIMIZATION_PERF=y ================================================ FILE: thorvg/examples/thorvg_lottie/sdkconfig.defaults.esp32p4 ================================================ CONFIG_IDF_EXPERIMENTAL_FEATURES=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_HEX=y CONFIG_SPIRAM_SPEED_200M=y ================================================ FILE: thorvg/examples/thorvg_lottie/sdkconfig.defaults.esp32s3 ================================================ CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM_XIP_FROM_PSRAM=y CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y ================================================ FILE: thorvg/idf_component.yml ================================================ version: "1.0.1" description: "ThorVG is an open-source graphics library designed for creating vector-based scenes and animations" url: https://github.com/espressif/idf-extra-components/tree/master/thorvg repository: "https://github.com/espressif/idf-extra-components.git" documentation: "https://www.thorvg.org/native-apis" issues: "https://github.com/espressif/idf-extra-components/issues" dependencies: idf: ">=5.1" sbom: manifests: - path: sbom_thorvg.yml dest: thorvg ================================================ FILE: thorvg/project_include.cmake ================================================ # ThorVG uses meson as its build system, check if it's installed find_program(MESON_EXECUTABLE meson) if(NOT MESON_EXECUTABLE) message(STATUS "Meson build system not found. Attempting to install it using pip...") # Try to install meson using pip idf_build_get_property(python PYTHON) execute_process( COMMAND ${python} -m pip install -U meson RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if(result) message(FATAL_ERROR "Failed to install meson using pip. Please install it manually.\nError: ${error}") else() message(STATUS "Meson successfully installed.") endif() else() message(STATUS "Meson build system found: ${MESON_EXECUTABLE}") endif() ================================================ FILE: thorvg/sbom_thorvg.yml ================================================ name: thorvg version: 1.0.1 cpe: cpe:2.3:a:thorvg:thorvg:{}:*:*:*:*:*:*:* supplier: 'Organization: thorvg ' description: ThorVG is an open-source graphics library designed for creating vector-based scenes and animations. url: https://github.com/thorvg/thorvg hash: 6648d791972169d9cd22168f5bd11089bbec56c9 ================================================ FILE: touch_element/.build-test-rules.yml ================================================ touch_element: disable: - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 3 reason: IDF version below v5.3 is not supported - if: IDF_TARGET not in ["esp32s2", "esp32s3"] reason: only supports esp32s2 and esp32s3 ================================================ FILE: touch_element/CHANGELOG.md ================================================ ## 1.1.2 - Fixed the read stuck issue after deep sleep by resetting the hardware while initializing ## 1.1.1 - Fixed the incorrect interrupt mask ## 1.1.0 - Refactor to remove the dependency on the hal and soc caps in IDF - Moved the enums in `touch_pad_intr_mask_t` to ll header, but still keep `touch_pad_intr_mask_t` for backward compatibility ## 1.0.0 - Added test app, examples and documentations for the touch element library ## 0.1.0 - Initial touch element version, based on the legacy Touch Sensor driver ================================================ FILE: touch_element/CMakeLists.txt ================================================ idf_build_get_property(target IDF_TARGET) if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() idf_component_register(SRCS "touch_element.c" "touch_button.c" "touch_slider.c" "touch_matrix.c" "touch_sensor_legacy_hal.c" INCLUDE_DIRS include PRIV_REQUIRES esp_timer esp_pm esp_driver_gpio) ================================================ FILE: touch_element/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: touch_element/README.md ================================================ # Touch Element Library [![Component Registry](https://components.espressif.com/components/espressif/touch_element/badge.svg)](https://components.espressif.com/components/espressif/touch_element) The Touch Element Library is moved from [esp-idf](https://github.com/espressif/esp-idf) to the component registry since v6.0. It can help you design touch applications fast with the common elements like `button`, `slider` and `matrix`. It supports the touch events like `Press`, `Release`, `Long Press` and so on. You can get notified by registering the event callback or subscribe the events in the task. > **Note**: The touch element library only supports ESP32-S2 and ESP32-S3 with the legacy touch driver. For the new touch driver, please refer to the [Touch Sensor](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/cap_touch_sens.html) documentation. ## Documentation For detailed information about the Touch Element Library, including API reference and user guides, please visit: - **Programming Guide & API Reference**: [Touch Element Documentation](https://espressif.github.io/idf-extra-components/latest/touch_element/index.html) ================================================ FILE: touch_element/docs/Doxyfile ================================================ # Set this to the header file you want INPUT = \ ../include/touch_element/ # The output directory for the generated XML documentation OUTPUT_DIRECTORY = doxygen_output # Warning-related settings, it's recommended to keep them enabled WARN_IF_UNDOC_ENUM_VAL = YES WARN_AS_ERROR = YES # Other common settings FULL_PATH_NAMES = YES STRIP_FROM_PATH = ../ STRIP_FROM_INC_PATH = ../ ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES OPTIMIZE_OUTPUT_FOR_C = YES EXPAND_ONLY_PREDEF = YES EXTRACT_ALL = YES PREDEFINED = $(ENV_DOXYGEN_DEFINES) HAVE_DOT = NO GENERATE_XML = YES XML_OUTPUT = xml GENERATE_HTML = NO HAVE_DOT = NO GENERATE_LATEX = NO QUIET = YES MARKDOWN_SUPPORT = YES ================================================ FILE: touch_element/docs/book.toml ================================================ [book] title = "Touch Element Documentation" language = "en" [output.html] default-theme = "light" git-repository-url = "https://github.com/espressif/idf-extra-components/tree/master/touch_element" edit-url-template = "https://github.com/espressif/idf-extra-components/edit/master/touch_element/docs/{path}" mathjax-support = true ================================================ FILE: touch_element/docs/src/SUMMARY.md ================================================ # Summary --- # Programming Guide - [Touch Element](index.md) --- # API Reference - [API Reference](api.md) ================================================ FILE: touch_element/docs/src/api.md ================================================ # API Reference
This file is automatically generated by esp-doxybook. DO NOT edit it manually.
================================================ FILE: touch_element/docs/src/img/source/te_architecture.drawio ================================================ 7Vxtc6M2EP41nmk/JIMkEPAxb22v05veNO209ykjg2zTw8gFnDj99ZVA4k3Cdi6AE/dubiYgCwmeXe0+Wq00Qzfr3Y8p2aw+spDGM2iFuxm6nUEIbAhn4r8VPpclHnDLgmUahbJSXXAf/UvLQkcWbqOQZq16OWNxHm3ahQFLEhrkrTKSpuypXW3B4nanG7KkWsF9QGK99M8ozFfyIxyrLv+JRsuV6hlY8pc1UZVlQbYiIXtqFKG7GbpJGcvLq/XuhsYCOwVL+dwPPb9WL5bSJD/mgbvPj27yJVvm948fvqx/fvKu/vrtwpMwP5J4K79Yvm3+rCDgL74RlwFLE5rO0PXTKsrp/YYEovSJi52XrfJ1zO8AvwyjlEsiYgm/T1gqYLgu9AF6lrgUimBjUXMRxfENi1ladIQWC4qDgJdnecq+0MYvoevPLfGwfFua5nTXiwOo0OVaSdma5ukzryIfQPal/GipkUCB8FQL2PWk1FYN4TpKKYlUqmXVeI07v5DQv0QM7mExpGybhFS0Yh2WQRdZL6BmZOeeYzsDIYv9DrJQDYYGskih3UTWVnAPjixQ5qaBJA35EJe3Qj/ZkiUkvqtLr9tY13V+YWwjEf6b5vmzNFdkm7M2/hyx9Pkv8fylB7Aq+MwLLqxLy/JVye1O9lHePTfvPtE04iCIIVcW7qK8bBJxfSnvPxf3vu/L+7o9cfPcuOm2VqIioNgvbY4c26YB3YOxLe0ySZc03ysLZNaflMYkjx7bb2JSBfnoJxbxd2zoHWipHfZgu4nyC+RTTROpNcTFxcG1fOy6NvBRq1kX4ktR6NiyTruT8vO1Tq7SlDw3qm1EhWzPx7jtj3EtuzMIyhbrIVEB+fWjxD4L86OsjUTOdnW7DkzWpyoc3Pro3vV3tg1WvOgupmtayD2O5imRfTUB50DkbVTb6CUsoR2oZRGJo6VwvgHvoHDYAtaIc5or+cM6CsPCzpnE2Bb0EA7X63rcaoA2JWMbPC4czS0gTTIfBFjpdiNksqZZJojh2crE1ny1gwyjBU0qE50E/bOl2zOWggM6MsC6DCYdFqrhpsHirCEVPppt8yg5Y2FABLpmytHFYZoXjDcirNOQV0k01XVJMp29FDMk2aroEwzLLeGx5NL1zdJ9Gbd8KVtDVltlkBzRfQzzQP1x2B000Tsci2GbbUjS0i/8z1YEJMohd5GVY+6KV+Ez+TWJ6wr8ain+3ka8Ul5wGlpxGvpY/C374O9cdlM+sIdZgpczy8UC9oQM8Bw7eBjT4Hsdy1CJseksXTilpdYJzKeUBZy38MJgRZKkiMNlOcl1m/0OEYcmMj8p4rb3/0LchnrwZlrEgeWdxP3VsRvoNEM31Y89UZuxfKDtT+UDXzdA/G9u5nVDsDsns51TD0EHnrnRa+Ht4CpScUKj55/E6Bk5v7uf9DcspfsSQ2kMb49lPR30LqynoxO6b9bzNWPZP/VIdvRp12XxTwP3bGIowOm6MGwIK04a0nJ0VnK9zXOWaFLInqJ1TDSkuZoGAXUWC5MCI4x8FA4DnloEVjMeYFBg37R8MRp2WA8H3sdRKOKBbxs7hA3EaWLsgIbdR5Kn0e7tYYe7SwGm6MaL0ZP9/UaDnCRL/rG1lVDLP3sW6rChOxXzUL2RmNu8hDPPa2HHMk1kA4TOsM5//9iEBdk9B/qrJWzYp3aaWKdB5wS4Fkj0Tz3DwzpLOSvA25bNtMg5LdxYg1uDlSbhlUhdFLwuJlkWBb0BqlZ8qp5o9Uy7qgQiNa2rJmu9U7zZgBOxY8NY2DLLtCE0x0AhVdkrs4mA27WKSGnNS/OJuP51DWw31aQna2gw/3VEYuF7VLeTKYeWy6KlLh6rHHpTFe+aSDlcU8B2QBM/DX1Fls5iXFMYD5kYrOv0q8DrUnqPMPSvyazmalZnVtvCXRaJ1dC3Tb72NInVhhUkY2I19MeSgq7hKgEvaqR7nX1uCwbt1EiMDLTTZDZHDHNrkrmp2GayXc8NQYa3Tzcx9DsW3dVxhgac7dFw1sMQw4WVb7rzg3OJJWtyxMDkUSaVpN+fxbNgBbHQJSl+uMiKNR8hRw7BTpfi1WYTc9NU+JVagmWbPRI8G8OIHM1pmUzjpBFrf+TNQCGh3sI4hHDg0fliGGQdu717AhqSKY1MvfJDwwOr+5y7cg3M4oaMa11azE6zL+/QYjlOz5y1CTfEBns1GvXSPY+O9mKblIT2PSLeNR3Q1U3HxJjrG05+1XnU2VhvbHeU3rBkNqnp9nQLc0+TUIQ0pOr37Sx5+/qOQFffTTYGQMMsezR993Qb8yF55BjwshsSx3MSvEdjrkNtMi3TQu3rJGTKXMzqprEZYU/ssXzXQcLWyo8dDFt7PUKdJn9IbVadVj4D4qxs50Gc/Z7lgYlw1uN65+xiUZdXntrF+noavgrpkeZEtiaZ5yoax4Vdh+wZvIRpx/N40tEDrqaEt3c4fQUGbq+yo5vQdtcKB4yY6uRSxWvC6NEY/5mznQj/RMmyDADNWRrS9IIXHwgDWQELm/G8ooOD4by3K8zu1k5g2Gk7tThNkdkOtGqBaBHTnVyjvT60XPu1CL/dRVcMuuJzMBSnqNg+wq7lebjT5LFLsLbTbbaTYTbyAiyw9GynZqp2OWc8V/+FUPukE9twWpDnTei8wJ7FkmND7MAYYs8FQ3nY7XYPGc0fAjkz/e77Y+PtDWGbRBOwtbACDWVQZ5VB+YY/kHUUC5xviqrQuieJ2MLz8V5WUPMEgwWhIHSoa7IgPnYRGWjOa3fOQoAqXaiVqmXiMqNZaABHVQe5E+NBhoQeUhpQbiTfiVbcgVvnrlcrrkbSCoS9k2vFqTelOtwotoIh8vivrz9PzON+r87QKk4og7MTHigGwLF7tmyz9ox76INrtbNKJzmiCxiOLBpm/fc25UilxxqdF7H9/nyjvgylIXhF22S4agXmUBbSeFQf9O+MHsKRbEgouMU3x9FcGlLL+NWRY0cG0L/CcfDb+hjXcrzXZ+Giu/8A ================================================ FILE: touch_element/docs/src/index.md ================================================ # Touch Element ## Overview The Touch Element Library is a highly abstracted element library designed on the basis of the touch sensor driver. The library provides a unified and user-friendly software interface to quickly build capacitive touch sensor applications. > WARNING: The Touch Element Library is only usable for the ESP32-S2 and ESP32-S3 chips. > WARNING: The Touch Element Library currently is still based on the legacy touch driver. Please refer to the [new driver of Capacitive Touch Sensor](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/peripherals/cap_touch_sens.html) if you don't need the Touch Element Library. ### Architecture The Touch Element library configures touch sensor peripherals via the touch sensor driver. However, some necessary hardware parameters should be passed to [touch_element_install](api.md#function-touch_element_install) and will be configured automatically only after calling [touch_element_start](api.md#function-touch_element_start). This sequential order is essential because configuring these parameters has a significant impact on the run-time system. Therefore, they must be configured after calling the start function to ensure the system functions properly. These parameters include touch channel threshold, driver-level of waterproof shield sensor, etc. The Touch Element library sets the touch sensor interrupt and the esp_timer routine up, and the hardware information of the touch sensor (channel state, channel number) will be obtained in the touch sensor interrupt service routine. When the specified channel event occurs, the hardware information is passed to the esp_timer callback routine, which then dispatches the touch sensor channel information to the touch elements (such as button, slider, etc.). The library then runs a specified algorithm to update the touch element's state or calculate its position and dispatches the result accordingly. So when using the Touch Element library, you are relieved from the implementation details of the touch sensor peripheral. The library handles most of the hardware information and passes the more meaningful messages to the event handler routine. The workflow of the Touch Element library is illustrated in the picture below. ![Touch Element architecture](img/te_architecture.svg) The features in relation to the Touch Element library in ESP32-S2 / ESP32-S3 are given in the table below. | Touch Element waterproof | Touch Element button | Touch Element slider | Touch Element matrix button | | :-----------------------: | :------------------: | :------------------: | :-------------------------: | | ✔ | ✔ | ✔ | ✔ | ### Peripheral ESP32-S2 / ESP32-S3 integrates one touch sensor peripheral with several physical channels. - 14 physical capacitive touch channels - Timer or software FSM trigger mode - Up to 5 kinds of interrupt (Upper threshold and lower threshold interrupt, measure one channel finish and measure all channels finish interrupt, measurement timeout interrupt) - Sleep mode wakeup source - Hardware internal de-noise - Hardware filter - Hardware waterproof sensor - Hardware proximity sensor The channels are located as follows: | Channel | GPIO | |:-------:|:----:| | CH0 | GPIO0 (internal) | | CH1 | GPIO1 | | CH2 | GPIO2 | | CH3 | GPIO3 | | CH4 | GPIO4 | | CH5 | GPIO5 | | CH6 | GPIO6 | | CH7 | GPIO7 | | CH8 | GPIO8 | | CH9 | GPIO9 | | CH10 | GPIO10 | | CH11 | GPIO11 | | CH12 | GPIO12 | | CH13 | GPIO13 | | CH14 | GPIO14 | ## Terminology The terms used in relation to the Touch Element library are given below. **Touch sensor** - Touch sensor peripheral inside the chip **Touch channel** - Touch sensor channels inside the touch sensor peripheral **Touch pad** - Off-chip physical solder pad, generally inside the PCB **De-noise channel** - Internal de-noise channel, which is always Channel 0 and is reserved **Shield sensor** - One of the waterproof sensors for detecting droplets in small areas and compensating for the influence of water drops on measurements **Guard sensor** - One of the waterproof sensors for detecting extensive wading and to temporarily disable the touch sensor **Shield channel** - The channel that waterproof shield sensor connected to, which is always Channel 14 **Guard channel** - The channel that waterproof guard sensor connected to **Shield pad** - Off-chip physical solder pad, generally is grids, and is connected to shield the sensor **Guard pad** - Off-chip physical solder pad, usually a ring, and is connected to the guard sensor ![Touch sensor application system components](img/te_component.svg) ### Touch Sensor Signal Each touch sensor is able to provide the following types of signals: - Raw: The Raw signal is the unfiltered signal from the touch sensor. - Smooth: The Smooth signal is a filtered version of the Raw signal via an internal hardware filter. - Benchmark: The Benchmark signal is also a filtered signal that filters out extremely low-frequency noise. All of these signals can be obtained using touch sensor driver API. ![Touch sensor signals](img/te_signal.png) ### Touch Sensor Signal Threshold The Touch Sensor Threshold value is a configurable threshold value used to determine when a touch sensor is touched or not. When the difference between the Smooth signal and the Benchmark signal becomes greater than the threshold value (i.e., ``(smooth - benchmark) > threshold``), the touch channel's state will be changed and a touch interrupt will be triggered simultaneously. ![Touch sensor signal threshold](img/te_threshold.svg) ### Sensitivity Important performance parameter of the touch sensor, the larger it is, the better touch the sensor performs. It could be calculated by the format below: $$ Sensitivity = \frac{Signal_{press} - Signal_{release}}{Signal_{release}} = \frac{Signal_{delta}}{Signal_{benchmark}} $$ ### Waterproof Waterproof is the hardware feature of a touch sensor which has a guard sensor and shield sensor (always connect to Channel 14) that has the ability to resist a degree of influence of water drop and detect the water stream. ### Touch Button The touch button consumes one channel of the touch sensor, and it looks like as the picture below: ![Touch button](img/te_button.svg) ### Touch Slider The touch slider consumes several channels (at least three channels) of the touch sensor, the more channels consumed, the higher resolution and accuracy position it performs. The touch slider looks like as the picture below: ![Touch slider](img/te_slider.svg) ### Touch Matrix The touch matrix button consumes several channels (at least 2 + 2 = 4 channels), and it gives a solution to use fewer channels and get more buttons. ESP32-S2 / ESP32-S3 supports up to 49 buttons. The touch matrix button looks like as the picture below: ![Touch matrix](img/te_matrix.svg) ## Touch Element Library Usage Using this library should follow the initialization flow below: 1. To initialize the Touch Element library by calling [touch_element_install](api.md#function-touch_element_install). 2. To initialize touch elements (button/slider etc) by calling [touch_button_install](api.md#function-touch_button_install), [touch_slider_install](api.md#function-touch_slider_install) or [touch_matrix_install](api.md#function-touch_matrix_install). 3. To create a new element instance by calling [touch_button_create](api.md#function-touch_button_create), [touch_slider_create](api.md#function-touch_slider_create) or [touch_matrix_create](api.md#function-touch_matrix_create). 4. To subscribe events by calling [touch_button_subscribe_event](api.md#function-touch_button_subscribe_event), [touch_slider_subscribe_event](api.md#function-touch_slider_subscribe_event) or [touch_matrix_subscribe_event](api.md#function-touch_matrix_subscribe_event). 5. To choose a dispatch method by calling [touch_button_set_dispatch_method](api.md#function-touch_button_set_dispatch_method), [touch_slider_set_dispatch_method](api.md#function-touch_slider_set_dispatch_method) or [touch_matrix_set_dispatch_method](api.md#function-touch_matrix_set_dispatch_method) that tells the library how to notify you while the subscribed event occurs. 6. If dispatch by callback, call [touch_button_set_callback](api.md#function-touch_button_set_callback), [touch_slider_set_callback](api.md#function-touch_slider_set_callback) or [touch_matrix_set_callback](api.md#function-touch_matrix_set_callback) to set the event handler function. 7. To start the Touch Element library by calling [touch_element_start](api.md#function-touch_element_start). 8. If dispatch by callback, the callback will be called by the driver core when an event happens, no need to do anything; If dispatch by event task, create an event task and call [touch_element_message_receive](api.md#function-touch_element_message_receive) to obtain messages in a loop. 9. (Optional) If you want to suspend the Touch Element run-time system or for some reason that could not obtain the touch element message, [touch_element_stop](api.md#function-touch_element_stop) should be called to suspend the Touch Element system and then resume it by calling [touch_element_start](api.md#function-touch_element_start) again. In code, the flow above may look like as follows: ```c static touch_button_handle_t element_handle; //Declare a touch element handle //Define the subscribed event handler void event_handler(touch_button_handle_t out_handle, touch_button_message_t out_message, void *arg) { //Event handler logic } void app_main() { //Using the default initializer to config Touch Element library touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); touch_element_install(&global_config); //Using the default initializer to config Touch elements touch_slider_global_config_t elem_global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); touch_slider_install(&elem_global_config); //Create a new instance touch_slider_config_t element_config = { ... ... }; touch_button_create(&element_config, &element_handle); //Subscribe the specified events by using the event mask touch_button_subscribe_event(element_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL); //Choose CALLBACK as the dispatch method touch_button_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_CALLBACK); //Register the callback routine touch_button_set_callback(element_handle, event_handler); //Start Touch Element library processing touch_element_start(); } ``` ### Initialization 1. To initialize the Touch Element library, you have to configure the touch sensor peripheral and Touch Element library by calling [touch_element_install](api.md#function-touch_element_install) with [touch_elem_global_config_t](api.md#struct-touch_elem_global_config_t), the default initializer is available in [TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG](api.md#define-touch_elem_global_default_config) and this default configuration is suitable for the most general application scene, and it is suggested not to change the default configuration before fully understanding Touch Sensor peripheral because some changes might bring several impacts to the system. 2. To initialize the specified element, all the elements will not work before its constructor [touch_button_install](api.md#function-touch_button_install), [touch_slider_install](api.md#function-touch_slider_install) or [touch_matrix_install](api.md#function-touch_matrix_install) is called so as to save memory, so you have to call the constructor of each used touch element respectively, to set up the specified element. ### Touch Element Instance Startup 1. To create a new touch element instance, call [touch_button_create](api.md#function-touch_button_create), [touch_slider_create](api.md#function-touch_slider_create) or [touch_matrix_create](api.md#function-touch_matrix_create), select a channel, and provide its `Sensitivity`_ value for the new element instance. 2. To subscribe to events, call [touch_button_subscribe_event](api.md#function-touch_button_subscribe_event), [touch_slider_subscribe_event](api.md#function-touch_slider_subscribe_event) or [touch_matrix_subscribe_event](api.md#function-touch_matrix_subscribe_event). The Touch Element library offers several events, and the event mask is available in [touch_element.h](https://github.com/espressif/idf-extra-components/tree/master/touch_element/include/touch_element/touch_element.h). You can use these event masks to subscribe to specific events individually or combine them to subscribe to multiple events. 3. To configure the dispatch method, use [touch_button_set_dispatch_method](api.md#function-touch_button_set_dispatch_method), [touch_slider_set_dispatch_method](api.md#function-touch_slider_set_dispatch_method) or [touch_matrix_set_dispatch_method](api.md#function-touch_matrix_set_dispatch_method). The Touch Element library provides two dispatch methods defined in [touch_elem_dispatch_t](api.md#enum-touch_elem_dispatch_t): `TOUCH_ELEM_DISP_EVENT` and `TOUCH_ELEM_DISP_CALLBACK`. These methods allow you to obtain the touch element message and handle it using different approaches. ### Events Processing If `TOUCH_ELEM_DISP_EVENT` dispatch method is configured, you need to start up an event handler task to obtain the touch element message, all the elements' raw message could be obtained by calling [touch_element_message_receive](api.md#function-touch_element_message_receive), then extract the element-class-specific message by calling the corresponding message decoder with [touch_button_get_message](api.md#function-touch_button_set_callback), [touch_slider_get_message](api.md#function-touch_slider_get_message) to get the touch element's extracted message; If `TOUCH_ELEM_DISP_CALLBACK` dispatch method is configured, you need to pass an event handler by calling [touch_slider_set_callback](api.md#function-touch_slider_set_callback) or [touch_matrix_get_message](api.md#function-touch_matrix_get_message) to get the touch element's extracted message; If `TOUCH_ELEM_DISP_CALLBACK` dispatch method is configured, you need to pass an event handler by calling [touch_matrix_set_callback](api.md#function-touch_matrix_set_callback) before the touch element starts working, all the element's extracted message will be passed to the event handler function. > WARNING: Since the event handler function runs on the core of the element library, i.e., in the esp_timer callback routine, please avoid performing operations that may cause blocking or delays, such as calling `vTaskDelay`. In code, the events handle procedure may look like as follows: ```c /* ---------------------------------------------- TOUCH_ELEM_DISP_EVENT ----------------------------------------------- */ void element_handler_task(void *arg) { touch_elem_message_t element_message; while(1) { if (touch_element_message_receive(&element_message, Timeout) == ESP_OK) { const touch_matrix_message_t *extracted_message = touch_matrix_get_message(&element_message); //Decode message ... //Event handler logic } } } void app_main() { ... touch_matrix_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_EVENT); //Set TOUCH_ELEM_DISP_EVENT as the dispatch method xTaskCreate(&element_handler_task, "element_handler_task", 2048, NULL, 5, NULL); //Create a handler task ... } /* -------------------------------------------------------------------------------------------------------------- */ ... /* ---------------------------------------------- TOUCH_ELEM_DISP_CALLBACK ----------------------------------------------- */ void element_handler(touch_matrix_handle_t out_handle, touch_matrix_message_t out_message, void *arg) { //Event handler logic } void app_main() { ... touch_matrix_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_CALLBACK); //Set TOUCH_ELEM_DISP_CALLBACK as the dispatch method touch_matrix_set_callback(element_handle, element_handler); //Register an event handler function ... } /* -------------------------------------------------------------------------------------------------------------- */ ``` ### Waterproof Usage 1. The waterproof shield sensor is always-on after Touch Element waterproof is initialized, however, the waterproof guard sensor is optional, hence if the you do not need the guard sensor, ``TOUCH_WATERPROOF_GUARD_NOUSE`` has to be passed to [touch_element_waterproof_install](api.md#function-touch_element_waterproof_install) by the configuration struct. 2. To associate the touch element with the guard sensor, pass the touch element's handle to the Touch Element waterproof's masked list by calling [touch_element_waterproof_add](api.md#function-touch_element_waterproof_add). By associating a touch element with the Guard sensor, the touch element will be disabled when the guard sensor is triggered by a stream of water so as to protect the touch element. The Touch Element Waterproof example is available under the `examples/touch_element_waterproof` directory. In code, the waterproof configuration may look as follows: ```c void app_main() { ... touch_button_install(); //Initialize instance (button, slider, etc) touch_button_create(&element_handle); //Create a new Touch element ... touch_element_waterproof_install(); //Initialize Touch Element waterproof touch_element_waterproof_add(element_handle); //Let an element associate with the guard sensor ... } ``` ### Wakeup from Light/Deep-sleep Mode Only Touch Button can be configured as a wake-up source. Light- or Deep-sleep modes are both supported to be wakened up by a touch sensor. For the Light-sleep mode, any installed touch button can wake it up. But only the sleep button can wake up from Deep-sleep mode, and the touch sensor will do a calibration immediately, the reference value will be calibrated to a wrong value if our finger does not remove timely. Though the wrong reference value recovers after the finger removes away and has no effect on the driver logic, if you do not want to see a wrong reference value while waking up from Deep-sleep mode, you can call [touch_element_sleep_enable_wakeup_calibration](api.md#function-touch_element_sleep_enable_wakeup_calibration) to disable the wakeup calibration. ```c void app_main() { ... touch_element_install(); touch_button_install(); //Initialize the touch button touch_button_create(&element_handle); //Create a new Touch element ... // ESP_ERROR_CHECK(touch_element_enable_light_sleep(&sleep_config)); ESP_ERROR_CHECK(touch_element_enable_deep_sleep(button_handle[0], &sleep_config)); // ESP_ERROR_CHECK(touch_element_sleep_enable_wakeup_calibration(button_handle[0], false)); // (optional) Disable wakeup calibration to prevent updating the benchmark to a wrong value touch_element_start(); ... } ``` ================================================ FILE: touch_element/examples/touch_button/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(touch_button) ================================================ FILE: touch_element/examples/touch_button/README.md ================================================ # Touch button example (See the README.md file in the upper level 'examples' directory for more information about examples.) This example demonstrates how to use the Touch Element library of capacitive touch sensor and set up touch button. ## How to use example ### Hardware Required * A development board with ESP32-S2 or ESP32-S3 chip * A touch extension board like [esp32-s2-touch-devkit-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32s2/esp32-s2-touch-devkit-1/user_guide.html) ### Configure the project * Set the target of the build by following command, where TARGET can be `esp32s2` or `esp32s3`. ``` idf.py set-target TARGET ``` * Run `idf.py menuconfig` to select a dispatch method for the example. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (331) Touch Button Example: Touch element library installed I (331) Touch Button Example: Touch button installed I (341) Touch Button Example: Touch buttons created I (341) Touch Button Example: Touch element library start I (1481) Touch Button Example: Button[1] Press I (1701) Touch Button Example: Button[1] Release I (2731) Touch Button Example: Button[2] Press I (2921) Touch Button Example: Button[2] Release I (3581) Touch Button Example: Button[5] Press I (3781) Touch Button Example: Button[5] Release I (3931) Touch Button Example: Button[4] Press I (4121) Touch Button Example: Button[4] Release I (4271) Touch Button Example: Button[3] Press I (4491) Touch Button Example: Button[3] Release I (4671) Touch Button Example: Button[6] Press I (4891) Touch Button Example: Button[6] Release I (5091) Touch Button Example: Button[7] Press I (5311) Touch Button Example: Button[7] Release I (5491) Touch Button Example: Button[8] Press I (5741) Touch Button Example: Button[8] Release I (5991) Touch Button Example: Button[9] Press I (7991) Touch Button Example: Button[9] LongPress I (9991) Touch Button Example: Button[9] LongPress I (11991) Touch Button Example: Button[9] LongPress I (12881) Touch Button Example: Button[9] Release ``` ## Troubleshooting For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. ================================================ FILE: touch_element/examples/touch_button/main/CMakeLists.txt ================================================ idf_component_register(SRCS "touch_button_example_main.c" INCLUDE_DIRS ".") ================================================ FILE: touch_element/examples/touch_button/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice TOUCH_SENSOR_EXAMPLE_TYPE bool "Select touch element dispatch method" default TOUCH_ELEM_EVENT help Select touch element dispatch method (event task or callback) for this example. config TOUCH_ELEM_EVENT bool "Dispatch by event task" config TOUCH_ELEM_CALLBACK bool "Dispatch by callback" endchoice endmenu ================================================ FILE: touch_element/examples/touch_button/main/idf_component.yml ================================================ dependencies: espressif/touch_element: version: '*' override_path: '../../../' ================================================ FILE: touch_element/examples/touch_button/main/touch_button_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "touch_element/touch_button.h" #include "esp_log.h" static const char *TAG = "Touch Button Example"; #define TOUCH_BUTTON_NUM 14 /* Touch buttons handle */ static touch_button_handle_t button_handle[TOUCH_BUTTON_NUM]; /* Touch buttons channel array */ static const touch_pad_t channel_array[TOUCH_BUTTON_NUM] = { TOUCH_PAD_NUM1, TOUCH_PAD_NUM2, TOUCH_PAD_NUM3, TOUCH_PAD_NUM4, TOUCH_PAD_NUM5, TOUCH_PAD_NUM6, TOUCH_PAD_NUM7, TOUCH_PAD_NUM8, TOUCH_PAD_NUM9, TOUCH_PAD_NUM10, TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, TOUCH_PAD_NUM13, TOUCH_PAD_NUM14, }; /* Touch buttons channel sensitivity array */ static const float channel_sens_array[TOUCH_BUTTON_NUM] = { 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, 0.1F, }; #ifdef CONFIG_TOUCH_ELEM_EVENT /* Button event handler task */ static void button_handler_task(void *arg) { (void) arg; //Unused touch_elem_message_t element_message; while (1) { /* Waiting for touch element messages */ touch_element_message_receive(&element_message, portMAX_DELAY); if (element_message.element_type != TOUCH_ELEM_TYPE_BUTTON) { continue; } /* Decode message */ const touch_button_message_t *button_message = touch_button_get_message(&element_message); if (button_message->event == TOUCH_BUTTON_EVT_ON_PRESS) { ESP_LOGI(TAG, "Button[%d] Press", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Button[%d] Release", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Button[%d] LongPress", (int)element_message.arg); } } } #elif CONFIG_TOUCH_ELEM_CALLBACK /* Button callback routine */ static void button_handler(touch_button_handle_t out_handle, touch_button_message_t *out_message, void *arg) { (void) out_handle; //Unused if (out_message->event == TOUCH_BUTTON_EVT_ON_PRESS) { ESP_LOGI(TAG, "Button[%d] Press", (int)arg); } else if (out_message->event == TOUCH_BUTTON_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Button[%d] Release", (int)arg); } else if (out_message->event == TOUCH_BUTTON_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Button[%d] LongPress", (int)arg); } } #endif void app_main(void) { /* Initialize Touch Element library */ touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_element_install(&global_config)); ESP_LOGI(TAG, "Touch element library installed"); touch_button_global_config_t button_global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_button_install(&button_global_config)); ESP_LOGI(TAG, "Touch button installed"); for (int i = 0; i < TOUCH_BUTTON_NUM; i++) { touch_button_config_t button_config = { .channel_num = channel_array[i], .channel_sens = channel_sens_array[i] }; /* Create Touch buttons */ ESP_ERROR_CHECK(touch_button_create(&button_config, &button_handle[i])); /* Subscribe touch button events (On Press, On Release, On LongPress) */ ESP_ERROR_CHECK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *)channel_array[i])); #ifdef CONFIG_TOUCH_ELEM_EVENT /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); #elif CONFIG_TOUCH_ELEM_CALLBACK /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_CALLBACK)); /* Register a handler function to handle event messages */ ESP_ERROR_CHECK(touch_button_set_callback(button_handle[i], button_handler)); #endif /* Set LongPress event trigger threshold time */ ESP_ERROR_CHECK(touch_button_set_longpress(button_handle[i], 2000)); } ESP_LOGI(TAG, "Touch buttons created"); #ifdef CONFIG_TOUCH_ELEM_EVENT /* Create a handler task to handle event messages */ xTaskCreate(&button_handler_task, "button_handler_task", 4 * 1024, NULL, 5, NULL); #endif touch_element_start(); ESP_LOGI(TAG, "Touch element library start"); } ================================================ FILE: touch_element/examples/touch_button/pytest_touch_button.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pathlib import Path import glob @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_button(dut: Dut) -> None: dut.expect_exact('Touch Button Example: Touch element library installed') dut.expect_exact('Touch Button Example: Touch button installed') dut.expect_exact('Touch Button Example: Touch buttons created') dut.expect_exact('Touch Button Example: Touch element library start') ================================================ FILE: touch_element/examples/touch_element_waterproof/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(touch_element_waterproof) ================================================ FILE: touch_element/examples/touch_element_waterproof/README.md ================================================ # Touch Element waterproof Example (See the README.md file in the upper level 'examples' directory for more information about examples.) This example demonstrates how to use the Touch Element library of capacitive Touch Sensor and setup the touch elements with touch element waterproof protection. ## How to use example ### Hardware Required * A development board with ESP32-S2 or ESP32-S3 chip * A touch extension board like [esp32-s2-touch-devkit-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32s2/esp32-s2-touch-devkit-1/user_guide.html) ### Configure the project * Set the target of the build by following command, where TARGET can be `esp32s2` or `esp32s3`. ``` idf.py set-target TARGET ``` * Run `idf.py menuconfig` to select weather to enable waterproof function. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output This example's output maybe could not give a strong feeling to user since the waterproof function works automatically and silently inside the Touch Element library ``` I (331) Touch Element Waterproof Example: Touch Element library install I (331) Touch Element Waterproof Example: Touch Element waterproof install I (341) Touch Element Waterproof Example: Touch button install I (351) Touch Element Waterproof Example: Touch buttons create I (3191) Touch Element Waterproof Example: Button[7] Press I (4191) Touch Element Waterproof Example: Button[7] LongPress I (5191) Touch Element Waterproof Example: Button[7] LongPress I (5671) Touch Element Waterproof Example: Button[7] Release I (12561) Touch Element Waterproof Example: Button[9] Press I (12811) Touch Element Waterproof Example: Button[9] Release ``` ## Troubleshooting For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. ================================================ FILE: touch_element/examples/touch_element_waterproof/main/CMakeLists.txt ================================================ idf_component_register(SRCS "waterproof_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES touch_element) ================================================ FILE: touch_element/examples/touch_element_waterproof/main/Kconfig.projbuild ================================================ menu "Example Configuration" config TOUCH_WATERPROOF_GUARD_ENABLE bool "Enable touch sense waterproof guard sensor" default y help This option enables touch sense waterproof guard sensor, while the shield sensor is not optional. endmenu ================================================ FILE: touch_element/examples/touch_element_waterproof/main/idf_component.yml ================================================ dependencies: espressif/touch_element: version: '*' override_path: '../../../' ================================================ FILE: touch_element/examples/touch_element_waterproof/main/waterproof_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "touch_element/touch_button.h" static const char *TAG = "Touch Element Waterproof Example"; #define TOUCH_BUTTON_NUM 3 /*< Touch buttons handle */ static touch_button_handle_t button_handle[TOUCH_BUTTON_NUM]; //Button handler /* Touch buttons channel array */ static const touch_pad_t channel_array[TOUCH_BUTTON_NUM] = { TOUCH_PAD_NUM7, TOUCH_PAD_NUM9, TOUCH_PAD_NUM11, }; /* Touch buttons channel sensitivity array */ static const float channel_sens_array[TOUCH_BUTTON_NUM] = { 0.15F, 0.15F, 0.15F, }; static void button_handler_task(void *arg) { touch_elem_message_t element_message; while (1) { touch_element_message_receive(&element_message, portMAX_DELAY); //Block take const touch_button_message_t *button_message = touch_button_get_message(&element_message); if (button_message->event == TOUCH_BUTTON_EVT_ON_PRESS) { ESP_LOGI(TAG, "Button[%d] Press", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Button[%d] Release", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Button[%d] LongPress", (int)element_message.arg); } } } void app_main(void) { /*< Initialize Touch Element library */ touch_elem_global_config_t element_global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_element_install(&element_global_config)); ESP_LOGI(TAG, "Touch Element library install"); /*< Create and configure touch element waterproof */ touch_elem_waterproof_config_t waterproof_config = { #ifdef CONFIG_TOUCH_WATERPROOF_GUARD_ENABLE .guard_channel = TOUCH_PAD_NUM13, #else .guard_channel = TOUCH_WATERPROOF_GUARD_NOUSE, #endif .guard_sensitivity = 0.05F //The guard sensor sensitivity has to be explored in experiments }; ESP_ERROR_CHECK(touch_element_waterproof_install(&waterproof_config)); ESP_LOGI(TAG, "Touch Element waterproof install"); touch_button_global_config_t button_global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_button_install(&button_global_config)); ESP_LOGI(TAG, "Touch button install"); for (int i = 0; i < TOUCH_BUTTON_NUM; i++) { touch_button_config_t button_config = { .channel_num = channel_array[i], .channel_sens = channel_sens_array[i] }; /* Create touch button */ ESP_ERROR_CHECK(touch_button_create(&button_config, &button_handle[i])); /* Subscribe touch button event(Press, Release, LongPress) */ ESP_ERROR_CHECK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *)channel_array[i])); /* Button set dispatch method */ ESP_ERROR_CHECK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); #ifdef CONFIG_TOUCH_WATERPROOF_GUARD_ENABLE /* Add button element into waterproof guard sensor's protection */ ESP_ERROR_CHECK(touch_element_waterproof_add(button_handle[i])); #endif } ESP_LOGI(TAG, "Touch buttons create"); /*< Create a monitor task to take Touch Button event */ xTaskCreate(&button_handler_task, "button_handler_task", 4 * 1024, NULL, 5, NULL); touch_element_start(); } ================================================ FILE: touch_element/examples/touch_element_waterproof/pytest_touch_element_waterproof.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut from pathlib import Path import glob @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_element_waterproof(dut: Dut) -> None: dut.expect_exact('Touch Element Waterproof Example: Touch Element library install') dut.expect_exact('Touch Element Waterproof Example: Touch Element waterproof install') dut.expect_exact('Touch Element Waterproof Example: Touch button install') dut.expect_exact('Touch Element Waterproof Example: Touch buttons create') ================================================ FILE: touch_element/examples/touch_elements_combination/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(touch_elements_combination) ================================================ FILE: touch_element/examples/touch_elements_combination/README.md ================================================ # Touch button example (See the README.md file in the upper level 'examples' directory for more information about examples.) This example demonstrates how to use the Touch Element library of capacitive touch sensor and set up more than one type of touch elements and handle all the event messages in one task. ## How to use example ### Hardware Required * A development board with ESP32-S2 or ESP32-S3 chip * A touch extension board like [esp32-s2-touch-devkit-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32s2/esp32-s2-touch-devkit-1/user_guide.html) ### Configure the project * Set the target of the build by following command, where TARGET can be `esp32s2` or `esp32s3`. ``` idf.py set-target TARGET ``` ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (331) Touch Elements Combination Example: Touch element library installed I (331) Touch Elements Combination Example: Touch button installed I (341) Touch Elements Combination Example: Touch buttons created I (351) Touch Elements Combination Example: Touch slider installed I (351) Touch Elements Combination Example: Touch slider created I (361) Touch Elements Combination Example: Touch element library start I (1841) Touch Elements Combination Example: Button[6] Press I (1971) Touch Elements Combination Example: Button[6] Release I (2201) Touch Elements Combination Example: Button[8] Press I (2351) Touch Elements Combination Example: Button[8] Release I (2561) Touch Elements Combination Example: Button[10] Press I (2721) Touch Elements Combination Example: Button[10] Release I (3431) Touch Elements Combination Example: Slider Press, position: 0 I (3441) Touch Elements Combination Example: Slider Calculate, position: 0 I (3451) Touch Elements Combination Example: Slider Calculate, position: 0 I (3461) Touch Elements Combination Example: Slider Calculate, position: 0 I (3471) Touch Elements Combination Example: Slider Calculate, position: 0 I (3481) Touch Elements Combination Example: Slider Calculate, position: 0 I (3491) Touch Elements Combination Example: Slider Calculate, position: 0 I (3501) Touch Elements Combination Example: Slider Calculate, position: 1 I (3511) Touch Elements Combination Example: Slider Calculate, position: 1 I (3521) Touch Elements Combination Example: Slider Calculate, position: 2 I (3531) Touch Elements Combination Example: Slider Calculate, position: 2 I (3541) Touch Elements Combination Example: Slider Calculate, position: 3 I (3551) Touch Elements Combination Example: Slider Calculate, position: 4 I (3561) Touch Elements Combination Example: Slider Calculate, position: 5 I (3571) Touch Elements Combination Example: Slider Calculate, position: 6 I (3581) Touch Elements Combination Example: Slider Calculate, position: 7 I (3591) Touch Elements Combination Example: Slider Calculate, position: 8 I (3601) Touch Elements Combination Example: Slider Calculate, position: 10 I (3611) Touch Elements Combination Example: Slider Calculate, position: 11 I (3621) Touch Elements Combination Example: Slider Calculate, position: 12 I (3631) Touch Elements Combination Example: Slider Calculate, position: 13 I (3641) Touch Elements Combination Example: Slider Calculate, position: 15 I (3651) Touch Elements Combination Example: Slider Calculate, position: 16 I (3661) Touch Elements Combination Example: Slider Calculate, position: 17 I (3671) Touch Elements Combination Example: Slider Calculate, position: 19 I (3681) Touch Elements Combination Example: Slider Calculate, position: 20 I (3691) Touch Elements Combination Example: Slider Calculate, position: 21 I (3701) Touch Elements Combination Example: Slider Calculate, position: 23 I (3711) Touch Elements Combination Example: Slider Calculate, position: 24 I (3721) Touch Elements Combination Example: Slider Calculate, position: 26 I (3731) Touch Elements Combination Example: Slider Calculate, position: 27 I (3741) Touch Elements Combination Example: Slider Calculate, position: 28 I (3751) Touch Elements Combination Example: Slider Calculate, position: 29 I (3761) Touch Elements Combination Example: Slider Calculate, position: 31 I (3771) Touch Elements Combination Example: Slider Calculate, position: 32 I (3781) Touch Elements Combination Example: Slider Calculate, position: 33 I (3791) Touch Elements Combination Example: Slider Calculate, position: 34 I (3801) Touch Elements Combination Example: Slider Calculate, position: 36 I (3811) Touch Elements Combination Example: Slider Calculate, position: 37 I (3821) Touch Elements Combination Example: Slider Calculate, position: 38 I (3831) Touch Elements Combination Example: Slider Calculate, position: 39 I (3841) Touch Elements Combination Example: Slider Calculate, position: 41 I (3851) Touch Elements Combination Example: Slider Calculate, position: 42 I (3861) Touch Elements Combination Example: Slider Calculate, position: 43 I (3871) Touch Elements Combination Example: Slider Calculate, position: 45 I (3881) Touch Elements Combination Example: Slider Calculate, position: 47 I (3891) Touch Elements Combination Example: Slider Calculate, position: 48 I (3901) Touch Elements Combination Example: Slider Calculate, position: 50 I (3911) Touch Elements Combination Example: Slider Calculate, position: 52 I (3921) Touch Elements Combination Example: Slider Calculate, position: 53 I (3931) Touch Elements Combination Example: Slider Calculate, position: 55 I (3941) Touch Elements Combination Example: Slider Calculate, position: 57 I (3951) Touch Elements Combination Example: Slider Calculate, position: 58 I (3961) Touch Elements Combination Example: Slider Calculate, position: 60 I (3971) Touch Elements Combination Example: Slider Calculate, position: 61 I (3981) Touch Elements Combination Example: Slider Calculate, position: 62 I (3991) Touch Elements Combination Example: Slider Calculate, position: 64 I (4001) Touch Elements Combination Example: Slider Calculate, position: 65 I (4011) Touch Elements Combination Example: Slider Calculate, position: 66 I (4021) Touch Elements Combination Example: Slider Calculate, position: 68 I (4031) Touch Elements Combination Example: Slider Calculate, position: 69 I (4041) Touch Elements Combination Example: Slider Calculate, position: 70 I (4051) Touch Elements Combination Example: Slider Calculate, position: 72 I (4061) Touch Elements Combination Example: Slider Calculate, position: 73 I (4071) Touch Elements Combination Example: Slider Calculate, position: 75 I (4081) Touch Elements Combination Example: Slider Calculate, position: 76 I (4091) Touch Elements Combination Example: Slider Calculate, position: 77 I (4101) Touch Elements Combination Example: Slider Calculate, position: 79 I (4111) Touch Elements Combination Example: Slider Calculate, position: 80 I (4121) Touch Elements Combination Example: Slider Calculate, position: 81 I (4131) Touch Elements Combination Example: Slider Calculate, position: 83 I (4141) Touch Elements Combination Example: Slider Calculate, position: 84 I (4151) Touch Elements Combination Example: Slider Calculate, position: 85 I (4161) Touch Elements Combination Example: Slider Calculate, position: 86 I (4171) Touch Elements Combination Example: Slider Calculate, position: 88 I (4181) Touch Elements Combination Example: Slider Calculate, position: 89 I (4191) Touch Elements Combination Example: Slider Calculate, position: 90 I (4201) Touch Elements Combination Example: Slider Calculate, position: 91 I (4211) Touch Elements Combination Example: Slider Calculate, position: 92 I (4221) Touch Elements Combination Example: Slider Calculate, position: 93 I (4231) Touch Elements Combination Example: Slider Calculate, position: 94 I (4241) Touch Elements Combination Example: Slider Calculate, position: 95 I (4251) Touch Elements Combination Example: Slider Calculate, position: 96 I (4261) Touch Elements Combination Example: Slider Calculate, position: 96 I (4271) Touch Elements Combination Example: Slider Calculate, position: 97 I (4281) Touch Elements Combination Example: Slider Calculate, position: 98 I (4291) Touch Elements Combination Example: Slider Calculate, position: 99 I (4301) Touch Elements Combination Example: Slider Calculate, position: 99 I (4311) Touch Elements Combination Example: Slider Calculate, position: 100 I (4321) Touch Elements Combination Example: Slider Calculate, position: 100 I (4331) Touch Elements Combination Example: Slider Calculate, position: 100 I (4341) Touch Elements Combination Example: Slider Calculate, position: 101 I (4351) Touch Elements Combination Example: Slider Release, position: 101 ``` ## Troubleshooting For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. ================================================ FILE: touch_element/examples/touch_elements_combination/main/CMakeLists.txt ================================================ idf_component_register(SRCS "touch_elements_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES touch_element) ================================================ FILE: touch_element/examples/touch_elements_combination/main/idf_component.yml ================================================ dependencies: espressif/touch_element: version: '*' override_path: '../../../' ================================================ FILE: touch_element/examples/touch_elements_combination/main/touch_elements_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "touch_element/touch_button.h" #include "touch_element/touch_slider.h" #include "esp_log.h" static const char *TAG = "Touch Elements Combination Example"; #define TOUCH_BUTTON_NUM 3 #define TOUCH_SLIDER_CHANNEL_NUM 5 static touch_button_handle_t button_handle[TOUCH_BUTTON_NUM]; //Touch buttons handle static touch_slider_handle_t slider_handle; //Touch slider handle /* Touch buttons channel array */ static const touch_pad_t button_channel_array[TOUCH_BUTTON_NUM] = { TOUCH_PAD_NUM6, TOUCH_PAD_NUM8, TOUCH_PAD_NUM10 }; /* Touch buttons channel sensitivity array */ static const float button_channel_sens_array[TOUCH_BUTTON_NUM] = { 0.1F, 0.1F, 0.1F }; /* Touch slider channels array */ static const touch_pad_t slider_channel_array[TOUCH_SLIDER_CHANNEL_NUM] = { TOUCH_PAD_NUM5, TOUCH_PAD_NUM7, TOUCH_PAD_NUM9, TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, }; /* Touch slider channels sensitivity array */ static const float slider_channel_sens_array[TOUCH_SLIDER_CHANNEL_NUM] = { 0.252F, 0.246F, 0.277F, 0.250F, 0.257F, }; static void button_handler(touch_elem_message_t element_message) { const touch_button_message_t *button_message = touch_button_get_message(&element_message); if (button_message->event == TOUCH_BUTTON_EVT_ON_PRESS) { ESP_LOGI(TAG, "Button[%d] Press", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Button[%d] Release", (int)element_message.arg); } else if (button_message->event == TOUCH_BUTTON_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Button[%d] LongPress", (int)element_message.arg); } } static void slider_handler(touch_elem_message_t element_message) { const touch_slider_message_t *slider_message = touch_slider_get_message(&element_message); if (slider_message->event == TOUCH_SLIDER_EVT_ON_PRESS) { ESP_LOGI(TAG, "Slider Press, position: %"PRIu32, slider_message->position); } else if (slider_message->event == TOUCH_SLIDER_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Slider Release, position: %"PRIu32, slider_message->position); } else if (slider_message->event == TOUCH_SLIDER_EVT_ON_CALCULATION) { ESP_LOGI(TAG, "Slider Calculate, position: %"PRIu32, slider_message->position); } } static void event_handler_task(void *arg) { (void) arg; //Unused touch_elem_message_t element_message; while (1) { /* Waiting for touch element messages */ touch_element_message_receive(&element_message, portMAX_DELAY); switch (element_message.element_type) { case TOUCH_ELEM_TYPE_BUTTON: button_handler(element_message); break; case TOUCH_ELEM_TYPE_SLIDER: slider_handler(element_message); break; default: ESP_LOGW(TAG, "Unknown element message"); break; } } } void button_example_init(void) { touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_button_install(&global_config)); ESP_LOGI(TAG, "Touch button installed"); for (int i = 0; i < TOUCH_BUTTON_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = button_channel_sens_array[i] }; /* Create Touch buttons */ ESP_ERROR_CHECK(touch_button_create(&button_config, &button_handle[i])); /* Subscribe touch button events (On Press, On Release, On LongPress) */ ESP_ERROR_CHECK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *)button_channel_array[i])); /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); /* Set LongPress event trigger threshold time */ ESP_ERROR_CHECK(touch_button_set_longpress(button_handle[i], 2000)); } ESP_LOGI(TAG, "Touch buttons created"); } void slider_example_init(void) { touch_slider_global_config_t global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_slider_install(&global_config)); ESP_LOGI(TAG, "Touch slider installed"); /* Create Touch slider */ touch_slider_config_t slider_config = { .channel_array = slider_channel_array, .sensitivity_array = slider_channel_sens_array, .channel_num = (sizeof(slider_channel_array) / sizeof(slider_channel_array[0])), .position_range = 101 }; ESP_ERROR_CHECK(touch_slider_create(&slider_config, &slider_handle)); /* Subscribe touch slider events (On Press, On Release, On Calculation) */ ESP_ERROR_CHECK(touch_slider_subscribe_event(slider_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_CALCULATION, NULL)); /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_EVENT)); ESP_LOGI(TAG, "Touch slider created"); } void app_main(void) { /* Initialize Touch Element library */ touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_element_install(&global_config)); ESP_LOGI(TAG, "Touch element library installed"); button_example_init(); slider_example_init(); touch_element_start(); ESP_LOGI(TAG, "Touch element library start"); /* Create a handler task to handle event messages */ xTaskCreate(&event_handler_task, "event_handler_task", 4 * 1024, NULL, 5, NULL); } ================================================ FILE: touch_element/examples/touch_elements_combination/pytest_touch_elements_combination.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_elements_combination(dut: Dut) -> None: dut.expect_exact('Touch Elements Combination Example: Touch element library installed') dut.expect_exact('Touch Elements Combination Example: Touch button installed') dut.expect_exact('Touch Elements Combination Example: Touch buttons created') dut.expect_exact('Touch Elements Combination Example: Touch slider installed') dut.expect_exact('Touch Elements Combination Example: Touch slider created') dut.expect_exact('Touch Elements Combination Example: Touch element library start') ================================================ FILE: touch_element/examples/touch_matrix/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(touch_matrix) ================================================ FILE: touch_element/examples/touch_matrix/README.md ================================================ # Touch Element matrix example (See the README.md file in the upper level 'examples' directory for more information about examples.) This example demonstrates how to use the Touch Element library of capacitive touch sensor and set up touch matrix. ## How to use example ### Hardware Required * A development board with ESP32-S2 or ESP32-S3 chip * A touch extension board like [esp32-s2-touch-devkit-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32s2/esp32-s2-touch-devkit-1/user_guide.html) ### Configure the project * Set the target of the build by following command, where TARGET can be `esp32s2` or `esp32s3`. ``` idf.py set-target TARGET ``` * Run `idf.py menuconfig` to select a dispatch method for the example. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (331) Touch Matrix Example: Touch element library installed I (331) Touch Matrix Example: Touch matrix installed I (341) Touch Matrix Example: Touch matrix created I (341) Touch Matrix Example: Touch element library start I (1951) Touch Matrix Example: Matrix Press, axis: (0, 0) index: 0 I (2131) Touch Matrix Example: Matrix Release, axis: (0, 0) index: 0 I (3121) Touch Matrix Example: Matrix Press, axis: (1, 1) index: 4 I (3281) Touch Matrix Example: Matrix Release, axis: (1, 1) index: 4 I (4621) Touch Matrix Example: Matrix Press, axis: (2, 0) index: 6 I (4801) Touch Matrix Example: Matrix Release, axis: (2, 0) index: 6 I (5381) Touch Matrix Example: Matrix Press, axis: (2, 2) index: 8 I (5571) Touch Matrix Example: Matrix Release, axis: (2, 2) index: 8 I (6221) Touch Matrix Example: Matrix Press, axis: (0, 2) index: 2 I (6441) Touch Matrix Example: Matrix Release, axis: (0, 2) index: 2 I (7551) Touch Matrix Example: Matrix Press, axis: (1, 1) index: 4 I (8551) Touch Matrix Example: Matrix LongPress, axis: (1, 1) index: 4 I (9551) Touch Matrix Example: Matrix LongPress, axis: (1, 1) index: 4 I (10551) Touch Matrix Example: Matrix LongPress, axis: (1, 1) index: 4 I (11031) Touch Matrix Example: Matrix Release, axis: (1, 1) index: 4 ``` ## Troubleshooting For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. ================================================ FILE: touch_element/examples/touch_matrix/main/CMakeLists.txt ================================================ idf_component_register(SRCS "touch_matrix_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES touch_element) ================================================ FILE: touch_element/examples/touch_matrix/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice TOUCH_SENSOR_EXAMPLE_TYPE bool "Select touch element dispatch method" default TOUCH_ELEM_EVENT help Select touch element dispatch method (event task or callback) for this example. config TOUCH_ELEM_EVENT bool "Dispatch by event task" config TOUCH_ELEM_CALLBACK bool "Dispatch by callback" endchoice endmenu ================================================ FILE: touch_element/examples/touch_matrix/main/idf_component.yml ================================================ dependencies: espressif/touch_element: version: '*' override_path: '../../../' ================================================ FILE: touch_element/examples/touch_matrix/main/touch_matrix_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "touch_element/touch_matrix.h" #include "esp_log.h" static const char *TAG = "Touch Matrix Example"; #define X_AXIS_CHANNEL_NUM 3 #define Y_AXIS_CHANNEL_NUM 3 static touch_matrix_handle_t matrix_handle; /* Touch Matrix Button x-axis channels array */ static const touch_pad_t x_axis_channel[X_AXIS_CHANNEL_NUM] = { TOUCH_PAD_NUM5, TOUCH_PAD_NUM7, TOUCH_PAD_NUM9, }; /* Touch Matrix Button y-axis channels array */ static const touch_pad_t y_axis_channel[Y_AXIS_CHANNEL_NUM] = { TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, TOUCH_PAD_NUM14, }; /* Touch Matrix Button x-axis channels sensitivity array */ static const float x_axis_channel_sens[X_AXIS_CHANNEL_NUM] = { 0.1F, 0.1F, 0.1F, }; /* Touch Matrix Button y-axis channel sensitivity array */ static const float y_axis_channel_sens[Y_AXIS_CHANNEL_NUM] = { 0.1F, 0.1F, 0.1F, }; #ifdef CONFIG_TOUCH_ELEM_EVENT /* Matrix event handler task */ static void matrix_handler_task(void *arg) { (void) arg; //Unused touch_elem_message_t element_message; while (1) { /* Waiting for touch element messages */ touch_element_message_receive(&element_message, portMAX_DELAY); //Block take if (element_message.element_type != TOUCH_ELEM_TYPE_MATRIX) { continue; } /* Decode message */ const touch_matrix_message_t *matrix_message = touch_matrix_get_message(&element_message); if (matrix_message->event == TOUCH_MATRIX_EVT_ON_PRESS) { ESP_LOGI(TAG, "Matrix Press, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, matrix_message->position.x_axis, matrix_message->position.y_axis, matrix_message->position.index); } else if (matrix_message->event == TOUCH_MATRIX_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Matrix Release, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, matrix_message->position.x_axis, matrix_message->position.y_axis, matrix_message->position.index); } else if (matrix_message->event == TOUCH_MATRIX_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Matrix LongPress, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, matrix_message->position.x_axis, matrix_message->position.y_axis, matrix_message->position.index); } } } #elif CONFIG_TOUCH_ELEM_CALLBACK /* Matrix callback routine */ void matrix_handler(touch_matrix_handle_t out_handle, touch_matrix_message_t *out_message, void *arg) { (void) arg; //Unused if (out_handle != matrix_handle) { return; } if (out_message->event == TOUCH_MATRIX_EVT_ON_PRESS) { ESP_LOGI(TAG, "Matrix Press, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, out_message->position.x_axis, out_message->position.y_axis, out_message->position.index); } else if (out_message->event == TOUCH_MATRIX_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Matrix Release, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, out_message->position.x_axis, out_message->position.y_axis, out_message->position.index); } else if (out_message->event == TOUCH_MATRIX_EVT_ON_LONGPRESS) { ESP_LOGI(TAG, "Matrix LongPress, axis: (%"PRIu8", %"PRIu8") index: %"PRIu8, out_message->position.x_axis, out_message->position.y_axis, out_message->position.index); } } #endif void app_main(void) { /* Initialize Touch Element library */ touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_element_install(&global_config)); ESP_LOGI(TAG, "Touch element library installed"); touch_matrix_global_config_t matrix_global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_matrix_install(&matrix_global_config)); ESP_LOGI(TAG, "Touch matrix installed"); /* Create Touch Matrix Button */ touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = (sizeof(x_axis_channel) / sizeof(x_axis_channel[0])), .y_channel_num = (sizeof(y_axis_channel) / sizeof(y_axis_channel[0])) }; ESP_ERROR_CHECK(touch_matrix_create(&matrix_config, &matrix_handle)); /* Subscribe touch matrix events (On Press, On Release, On LongPress) */ ESP_ERROR_CHECK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, NULL)); #ifdef CONFIG_TOUCH_ELEM_EVENT /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_EVENT)); /* Create a handler task to handle event messages */ xTaskCreate(&matrix_handler_task, "matrix_handler_task", 4 * 1024, NULL, 5, NULL); #elif CONFIG_TOUCH_ELEM_CALLBACK /* Set CALLBACK as the dispatch method */ ESP_ERROR_CHECK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_CALLBACK)); /* Register a handler function to handle event messages */ ESP_ERROR_CHECK(touch_matrix_set_callback(matrix_handle, matrix_handler)); #endif ESP_LOGI(TAG, "Touch matrix created"); touch_element_start(); ESP_LOGI(TAG, "Touch element library start"); } ================================================ FILE: touch_element/examples/touch_matrix/pytest_touch_matrix.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_matrix(dut: Dut) -> None: dut.expect_exact('Touch Matrix Example: Touch element library installed') dut.expect_exact('Touch Matrix Example: Touch matrix installed') dut.expect_exact('Touch Matrix Example: Touch matrix created') dut.expect_exact('Touch Matrix Example: Touch element library start') ================================================ FILE: touch_element/examples/touch_slider/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(touch_slider) ================================================ FILE: touch_element/examples/touch_slider/README.md ================================================ # Touch Element slider example (See the README.md file in the upper level 'examples' directory for more information about examples.) This example demonstrates how to use the Touch Element library of capacitive touch sensor and set up touch slider. ## How to use example ### Hardware Required * A development board with ESP32-S2 or ESP32-S3 chip * A touch extension board like [esp32-s2-touch-devkit-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32s2/esp32-s2-touch-devkit-1/user_guide.html) ### Configure the project * Set the target of the build by following command, where TARGET can be `esp32s2` or `esp32s3`. ``` idf.py set-target TARGET ``` * Run `idf.py menuconfig` to select a dispatch method for the example. ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: ``` idf.py -p PORT flash monitor ``` (Replace PORT with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output ``` I (331) Touch Slider Example: Touch element library installed I (331) Touch Slider Example: Touch slider installed I (341) Touch Slider Example: Touch slider created I (341) Touch Slider Example: Touch element library start I (1911) Touch Slider Example: Slider Press, position: 0 I (1921) Touch Slider Example: Slider Calculate, position: 0 I (1931) Touch Slider Example: Slider Calculate, position: 0 I (1941) Touch Slider Example: Slider Calculate, position: 0 I (1951) Touch Slider Example: Slider Calculate, position: 0 I (1961) Touch Slider Example: Slider Calculate, position: 0 I (1971) Touch Slider Example: Slider Calculate, position: 0 I (1981) Touch Slider Example: Slider Calculate, position: 0 I (1991) Touch Slider Example: Slider Calculate, position: 0 I (2001) Touch Slider Example: Slider Calculate, position: 0 I (2011) Touch Slider Example: Slider Calculate, position: 0 I (2021) Touch Slider Example: Slider Calculate, position: 1 I (2031) Touch Slider Example: Slider Calculate, position: 1 I (2041) Touch Slider Example: Slider Calculate, position: 2 I (2051) Touch Slider Example: Slider Calculate, position: 2 I (2061) Touch Slider Example: Slider Calculate, position: 4 I (2071) Touch Slider Example: Slider Calculate, position: 5 I (2081) Touch Slider Example: Slider Calculate, position: 6 I (2091) Touch Slider Example: Slider Calculate, position: 8 I (2101) Touch Slider Example: Slider Calculate, position: 10 I (2111) Touch Slider Example: Slider Calculate, position: 12 I (2121) Touch Slider Example: Slider Calculate, position: 15 I (2131) Touch Slider Example: Slider Calculate, position: 17 I (2141) Touch Slider Example: Slider Calculate, position: 19 I (2151) Touch Slider Example: Slider Calculate, position: 22 I (2161) Touch Slider Example: Slider Calculate, position: 24 I (2171) Touch Slider Example: Slider Calculate, position: 26 I (2181) Touch Slider Example: Slider Calculate, position: 29 I (2191) Touch Slider Example: Slider Calculate, position: 31 I (2201) Touch Slider Example: Slider Calculate, position: 33 I (2211) Touch Slider Example: Slider Calculate, position: 35 I (2221) Touch Slider Example: Slider Calculate, position: 37 I (2231) Touch Slider Example: Slider Calculate, position: 40 I (2241) Touch Slider Example: Slider Calculate, position: 42 I (2251) Touch Slider Example: Slider Calculate, position: 44 I (2261) Touch Slider Example: Slider Calculate, position: 46 I (2271) Touch Slider Example: Slider Calculate, position: 48 I (2281) Touch Slider Example: Slider Calculate, position: 50 I (2291) Touch Slider Example: Slider Calculate, position: 52 I (2301) Touch Slider Example: Slider Calculate, position: 54 I (2311) Touch Slider Example: Slider Calculate, position: 56 I (2321) Touch Slider Example: Slider Calculate, position: 57 I (2331) Touch Slider Example: Slider Calculate, position: 59 I (2341) Touch Slider Example: Slider Calculate, position: 60 I (2351) Touch Slider Example: Slider Calculate, position: 61 I (2361) Touch Slider Example: Slider Release, position: 61 ``` ## Troubleshooting For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. ================================================ FILE: touch_element/examples/touch_slider/main/CMakeLists.txt ================================================ idf_component_register(SRCS "touch_slider_example_main.c" INCLUDE_DIRS "." PRIV_REQUIRES touch_element) ================================================ FILE: touch_element/examples/touch_slider/main/Kconfig.projbuild ================================================ menu "Example Configuration" choice TOUCH_SENSOR_EXAMPLE_TYPE bool "Select touch element dispatch method" default TOUCH_ELEM_EVENT help Select touch element dispatch method (event task or callback) for this example. config TOUCH_ELEM_EVENT bool "Dispatch by event task" config TOUCH_ELEM_CALLBACK bool "Dispatch by callback" endchoice endmenu ================================================ FILE: touch_element/examples/touch_slider/main/idf_component.yml ================================================ dependencies: espressif/touch_element: version: '*' override_path: '../../../' ================================================ FILE: touch_element/examples/touch_slider/main/touch_slider_example_main.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "touch_element/touch_slider.h" #include "esp_log.h" static const char *TAG = "Touch Slider Example"; #define TOUCH_SLIDER_CHANNEL_NUM 5 static touch_slider_handle_t slider_handle; //Touch slider handle static const touch_pad_t channel_array[TOUCH_SLIDER_CHANNEL_NUM] = { //Touch slider channels array TOUCH_PAD_NUM5, TOUCH_PAD_NUM7, TOUCH_PAD_NUM9, TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, }; /** * Using finger slide from slider's beginning to the ending, and output the RAW channel signal, then calculate all the * channels sensitivity of the slider, and you can decrease or increase the detection sensitivity by adjusting the threshold divider * which locates in touch_slider_global_config_t. Please keep in mind that the real sensitivity totally depends on the * physical characteristics, if you want to decrease or increase the detection sensitivity, keep the ratio of those channels the same. */ static const float channel_sens_array[TOUCH_SLIDER_CHANNEL_NUM] = { //Touch slider channels sensitivity array 0.252F, 0.246F, 0.277F, 0.250F, 0.257F, }; #ifdef CONFIG_TOUCH_ELEM_EVENT /* Slider event handler task */ static void slider_handler_task(void *arg) { (void) arg; //Unused touch_elem_message_t element_message; while (1) { /* Waiting for touch element messages */ if (touch_element_message_receive(&element_message, portMAX_DELAY) == ESP_OK) { if (element_message.element_type != TOUCH_ELEM_TYPE_SLIDER) { continue; } /* Decode message */ const touch_slider_message_t *slider_message = touch_slider_get_message(&element_message); if (slider_message->event == TOUCH_SLIDER_EVT_ON_PRESS) { ESP_LOGI(TAG, "Slider Press, position: %"PRIu32, slider_message->position); } else if (slider_message->event == TOUCH_SLIDER_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Slider Release, position: %"PRIu32, slider_message->position); } else if (slider_message->event == TOUCH_SLIDER_EVT_ON_CALCULATION) { ESP_LOGI(TAG, "Slider Calculate, position: %"PRIu32, slider_message->position); } } } } #elif CONFIG_TOUCH_ELEM_CALLBACK /* Slider callback routine */ void slider_handler(touch_slider_handle_t out_handle, touch_slider_message_t *out_message, void *arg) { (void) arg; //Unused if (out_handle != slider_handle) { return; } if (out_message->event == TOUCH_SLIDER_EVT_ON_PRESS) { ESP_LOGI(TAG, "Slider Press, position: %"PRIu32, out_message->position); } else if (out_message->event == TOUCH_SLIDER_EVT_ON_RELEASE) { ESP_LOGI(TAG, "Slider Release, position: %"PRIu32, out_message->position); } else if (out_message->event == TOUCH_SLIDER_EVT_ON_CALCULATION) { ESP_LOGI(TAG, "Slider Calculate, position: %"PRIu32, out_message->position); } } #endif void app_main(void) { /* Initialize Touch Element library */ touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_element_install(&global_config)); ESP_LOGI(TAG, "Touch element library installed"); touch_slider_global_config_t slider_global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); ESP_ERROR_CHECK(touch_slider_install(&slider_global_config)); ESP_LOGI(TAG, "Touch slider installed"); /* Create Touch slider */ touch_slider_config_t slider_config = { .channel_array = channel_array, .sensitivity_array = channel_sens_array, .channel_num = (sizeof(channel_array) / sizeof(channel_array[0])), .position_range = 101 }; ESP_ERROR_CHECK(touch_slider_create(&slider_config, &slider_handle)); /* Subscribe touch slider events (On Press, On Release, On Calculation) */ ESP_ERROR_CHECK(touch_slider_subscribe_event(slider_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_CALCULATION, NULL)); #ifdef CONFIG_TOUCH_ELEM_EVENT /* Set EVENT as the dispatch method */ ESP_ERROR_CHECK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_EVENT)); /* Create a handler task to handle event messages */ xTaskCreate(&slider_handler_task, "slider_handler_task", 4 * 1024, NULL, 5, NULL); #elif CONFIG_TOUCH_ELEM_CALLBACK /* Set CALLBACK as the dispatch method */ ESP_ERROR_CHECK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_CALLBACK)); /* Register a handler function to handle event messages */ ESP_ERROR_CHECK(touch_slider_set_callback(slider_handle, slider_handler)); #endif ESP_LOGI(TAG, "Touch slider created"); touch_element_start(); ESP_LOGI(TAG, "Touch element library start"); } ================================================ FILE: touch_element/examples/touch_slider/pytest_touch_slider.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_slider(dut: Dut) -> None: dut.expect_exact('Touch Slider Example: Touch element library installed') dut.expect_exact('Touch Slider Example: Touch slider installed') dut.expect_exact('Touch Slider Example: Touch slider created') dut.expect_exact('Touch Slider Example: Touch element library start') ================================================ FILE: touch_element/idf_component.yml ================================================ version: "1.1.2" description: Touch Element Library url: https://github.com/espressif/idf-extra-components/tree/master/touch_element repository: https://github.com/espressif/idf-extra-components.git documentation: https://espressif.github.io/idf-extra-components/latest/touch_element/index.html issues: https://github.com/espressif/idf-extra-components/issues dependencies: idf: ">=5.3" targets: - esp32s2 - esp32s3 ================================================ FILE: touch_element/include/esp_private/touch_element_private.h ================================================ /* * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "touch_element/touch_element.h" #include "touch_element/touch_button.h" #include "touch_element/touch_slider.h" #include "touch_element/touch_matrix.h" #include "esp_pm.h" #include "sdkconfig.h" #ifdef __cplusplus extern "C" { #endif #define TE_TAG "Touch Element" #define TE_DEBUG_TAG "Touch Element Debug" #define TE_UNUSED(arg) (void)arg #define TE_CHECK(cond, ret_val) ({ \ if (!(cond)) { \ ESP_LOGE(TE_TAG, "%s(%d)", __FUNCTION__, __LINE__); \ return (ret_val); \ } \ }) #define TE_CHECK_GOTO(cond, label) ({ \ if (!(cond)) { \ goto label; \ } \ }) #define TE_FREE_AND_NULL(ptr) ({ \ free(ptr); \ (ptr) = NULL; \ }) #define TE_DEFAULT_THRESHOLD_DIVIDER(obj) ((obj)->global_config->threshold_divider) #define TE_DEFAULT_LONGPRESS_TIME(obj) ((obj)->global_config->default_lp_time) typedef enum { TE_STATE_IDLE = 0, TE_STATE_PRESS, TE_STATE_RELEASE, } te_state_t; typedef te_state_t te_dev_state_t; typedef touch_elem_type_t te_dev_type_t; typedef struct { float sens; //!< Touch channel sensitivity touch_pad_t channel; //!< Touch channel number(index) te_dev_type_t type; //!< Touch channel type TODO: need to refactor as te_class_type_t te_dev_state_t state; //!< Touch channel current state bool is_use_last_threshold; } te_dev_t; typedef enum { TE_CLS_TYPE_BUTTON = 0, TE_CLS_TYPE_SLIDER, TE_CLS_TYPE_MATRIX, TE_CLS_TYPE_MAX //TODO: add waterproof class } te_class_type_t; typedef struct { touch_elem_handle_t handle; bool (*check_channel) (touch_pad_t); esp_err_t (*set_threshold) (void); void (*process_state) (void); void (*update_state) (touch_pad_t, te_state_t); } te_object_methods_t; /* -------------------------------------------- Waterproof basic type --------------------------------------------- */ struct te_waterproof_s { te_dev_t *guard_device; //Waterproof guard channel device touch_elem_handle_t *mask_handle; //Waterproof masked handle array touch_pad_t shield_channel; //Waterproof shield channel bool is_shield_level_set; //Waterproof shield level setting bit }; typedef struct te_waterproof_s *te_waterproof_handle_t; /* -------------------------------------------- Sleep basic type --------------------------------------------- */ struct te_sleep_s { touch_elem_handle_t wakeup_handle; #ifdef CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; #endif uint32_t *non_volatile_threshold; }; typedef struct te_sleep_s *te_sleep_handle_t; /* -------------------------------------------- Button basic type --------------------------------------------- */ typedef struct { touch_elem_dispatch_t dispatch_method; //Button dispatch method touch_button_callback_t callback; //Button callback routine uint32_t event_mask; //Button subscribed event mask void *arg; //User input argument } te_button_handle_config_t; typedef te_state_t te_button_state_t; //TODO: add Long Press state struct te_button_s { te_button_handle_config_t *config; //Button configuration te_dev_t *device; //Base device information te_button_state_t current_state; //Button current state te_button_state_t last_state; //Button last state touch_button_event_t event; //Button outside state(for application layer) uint32_t trigger_cnt; //Button long time trigger counter uint32_t trigger_thr; //Button long time trigger counter threshold }; typedef struct te_button_s *te_button_handle_t; /* -------------------------------------------- Slider basic type --------------------------------------------- */ typedef struct { touch_elem_dispatch_t dispatch_method; //Slider dispatch method touch_slider_callback_t callback; //Slider callback routine uint32_t event_mask; //Slider subscribed event mask void *arg; //User input argument } te_slider_handle_config_t; typedef te_state_t te_slider_state_t; struct te_slider_s { te_slider_handle_config_t *config; //Slider configuration te_dev_t **device; //Base device information set te_slider_state_t current_state; //Slider current state te_slider_state_t last_state; //Slider last state touch_slider_event_t event; //Slider outside state(for application layer) float position_scale; //Slider position scale(step size) float *quantify_signal_array; //Slider re-quantization array uint32_t *channel_bcm; //Channel benchmark array uint32_t channel_bcm_update_cnt; //Channel benchmark update counter uint32_t filter_reset_cnt; //Slider reset counter uint32_t last_position; //Slider last position touch_slider_position_t position; //Slider position uint8_t position_range; //Slider position range([0, position_range]) uint8_t channel_sum; //Slider channel sum uint8_t *pos_filter_window; //Slider position moving average filter window uint8_t pos_window_idx; //Slider position moving average filter window index bool is_first_sample; //Slider first time sample record bit }; typedef struct te_slider_s *te_slider_handle_t; /* -------------------------------------------- Matrix basic type --------------------------------------------- */ typedef struct { touch_elem_dispatch_t dispatch_method; //Matrix button dispatch method touch_matrix_callback_t callback; //Matrix button callback routine uint32_t event_mask; //Matrix button subscribed event mask void *arg; //User input argument } te_matrix_handle_config_t; typedef te_state_t te_matrix_state_t; //TODO: add Long Press state struct te_matrix_s { te_matrix_handle_config_t *config; //Matrix button configuration te_dev_t **device; //Base device information te_matrix_state_t current_state; //Matrix button current state te_matrix_state_t last_state; //Matrix button current state touch_matrix_event_t event; //Matrix button outside state(for application layer) uint32_t trigger_cnt; //Matrix button long time trigger counter uint32_t trigger_thr; //Matrix button long time trigger counter threshold touch_matrix_position_t position; //Matrix button position uint8_t x_channel_num; //The number of touch sensor channel in x axis uint8_t y_channel_num; //The number of touch sensor channel in y axis }; typedef struct te_matrix_s *te_matrix_handle_t; /* ------------------------------------------------------------------------------------------------------------------ */ /* --------------------------------------------- Global system methods ---------------------------------------------- */ uint32_t te_read_smooth_signal(touch_pad_t channel_num); bool te_system_check_state(void); //TODO: Refactor this function with function overload esp_err_t te_dev_init(te_dev_t **device, uint8_t device_num, te_dev_type_t type, const touch_pad_t *channel, const float *sens, float divider); void te_dev_deinit(te_dev_t **device, uint8_t device_num); esp_err_t te_dev_set_threshold(te_dev_t *device); esp_err_t te_event_give(touch_elem_message_t te_message); uint8_t te_get_timer_period(void); void te_object_method_register(te_object_methods_t *object_methods, te_class_type_t object_type); void te_object_method_unregister(te_class_type_t object_type); bool te_object_check_channel(const touch_pad_t *channel_array, uint8_t channel_sum); bool waterproof_check_mask_handle(touch_elem_handle_t te_handle); bool te_is_touch_dsleep_wakeup(void); touch_pad_t te_get_sleep_channel(void); bool is_button_object_handle(touch_elem_handle_t element_handle); bool is_slider_object_handle(touch_elem_handle_t element_handle); bool is_matrix_object_handle(touch_elem_handle_t element_handle); void button_enable_wakeup_calibration(te_button_handle_t button_handle, bool en); void slider_enable_wakeup_calibration(te_slider_handle_t slider_handle, bool en); void matrix_enable_wakeup_calibration(te_matrix_handle_t matrix_handle, bool en); /* ------------------------------------------------------------------------------------------------------------------ */ #ifdef __cplusplus } #endif ================================================ FILE: touch_element/include/esp_private/touch_sensor_legacy_hal.h ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "esp_private/touch_sensor_legacy_ll.h" #include "touch_element/touch_sensor_legacy_types.h" #ifdef __cplusplus extern "C" { #endif typedef struct { touch_high_volt_t refh; touch_low_volt_t refl; touch_volt_atten_t atten; } touch_hal_volt_t; typedef struct { touch_cnt_slope_t slope; /*! #include #include "esp_bit_defs.h" #include "hal/misc.h" #include "hal/assert.h" #include "soc/rtc_cntl_struct.h" #include "soc/rtc_cntl_reg.h" #include "soc/rtc_io_struct.h" #include "soc/sens_struct.h" #include "touch_element/touch_sensor_legacy_types.h" #include "esp_idf_version.h" #ifdef __cplusplus extern "C" { #endif #define TOUCH_LL_CHAN_NUM 15 #define TOUCH_LL_PROXIMITY_CHANNEL_NUM 3 #define TOUCH_LL_SHIELD_CHANNEL 14 #define TOUCH_LL_DENOISE_CHANNEL 0 #define TOUCH_LL_INTR_MASK_DONE 0x01/*!= ESP_IDF_VERSION_VAL(5, 5, 0)) RTCIO.touch_pad[touch_num].slope = slope; #else RTCIO.touch_pad[touch_num].dac = slope; #endif #endif } /** * Get touch sensor charge/discharge speed(currents) for each pad. * If the slope is 0, the counter would always be zero. * If the slope is 1, the charging and discharging would be slow. The measurement time becomes longer. * If the slope is set 7, which is the maximum value, the charging and discharging would be fast. * The measurement time becomes shorter. * * @param touch_num Touch pad index. * @param slope touch pad charge/discharge speed(currents). */ static inline void touch_ll_get_slope(touch_pad_t touch_num, touch_cnt_slope_t *slope) { #ifdef RTC_CNTL_TOUCH_DAC_REG if (touch_num < TOUCH_PAD_NUM10) { *slope = (touch_cnt_slope_t)((RTCCNTL.touch_dac.val >> (29 - touch_num * 3)) & 0x07); } else { *slope = (touch_cnt_slope_t)((RTCCNTL.touch_dac1.val >> (29 - (touch_num - TOUCH_PAD_NUM10) * 3)) & 0x07); } #else #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)) *slope = (touch_cnt_slope_t)RTCIO.touch_pad[touch_num].slope; #else *slope = (touch_cnt_slope_t)RTCIO.touch_pad[touch_num].dac; #endif #endif } /** * Set initial voltage state of touch channel for each measurement. * * @param touch_num Touch pad index. * @param opt Initial voltage state. */ static inline void touch_ll_set_tie_option(touch_pad_t touch_num, touch_tie_opt_t opt) { if (opt == TOUCH_PAD_TIE_OPT_FLOAT) { RTCIO.touch_pad[touch_num].xpd = 0; } else { RTCIO.touch_pad[touch_num].xpd = 1; RTCIO.touch_pad[touch_num].tie_opt = opt; } } /** * Get initial voltage state of touch channel for each measurement. * * @param touch_num Touch pad index. * @param opt Initial voltage state. */ static inline void touch_ll_get_tie_option(touch_pad_t touch_num, touch_tie_opt_t *opt) { if (RTCIO.touch_pad[touch_num].xpd) { *opt = (touch_tie_opt_t)RTCIO.touch_pad[touch_num].tie_opt; } else { *opt = TOUCH_PAD_TIE_OPT_FLOAT; } } /** * Set touch sensor FSM mode. * The measurement action can be triggered by the hardware timer, as well as by the software instruction. * * @param mode FSM mode. */ __attribute__((always_inline)) static inline void touch_ll_set_fsm_mode(touch_fsm_mode_t mode) { RTCCNTL.touch_ctrl2.touch_start_force = mode; } /** * Get touch sensor FSM mode. * The measurement action can be triggered by the hardware timer, as well as by the software instruction. * * @param mode FSM mode. */ static inline void touch_ll_get_fsm_mode(touch_fsm_mode_t *mode) { *mode = (touch_fsm_mode_t)RTCCNTL.touch_ctrl2.touch_start_force; } /** * Enable/disable clock gate of touch sensor. * * @param enable true/false. */ static inline void touch_ll_clkgate(bool enable) { RTCCNTL.touch_ctrl2.touch_clkgate_en = enable; //enable touch clock for FSM. or force enable. } /** * Get touch sensor FSM state. * @return * - true: fsm state is open. * - false: fsm state is close. */ static inline bool touch_ll_clkgate_get_state(void) { return RTCCNTL.touch_ctrl2.touch_clkgate_en; } /** * Touch timer trigger measurement and always wait measurement done. * Force done for touch timer ensures that the timer always can get the measurement done signal. */ static inline void touch_ll_timer_force_done(void) { RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_FORCE_DONE; RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_DONE; } /** * Start touch sensor FSM timer. * The measurement action can be triggered by the hardware timer, as well as by the software instruction. */ static inline void touch_ll_start_fsm(void) { /** * Touch timer trigger measurement and always wait measurement done. * Force done for touch timer ensures that the timer always can get the measurement done signal. */ RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_FORCE_DONE; RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_DONE; RTCCNTL.touch_ctrl2.touch_slp_timer_en = (RTCCNTL.touch_ctrl2.touch_start_force == TOUCH_FSM_MODE_TIMER ? 1 : 0); } /** * Stop touch sensor FSM timer. * The measurement action can be triggered by the hardware timer, as well as by the software instruction. */ __attribute__((always_inline)) static inline void touch_ll_stop_fsm(void) { RTCCNTL.touch_ctrl2.touch_start_en = 0; //stop touch fsm RTCCNTL.touch_ctrl2.touch_slp_timer_en = 0; RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_FORCE_DONE; RTCCNTL.touch_ctrl2.touch_timer_force_done = TOUCH_LL_TIMER_DONE; } /** * Get touch sensor FSM timer state. * @return * - true: FSM enabled * - false: FSM disabled */ static inline bool touch_ll_get_fsm_state(void) { return (bool)RTCCNTL.touch_ctrl2.touch_slp_timer_en; } /** * Trigger a touch sensor measurement, only support in SW mode of FSM. */ static inline void touch_ll_start_sw_meas(void) { RTCCNTL.touch_ctrl2.touch_start_en = 1; RTCCNTL.touch_ctrl2.touch_start_en = 0; } /** * Set the trigger threshold of touch sensor. * The threshold determines the sensitivity of the touch sensor. * The threshold is the original value of the trigger state minus the benchmark value. * * @note If set "TOUCH_PAD_THRESHOLD_MAX", the touch is never be triggered. * @param touch_num touch pad index * @param threshold threshold of touch sensor. */ static inline void touch_ll_set_threshold(touch_pad_t touch_num, uint32_t threshold) { HAL_ASSERT(touch_num > 0); SENS.touch_thresh[touch_num - 1].thresh = threshold; } /** * Get the trigger threshold of touch sensor. * The threshold determines the sensitivity of the touch sensor. * The threshold is the original value of the trigger state minus the benchmark value. * * @param touch_num touch pad index. * @param threshold pointer to accept threshold. */ static inline void touch_ll_get_threshold(touch_pad_t touch_num, uint32_t *threshold) { HAL_ASSERT(touch_num > 0); *threshold = SENS.touch_thresh[touch_num - 1].thresh; } /** * Enable touch sensor channel. Register touch channel into touch sensor measurement group. * The working mode of the touch sensor is simultaneous measurement. * This function will set the measure bits according to the given bitmask. * * @note If set this mask, the FSM timer should be stop firsty. * @note The touch sensor that in scan map, should be deinit GPIO function firstly. * @param enable_mask bitmask of touch sensor scan group. * e.g. TOUCH_PAD_NUM1 -> BIT(1) * @return * - ESP_OK on success */ static inline void touch_ll_set_channel_mask(uint16_t enable_mask) { RTCCNTL.touch_scan_ctrl.touch_scan_pad_map |= (enable_mask & TOUCH_PAD_BIT_MASK_ALL); SENS.sar_touch_conf.touch_outen |= (enable_mask & TOUCH_PAD_BIT_MASK_ALL); } /** * Get touch sensor channel mask. * * @param enable_mask bitmask of touch sensor scan group. * e.g. TOUCH_PAD_NUM1 -> BIT(1) */ static inline void touch_ll_get_channel_mask(uint16_t *enable_mask) { *enable_mask = SENS.sar_touch_conf.touch_outen \ & RTCCNTL.touch_scan_ctrl.touch_scan_pad_map \ & TOUCH_PAD_BIT_MASK_ALL; } /** * Disable touch sensor channel by bitmask. * * @param enable_mask bitmask of touch sensor scan group. * e.g. TOUCH_PAD_NUM1 -> BIT(1) */ static inline void touch_ll_clear_channel_mask(uint16_t disable_mask) { SENS.sar_touch_conf.touch_outen &= ~(disable_mask & TOUCH_PAD_BIT_MASK_ALL); RTCCNTL.touch_scan_ctrl.touch_scan_pad_map &= ~(disable_mask & TOUCH_PAD_BIT_MASK_ALL); } /** * Get the touch sensor trigger status, usually used in ISR to decide which pads are 'touched'. * * @param status_mask The touch sensor status. e.g. Touch1 trigger status is `status_mask & (BIT1)`. */ static inline void touch_ll_read_trigger_status_mask(uint32_t *status_mask) { *status_mask = SENS.sar_touch_chn_st.touch_pad_active; } /** * Clear all touch sensor status. * * @note Generally no manual removal is required. */ static inline void touch_ll_clear_trigger_status_mask(void) { SENS.sar_touch_conf.touch_status_clr = 1; } /** * Get touch sensor raw data (touch sensor counter value) from register. No block. * * @param touch_num touch pad index. * @return touch_value pointer to accept touch sensor value. */ static inline uint32_t IRAM_ATTR touch_ll_read_raw_data(touch_pad_t touch_num) { SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_RAW; HAL_ASSERT(touch_num > 0); return SENS.sar_touch_status[touch_num - 1].touch_pad_data; } /** * Reset the whole of touch module. * * @note Call this function after `touch_pad_fsm_stop`. */ static inline void touch_ll_reset(void) { RTCCNTL.touch_ctrl2.touch_reset = 0; RTCCNTL.touch_ctrl2.touch_reset = 1; RTCCNTL.touch_ctrl2.touch_reset = 0; // Should be set 0. } /** * Set connection type of touch channel in idle status. * When a channel is in measurement mode, other initialized channels are in idle mode. * The touch channel is generally adjacent to the trace, so the connection state of the idle channel * affects the stability and sensitivity of the test channel. * The `CONN_HIGHZ`(high resistance) setting increases the sensitivity of touch channels. * The `CONN_GND`(grounding) setting increases the stability of touch channels. * * @param type Select idle channel connect to high resistance state or ground. */ static inline void touch_ll_set_idle_channel_connect(touch_pad_conn_type_t type) { RTCCNTL.touch_scan_ctrl.touch_inactive_connection = type; } /** * Set connection type of touch channel in idle status. * When a channel is in measurement mode, other initialized channels are in idle mode. * The touch channel is generally adjacent to the trace, so the connection state of the idle channel * affects the stability and sensitivity of the test channel. * The `CONN_HIGHZ`(high resistance) setting increases the sensitivity of touch channels. * The `CONN_GND`(grounding) setting increases the stability of touch channels. * * @param type Select idle channel connect to high resistance state or ground. */ static inline void touch_ll_get_idle_channel_connect(touch_pad_conn_type_t *type) { *type = (touch_pad_conn_type_t)(RTCCNTL.touch_scan_ctrl.touch_inactive_connection); } #ifdef RTC_CNTL_INT_ENA_W1TS_REG /** * Enable touch sensor interrupt by bitmask. * * @param type interrupt type */ static inline void touch_ll_intr_enable(touch_pad_intr_mask_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_ena_w1ts.rtc_touch_done_w1ts = 1; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_ena_w1ts.rtc_touch_active_w1ts = 1; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_ena_w1ts.rtc_touch_inactive_w1ts = 1; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_ena_w1ts.rtc_touch_scan_done_w1ts = 1; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_ena_w1ts.rtc_touch_timeout_w1ts = 1; } if (int_mask & TOUCH_LL_INTR_MASK_PROXI_MEAS_DONE) { RTCCNTL.int_ena_w1ts.rtc_touch_approach_loop_done_w1ts = 1; } } /** * Disable touch sensor interrupt by bitmask. * * @param type interrupt type */ static inline void touch_ll_intr_disable(touch_pad_intr_mask_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_ena_w1tc.rtc_touch_done_w1tc = 1; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_ena_w1tc.rtc_touch_active_w1tc = 1; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_ena_w1tc.rtc_touch_inactive_w1tc = 1; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_ena_w1tc.rtc_touch_scan_done_w1tc = 1; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_ena_w1tc.rtc_touch_timeout_w1tc = 1; } if (int_mask & TOUCH_LL_INTR_MASK_PROXI_MEAS_DONE) { RTCCNTL.int_ena_w1tc.rtc_touch_approach_loop_done_w1tc = 1; } } /** * Clear touch sensor interrupt by bitmask. * * @param int_mask Pad mask to clear interrupts */ static inline void touch_ll_intr_clear(touch_pad_intr_mask_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_clr.rtc_touch_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_clr.rtc_touch_active = 1; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_clr.rtc_touch_inactive = 1; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_clr.rtc_touch_scan_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_clr.rtc_touch_timeout = 1; } if (int_mask & TOUCH_LL_INTR_MASK_PROXI_MEAS_DONE) { RTCCNTL.int_clr.rtc_touch_approach_loop_done = 1; } } /** * Get the bitmask of touch sensor interrupt status. * * @return type interrupt type */ static inline uint32_t touch_ll_read_intr_status_mask(void) { typeof(RTCCNTL.int_st) intr_st; intr_st.val = RTCCNTL.int_st.val; uint32_t intr_msk = 0; if (intr_st.rtc_touch_done) { intr_msk |= TOUCH_LL_INTR_MASK_DONE; } if (intr_st.rtc_touch_active) { intr_msk |= TOUCH_LL_INTR_MASK_ACTIVE; } if (intr_st.rtc_touch_inactive) { intr_msk |= TOUCH_LL_INTR_MASK_INACTIVE; } if (intr_st.rtc_touch_scan_done) { intr_msk |= TOUCH_LL_INTR_MASK_SCAN_DONE; } if (intr_st.rtc_touch_timeout) { intr_msk |= TOUCH_LL_INTR_MASK_TIMEOUT; } if (intr_st.rtc_touch_approach_loop_done) { intr_msk |= TOUCH_LL_INTR_MASK_PROXI_MEAS_DONE; } return (intr_msk & TOUCH_LL_INTR_MASK_ALL); } #else /** * Enable touch sensor interrupt by bitmask. * * @param type interrupt type */ static inline void touch_ll_intr_enable(touch_pad_intr_mask_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_ena.rtc_touch_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_ena.rtc_touch_active = 1; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_ena.rtc_touch_inactive = 1; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_ena.rtc_touch_scan_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_ena.rtc_touch_timeout = 1; } } /** * Disable touch sensor interrupt by bitmask. * * @param type interrupt type */ static inline void touch_ll_intr_disable(uint32_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_ena.rtc_touch_done = 0; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_ena.rtc_touch_active = 0; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_ena.rtc_touch_inactive = 0; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_ena.rtc_touch_scan_done = 0; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_ena.rtc_touch_timeout = 0; } } /** * Clear touch sensor interrupt by bitmask. * * @param int_mask Pad mask to clear interrupts */ static inline void touch_ll_intr_clear(touch_pad_intr_mask_t int_mask) { if (int_mask & TOUCH_LL_INTR_MASK_DONE) { RTCCNTL.int_clr.rtc_touch_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_ACTIVE) { RTCCNTL.int_clr.rtc_touch_active = 1; } if (int_mask & TOUCH_LL_INTR_MASK_INACTIVE) { RTCCNTL.int_clr.rtc_touch_inactive = 1; } if (int_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { RTCCNTL.int_clr.rtc_touch_scan_done = 1; } if (int_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { RTCCNTL.int_clr.rtc_touch_timeout = 1; } } /** * Get the bitmask of touch sensor interrupt status. * * @return type interrupt type */ static inline uint32_t touch_ll_read_intr_status_mask(void) { typeof(RTCCNTL.int_st) intr_st; intr_st.val = RTCCNTL.int_st.val; uint32_t intr_msk = 0; if (intr_st.rtc_touch_done) { intr_msk |= TOUCH_LL_INTR_MASK_DONE; } if (intr_st.rtc_touch_active) { intr_msk |= TOUCH_LL_INTR_MASK_ACTIVE; } if (intr_st.rtc_touch_inactive) { intr_msk |= TOUCH_LL_INTR_MASK_INACTIVE; } if (intr_st.rtc_touch_scan_done) { intr_msk |= TOUCH_LL_INTR_MASK_SCAN_DONE; } if (intr_st.rtc_touch_timeout) { intr_msk |= TOUCH_LL_INTR_MASK_TIMEOUT; } return (intr_msk & TOUCH_LL_INTR_MASK_ALL); } #endif /** * Enable the timeout check for all touch sensor channels measurements. * When the touch reading of a touch channel exceeds the measurement threshold, * If enable: a timeout interrupt will be generated and it will go to the next channel measurement. * If disable: the FSM is always on the channel, until the measurement of this channel is over. * * @note Set the timeout threshold correctly before enabling it. */ static inline void touch_ll_timeout_enable(void) { RTCCNTL.touch_timeout_ctrl.touch_timeout_en = 1; } /** * Disable the timeout check for all touch sensor channels measurements. * When the touch reading of a touch channel exceeds the measurement threshold, * If enable: a timeout interrupt will be generated and it will go to the next channel measurement. * If disable: the FSM is always on the channel, until the measurement of this channel is over. * * @note Set the timeout threshold correctly before enabling it. */ static inline void touch_ll_timeout_disable(void) { RTCCNTL.touch_timeout_ctrl.touch_timeout_en = 0; } /** * Set timeout threshold for all touch sensor channels measurements. * Compared with touch readings. * * @param threshold Set to the maximum time measured on one channel. */ static inline void touch_ll_timeout_set_threshold(uint32_t threshold) { RTCCNTL.touch_timeout_ctrl.touch_timeout_num = threshold; } /** * Get timeout threshold for all touch sensor channels measurements. * Compared with touch readings. * * @param threshold Point to timeout threshold. */ static inline void touch_ll_timeout_get_threshold(uint32_t *threshold) { *threshold = RTCCNTL.touch_timeout_ctrl.touch_timeout_num; } /************************ Filter register setting ************************/ /** * Get smoothed data that obtained by filtering the raw data. * * @param touch_num touch pad index * @param smooth_data pointer to smoothed data */ static inline void IRAM_ATTR touch_ll_filter_read_smooth(touch_pad_t touch_num, uint32_t *smooth_data) { SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_SMOOTH; HAL_ASSERT(touch_num > 0); *smooth_data = SENS.sar_touch_status[touch_num - 1].touch_pad_data; } /** * Get benchmark value of touch sensor. * * @note After initialization, the benchmark value is the maximum during the first measurement period. * @param touch_num touch pad index * @param touch_value pointer to accept touch sensor value */ static inline void IRAM_ATTR touch_ll_read_benchmark(touch_pad_t touch_num, uint32_t *benchmark) { SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_BENCHMARK; HAL_ASSERT(touch_num > 0); *benchmark = SENS.sar_touch_status[touch_num - 1].touch_pad_data; } /** * Force reset benchmark to raw data of touch sensor. * * @note If call this API, make sure enable clock gate(`touch_ll_clkgate`) first. * @param touch_num touch pad index * - TOUCH_PAD_MAX Reset baseline of all channels. */ static inline void touch_ll_reset_benchmark(touch_pad_t touch_num) { /* Clear touch channels to initialize the channel value (benchmark, raw_data). */ if (touch_num == TOUCH_PAD_MAX) { SENS.sar_touch_chn_st.touch_channel_clr = TOUCH_PAD_BIT_MASK_ALL; } else { SENS.sar_touch_chn_st.touch_channel_clr = (1U << touch_num); } } /** * Get filter mode. The input of the filter is the raw value of touch reading, * and the output of the filter is involved in the judgment of the touch state. * * @param mode Filter mode type. Refer to ``touch_filter_mode_t``. */ static inline void touch_ll_filter_get_filter_mode(touch_filter_mode_t *mode) { *mode = (touch_filter_mode_t)RTCCNTL.touch_filter_ctrl.touch_filter_mode; } /** * Get filter mode. The smooth data is used to determine the touch status. * * @param mode Filter mode type. Refer to ``touch_smooth_mode_t``. */ static inline void touch_ll_filter_get_smooth_mode(touch_smooth_mode_t *mode) { *mode = (touch_smooth_mode_t)(RTCCNTL.touch_filter_ctrl.touch_smooth_lvl); } /** * Get debounce count. * * @param dbc_cnt Debounce count value. */ static inline void touch_ll_filter_get_debounce(uint32_t *dbc_cnt) { *dbc_cnt = RTCCNTL.touch_filter_ctrl.touch_debounce; } /** * Set noise threshold coefficient. Higher = More noise resistance. * The actual noise should be less than (noise coefficient * touch threshold). * Range: 0 ~ 3. The coefficient is 0: 4/8; 1: 3/8; 2: 2/8; 3: 1; * * @param hys_thr Noise threshold coefficient. */ static inline void touch_ll_filter_set_noise_thres(uint32_t noise_thr) { RTCCNTL.touch_filter_ctrl.touch_noise_thres = noise_thr; RTCCNTL.touch_filter_ctrl.config2 = noise_thr; RTCCNTL.touch_filter_ctrl.config1 = 0xF; RTCCNTL.touch_filter_ctrl.config3 = 2; } /** * Get noise threshold coefficient. Higher = More noise resistance. * The actual noise should be less than (noise coefficient * touch threshold). * Range: 0 ~ 3. The coefficient is 0: 4/8; 1: 3/8; 2: 2/8; 3: 1; * * @param noise_thr Noise threshold coefficient. */ static inline void touch_ll_filter_get_noise_thres(uint32_t *noise_thr) { *noise_thr = RTCCNTL.touch_filter_ctrl.touch_noise_thres; } /** * Get jitter filter step size. * If filter mode is jitter, should set filter step for jitter. * Range: 0 ~ 15 * * @param step The step size of the data change. */ static inline void touch_ll_filter_get_jitter_step(uint32_t *step) { *step = RTCCNTL.touch_filter_ctrl.touch_jitter_step; } /************************ Denoise register setting ************************/ /** * Set internal reference capacitance of denoise channel. * Select the appropriate internal reference capacitance value so that * the reading of denoise channel is closest to the reading of the channel being measured. * * @param cap_level Capacitance level. */ static inline void touch_ll_denoise_set_cap_level(touch_pad_denoise_cap_t cap_level) { RTCCNTL.touch_ctrl2.touch_refc = cap_level; } /** * Get internal reference capacitance of denoise channel. * Select the appropriate internal reference capacitance value so that * the reading of denoise channel is closest to the reading of the channel being measured. * * @param cap_level Capacitance level. */ static inline void touch_ll_denoise_get_cap_level(touch_pad_denoise_cap_t *cap_level) { *cap_level = (touch_pad_denoise_cap_t)(RTCCNTL.touch_ctrl2.touch_refc); } /** * Set denoise range of denoise channel. * Determined by measuring the noise amplitude of the denoise channel. * * @param grade Denoise range of denoise channel. */ static inline void touch_ll_denoise_set_grade(touch_pad_denoise_grade_t grade) { RTCCNTL.touch_scan_ctrl.touch_denoise_res = grade; } /** * Set denoise range of denoise channel. * Determined by measuring the noise amplitude of the denoise channel. * * @param grade Denoise range of denoise channel. */ static inline void touch_ll_denoise_get_grade(touch_pad_denoise_grade_t *grade) { *grade = (touch_pad_denoise_grade_t)(RTCCNTL.touch_scan_ctrl.touch_denoise_res); } /************************ Waterproof register setting ************************/ /** * Set touch channel use for guard pad. * * @param pad_num Touch sensor channel number. */ static inline void touch_ll_waterproof_set_guard_pad(touch_pad_t pad_num) { RTCCNTL.touch_scan_ctrl.touch_out_ring = pad_num; } /** * Get touch channel use for guard pad. * * @param pad_num Touch sensor channel number. */ static inline void touch_ll_waterproof_get_guard_pad(touch_pad_t *pad_num) { *pad_num = (touch_pad_t)(RTCCNTL.touch_scan_ctrl.touch_out_ring); } /** * Get max equivalent capacitance for shield channel. * The equivalent capacitance of the shielded channel can be calculated * from the reading of denoise channel. * * @param pad_num Touch sensor channel number. */ static inline void touch_ll_waterproof_get_shield_driver(touch_pad_shield_driver_t *driver_level) { *driver_level = (touch_pad_shield_driver_t)(RTCCNTL.touch_scan_ctrl.touch_bufdrv); } /************************ Proximity register setting ************************/ /** * Set touch channel number for proximity pad. * If disable the proximity pad, point this pad to `TOUCH_PAD_NUM0` * * @param prox_pad The array of three proximity pads. */ static inline void touch_ll_proximity_set_channel_num(const touch_pad_t prox_pad[]) { SENS.sar_touch_conf.touch_approach_pad0 = prox_pad[0]; SENS.sar_touch_conf.touch_approach_pad1 = prox_pad[1]; SENS.sar_touch_conf.touch_approach_pad2 = prox_pad[2]; } /** * Get touch channel number for proximity pad. * If disable the proximity pad, point this pad to `TOUCH_PAD_NUM0` * * @param prox_pad The array of three proximity pads. */ static inline void touch_ll_proximity_get_channel_num(touch_pad_t prox_pad[]) { prox_pad[0] = (touch_pad_t)(SENS.sar_touch_conf.touch_approach_pad0); prox_pad[1] = (touch_pad_t)(SENS.sar_touch_conf.touch_approach_pad1); prox_pad[2] = (touch_pad_t)(SENS.sar_touch_conf.touch_approach_pad2); } /** * Set cumulative measurement times for proximity pad. * * @param times The cumulative number of measurement cycles. */ static inline void touch_ll_proximity_set_meas_times(uint32_t times) { HAL_FORCE_MODIFY_U32_REG_FIELD(RTCCNTL.touch_approach, touch_approach_meas_time, times); } /** * Get cumulative measurement times for proximity pad. * * @param times The cumulative number of measurement cycles. */ static inline void touch_ll_proximity_get_meas_times(uint32_t *times) { *times = HAL_FORCE_READ_U32_REG_FIELD(RTCCNTL.touch_approach, touch_approach_meas_time); } /** * Read current cumulative measurement times for proximity pad. * * @param times The cumulative number of measurement cycles. */ static inline void touch_ll_proximity_read_meas_cnt(touch_pad_t touch_num, uint32_t *cnt) { if (SENS.sar_touch_conf.touch_approach_pad0 == touch_num) { *cnt = HAL_FORCE_READ_U32_REG_FIELD(SENS.sar_touch_appr_status, touch_approach_pad0_cnt); } else if (SENS.sar_touch_conf.touch_approach_pad1 == touch_num) { *cnt = HAL_FORCE_READ_U32_REG_FIELD(SENS.sar_touch_appr_status, touch_approach_pad1_cnt); } else if (SENS.sar_touch_conf.touch_approach_pad2 == touch_num) { *cnt = HAL_FORCE_READ_U32_REG_FIELD(SENS.sar_touch_appr_status, touch_approach_pad2_cnt); } } /** * Check if the touch sensor channel is the proximity pad. * * @param touch_num The touch sensor channel number. */ static inline bool touch_ll_proximity_pad_check(touch_pad_t touch_num) { if ((SENS.sar_touch_conf.touch_approach_pad0 != touch_num) && (SENS.sar_touch_conf.touch_approach_pad1 != touch_num) && (SENS.sar_touch_conf.touch_approach_pad2 != touch_num)) { return false; } else { return true; } } /************** sleep pad setting ***********************/ /** * Get the trigger threshold of touch sensor in deep sleep. * The threshold determines the sensitivity of the touch sensor. * The threshold is the original value of the trigger state minus the benchmark value. * * @note In general, the touch threshold during sleep can use the threshold parameter parameters before sleep. */ static inline void touch_ll_sleep_get_threshold(uint32_t *touch_thres) { *touch_thres = RTCCNTL.touch_slp_thres.touch_slp_th; } /** * Get proximity function status for sleep pad. */ static inline bool touch_ll_sleep_is_proximity_enabled(void) { return (bool)RTCCNTL.touch_slp_thres.touch_slp_approach_en; } /** * Read benchmark of touch sensor for sleep pad. * * @param benchmark Pointer to accept touch sensor benchmark value. */ static inline void touch_ll_sleep_read_benchmark(uint32_t *benchmark) { SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_BENCHMARK; *benchmark = SENS.sar_touch_slp_status.touch_slp_data; } static inline void touch_ll_sleep_read_smooth(uint32_t *smooth_data) { SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_SMOOTH; *smooth_data = SENS.sar_touch_slp_status.touch_slp_data; } /* Workaround: Note: sleep pad raw data is not in `sar_touch_slp_status` */ static inline void touch_ll_sleep_read_data(uint32_t *raw_data) { uint32_t touch_num = RTCCNTL.touch_slp_thres.touch_slp_pad; SENS.sar_touch_conf.touch_data_sel = TOUCH_LL_READ_RAW; HAL_ASSERT(touch_num > 0); *raw_data = SENS.sar_touch_status[touch_num - 1].touch_pad_data; } /** * Get the data of the touch channel according to the types * * @param sample_cfg_id The sample configuration index * @param type data type * 0/1: TOUCH_LL_READ_RAW, the raw data of the touch channel * 2: TOUCH_LL_READ_BENCHMARK, benchmark value of touch channel, * the benchmark value is the maximum during the first measurement period * 3: TOUCH_LL_READ_SMOOTH, the smoothed data that obtained by filtering the raw data. * @param smooth_data pointer to smoothed data */ __attribute__((always_inline)) static inline void touch_ll_sleep_read_chan_data(uint8_t type, uint32_t *data) { SENS.sar_touch_conf.touch_data_sel = type; if (type == TOUCH_LL_READ_RAW) { uint32_t touch_num = RTCCNTL.touch_slp_thres.touch_slp_pad; HAL_ASSERT(touch_num > 0); *data = SENS.sar_touch_status[touch_num - 1].touch_pad_data; } else { *data = SENS.sar_touch_slp_status.touch_slp_data; } } /** * Select touch sensor dbias to save power in sleep mode. * * @note If change the dbias, the reading of touch sensor will changed. Users should make sure the threshold. */ static inline void touch_ll_sleep_low_power(bool is_low_power) { RTCCNTL.touch_ctrl2.touch_dbias = is_low_power; } /** * Read debounce of touch sensor for sleep pad. * * @param debounce Pointer to accept touch sensor debounce value. */ static inline void touch_ll_sleep_read_debounce(uint32_t *debounce) { *debounce = SENS.sar_touch_slp_status.touch_slp_debounce; } /** * Read proximity count of touch sensor for sleep pad. * @param prox_cnt Pointer to accept touch sensor proximity count value. */ static inline void touch_ll_sleep_read_proximity_cnt(uint32_t *prox_cnt) { *prox_cnt = HAL_FORCE_READ_U32_REG_FIELD(SENS.sar_touch_appr_status, touch_slp_approach_cnt); } /** * Get the touch pad which caused wakeup from deep sleep. * * @param pad_num pointer to touch pad which caused wakeup. */ static inline void touch_ll_get_wakeup_status(touch_pad_t *pad_num) { *pad_num = (touch_pad_t)RTCCNTL.touch_slp_thres.touch_slp_pad; } #ifdef __cplusplus } #endif ================================================ FILE: touch_element/include/touch_element/touch_button.h ================================================ /* * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "touch_element/touch_element.h" #ifdef __cplusplus extern "C" { #endif /* --------------------------------- General button instance default configuration --------------------------------- */ #define TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG() \ { \ .threshold_divider = 0.8, \ .default_lp_time = 1000 \ } /* ------------------------------------------------------------------------------------------------------------------ */ /** * @brief Button initialization configuration passed to touch_button_install */ typedef struct { float threshold_divider; //!< Button channel threshold divider uint32_t default_lp_time; //!< Button default LongPress event time (ms) } touch_button_global_config_t; /** * @brief Button configuration (for new instance) passed to touch_button_create() */ typedef struct { touch_pad_t channel_num; //!< Button channel number (index) float channel_sens; //!< Button channel sensitivity } touch_button_config_t; /** * @brief Button event type */ typedef enum { TOUCH_BUTTON_EVT_ON_PRESS, //!< Button Press event TOUCH_BUTTON_EVT_ON_RELEASE, //!< Button Release event TOUCH_BUTTON_EVT_ON_LONGPRESS, //!< Button LongPress event TOUCH_BUTTON_EVT_MAX } touch_button_event_t; /** * @brief Button message type */ typedef struct { touch_button_event_t event; //!< Button event } touch_button_message_t; typedef touch_elem_handle_t touch_button_handle_t; //!< Button handle typedef void(*touch_button_callback_t)(touch_button_handle_t, touch_button_message_t *, void *); //!< Button callback type /** * @brief Touch Button initialize * * This function initializes touch button global and acts on all * touch button instances. * * @param[in] global_config Button object initialization configuration * * @return * - ESP_OK: Successfully initialized touch button * - ESP_ERR_INVALID_STATE: Touch element library was not initialized * - ESP_ERR_INVALID_ARG: button_init is NULL * - ESP_ERR_NO_MEM: Insufficient memory */ esp_err_t touch_button_install(const touch_button_global_config_t *global_config); /** * @brief Release resources allocated using touch_button_install() */ void touch_button_uninstall(void); /** * @brief Create a new touch button instance * * @param[in] button_config Button configuration * @param[out] button_handle Button handle * * @note The sensitivity has to be explored in experiments, * Sensitivity = (Raw(touch) - Raw(release)) / Raw(release) * 100% * * @return * - ESP_OK: Successfully create touch button * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_NO_MEM: Insufficient memory * - ESP_ERR_INVALID_ARG: Invalid configuration struct or arguments is NULL */ esp_err_t touch_button_create(const touch_button_config_t *button_config, touch_button_handle_t *button_handle); /** * @brief Release resources allocated using touch_button_create() * * @param[in] button_handle Button handle * @return * - ESP_OK: Successfully released resources * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_INVALID_ARG: button_handle is null * - ESP_ERR_NOT_FOUND: Input handle is not a button handle */ esp_err_t touch_button_delete(touch_button_handle_t button_handle); /** * @brief Touch button subscribes event * * This function uses event mask to subscribe to touch button events, once one of * the subscribed events occurs, the event message could be retrieved by calling * touch_element_message_receive() or input callback routine. * * @param[in] button_handle Button handle * @param[in] event_mask Button subscription event mask * @param[in] arg User input argument * * @note Touch button only support three kind of event masks, they are * TOUCH_ELEM_EVENT_ON_PRESS, TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS. * You can use those event masks in any combination to achieve the desired effect. * * @return * - ESP_OK: Successfully subscribed touch button event * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_INVALID_ARG: button_handle is null or event is not supported */ esp_err_t touch_button_subscribe_event(touch_button_handle_t button_handle, uint32_t event_mask, void *arg); /** * @brief Touch button set dispatch method * * This function sets a dispatch method that the driver core will use * this method as the event notification method. * * @param[in] button_handle Button handle * @param[in] dispatch_method Dispatch method (By callback/event) * * @return * - ESP_OK: Successfully set dispatch method * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_INVALID_ARG: button_handle is null or dispatch_method is invalid */ esp_err_t touch_button_set_dispatch_method(touch_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method); /** * @brief Touch button set callback * * This function sets a callback routine into touch element driver core, * when the subscribed events occur, the callback routine will be called. * * @param[in] button_handle Button handle * @param[in] button_callback User input callback * * @note Button message will be passed from the callback function and it will be destroyed when the callback function return. * * @warning Since this input callback routine runs on driver core (esp-timer callback routine), * it should not do something that attempts to Block, such as calling vTaskDelay(). * * @return * - ESP_OK: Successfully set callback * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_INVALID_ARG: button_handle or button_callback is null */ esp_err_t touch_button_set_callback(touch_button_handle_t button_handle, touch_button_callback_t button_callback); /** * @brief Touch button set long press trigger time * * This function sets the threshold time (ms) for a long press event. If a button is pressed * and held for a period of time that exceeds the threshold time, a long press event is triggered. * * @param[in] button_handle Button handle * @param[in] threshold_time Threshold time (ms) of long press event occur * * @return * - ESP_OK: Successfully set the threshold time of long press event * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized * - ESP_ERR_INVALID_ARG: button_handle is null or time (ms) is not lager than 0 */ esp_err_t touch_button_set_longpress(touch_button_handle_t button_handle, uint32_t threshold_time); /** * @brief Touch button get message * * This function decodes the element message from touch_element_message_receive() and return * a button message pointer. * * @param[in] element_message element message * * @return Touch button message pointer */ const touch_button_message_t *touch_button_get_message(const touch_elem_message_t *element_message); #ifdef __cplusplus } #endif ================================================ FILE: touch_element/include/touch_element/touch_element.h ================================================ /* * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "touch_sensor_legacy_types.h" #ifdef __cplusplus extern "C" { #endif /* -------------------------------- General hardware & system default configuration -------------------------------- */ /* Since those are important hardware and algorithm parameters, user should not change them before knowing all details*/ /* ------------------------------------------------------------------------------------------------------------------ */ #define TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG() \ { \ .hardware = { \ .upper_voltage = TOUCH_HVOLT_2V7, \ .voltage_attenuation = TOUCH_HVOLT_ATTEN_0V5, \ .lower_voltage = TOUCH_LVOLT_0V5, \ .suspend_channel_polarity = TOUCH_PAD_CONN_HIGHZ, \ .denoise_level = TOUCH_PAD_DENOISE_BIT4, \ .denoise_equivalent_cap = TOUCH_PAD_DENOISE_CAP_L0, \ .smooth_filter_mode = TOUCH_PAD_SMOOTH_IIR_2, \ .benchmark_filter_mode = TOUCH_PAD_FILTER_IIR_16, \ .sample_count = 500, \ .sleep_cycle = 0xf, \ .benchmark_debounce_count = 2, \ .benchmark_calibration_threshold = 2, \ .benchmark_jitter_step = 5 \ }, \ .software = { \ .waterproof_threshold_divider = 0.8, \ .processing_period = 10, \ .intr_message_size = 14, \ .event_message_size = 20 \ } \ } /* ------------------------------------------------------------------------------------------------------------------ */ /* ---------------------------------------------- Event subscription ----------------------------------------------- */ #define TOUCH_ELEM_EVENT_NONE BIT(0) //!< None event #define TOUCH_ELEM_EVENT_ON_PRESS BIT(1) //!< On Press event #define TOUCH_ELEM_EVENT_ON_RELEASE BIT(2) //!< On Release event #define TOUCH_ELEM_EVENT_ON_LONGPRESS BIT(3) //!< On LongPress event #define TOUCH_ELEM_EVENT_ON_CALCULATION BIT(4) //!< On Calculation event /* ------------------------------------------------------------------------------------------------------------------ */ #define TOUCH_WATERPROOF_GUARD_NOUSE (0) //!< Waterproof no use guard sensor /* -------------------------------- Global hardware & software configuration struct --------------------------------- */ /** * @brief Touch element software configuration */ typedef struct { float waterproof_threshold_divider; //!< Waterproof guard channel threshold divider uint8_t processing_period; //!< Processing period(ms) uint8_t intr_message_size; //!< Interrupt message queue size uint8_t event_message_size; //!< Event message queue size } touch_elem_sw_config_t; /** * @brief Touch element hardware configuration */ typedef struct { touch_high_volt_t upper_voltage; //!< Touch sensor channel upper charge voltage touch_volt_atten_t voltage_attenuation; //!< Touch sensor channel upper charge voltage attenuation (Diff voltage is upper - attenuation - lower) touch_low_volt_t lower_voltage; //!< Touch sensor channel lower charge voltage touch_pad_conn_type_t suspend_channel_polarity; //!< Suspend channel polarity (High Impedance State or GND) touch_pad_denoise_grade_t denoise_level; //!< Internal de-noise level touch_pad_denoise_cap_t denoise_equivalent_cap; //!< Internal de-noise channel (Touch channel 0) equivalent capacitance touch_smooth_mode_t smooth_filter_mode; //!< Smooth value filter mode (This only apply to touch_pad_filter_read_smooth()) touch_filter_mode_t benchmark_filter_mode; //!< Benchmark filter mode uint16_t sample_count; //!< The count of sample in each measurement of touch sensor uint16_t sleep_cycle; //!< The cycle (RTC slow clock) of sleep uint8_t benchmark_debounce_count; //!< Benchmark debounce count uint8_t benchmark_calibration_threshold; //!< Benchmark calibration threshold uint8_t benchmark_jitter_step; //!< Benchmark jitter filter step (This only works at while benchmark filter mode is jitter filter) } touch_elem_hw_config_t; /** * @brief Touch element global configuration passed to touch_element_install */ typedef struct { touch_elem_hw_config_t hardware; //!< Hardware configuration touch_elem_sw_config_t software; //!< Software configuration } touch_elem_global_config_t; /** * @brief Touch element waterproof configuration passed to touch_element_waterproof_install */ typedef struct { touch_pad_t guard_channel; //!< Waterproof Guard-Sensor channel number (index) float guard_sensitivity; //!< Waterproof Guard-Sensor sensitivity } touch_elem_waterproof_config_t; /** * @brief Touch element sleep configuration passed to touch_element_enable_light_sleep or touch_element_enable_deep_sleep */ typedef struct { uint16_t sample_count; //!< scan times in every measurement, normally equal to the 'sample_count' field in 'touch_elem_hw_config_t'. uint16_t sleep_cycle; //!< sleep_cycle decide the interval between two measurements, t_sleep = sleep_cycle / (RTC_SLOW_CLK frequency), normally equal to the 'sleep_cycle' field in 'touch_elem_hw_config_t'. } touch_elem_sleep_config_t; /* ------------------------------------------------------------------------------------------------------------------ */ typedef void *touch_elem_handle_t; //!< Touch element handle type typedef uint32_t touch_elem_event_t; //!< Touch element event type /** * @brief Touch element handle type */ typedef enum { TOUCH_ELEM_TYPE_BUTTON, //!< Touch element button TOUCH_ELEM_TYPE_SLIDER, //!< Touch element slider TOUCH_ELEM_TYPE_MATRIX, //!< Touch element matrix button } touch_elem_type_t; /** * @brief Touch element event dispatch methods (event queue/callback) */ typedef enum { TOUCH_ELEM_DISP_EVENT, //!< Event queue dispatch TOUCH_ELEM_DISP_CALLBACK, //!< Callback dispatch TOUCH_ELEM_DISP_MAX } touch_elem_dispatch_t; /** * @brief Touch element event message type from touch_element_message_receive() */ typedef struct { touch_elem_handle_t handle; //!< Touch element handle touch_elem_type_t element_type; //!< Touch element type void *arg; //!< User input argument uint8_t child_msg[8]; //!< Encoded message } touch_elem_message_t; /* ------------------------------------------------------------------------------------------------------------------ */ /** * @brief Touch element processing initialization * * @param[in] global_config Global initialization configuration structure * * @note To reinitialize the touch element object, call touch_element_uninstall() first * * @return * - ESP_OK: Successfully initialized * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_NO_MEM: Insufficient memory * - ESP_ERR_INVALID_STATE: Touch element is already initialized * - Others: Unknown touch driver layer or lower layer error */ esp_err_t touch_element_install(const touch_elem_global_config_t *global_config); /** * @brief Touch element processing start * * This function starts the touch element processing system * * @note This function must only be called after all the touch element instances finished creating * * @return * - ESP_OK: Successfully started to process * - Others: Unknown touch driver layer or lower layer error */ esp_err_t touch_element_start(void); /** * @brief Touch element processing stop * * This function stops the touch element processing system * * @note This function must be called before changing the system (hardware, software) parameters * * @return * - ESP_OK: Successfully stopped to process * - Others: Unknown touch driver layer or lower layer error */ esp_err_t touch_element_stop(void); /** * @brief Release resources allocated using touch_element_install * */ void touch_element_uninstall(void); /** * @brief Get current event message of touch element instance * * This function will receive the touch element message (handle, event type, etc...) * from te_event_give(). It will block until a touch element event or a timeout occurs. * * @param[out] element_message Touch element event message structure * @param[in] ticks_to_wait Number of FreeRTOS ticks to block for waiting event * @return * - ESP_OK: Successfully received touch element event * - ESP_ERR_INVALID_STATE: Touch element library is not initialized * - ESP_ERR_INVALID_ARG: element_message is null * - ESP_ERR_TIMEOUT: Timed out waiting for event */ esp_err_t touch_element_message_receive(touch_elem_message_t *element_message, uint32_t ticks_to_wait); /** * @brief Touch element waterproof initialization * * This function enables the hardware waterproof, then touch element system uses Shield-Sensor * and Guard-Sensor to mitigate the influence of water-drop and water-stream. * * @param[in] waterproof_config Waterproof configuration * * @note If the waterproof function is used, Shield-Sensor can not be disabled and it will use channel 14 as * it's internal channel. Hence, the user can not use channel 14 for another propose. And the Guard-Sensor * is not necessary since it is optional. * * @note Shield-Sensor: It always uses channel 14 as the shield channel, so user must connect * the channel 14 and Shield-Layer in PCB since it will generate a synchronous signal automatically * * @note Guard-Sensor: This function is optional. If used, the user must connect the guard channel and Guard-Ring * in PCB. Any channels user wants to protect should be added into Guard-Ring in PCB. * * @return * - ESP_OK: Successfully initialized * - ESP_ERR_INVALID_STATE: Touch element library is not initialized * - ESP_ERR_INVALID_ARG: waterproof_config is null or invalid Guard-Sensor channel * - ESP_ERR_NO_MEM: Insufficient memory */ esp_err_t touch_element_waterproof_install(const touch_elem_waterproof_config_t *waterproof_config); /** * @brief Release resources allocated using touch_element_waterproof_install() */ void touch_element_waterproof_uninstall(void); /** * @brief Add a masked handle to protect while Guard-Sensor has been triggered * * This function will add an application handle (button, slider, etc...) as a masked handle. While Guard-Sensor * has been triggered, waterproof function will start working and lock the application internal state. While the * influence of water is reduced, the application will be unlock and reset into IDLE state. * * @param[in] element_handle Touch element instance handle * * @note The waterproof protection logic must follow the real circuit in PCB, it means that all of the channels * inside the input handle must be inside the Guard-Ring in real circuit. * * @return * - ESP_OK: Successfully added a masked handle * - ESP_ERR_INVALID_STATE: Waterproof is not initialized * - ESP_ERR_INVALID_ARG: element_handle is null */ esp_err_t touch_element_waterproof_add(touch_elem_handle_t element_handle); /** * @brief Remove a masked handle to protect * * This function will remove an application handle from masked handle table. * * @param[in] element_handle Touch element instance handle * * @return * - ESP_OK: Successfully removed a masked handle * - ESP_ERR_INVALID_STATE: Waterproof is not initialized * - ESP_ERR_INVALID_ARG: element_handle is null * - ESP_ERR_NOT_FOUND: Failed to search element_handle from waterproof mask_handle list */ esp_err_t touch_element_waterproof_remove(touch_elem_handle_t element_handle); /** * @brief Touch element light sleep initialization * * @note It should be called after touch button element installed. * Any of installed touch element can wake up from the light sleep * * @param[in] sleep_config Sleep configurations, set NULL to use default config * @return * - ESP_OK: Successfully initialized touch sleep * - ESP_ERR_INVALID_STATE: Touch element is not installed or touch sleep has been installed * - ESP_ERR_INVALID_ARG: inputted argument is NULL * - ESP_ERR_NO_MEM: no memory for touch sleep struct * - ESP_ERR_NOT_SUPPORTED: inputted wakeup_elem_handle is not touch_button_handle_t type, currently only touch_button_handle_t supported */ esp_err_t touch_element_enable_light_sleep(const touch_elem_sleep_config_t *sleep_config); /** * @brief Release the resources that allocated by touch_element_enable_deep_sleep() * * This function will also disable the touch sensor to wake up the device * * @return * - ESP_OK: uninstall success * - ESP_ERR_INVALID_STATE: touch sleep has not been installed */ esp_err_t touch_element_disable_light_sleep(void); /** * @brief Touch element deep sleep initialization * * This function will enable the device wake-up from deep sleep or light sleep by touch sensor * * @note It should be called after touch button element installed. * Only one touch button can be registered as the deep sleep wake-up button * * @param[in] wakeup_elem_handle Touch element instance handle for waking up the device, only support button element * @param[in] sleep_config Sleep configurations, set NULL to use default config * * @return * - ESP_OK: Successfully initialized touch sleep * - ESP_ERR_INVALID_STATE: Touch element is not installed or touch sleep has been installed * - ESP_ERR_INVALID_ARG: inputted argument is NULL * - ESP_ERR_NO_MEM: no memory for touch sleep struct * - ESP_ERR_NOT_SUPPORTED: inputted wakeup_elem_handle is not touch_button_handle_t type, currently only touch_button_handle_t supported */ esp_err_t touch_element_enable_deep_sleep(touch_elem_handle_t wakeup_elem_handle, const touch_elem_sleep_config_t *sleep_config); /** * @brief Release the resources that allocated by touch_element_enable_deep_sleep() * * This function will also disable the touch sensor to wake up the device * * @return * - ESP_OK: uninstall success * - ESP_ERR_INVALID_STATE: touch sleep has not been installed */ esp_err_t touch_element_disable_deep_sleep(void); /** * @brief Touch element wake up calibrations * * This function will also disable the touch sensor to wake up the device * * @return * - ESP_OK: uninstall success * - ESP_ERR_INVALID_STATE: touch sleep has not been installed */ esp_err_t touch_element_sleep_enable_wakeup_calibration(touch_elem_handle_t element_handle, bool en); #ifdef __cplusplus } #endif ================================================ FILE: touch_element/include/touch_element/touch_matrix.h ================================================ /* * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "touch_element/touch_element.h" #ifdef __cplusplus extern "C" { #endif /* ----------------------------- General matrix button instance default configuration ------------------------------ */ #define TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG() \ { \ .threshold_divider = 0.8, \ .default_lp_time = 1000 \ } /* ------------------------------------------------------------------------------------------------------------------ */ /** * @brief Matrix button initialization configuration passed to touch_matrix_install */ typedef struct { float threshold_divider; //!< Matrix button channel threshold divider uint32_t default_lp_time; //!< Matrix button default LongPress event time (ms) } touch_matrix_global_config_t; /** * @brief Matrix button configuration (for new instance) passed to touch_matrix_create() */ typedef struct { const touch_pad_t *x_channel_array; //!< Matrix button x-axis channels array const touch_pad_t *y_channel_array; //!< Matrix button y-axis channels array const float *x_sensitivity_array; //!< Matrix button x-axis channels sensitivity array const float *y_sensitivity_array; //!< Matrix button y-axis channels sensitivity array uint8_t x_channel_num; //!< The number of channels in x-axis uint8_t y_channel_num; //!< The number of channels in y-axis } touch_matrix_config_t; /** * @brief Matrix button event type */ typedef enum { TOUCH_MATRIX_EVT_ON_PRESS, //!< Matrix button Press event TOUCH_MATRIX_EVT_ON_RELEASE, //!< Matrix button Press event TOUCH_MATRIX_EVT_ON_LONGPRESS, //!< Matrix button LongPress event TOUCH_MATRIX_EVT_MAX } touch_matrix_event_t; /** * @brief Matrix button position data type */ typedef struct { uint8_t x_axis; //!< Matrix button x axis position uint8_t y_axis; //!< Matrix button y axis position uint8_t index; //!< Matrix button position index } touch_matrix_position_t; /** * @brief Matrix message type */ typedef struct { touch_matrix_event_t event; //!< Matrix event touch_matrix_position_t position; //!< Matrix position } touch_matrix_message_t; typedef touch_elem_handle_t touch_matrix_handle_t; //!< Matrix button instance handle typedef void(*touch_matrix_callback_t)(touch_matrix_handle_t, touch_matrix_message_t *, void *); //!< Matrix button callback type /** * @brief Touch matrix button initialize * * This function initializes touch matrix button object and acts on all * touch matrix button instances. * * @param[in] global_config Touch matrix global initialization configuration * * @return * - ESP_OK: Successfully initialized touch matrix button * - ESP_ERR_INVALID_STATE: Touch element library was not initialized * - ESP_ERR_INVALID_ARG: matrix_init is NULL * - ESP_ERR_NO_MEM: Insufficient memory */ esp_err_t touch_matrix_install(const touch_matrix_global_config_t *global_config); /** * @brief Release resources allocated using touch_matrix_install() * */ void touch_matrix_uninstall(void); /** * @brief Create a new touch matrix button instance * * @param[in] matrix_config Matrix button configuration * @param[out] matrix_handle Matrix button handle * * @note Channel array and sensitivity array must be one-one correspondence in those array * * @note Touch matrix button does not support Multi-Touch now * * @return * - ESP_OK: Successfully create touch matrix button * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: Invalid configuration struct or arguments is NULL * - ESP_ERR_NO_MEM: Insufficient memory */ esp_err_t touch_matrix_create(const touch_matrix_config_t *matrix_config, touch_matrix_handle_t *matrix_handle); /** * @brief Release resources allocated using touch_matrix_create() * * @param[in] matrix_handle Matrix handle * @return * - ESP_OK: Successfully released resources * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: matrix_handle is null * - ESP_ERR_NOT_FOUND: Input handle is not a matrix handle */ esp_err_t touch_matrix_delete(touch_matrix_handle_t matrix_handle); /** * @brief Touch matrix button subscribes event * * This function uses event mask to subscribe to touch matrix events, once one of * the subscribed events occurs, the event message could be retrieved by calling * touch_element_message_receive() or input callback routine. * * @param[in] matrix_handle Matrix handle * @param[in] event_mask Matrix subscription event mask * @param[in] arg User input argument * * @note Touch matrix button only support three kind of event masks, they are * TOUCH_ELEM_EVENT_ON_PRESS, TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS. You can use those event * masks in any combination to achieve the desired effect. * * @return * - ESP_OK: Successfully subscribed touch matrix event * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: matrix_handle is null or event is not supported */ esp_err_t touch_matrix_subscribe_event(touch_matrix_handle_t matrix_handle, uint32_t event_mask, void *arg); /** * @brief Touch matrix button set dispatch method * * This function sets a dispatch method that the driver core will use * this method as the event notification method. * * @param[in] matrix_handle Matrix button handle * @param[in] dispatch_method Dispatch method (By callback/event) * * @return * - ESP_OK: Successfully set dispatch method * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: matrix_handle is null or dispatch_method is invalid */ esp_err_t touch_matrix_set_dispatch_method(touch_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method); /** * @brief Touch matrix button set callback * * This function sets a callback routine into touch element driver core, * when the subscribed events occur, the callback routine will be called. * * @param[in] matrix_handle Matrix button handle * @param[in] matrix_callback User input callback * * @note Matrix message will be passed from the callback function and it will be destroyed when the callback function return. * * @warning Since this input callback routine runs on driver core (esp-timer callback routine), * it should not do something that attempts to Block, such as calling vTaskDelay(). * * @return * - ESP_OK: Successfully set callback * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: matrix_handle or matrix_callback is null */ esp_err_t touch_matrix_set_callback(touch_matrix_handle_t matrix_handle, touch_matrix_callback_t matrix_callback); /** * @brief Touch matrix button set long press trigger time * * This function sets the threshold time (ms) for a long press event. If a matrix button is pressed * and held for a period of time that exceeds the threshold time, a long press event is triggered. * * @param[in] matrix_handle Matrix button handle * @param[in] threshold_time Threshold time (ms) of long press event occur * * @return * - ESP_OK: Successfully set the time of long press event * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized * - ESP_ERR_INVALID_ARG: matrix_handle is null or time (ms) is 0 */ esp_err_t touch_matrix_set_longpress(touch_matrix_handle_t matrix_handle, uint32_t threshold_time); /** * @brief Touch matrix get message * * This function decodes the element message from touch_element_message_receive() and return * a matrix message pointer. * * @param[in] element_message element message * * @return Touch matrix message pointer */ const touch_matrix_message_t *touch_matrix_get_message(const touch_elem_message_t *element_message); #ifdef __cplusplus } #endif ================================================ FILE: touch_element/include/touch_element/touch_sensor_legacy_types.h ================================================ /* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include "esp_attr.h" #include "esp_bit_defs.h" #if __has_include("hal/touch_sensor_legacy_types.h") #include "hal/touch_sensor_legacy_types.h" #include "esp_idf_version.h" #if IDF_VERSION_MAJOR < 6 typedef touch_filter_mode_t touch_benchmark_filter_mode_t; typedef touch_smooth_mode_t touch_smooth_filter_mode_t; typedef touch_pad_shield_driver_t touch_chan_shield_cap_t; #endif #elif __has_include("hal/touch_sensor_types.h") #include "hal/touch_sensor_types.h" typedef touch_filter_mode_t touch_benchmark_filter_mode_t; typedef touch_smooth_mode_t touch_smooth_filter_mode_t; typedef touch_pad_shield_driver_t touch_chan_shield_cap_t; #else #ifdef __cplusplus extern "C" { #endif /** Touch pad channel */ typedef enum { TOUCH_PAD_NUM0 = 0, /*!< Internal touch channel */ TOUCH_PAD_NUM1, /*!< Touch channel 1 is GPIO1 */ TOUCH_PAD_NUM2, /*!< Touch channel 2 is GPIO2 */ TOUCH_PAD_NUM3, /*!< Touch channel 3 is GPIO3 */ TOUCH_PAD_NUM4, /*!< Touch channel 4 is GPIO4 */ TOUCH_PAD_NUM5, /*!< Touch channel 5 is GPIO5 */ TOUCH_PAD_NUM6, /*!< Touch channel 6 is GPIO6 */ TOUCH_PAD_NUM7, /*!< Touch channel 7 is GPIO7 */ TOUCH_PAD_NUM8, /*!< Touch channel 8 is GPIO8 */ TOUCH_PAD_NUM9, /*!< Touch channel 9 is GPIO9 */ TOUCH_PAD_NUM10, /*!< Touch channel 10 is GPIO10 */ TOUCH_PAD_NUM11, /*!< Touch channel 11 is GPIO11 */ TOUCH_PAD_NUM12, /*!< Touch channel 12 is GPIO12 */ TOUCH_PAD_NUM13, /*!< Touch channel 13 is GPIO13 */ TOUCH_PAD_NUM14, /*!< Touch channel 14 is GPIO14 */ TOUCH_PAD_MAX, } touch_pad_t; /** Touch sensor high reference voltage */ typedef enum { TOUCH_HVOLT_KEEP = -1, /*!= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); } void setUp(void) { before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); } void tearDown(void) { size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); check_leak(before_free_8bit, after_free_8bit, "8BIT"); check_leak(before_free_32bit, after_free_32bit, "32BIT"); } void app_main(void) { unity_run_menu(); } ================================================ FILE: touch_element/test_apps/main/test_touch_button.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "unity.h" #include "esp_random.h" #include "esp_private/touch_element_private.h" #include "esp_private/touch_sensor_legacy_ll.h" #include "touch_element/touch_button.h" static portMUX_TYPE test_button_spinlock = portMUX_INITIALIZER_UNLOCKED; #define TEST_BUTTON_ENTER_CRITICAL() portENTER_CRITICAL(&test_button_spinlock) #define TEST_BUTTON_EXIT_CRITICAL() portEXIT_CRITICAL(&test_button_spinlock) static const touch_pad_t button_channel_array[14] = { TOUCH_PAD_NUM1, TOUCH_PAD_NUM2, TOUCH_PAD_NUM3, TOUCH_PAD_NUM4, TOUCH_PAD_NUM5, TOUCH_PAD_NUM6, TOUCH_PAD_NUM7, TOUCH_PAD_NUM8, TOUCH_PAD_NUM9, TOUCH_PAD_NUM10, TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, TOUCH_PAD_NUM13, TOUCH_PAD_NUM14, }; const uint8_t BUTTON_CHANNEL_NUM = sizeof(button_channel_array) / sizeof(touch_pad_t); typedef struct { QueueHandle_t valid_msg_handle; SemaphoreHandle_t response_sig_handle; } test_monitor_t; typedef struct { QueueHandle_t valid_msg_handle; SemaphoreHandle_t response_sig_handle; touch_button_handle_t button_handle; } test_concurrent_monitor_t; /* ------------------------------------------------------------------------------------------------------------------ */ void test_button_event_simulator(touch_button_handle_t button_handle, touch_button_event_t button_event); void test_button_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); static void test_button_callback_check(touch_button_handle_t current_handle, touch_button_message_t *current_message, touch_elem_message_t *valid_message); void test_button_event_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event); void test_button_callback_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event, bool should_trigger, test_monitor_t *monitor); /* ------------------------------------------------ Dispatch method test -------------------------------------------- */ static void test_button_disp_event(void); static void test_button_disp_callback(void); void test_button_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg); /* ------------------------------------------------ Run-time test --------------------------------------------------- */ static void test_button_event_change_lp(void); static void test_button_callback_change_lp(void); static void test_button_change_lp_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg); /* ------------------------------------------------ Concurrent test ------------------------------------------------- */ static void test_button_event_concurrent(void); static void test_button_random_trigger_concurrent(void); void test_random_trigger_concurrent_task(void *arg); static void random_trigger_concurrent_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg); /* ------------------------------------------------------------------------------------------------------------------ */ TEST_CASE("Touch button dispatch methods test", "[button][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_button_disp_event(); test_button_disp_callback(); touch_element_uninstall(); } TEST_CASE("Touch button run-time test", "[button][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_button_event_change_lp(); test_button_callback_change_lp(); touch_element_uninstall(); } TEST_CASE("Touch button concurrent test", "[button][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_button_event_concurrent(); test_button_random_trigger_concurrent(); touch_element_uninstall(); } void test_button_event_simulator(touch_button_handle_t button_handle, touch_button_event_t button_event) { te_button_handle_t te_button = (te_button_handle_t) button_handle; touch_pad_t channel = te_button->device->channel; if (button_event == TOUCH_BUTTON_EVT_ON_PRESS) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else if (button_event == TOUCH_BUTTON_EVT_ON_RELEASE) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } } void test_button_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message) { TEST_ASSERT_MESSAGE(current_message->handle == valid_message->handle, "check handle failed"); TEST_ASSERT_MESSAGE(current_message->element_type == valid_message->element_type, "check element type failed"); const touch_button_message_t *valid_button_message = touch_button_get_message(valid_message); const touch_button_message_t *current_button_message = touch_button_get_message(current_message); TEST_ASSERT_MESSAGE(current_button_message->event == valid_button_message->event, "check event failed"); } static void test_button_callback_check(touch_button_handle_t current_handle, touch_button_message_t *current_message, touch_elem_message_t *valid_message) { const touch_button_message_t *valid_button_message = touch_button_get_message(valid_message); TEST_ASSERT_MESSAGE(valid_message->handle == current_handle, "check handle failed"); TEST_ASSERT_MESSAGE(valid_message->element_type == TOUCH_ELEM_TYPE_BUTTON, "check element type failed"); TEST_ASSERT_MESSAGE(valid_button_message->event == current_message->event, "check event failed"); } void test_button_event_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event) { //TODO: refactor this with a constructor touch_elem_message_t valid_message = { .handle = handle, .element_type = TOUCH_ELEM_TYPE_BUTTON, .arg = NULL, }; touch_button_message_t button_message = { .event = button_event }; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message test_button_event_simulator(handle, button_event); //Trigger signal touch_elem_message_t current_message; te_button_handle_t te_button = handle; esp_err_t ret = touch_element_message_receive(¤t_message, pdMS_TO_TICKS(2 * te_button->trigger_thr * 10)); TEST_ASSERT_MESSAGE(ret == ESP_OK, "button event receive timeout"); test_button_event_check(&valid_message, ¤t_message); //Verification } void test_button_callback_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event, bool should_trigger, test_monitor_t *monitor) { if (should_trigger) { touch_elem_message_t valid_message = { .handle = handle, .element_type = TOUCH_ELEM_TYPE_BUTTON, .arg = NULL }; touch_button_message_t button_message = { .event = button_event }; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); } test_button_event_simulator(handle, button_event); //Trigger signal te_button_handle_t te_button = handle; if (should_trigger) { //Verification BaseType_t os_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(2 * te_button->trigger_thr * 10)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "Button queue timeout"); } else { BaseType_t os_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdFALSE, "Button invalid trigger"); } } static void test_button_disp_event(void) { touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *) button_channel_array[i])); TEST_ESP_OK(touch_button_set_longpress(button_handle[i], 300)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); //10 times random press/longpress/release test printf("Touch button event test start\n"); for (int i = 0; i < 10; i++) { printf("Touch button event test... (%d/10)\n", i + 1); touch_button_handle_t current_handle = button_handle[random() % 14]; test_button_event_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS); test_button_event_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_LONGPRESS); test_button_event_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE); } printf("Touch button event test finish\n"); TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); } static void test_button_disp_callback(void) { test_monitor_t monitor; touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_button_global_config_t button_init = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&button_init)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE | TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *) &monitor)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_button_set_callback(button_handle[i], &test_button_handler)); TEST_ESP_OK(touch_button_set_longpress(button_handle[i], 300)); } TEST_ESP_OK(touch_element_start()); srandom((unsigned int)time(NULL)); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 //10 times random press/longpress/release test printf("Touch button callback test start\n"); for (int i = 0; i < 10; i++) { printf("Touch button callback test... (%d/10)\n", i + 1); touch_button_handle_t current_handle = button_handle[random() % 14]; test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS, true, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_LONGPRESS, true, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE, true, &monitor); } printf("Touch button callback test finish\n"); TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); } void test_button_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(200)); //Get the valid message for the verification, 500ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "test_button_handler: queue timeout"); test_button_callback_check(handle, message, &valid_message); xSemaphoreGive(monitor->response_sig_handle); } static void test_button_event_change_lp(void) { touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_LONGPRESS, NULL)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); //10 times random press/longpress/release test printf("Touch button event change longtime test start\n"); for (int i = 0; i < 10; i++) { printf("Touch button event change longtime test... (%d/10)\n", i + 1); esp_err_t ret = ESP_OK; uint8_t channel_index = random() % BUTTON_CHANNEL_NUM; touch_elem_message_t valid_message = { .handle = button_handle[channel_index], .element_type = TOUCH_ELEM_TYPE_BUTTON, .arg = NULL }; touch_button_message_t button_message = { .event = TOUCH_BUTTON_EVT_ON_LONGPRESS }; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message TEST_ESP_OK(touch_button_set_longpress(valid_message.handle, 200 + (i + 1) * 50)); test_button_event_simulator(valid_message.handle, button_message.event); //Trigger signal touch_elem_message_t current_message; ret = touch_element_message_receive(¤t_message, pdMS_TO_TICKS(10 * 1000)); TEST_ASSERT_MESSAGE(ret == ESP_OK, "button event LongPress timeout"); test_button_event_check(&valid_message, ¤t_message); //Verification test_button_event_simulator(valid_message.handle, TOUCH_BUTTON_EVT_ON_RELEASE); //Release the button. } printf("Touch button event change longtime test finish\n"); TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); } static void test_button_callback_change_lp(void) { test_monitor_t monitor; touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *)&monitor)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_button_set_callback(button_handle[i], &test_button_change_lp_handler)); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 //10 times random press/longpress/release test printf("Touch button event change longtime test start\n"); for (int i = 0; i < 10; i++) { printf("Touch button event change longtime test... (%d/10)\n", i + 1); uint8_t channel_index = 5; //Always this channel touch_elem_message_t valid_message = { .handle = button_handle[channel_index], .element_type = TOUCH_ELEM_TYPE_BUTTON, .arg = NULL, }; touch_button_message_t button_message = { .event = TOUCH_BUTTON_EVT_ON_LONGPRESS }; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(button_handle[channel_index], button_message.event); BaseType_t os_ret = xSemaphoreTake(monitor.response_sig_handle, pdMS_TO_TICKS(10 * 1000)); //100ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "Button LongPress queue timeout"); test_button_event_simulator(valid_message.handle, TOUCH_BUTTON_EVT_ON_RELEASE); //Reset hardware } printf("Touch button event change longtime test finish\n"); TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); } static void test_button_change_lp_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(200)); //Get the valid message for the verification, 500ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "test_button_handler: queue timeout"); test_button_callback_check(handle, message, &valid_message); xSemaphoreGive(monitor->response_sig_handle); TEST_ESP_OK(touch_button_set_longpress(valid_message.handle, 300)); // Always 300ms } static void test_button_event_concurrent(void) { touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 //10 times random press/longpress/release test printf("Touch button event concurrent test start\n"); for (int i = 0; i < 10; i++) { printf("Touch button event concurrent test... (%d/10)\n", i + 1); esp_err_t ret = ESP_OK; uint32_t message_count = 0; touch_elem_message_t current_message; TEST_BUTTON_ENTER_CRITICAL(); for (int idx = 0; idx < BUTTON_CHANNEL_NUM; idx++) { test_button_event_simulator(button_handle[idx], TOUCH_BUTTON_EVT_ON_PRESS); //All channels trigger } TEST_BUTTON_EXIT_CRITICAL(); message_count = 0; do { ret = touch_element_message_receive(¤t_message, pdMS_TO_TICKS(500)); if (ret == ESP_OK) { message_count++; } } while (ret == ESP_OK); TEST_ASSERT_MESSAGE(message_count == BUTTON_CHANNEL_NUM, "button concurrent Press failed"); TEST_BUTTON_ENTER_CRITICAL(); for (int idx = 0; idx < BUTTON_CHANNEL_NUM; idx++) { test_button_event_simulator(button_handle[idx], TOUCH_BUTTON_EVT_ON_RELEASE); //All channels trigger } TEST_BUTTON_EXIT_CRITICAL(); message_count = 0; do { ret = touch_element_message_receive(¤t_message, pdMS_TO_TICKS(500)); if (ret == ESP_OK) { message_count++; } } while (ret == ESP_OK); TEST_ASSERT_MESSAGE(message_count == BUTTON_CHANNEL_NUM, "button concurrent Release failed"); } printf("Touch button event concurrent test finish\n"); TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); } static void test_button_random_trigger_concurrent(void) { uint64_t sem_and_monitor[BUTTON_CHANNEL_NUM]; printf("Touch button random trigger concurrent test start\n"); test_concurrent_monitor_t monitor[BUTTON_CHANNEL_NUM]; SemaphoreHandle_t count_sem = xSemaphoreCreateCounting(BUTTON_CHANNEL_NUM, 0); touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (uint32_t i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; monitor[i].response_sig_handle = xSemaphoreCreateBinary(); monitor[i].valid_msg_handle = xQueueCreate(BUTTON_CHANNEL_NUM, sizeof(touch_elem_message_t)); TEST_ASSERT(monitor[i].valid_msg_handle != NULL && monitor[i].response_sig_handle != NULL); uintptr_t temp_count_sem = (uint32_t)count_sem; uintptr_t temp_monitor = (uint32_t)&monitor[i]; //Prevent compiler warning sem_and_monitor[i] = (uint64_t)(((uint64_t)temp_count_sem << 32) | (uint64_t) temp_monitor); TEST_ESP_OK(touch_button_create(&button_config, &monitor[i].button_handle)); TEST_ESP_OK(touch_button_subscribe_event(monitor[i].button_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_LONGPRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *)&sem_and_monitor[i])); TEST_ESP_OK(touch_button_set_longpress(monitor[i].button_handle, 500)); TEST_ESP_OK(touch_button_set_dispatch_method(monitor[i].button_handle, TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_button_set_callback(monitor[i].button_handle, &random_trigger_concurrent_handler)); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 for (uint32_t i = 0; i < BUTTON_CHANNEL_NUM; i++) { BaseType_t os_ret = xTaskCreate(test_random_trigger_concurrent_task, "test_random_trigger_concurrent_task", 1024 * 4, (void *)&sem_and_monitor[i], 10, NULL); TEST_ASSERT(os_ret == pdPASS); } uint32_t run_count = 0; while (1) { if (run_count++ % 1000 == 0) { printf("Touch button random trigger concurrent test running... (1/1)\n"); } uint8_t count = uxSemaphoreGetCount(count_sem); if (count == BUTTON_CHANNEL_NUM) { vTaskDelay(1); //Let IDLE task running and get tasks cleanup break; } vTaskDelay(1); } TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { vQueueDelete(monitor[i].valid_msg_handle); vSemaphoreDelete(monitor[i].response_sig_handle); TEST_ESP_OK(touch_button_delete(monitor[i].button_handle)); } touch_button_uninstall(); printf("Touch button random trigger concurrent test stop\n"); } void test_random_trigger_concurrent_task(void *arg) { uintptr_t temp_monitor = *((uint32_t *) arg); uintptr_t temp_count_sem = (*((uint64_t *) arg) >> 32); //Prevent compiler warning test_concurrent_monitor_t *monitor = (test_concurrent_monitor_t *)temp_monitor; SemaphoreHandle_t count_sem = (SemaphoreHandle_t) temp_count_sem; uint32_t start_delay_time = (esp_random() % 100) * 10; vTaskDelay(pdMS_TO_TICKS(start_delay_time)); touch_elem_message_t valid_message = { .handle = monitor->button_handle, .element_type = TOUCH_ELEM_TYPE_BUTTON, .arg = NULL, }; touch_button_message_t button_message; button_message.event = TOUCH_BUTTON_EVT_ON_PRESS; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(valid_message.handle, button_message.event); //Trigger signal BaseType_t res_sem_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(1000)); TEST_ASSERT_MESSAGE(res_sem_ret == pdPASS, "Response timeout"); uint32_t hold_state_time_ms = (esp_random() % 100) * 10 + 100; te_button_handle_t te_button = (te_button_handle_t) valid_message.handle; if ((int)(hold_state_time_ms - te_button->trigger_thr * 10) > 50) { //should raise longpress event button_message.event = TOUCH_BUTTON_EVT_ON_LONGPRESS; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(valid_message.handle, button_message.event); //Trigger signal res_sem_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(1000)); //+100 make sure it will really raise longpress event TEST_ASSERT_MESSAGE(res_sem_ret == pdPASS, "Response timeout"); } else { //should not raise longpress event //Do nothing } button_message.event = TOUCH_BUTTON_EVT_ON_RELEASE; memcpy(valid_message.child_msg, &button_message, sizeof(touch_button_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(valid_message.handle, button_message.event); //Trigger signal res_sem_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(1000)); TEST_ASSERT_MESSAGE(res_sem_ret == pdPASS, "Response timeout"); xSemaphoreGive(count_sem); vTaskDelete(NULL); } static void random_trigger_concurrent_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg) { uintptr_t temp_monitor = *((uint32_t *) arg); //Prevent compiler warning test_concurrent_monitor_t *monitor = (test_concurrent_monitor_t *) temp_monitor; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(1000)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "valid message timeout"); const touch_button_message_t *button_message = touch_button_get_message(&valid_message); if (button_message->event == TOUCH_BUTTON_EVT_ON_LONGPRESS) { touch_button_set_longpress(handle, portMAX_DELAY); //Prevent button triggers LongPress event again } TEST_ASSERT_MESSAGE(handle == valid_message.handle, "check handle failed"); TEST_ASSERT_MESSAGE(valid_message.element_type == TOUCH_ELEM_TYPE_BUTTON, "check element type failed"); TEST_ASSERT_MESSAGE(message->event == button_message->event, "check event failed"); xSemaphoreGive(monitor->response_sig_handle); } ================================================ FILE: touch_element/test_apps/main/test_touch_element.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ /* ---------------------------------------------------------- README ------------------------------------------------ * This doc is aimed at explain some important code block and do some records for the test result, if developer or * test-owner has some question in reading this code implementation, please read it first. * * CODE Block: * `code-block-1`: Touch Element lib need some time to finish the initialization so as to configure the right threshold. * Since some hardware issue(Must to pass 2 times "meas_done" interrupt), Touch Element lib will spend * some time(Maybe <30ms) to finish the initialization, every operations (interrupts) happen to touch * sensor will be ignored before initialization. That's why "vTaskDelay()" could be saw in after call * "touch_element_start()". However, this just for the unit test, in the real application, users don't * need to delay something. * * NOTES: * `Simulator`: Currently the event simulator depend on the touch sensor driver and play some "hack" in some register so * as to raise a FAKE interrupt. It means that Touch Element lib test case must be burned on dev-kit and keep * the touch channel as clean as possible, ESP32-S2-Saola is good test board. //TODO: Remove the dependent of touch sensor driver. ------------------------------------------------------------------------------------------------------------------ */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "unity.h" #include "touch_element/touch_button.h" #include "touch_element/touch_slider.h" #include "touch_element/touch_matrix.h" #include "esp_private/touch_sensor_legacy_ll.h" typedef struct { QueueHandle_t valid_msg_handle; SemaphoreHandle_t response_sig_handle; } test_monitor_t; /* ------------------------------------------------------------------------------------------------------------------ */ extern void test_button_event_simulator(touch_button_handle_t button_handle, touch_button_event_t button_event); extern void test_button_handler(touch_button_handle_t handle, touch_button_message_t *message, void *arg); extern void test_button_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); extern void test_button_event_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event); extern void test_button_callback_trigger_and_check(touch_button_handle_t handle, touch_button_event_t button_event, bool should_trigger, test_monitor_t *monitor); extern void test_slider_event_simulator(touch_slider_handle_t slider_handle, touch_slider_event_t slider_event, uint32_t random); extern void test_slider_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); extern void test_matrix_event_simulator(touch_matrix_handle_t matrix_handle, touch_matrix_event_t matrix_event, uint32_t pos_index); extern void test_matrix_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); /* ------------------------------------------------------------------------------------------------------------------ */ static void test_waterproof_event_simulator(touch_pad_t guard_channel, touch_button_event_t guard_state); static void test_system_waterproof_guard(void); static void test_integrat_btn_sld_mat(void); static void test_integration_monitor_task(void *arg); /* ------------------------------------------------------------------------------------------------------------------ */ TEST_CASE("Touch element integration test", "[touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_integrat_btn_sld_mat(); touch_element_uninstall(); } TEST_CASE("Touch element waterproof test", "[touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_system_waterproof_guard(); //TODO: add waterproof work with slider and matrix touch_element_uninstall(); } static void test_system_waterproof_guard(void) { static const touch_pad_t button_channel_array[12] = { TOUCH_PAD_NUM1, TOUCH_PAD_NUM2, TOUCH_PAD_NUM3, TOUCH_PAD_NUM4, TOUCH_PAD_NUM5, TOUCH_PAD_NUM6, TOUCH_PAD_NUM7, TOUCH_PAD_NUM8, TOUCH_PAD_NUM9, TOUCH_PAD_NUM10, TOUCH_PAD_NUM11, TOUCH_PAD_NUM12 }; const uint8_t BUTTON_CHANNEL_NUM = sizeof(button_channel_array) / sizeof(touch_pad_t); test_monitor_t monitor; touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_button_global_config_t global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = 0.1F }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *)&monitor)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_button_set_callback(button_handle[i], test_button_handler)); } printf("Touch Element waterproof guard sensor test start\n"); srandom((unsigned int)time(NULL)); { //No use waterproof guard sensor touch_elem_waterproof_config_t waterproof_config = { .guard_channel = TOUCH_WATERPROOF_GUARD_NOUSE, .guard_sensitivity = 0.0F }; TEST_ESP_OK(touch_element_waterproof_install(&waterproof_config)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 for (int i = 0; i < 10; i++) { //Start state test printf("Touch Element waterproof no-use guard sensor test... (%d/10)\n", i + 1); touch_button_handle_t current_handle = button_handle[random() % 12]; test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS, true, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE, true, &monitor); } TEST_ESP_OK(touch_element_stop()); touch_element_waterproof_uninstall(); } { //Use waterproof guard sensor(Add all handles) touch_elem_waterproof_config_t waterproof_config = { .guard_channel = TOUCH_PAD_NUM13, .guard_sensitivity = 0.1F }; TEST_ESP_OK(touch_element_waterproof_install(&waterproof_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_element_waterproof_add(button_handle[i])); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 for (int i = 0; i < 10; i++) { printf("Touch Element waterproof use guard sensor random trigger test... (%d/10)\n", i + 1); bool should_trigger = random() % 2; if (should_trigger) { touch_button_handle_t current_handle = button_handle[random() % 12]; test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS, should_trigger, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE, should_trigger, &monitor); } else { test_waterproof_event_simulator(waterproof_config.guard_channel, TOUCH_BUTTON_EVT_ON_PRESS); //Waterproof guard sensor trigger touch_button_handle_t current_handle = button_handle[random() % 12]; test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS, should_trigger, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE, should_trigger, &monitor); test_waterproof_event_simulator(waterproof_config.guard_channel, TOUCH_BUTTON_EVT_ON_RELEASE); //Waterproof guard sensor release } } TEST_ESP_OK(touch_element_stop()); touch_element_waterproof_uninstall(); } { //Put half button handles into guard ring const uint8_t protect_handle_threshold = BUTTON_CHANNEL_NUM / 2; touch_elem_waterproof_config_t waterproof_config = { .guard_channel = TOUCH_PAD_NUM13, .guard_sensitivity = 0.1F }; TEST_ESP_OK(touch_element_waterproof_install(&waterproof_config)); for (int i = 0; i < protect_handle_threshold; i++) { TEST_ESP_OK(touch_element_waterproof_add(button_handle[i])); } TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 for (int i = 0; i < 10; i++) { printf("Touch Element waterproof use guard sensor test(guard sensor is triggered will half button handles)... (%d/10)\n", i + 1); test_waterproof_event_simulator(waterproof_config.guard_channel, TOUCH_BUTTON_EVT_ON_PRESS); //Waterproof guard sensor trigger uint32_t handle_index = random() % 12; touch_button_handle_t current_handle = button_handle[handle_index]; bool should_trigger = (handle_index < protect_handle_threshold) ? false : true; test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_PRESS, should_trigger, &monitor); test_button_callback_trigger_and_check(current_handle, TOUCH_BUTTON_EVT_ON_RELEASE, should_trigger, &monitor); test_waterproof_event_simulator(waterproof_config.guard_channel, TOUCH_BUTTON_EVT_ON_RELEASE); //Waterproof guard sensor release } TEST_ESP_OK(touch_element_stop()); touch_element_waterproof_uninstall(); } for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } touch_button_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); printf("Touch Element waterproof guard sensor test finish\n"); } static void test_waterproof_event_simulator(touch_pad_t guard_channel, touch_button_event_t guard_state) { if (guard_state == TOUCH_BUTTON_EVT_ON_PRESS) { touch_ll_set_slope(guard_channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(guard_channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else if (guard_state == TOUCH_BUTTON_EVT_ON_RELEASE) { touch_ll_set_slope(guard_channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(guard_channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else { printf("guard sensor simulator doesn't support this operation\n"); } /* Fixme: If the normal instance and guard sensor trigger at the same time, guard sensor will lock the state failed */ vTaskDelay(pdMS_TO_TICKS(100)); } static void test_integrat_btn_sld_mat(void) { static const touch_pad_t button_channel_array[3] = { TOUCH_PAD_NUM1, TOUCH_PAD_NUM2, TOUCH_PAD_NUM3 }; static const float button_sens_array[3] = { 0.1F, 0.1F, 0.1F }; static const touch_pad_t slider_channel_array[5] = { TOUCH_PAD_NUM4, TOUCH_PAD_NUM5, TOUCH_PAD_NUM6, TOUCH_PAD_NUM7, TOUCH_PAD_NUM8 }; static const float slider_sens_array[5] = { 0.1F, 0.1F, 0.1F, 0.1F, 0.1F }; static const touch_pad_t x_axis_channel[3] = { TOUCH_PAD_NUM9, TOUCH_PAD_NUM10, TOUCH_PAD_NUM11, }; static const touch_pad_t y_axis_channel[3] = { TOUCH_PAD_NUM12, TOUCH_PAD_NUM13, TOUCH_PAD_NUM14, }; static const float x_axis_channel_sens[3] = { 0.1F, 0.1F, 0.1F, }; static const float y_axis_channel_sens[3] = { 0.1F, 0.1F, 0.1F, }; const uint8_t BUTTON_CHANNEL_NUM = sizeof(button_channel_array) / sizeof(touch_pad_t); const uint8_t SLIDER_CHANNEL_NUM = sizeof(slider_channel_array) / sizeof(touch_pad_t); const uint8_t MATRIX_CHANNEL_NUM_X = sizeof(x_axis_channel) / sizeof(touch_pad_t); const uint8_t MATRIX_CHANNEL_NUM_Y = sizeof(y_axis_channel) / sizeof(touch_pad_t); printf("Integration test(button + slider + matrix) start\n"); BaseType_t os_ret; touch_button_handle_t button_handle[BUTTON_CHANNEL_NUM]; touch_slider_handle_t slider_handle; touch_matrix_handle_t matrix_handle; test_monitor_t monitor; TaskHandle_t task_handle = NULL; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); os_ret = xTaskCreate(&test_integration_monitor_task, "test_integration_monitor_task", 4096, (void *)&monitor, 5, &task_handle); TEST_ASSERT(monitor.valid_msg_handle != NULL && monitor.response_sig_handle != NULL && os_ret == pdPASS); touch_button_global_config_t button_global_config = TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG(); touch_slider_global_config_t slider_global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); touch_matrix_global_config_t matrix_global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_button_install(&button_global_config)); TEST_ESP_OK(touch_slider_install(&slider_global_config)); TEST_ESP_OK(touch_matrix_install(&matrix_global_config)); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { touch_button_config_t button_config = { .channel_num = button_channel_array[i], .channel_sens = button_sens_array[i] }; TEST_ESP_OK(touch_button_create(&button_config, &button_handle[i])); TEST_ESP_OK(touch_button_subscribe_event(button_handle[i], TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL)); TEST_ESP_OK(touch_button_set_dispatch_method(button_handle[i], TOUCH_ELEM_DISP_EVENT)); } touch_slider_config_t slider_config = { .channel_array = slider_channel_array, .sensitivity_array = slider_sens_array, .channel_num = SLIDER_CHANNEL_NUM, .position_range = 101 }; TEST_ESP_OK(touch_slider_create(&slider_config, &slider_handle)); TEST_ESP_OK(touch_slider_subscribe_event(slider_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL)); TEST_ESP_OK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_EVENT)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_EVENT)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); for (int i = 0; i < 30; i++) { printf("Integration test... (%d/30)\n", i + 1); touch_elem_message_t valid_message; touch_button_message_t button_message; touch_slider_message_t slider_message; touch_matrix_message_t matrix_message; valid_message.element_type = (random() % (TOUCH_ELEM_TYPE_MATRIX + 1)); if (valid_message.element_type == TOUCH_ELEM_TYPE_BUTTON) { uint32_t button_index = random() % BUTTON_CHANNEL_NUM; valid_message.handle = button_handle[button_index]; button_message.event = TOUCH_BUTTON_EVT_ON_PRESS; memcpy(valid_message.child_msg, &button_message, sizeof(button_message)); //Construct child message xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(valid_message.handle, button_message.event); } else if (valid_message.element_type == TOUCH_ELEM_TYPE_SLIDER) { valid_message.handle = slider_handle; slider_message.event = TOUCH_SLIDER_EVT_ON_PRESS; slider_message.position = 0; //No check memcpy(valid_message.child_msg, &slider_message, sizeof(slider_message)); //Construct child message xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_slider_event_simulator(valid_message.handle, slider_message.event, 1); } else if (valid_message.element_type == TOUCH_ELEM_TYPE_MATRIX) { uint32_t matrix_x_axis_index = random() % MATRIX_CHANNEL_NUM_X; uint32_t matrix_y_axis_index = random() % MATRIX_CHANNEL_NUM_Y; valid_message.handle = matrix_handle; matrix_message.event = TOUCH_MATRIX_EVT_ON_PRESS; matrix_message.position.x_axis = matrix_x_axis_index; matrix_message.position.y_axis = matrix_y_axis_index; matrix_message.position.index = matrix_x_axis_index * MATRIX_CHANNEL_NUM_Y + matrix_y_axis_index; memcpy(valid_message.child_msg, &matrix_message, sizeof(matrix_message)); //Construct child message xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_matrix_event_simulator(valid_message.handle, matrix_message.event, matrix_message.position.index); } else { TEST_ABORT(); } os_ret = xSemaphoreTake(monitor.response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "response queue timeout (500ms)"); if (valid_message.element_type == TOUCH_ELEM_TYPE_BUTTON) { button_message.event = TOUCH_BUTTON_EVT_ON_RELEASE; memcpy(valid_message.child_msg, &button_message, sizeof(button_message)); xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_button_event_simulator(valid_message.handle, button_message.event); } else if (valid_message.element_type == TOUCH_ELEM_TYPE_SLIDER) { slider_message.event = TOUCH_SLIDER_EVT_ON_RELEASE; memcpy(valid_message.child_msg, &slider_message, sizeof(slider_message)); xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); test_slider_event_simulator(valid_message.handle, slider_message.event, 1); } else if (valid_message.element_type == TOUCH_ELEM_TYPE_MATRIX) { matrix_message.event = TOUCH_MATRIX_EVT_ON_RELEASE; memcpy(valid_message.child_msg, &matrix_message, sizeof(matrix_message)); xQueueSend(monitor.valid_msg_handle, &valid_message, portMAX_DELAY); const touch_matrix_message_t *matrix_message_ptr = touch_matrix_get_message(&valid_message); test_matrix_event_simulator(valid_message.handle, matrix_message.event, matrix_message_ptr->position.index); } os_ret = xSemaphoreTake(monitor.response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "response queue timeout (500ms)"); } TEST_ESP_OK(touch_element_stop()); for (int i = 0; i < BUTTON_CHANNEL_NUM; i++) { TEST_ESP_OK(touch_button_delete(button_handle[i])); } TEST_ESP_OK(touch_slider_delete(slider_handle)); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_button_uninstall(); touch_slider_uninstall(); touch_matrix_uninstall(); while (eTaskGetState(task_handle) == eRunning) { vTaskDelay(pdTICKS_TO_MS(1)); } vTaskDelete(task_handle); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); printf("Integration test(button + slider + matrix) finish\n"); } static void test_integration_monitor_task(void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; BaseType_t os_ret; touch_elem_message_t current_message, valid_message; while (1) { touch_element_message_receive(¤t_message, portMAX_DELAY); os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(500)); //Get the valid message for the verification, 500ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "trigger queue timeout (500ms)"); if (current_message.element_type == TOUCH_ELEM_TYPE_BUTTON) { test_button_event_check(&valid_message, ¤t_message); } else if (current_message.element_type == TOUCH_ELEM_TYPE_SLIDER) { test_slider_event_check(&valid_message, ¤t_message); } else if (current_message.element_type == TOUCH_ELEM_TYPE_MATRIX) { test_matrix_event_check(&valid_message, ¤t_message); } xSemaphoreGive(monitor->response_sig_handle); } } ================================================ FILE: touch_element/test_apps/main/test_touch_matrix.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "unity.h" #include "esp_private/touch_element_private.h" #include "esp_private/touch_sensor_legacy_ll.h" #include "touch_element/touch_matrix.h" static const touch_pad_t x_axis_channel[3] = { TOUCH_PAD_NUM5, TOUCH_PAD_NUM7, TOUCH_PAD_NUM9, }; static const touch_pad_t y_axis_channel[3] = { TOUCH_PAD_NUM11, TOUCH_PAD_NUM12, TOUCH_PAD_NUM14, }; static const float x_axis_channel_sens[3] = { 0.1F, 0.1F, 0.1F, }; static const float y_axis_channel_sens[3] = { 0.1F, 0.1F, 0.1F, }; const uint8_t MATRIX_CHANNEL_NUM_X = sizeof(x_axis_channel) / sizeof(touch_pad_t); const uint8_t MATRIX_CHANNEL_NUM_Y = sizeof(y_axis_channel) / sizeof(touch_pad_t); typedef struct { QueueHandle_t valid_msg_handle; SemaphoreHandle_t response_sig_handle; } test_monitor_t; /* ------------------------------------------------------------------------------------------------------------------ */ void test_matrix_event_simulator(touch_matrix_handle_t matrix_handle, touch_matrix_event_t matrix_event, uint32_t pos_index); static void test_matrix_channel_simulator(touch_pad_t channel, touch_matrix_event_t matrix_event); void test_matrix_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); static void test_matrix_callback_check(touch_matrix_handle_t current_handle, touch_matrix_message_t *current_message, touch_elem_message_t *valid_message); void test_matrix_event_trigger_and_check(touch_matrix_handle_t handle, touch_matrix_event_t matrix_event, uint32_t pos_index); void test_matrix_callback_trigger_and_check(touch_matrix_handle_t handle, touch_matrix_event_t matrix_event, uint32_t pos_index, bool should_trigger, test_monitor_t *monitor); /* ------------------------------------------------ Dispatch method test -------------------------------------------- */ static void test_matrix_disp_event(void); static void test_matrix_disp_callback(void); static void test_matrix_handler(touch_matrix_handle_t handle, touch_matrix_message_t *message, void *arg); /* ------------------------------------------------ Run-time test --------------------------------------------------- */ static void test_matrix_event_change_lp(void); static void test_matrix_callback_change_lp(void); static void test_matrix_change_lp_handler(touch_matrix_handle_t out_handle, touch_matrix_message_t *out_message, void *arg); /* ----------------------------------------------- Random channel trigger test -------------------------------------- */ static void test_matrix_random_channel_trigger(void); /* ------------------------------------------------------------------------------------------------------------------ */ TEST_CASE("Touch matrix dispatch methods test", "[matrix][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_matrix_disp_event(); test_matrix_disp_callback(); touch_element_uninstall(); } TEST_CASE("Touch matrix run-time test", "[matrix][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_matrix_event_change_lp(); test_matrix_callback_change_lp(); touch_element_uninstall(); } TEST_CASE("Touch matrix random channel trigger test", "[matrix][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_matrix_random_channel_trigger(); touch_element_uninstall(); } void test_matrix_event_simulator(touch_matrix_handle_t matrix_handle, touch_matrix_event_t matrix_event, uint32_t pos_index) { te_matrix_handle_t te_matrix = (te_matrix_handle_t) matrix_handle; touch_pad_t x_channel = te_matrix->device[pos_index / te_matrix->y_channel_num]->channel; touch_pad_t y_channel = te_matrix->device[te_matrix->x_channel_num + (pos_index % te_matrix->y_channel_num)]->channel; if (matrix_event == TOUCH_MATRIX_EVT_ON_PRESS) { touch_ll_set_slope(x_channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(x_channel, TOUCH_PAD_TIE_OPT_DEFAULT); touch_ll_set_slope(y_channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(y_channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else if (matrix_event == TOUCH_MATRIX_EVT_ON_RELEASE) { touch_ll_set_slope(x_channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(x_channel, TOUCH_PAD_TIE_OPT_DEFAULT); touch_ll_set_slope(y_channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(y_channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else { touch_ll_set_slope(x_channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(x_channel, TOUCH_PAD_TIE_OPT_DEFAULT); touch_ll_set_slope(y_channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(y_channel, TOUCH_PAD_TIE_OPT_DEFAULT); } } static void test_matrix_channel_simulator(touch_pad_t channel, touch_matrix_event_t matrix_event) { if (matrix_event == TOUCH_MATRIX_EVT_ON_PRESS) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else if (matrix_event == TOUCH_MATRIX_EVT_ON_RELEASE) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } } void test_matrix_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message) { TEST_ASSERT_MESSAGE(current_message->handle == valid_message->handle, "check handle failed"); TEST_ASSERT_MESSAGE(current_message->element_type == valid_message->element_type, "check element type failed"); const touch_matrix_message_t *valid_matrix_message = touch_matrix_get_message(valid_message); const touch_matrix_message_t *current_matrix_message = touch_matrix_get_message(current_message); TEST_ASSERT_MESSAGE(current_matrix_message->event == valid_matrix_message->event, "check event failed"); TEST_ASSERT_MESSAGE(current_matrix_message->position.index == valid_matrix_message->position.index, "check index failed"); TEST_ASSERT_MESSAGE(current_matrix_message->position.x_axis == valid_matrix_message->position.x_axis, "check x_axis failed"); TEST_ASSERT_MESSAGE(current_matrix_message->position.y_axis == valid_matrix_message->position.y_axis, "check y_axis failed"); } static inline void test_matrix_callback_check(touch_matrix_handle_t current_handle, touch_matrix_message_t *current_message, touch_elem_message_t *valid_message) { const touch_matrix_message_t *valid_matrix_message = touch_matrix_get_message(valid_message); TEST_ASSERT_MESSAGE(valid_message->handle == current_handle, "check handle failed"); TEST_ASSERT_MESSAGE(valid_message->element_type == TOUCH_ELEM_TYPE_MATRIX, "check element type failed"); TEST_ASSERT_MESSAGE(valid_matrix_message->event == current_message->event, "check event failed"); TEST_ASSERT_MESSAGE(valid_matrix_message->position.index == current_message->position.index, "check index failed"); TEST_ASSERT_MESSAGE(valid_matrix_message->position.x_axis == current_message->position.x_axis, "check x_axis failed"); TEST_ASSERT_MESSAGE(valid_matrix_message->position.y_axis == current_message->position.y_axis, "check y_axis failed"); } void test_matrix_event_trigger_and_check(touch_matrix_handle_t handle, touch_matrix_event_t matrix_event, uint32_t pos_index) { touch_elem_message_t valid_message = { .handle = handle, .element_type = TOUCH_ELEM_TYPE_MATRIX, .arg = NULL }; touch_matrix_message_t matrix_message = { .event = matrix_event, .position.index = pos_index, .position.x_axis = pos_index / MATRIX_CHANNEL_NUM_Y, .position.y_axis = pos_index % MATRIX_CHANNEL_NUM_Y }; memcpy(valid_message.child_msg, &matrix_message, sizeof(touch_matrix_message_t)); //Construct valid_message test_matrix_event_simulator(handle, matrix_event, pos_index); //Trigger signal touch_elem_message_t current_message; te_matrix_handle_t te_matrix = handle; esp_err_t ret = touch_element_message_receive(¤t_message, pdMS_TO_TICKS(2 * te_matrix->trigger_thr * 10)); //Get current message for verification TEST_ASSERT_MESSAGE(ret == ESP_OK, "matrix event receive timeout"); test_matrix_event_check(&valid_message, ¤t_message); //Verification } void test_matrix_callback_trigger_and_check(touch_matrix_handle_t handle, touch_matrix_event_t matrix_event, uint32_t pos_index, bool should_trigger, test_monitor_t *monitor) { if (should_trigger) { touch_elem_message_t valid_message = { .handle = handle, .element_type = TOUCH_ELEM_TYPE_MATRIX, .arg = NULL }; touch_matrix_message_t matrix_message = { .event = matrix_event, .position.index = pos_index, .position.x_axis = pos_index / MATRIX_CHANNEL_NUM_Y, .position.y_axis = pos_index % MATRIX_CHANNEL_NUM_Y }; memcpy(valid_message.child_msg, &matrix_message, sizeof(touch_matrix_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); } test_matrix_event_simulator(handle, matrix_event, pos_index); //Trigger signal te_matrix_handle_t te_matrix = handle; if (should_trigger) { BaseType_t os_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(2 * te_matrix->trigger_thr * 10)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "Matrix queue timeout"); } else { BaseType_t os_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdFALSE, "Matrix invalid trigger"); } } static void test_matrix_disp_event(void) { touch_matrix_handle_t matrix_handle = NULL; touch_matrix_global_config_t global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_matrix_install(&global_config)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_LONGPRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL)); TEST_ESP_OK(touch_matrix_set_longpress(matrix_handle, 300)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_EVENT)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); printf("Touch matrix event test start\n"); for (int i = 0; i < 10; i++) { printf("Touch matrix event test... (%d/10)\n", i + 1); uint32_t button_num = random() % ( MATRIX_CHANNEL_NUM_X * MATRIX_CHANNEL_NUM_Y ); test_matrix_event_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_PRESS, button_num); test_matrix_event_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_LONGPRESS, button_num); test_matrix_event_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_RELEASE, button_num); } printf("Touch matrix event test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_matrix_uninstall(); } static void test_matrix_disp_callback(void) { test_monitor_t monitor; touch_matrix_handle_t matrix_handle = NULL; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_matrix_global_config_t global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_matrix_install(&global_config)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_LONGPRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *)&monitor)); TEST_ESP_OK(touch_matrix_set_longpress(matrix_handle, 300)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_matrix_set_callback(matrix_handle, test_matrix_handler)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); printf("Touch matrix callback test start\n"); for (int i = 0; i < 10; i++) { printf("Touch matrix callback test... (%d/10)\n", i + 1); uint32_t button_num = random() % (MATRIX_CHANNEL_NUM_X * MATRIX_CHANNEL_NUM_Y); test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_PRESS, button_num, true, &monitor); test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_LONGPRESS, button_num, true, &monitor); test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_RELEASE, button_num, true, &monitor); } printf("Touch matrix callback test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_matrix_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); } static void test_matrix_handler(touch_matrix_handle_t handle, touch_matrix_message_t *message, void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(200)); TEST_ASSERT_MESSAGE(os_ret == pdPASS, "test_matrix_handler: queue timeout"); test_matrix_callback_check(handle, message, &valid_message); xSemaphoreGive(monitor->response_sig_handle); } static void test_matrix_random_channel_trigger(void) { test_monitor_t monitor; touch_matrix_handle_t matrix_handle = NULL; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_matrix_global_config_t global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_matrix_install(&global_config)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *) &monitor)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_matrix_set_callback(matrix_handle, test_matrix_handler)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); printf("Touch matrix random channel trigger test start\n"); for (int i = 0; i < 10; i++) { printf("Touch matrix random channel trigger test... (%d/10)\n", i + 1); uint32_t channel_index_1 = random() % (MATRIX_CHANNEL_NUM_X + MATRIX_CHANNEL_NUM_Y); uint32_t channel_index_2 = random() % (MATRIX_CHANNEL_NUM_X + MATRIX_CHANNEL_NUM_Y); touch_pad_t channel_1 = (channel_index_1 < MATRIX_CHANNEL_NUM_X) ? x_axis_channel[channel_index_1] : y_axis_channel[channel_index_1 - MATRIX_CHANNEL_NUM_X]; touch_pad_t channel_2 = (channel_index_2 < MATRIX_CHANNEL_NUM_X) ? x_axis_channel[channel_index_2] : y_axis_channel[channel_index_2 - MATRIX_CHANNEL_NUM_X]; if ((channel_index_1 <= 2 && channel_index_2 <= 2) || (channel_index_1 > 2 && channel_index_2 > 2)) { //all x channels triggered or all y channels triggered //Should not be triggered BaseType_t os_ret; test_matrix_channel_simulator(channel_1, TOUCH_MATRIX_EVT_ON_PRESS); test_matrix_channel_simulator(channel_2, TOUCH_MATRIX_EVT_ON_PRESS); os_ret = xSemaphoreTake(monitor.response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdFAIL, "Matrix Press event invalid trigger"); test_matrix_channel_simulator(channel_1, TOUCH_MATRIX_EVT_ON_RELEASE); test_matrix_channel_simulator(channel_2, TOUCH_MATRIX_EVT_ON_RELEASE); os_ret = xSemaphoreTake(monitor.response_sig_handle, pdMS_TO_TICKS(500)); TEST_ASSERT_MESSAGE(os_ret == pdFAIL, "Matrix Release event invalid trigger"); } else { //Should be triggered uint8_t button_num; if (channel_index_1 <= 2) { button_num = channel_index_1 * matrix_config.y_channel_num + (channel_index_2 - MATRIX_CHANNEL_NUM_X); } else { button_num = channel_index_2 * matrix_config.x_channel_num + (channel_index_1 - MATRIX_CHANNEL_NUM_Y); } test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_PRESS, button_num, true, &monitor); test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_RELEASE, button_num, true, &monitor); } } printf("Touch matrix random channel trigger test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_matrix_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); } static void test_matrix_event_change_lp(void) { touch_matrix_handle_t matrix_handle = NULL; touch_matrix_global_config_t global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_matrix_install(&global_config)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_LONGPRESS, NULL)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_EVENT)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); //10 times random press/longpress/release test printf("Touch matrix event change longtime test start\n"); for (int i = 0; i < 10; i++) { printf("Touch matrix event change longtime test... (%d/10)\n", i + 1); uint32_t button_num = random() % ( MATRIX_CHANNEL_NUM_X * MATRIX_CHANNEL_NUM_Y ); TEST_ESP_OK(touch_matrix_set_longpress(matrix_handle, 200 + (i + 1) * 50)); test_matrix_event_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_LONGPRESS, button_num); test_matrix_event_simulator(matrix_handle, TOUCH_MATRIX_EVT_ON_RELEASE, button_num); //Reset hardware vTaskDelay(pdMS_TO_TICKS(100)); //Fixme: Waiting for driver core handle release event } printf("Touch matrix event change longtime test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_matrix_uninstall(); } static void test_matrix_callback_change_lp(void) { test_monitor_t monitor; touch_matrix_handle_t matrix_handle = NULL; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_matrix_global_config_t global_config = TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_matrix_install(&global_config)); touch_matrix_config_t matrix_config = { .x_channel_array = x_axis_channel, .y_channel_array = y_axis_channel, .x_sensitivity_array = x_axis_channel_sens, .y_sensitivity_array = y_axis_channel_sens, .x_channel_num = MATRIX_CHANNEL_NUM_X, .y_channel_num = MATRIX_CHANNEL_NUM_Y }; TEST_ESP_OK(touch_matrix_create(&matrix_config, &matrix_handle)); TEST_ESP_OK(touch_matrix_subscribe_event(matrix_handle, TOUCH_ELEM_EVENT_ON_LONGPRESS, (void *)&monitor)); TEST_ESP_OK(touch_matrix_set_longpress(matrix_handle, 300)); TEST_ESP_OK(touch_matrix_set_dispatch_method(matrix_handle, TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_matrix_set_callback(matrix_handle, test_matrix_change_lp_handler)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); printf("Touch matrix callback change longtime test start\n"); for (int i = 0; i < 10; i++) { printf("Touch matrix callback change longtime test... (%d/10)\n", i + 1); uint32_t button_num = random() % (MATRIX_CHANNEL_NUM_X * MATRIX_CHANNEL_NUM_Y); test_matrix_callback_trigger_and_check(matrix_handle, TOUCH_MATRIX_EVT_ON_LONGPRESS, button_num, true, &monitor); test_matrix_event_simulator(matrix_handle, TOUCH_MATRIX_EVT_ON_RELEASE, button_num); //Reset hardware vTaskDelay(pdMS_TO_TICKS(100)); //Fixme: Waiting for driver core handle release event } printf("Touch matrix callback change longtime test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_matrix_delete(matrix_handle)); touch_matrix_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); } static void test_matrix_change_lp_handler(touch_matrix_handle_t out_handle, touch_matrix_message_t *out_message, void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(200)); //500ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "test_matrix_handler: queue timeout"); test_matrix_callback_check(out_handle, out_message, &valid_message); xSemaphoreGive(monitor->response_sig_handle); TEST_ESP_OK(touch_matrix_set_longpress(valid_message.handle, 300)); // Always 300ms } ================================================ FILE: touch_element/test_apps/main/test_touch_slider.c ================================================ /* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "unity.h" #include "esp_private/touch_element_private.h" #include "esp_private/touch_sensor_legacy_ll.h" #include "touch_element/touch_slider.h" static const touch_pad_t slider_channel_array[5] = { TOUCH_PAD_NUM1, TOUCH_PAD_NUM2, TOUCH_PAD_NUM3, TOUCH_PAD_NUM4, TOUCH_PAD_NUM5 }; static const float slider_sens_array[5] = { 0.1F, 0.1F, 0.1F, 0.1F, 0.1F }; const uint8_t SLIDER_CHANNEL_NUM = sizeof(slider_channel_array) / sizeof(touch_pad_t); typedef struct { QueueHandle_t valid_msg_handle; SemaphoreHandle_t response_sig_handle; } test_monitor_t; /* ------------------------------------------------------------------------------------------------------------------ */ void test_slider_event_simulator(touch_slider_handle_t slider_handle, touch_slider_event_t slider_event, uint32_t random); void test_slider_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message); static void test_slider_callback_check(touch_slider_handle_t current_handle, touch_slider_message_t *current_message, touch_elem_message_t *valid_message); void test_slider_event_trigger_and_check(touch_slider_handle_t handle, touch_slider_event_t slider_event, uint32_t random_channel); void test_slider_callback_trigger_and_check(touch_slider_handle_t handle, touch_slider_event_t slider_event, bool should_trigger, test_monitor_t *monitor, uint32_t random_channel); /* ------------------------------------------------ Dispatch method test -------------------------------------------- */ static void test_slider_disp_event(void); static void test_slider_disp_callback(void); static void test_slider_handler(touch_slider_handle_t handle, touch_slider_message_t *message, void *arg); /* ------------------------------------------------------------------------------------------------------------------ */ TEST_CASE("Touch slider dispatch methods test", "[slider][touch_element]") { touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_element_install(&global_config)); test_slider_disp_event(); test_slider_disp_callback(); touch_element_uninstall(); } void test_slider_event_simulator(touch_slider_handle_t slider_handle, touch_slider_event_t slider_event, uint32_t random) { te_slider_handle_t te_slider = (te_slider_handle_t) slider_handle; touch_pad_t channel = te_slider->device[random % te_slider->channel_sum]->channel; if (slider_event == TOUCH_SLIDER_EVT_ON_PRESS) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_3); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } else if (slider_event == TOUCH_SLIDER_EVT_ON_RELEASE) { touch_ll_set_slope(channel, TOUCH_PAD_SLOPE_7); touch_ll_set_tie_option(channel, TOUCH_PAD_TIE_OPT_DEFAULT); } } void test_slider_event_check(touch_elem_message_t *valid_message, touch_elem_message_t *current_message) { TEST_ASSERT_MESSAGE(current_message->handle == valid_message->handle, "check handle failed"); TEST_ASSERT_MESSAGE(current_message->element_type == valid_message->element_type, "check element type failed"); const touch_slider_message_t *valid_slider_message = touch_slider_get_message(valid_message); const touch_slider_message_t *current_slider_message = touch_slider_get_message(current_message); TEST_ASSERT_MESSAGE(current_slider_message->event == valid_slider_message->event, "check event failed"); } static void test_slider_callback_check(touch_slider_handle_t current_handle, touch_slider_message_t *current_message, touch_elem_message_t *valid_message) { const touch_slider_message_t *valid_slider_message = touch_slider_get_message(valid_message); TEST_ASSERT_MESSAGE(valid_message->handle == current_handle, "check handle failed"); TEST_ASSERT_MESSAGE(valid_message->element_type == TOUCH_ELEM_TYPE_SLIDER, "check element type failed"); TEST_ASSERT_MESSAGE(valid_slider_message->event == current_message->event, "check event failed"); } void test_slider_event_trigger_and_check(touch_slider_handle_t handle, touch_slider_event_t slider_event, uint32_t random_channel) { touch_elem_message_t valid_message, current_message; touch_slider_message_t slider_message; valid_message.handle = handle; valid_message.element_type = TOUCH_ELEM_TYPE_SLIDER; slider_message.event = slider_event; memcpy(valid_message.child_msg, &slider_message, sizeof(touch_slider_message_t)); test_slider_event_simulator(handle, slider_event, random_channel); esp_err_t ret = touch_element_message_receive(¤t_message, 300); TEST_ASSERT_MESSAGE(ret == ESP_OK, "slider event receive timeout"); test_slider_event_check(&valid_message, ¤t_message); } void test_slider_callback_trigger_and_check(touch_slider_handle_t handle, touch_slider_event_t slider_event, bool should_trigger, test_monitor_t *monitor, uint32_t random_channel) { if (should_trigger) { touch_elem_message_t valid_message = { .handle = handle, .element_type = TOUCH_ELEM_TYPE_SLIDER, .arg = NULL }; touch_slider_message_t slider_message = { .event = slider_event, .position = 0 //No use }; memcpy(valid_message.child_msg, &slider_message, sizeof(touch_slider_message_t)); //Construct valid_message xQueueSend(monitor->valid_msg_handle, &valid_message, portMAX_DELAY); } test_slider_event_simulator(handle, slider_event, random_channel); //Trigger signal BaseType_t os_ret = xSemaphoreTake(monitor->response_sig_handle, pdMS_TO_TICKS(300)); if (should_trigger) { TEST_ASSERT_MESSAGE(os_ret == pdPASS, "Button queue timeout"); } else { TEST_ASSERT_MESSAGE(os_ret == pdFALSE, "Button invalid trigger"); } } static void test_slider_disp_event(void) { touch_slider_handle_t slider_handle; touch_slider_global_config_t global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_slider_install(&global_config)); /*< Create Touch Slider */ touch_slider_config_t slider_config = { .channel_array = slider_channel_array, .sensitivity_array = slider_sens_array, .channel_num = SLIDER_CHANNEL_NUM, .position_range = 101 }; TEST_ESP_OK(touch_slider_create(&slider_config, &slider_handle)); TEST_ESP_OK(touch_slider_subscribe_event(slider_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *) slider_handle)); TEST_ESP_OK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_EVENT)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); //10 times random (x channels) press/release test printf("Touch slider event test start\n"); for (int i = 0; i < 10; i++) { printf("Touch slider event test... (%d/10)\n", i + 1); uint32_t random_channel = random() % SLIDER_CHANNEL_NUM; test_slider_event_trigger_and_check(slider_handle, TOUCH_SLIDER_EVT_ON_PRESS, random_channel); test_slider_event_trigger_and_check(slider_handle, TOUCH_SLIDER_EVT_ON_RELEASE, random_channel); } printf("Touch slider event test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_slider_delete(slider_handle)); touch_slider_uninstall(); } static void test_slider_disp_callback(void) { test_monitor_t monitor; monitor.valid_msg_handle = xQueueCreate(10, sizeof(touch_elem_message_t)); monitor.response_sig_handle = xSemaphoreCreateBinary(); TEST_ASSERT(monitor.valid_msg_handle != NULL || monitor.response_sig_handle != NULL); touch_slider_handle_t slider_handle; touch_slider_global_config_t global_config = TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG(); TEST_ESP_OK(touch_slider_install(&global_config)); touch_slider_config_t slider_config = { .channel_array = slider_channel_array, .sensitivity_array = slider_sens_array, .channel_num = SLIDER_CHANNEL_NUM, .position_range = 101 }; TEST_ESP_OK(touch_slider_create(&slider_config, &slider_handle)); TEST_ESP_OK(touch_slider_subscribe_event(slider_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, (void *) &monitor)); TEST_ESP_OK(touch_slider_set_dispatch_method(slider_handle, TOUCH_ELEM_DISP_CALLBACK)); TEST_ESP_OK(touch_slider_set_callback(slider_handle, test_slider_handler)); TEST_ESP_OK(touch_element_start()); vTaskDelay(pdMS_TO_TICKS(500)); //Mention in README, code-block-1 srandom((unsigned int)time(NULL)); printf("Touch slider callback test start\n"); for (int i = 0; i < 10; i++) { printf("Touch slider callback test... (%d/10)\n", i + 1); uint32_t random_channel = random() % SLIDER_CHANNEL_NUM; test_slider_callback_trigger_and_check(slider_handle, TOUCH_SLIDER_EVT_ON_PRESS, true, &monitor, random_channel); test_slider_callback_trigger_and_check(slider_handle, TOUCH_SLIDER_EVT_ON_RELEASE, true, &monitor, random_channel); } printf("Touch slider callback test finish\n"); TEST_ESP_OK(touch_element_stop()); TEST_ESP_OK(touch_slider_delete(slider_handle)); touch_slider_uninstall(); vQueueDelete(monitor.valid_msg_handle); vSemaphoreDelete(monitor.response_sig_handle); } static void test_slider_handler(touch_slider_handle_t handle, touch_slider_message_t *message, void *arg) { test_monitor_t *monitor = (test_monitor_t *)arg; touch_elem_message_t valid_message; BaseType_t os_ret = xQueueReceive(monitor->valid_msg_handle, &valid_message, pdMS_TO_TICKS(200)); //Get the valid message for verification, 200ms timeout TEST_ASSERT_MESSAGE(os_ret == pdPASS, "test_slider_handler: queue timeout"); test_slider_callback_check(handle, message, &valid_message); //Verification xSemaphoreGive(monitor->response_sig_handle); } ================================================ FILE: touch_element/test_apps/pytest_touch_element.py ================================================ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut import glob from pathlib import Path @pytest.mark.generic @pytest.mark.skipif( not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that not build" ) def test_touch_element(dut: Dut) -> None: dut.run_all_single_board_cases(timeout=120) ================================================ FILE: touch_element/test_apps/sdkconfig.defaults ================================================ CONFIG_FREERTOS_HZ=1000 CONFIG_ESP_TASK_WDT_EN=n CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 ================================================ FILE: touch_element/touch_button.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_log.h" #include "esp_private/touch_element_private.h" typedef struct te_button_handle_list { te_button_handle_t button_handle; //Button handle SLIST_ENTRY(te_button_handle_list) next; //Button handle list entry } te_button_handle_list_t; typedef struct { SLIST_HEAD(te_button_handle_list_head, te_button_handle_list) handle_list; //Button handle (instance) list touch_button_global_config_t *global_config; //Button global configuration SemaphoreHandle_t mutex; //Button object mutex } te_button_obj_t; static te_button_obj_t *s_te_btn_obj = NULL; //Button object /* ---------------------------------------- Button handle(instance) methods ----------------------------------------- */ static bool button_channel_check(te_button_handle_t button_handle, touch_pad_t channel_num); static esp_err_t button_set_threshold(te_button_handle_t button_handle); static inline te_state_t button_get_state(te_dev_t *device); static void button_reset_state(te_button_handle_t button_handle); static void button_update_state(te_button_handle_t button_handle, touch_pad_t channel_num, te_state_t channel_state); static void button_proc_state(te_button_handle_t button_handle); static void button_event_give(te_button_handle_t button_handle); static inline void button_dispatch(te_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method); /* ------------------------------------------ Button object(class) methods ------------------------------------------ */ static esp_err_t button_object_add_instance(te_button_handle_t button_handle); static esp_err_t button_object_remove_instance(te_button_handle_t button_handle); static bool button_object_check_channel(touch_pad_t channel_num); static esp_err_t button_object_set_threshold(void); static void button_object_process_state(void); static void button_object_update_state(touch_pad_t channel_num, te_state_t channel_state); /* ------------------------------------------------------------------------------------------------------------------ */ esp_err_t touch_button_install(const touch_button_global_config_t *global_config) { TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); //Fixme: Make it thread-safe s_te_btn_obj = (te_button_obj_t *)calloc(1, sizeof(te_button_obj_t)); TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_NO_MEM); s_te_btn_obj->global_config = (touch_button_global_config_t *)calloc(1, sizeof(touch_button_global_config_t)); s_te_btn_obj->mutex = xSemaphoreCreateMutex(); TE_CHECK_GOTO(s_te_btn_obj->global_config != NULL && s_te_btn_obj->mutex != NULL, cleanup); xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); SLIST_INIT(&s_te_btn_obj->handle_list); memcpy(s_te_btn_obj->global_config, global_config, sizeof(touch_button_global_config_t)); te_object_methods_t button_methods = { .handle = s_te_btn_obj, .check_channel = button_object_check_channel, .set_threshold = button_object_set_threshold, .process_state = button_object_process_state, .update_state = button_object_update_state }; te_object_method_register(&button_methods, TE_CLS_TYPE_BUTTON); xSemaphoreGive(s_te_btn_obj->mutex); return ESP_OK; cleanup: TE_FREE_AND_NULL(s_te_btn_obj->global_config); if (s_te_btn_obj->mutex != NULL) { vSemaphoreDelete(s_te_btn_obj->mutex); } TE_FREE_AND_NULL(s_te_btn_obj); return ESP_ERR_NO_MEM; } void touch_button_uninstall(void) { xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); if (s_te_btn_obj == NULL) { xSemaphoreGive(s_te_btn_obj->mutex); return; } te_object_method_unregister(TE_CLS_TYPE_BUTTON); free(s_te_btn_obj->global_config); s_te_btn_obj->global_config = NULL; while (!SLIST_EMPTY(&s_te_btn_obj->handle_list)) { SLIST_REMOVE_HEAD(&s_te_btn_obj->handle_list, next); } xSemaphoreGive(s_te_btn_obj->mutex); vSemaphoreDelete(s_te_btn_obj->mutex); free(s_te_btn_obj); s_te_btn_obj = NULL; } esp_err_t touch_button_create(const touch_button_config_t *button_config, touch_button_handle_t *button_handle) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL && button_config != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(button_config->channel_num > TOUCH_PAD_NUM0 && button_config->channel_num < TOUCH_PAD_MAX && button_config->channel_sens > 0, ESP_ERR_INVALID_ARG); TE_CHECK(te_object_check_channel(&button_config->channel_num, 1) == false, ESP_ERR_INVALID_ARG); te_button_handle_t te_button = (te_button_handle_t)calloc(1, sizeof(struct te_button_s)); TE_CHECK(te_button != NULL, ESP_ERR_NO_MEM); esp_err_t ret = ESP_ERR_NO_MEM; te_button->config = (te_button_handle_config_t *)calloc(1, sizeof(te_button_handle_config_t)); te_button->device = (te_dev_t *)calloc(1, sizeof(te_dev_t)); TE_CHECK_GOTO(te_button->config != NULL && te_button->device != NULL, cleanup); ret = te_dev_init(&te_button->device, 1, TOUCH_ELEM_TYPE_BUTTON, &button_config->channel_num, &button_config->channel_sens, TE_DEFAULT_THRESHOLD_DIVIDER(s_te_btn_obj)); TE_CHECK_GOTO(ret == ESP_OK, cleanup); te_button->config->event_mask = TOUCH_ELEM_EVENT_NONE; te_button->config->dispatch_method = TOUCH_ELEM_DISP_MAX; te_button->config->callback = NULL; te_button->config->arg = NULL; te_button->current_state = TE_STATE_IDLE; te_button->last_state = TE_STATE_IDLE; te_button->event = TOUCH_BUTTON_EVT_MAX; te_button->trigger_cnt = 0; te_button->trigger_thr = 0xffffffff; ret = button_object_add_instance(te_button); TE_CHECK_GOTO(ret == ESP_OK, cleanup); *button_handle = (touch_elem_handle_t)te_button; ESP_LOGD(TE_DEBUG_TAG, "channel: %d, channel_sens: %f", button_config->channel_num, button_config->channel_sens); return ESP_OK; cleanup: TE_FREE_AND_NULL(te_button->config); TE_FREE_AND_NULL(te_button->device); TE_FREE_AND_NULL(te_button); return ret; } esp_err_t touch_button_delete(touch_button_handle_t button_handle) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret = button_object_remove_instance(button_handle); TE_CHECK(ret == ESP_OK, ret); te_button_handle_t te_button = (te_button_handle_t)button_handle; te_dev_deinit(&te_button->device, 1); free(te_button->config); free(te_button->device); free(te_button); return ESP_OK; } esp_err_t touch_button_set_dispatch_method(touch_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); te_button_handle_t te_button = (te_button_handle_t)button_handle; te_button->config->dispatch_method = dispatch_method; xSemaphoreGive(s_te_btn_obj->mutex); return ESP_OK; } esp_err_t touch_button_subscribe_event(touch_button_handle_t button_handle, uint32_t event_mask, void *arg) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && !(event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) && !(event_mask & TOUCH_ELEM_EVENT_NONE)) { ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS event mask"); return ESP_ERR_INVALID_ARG; } if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { touch_button_set_longpress(button_handle, TE_DEFAULT_LONGPRESS_TIME(s_te_btn_obj)); //set the default time(1000ms) for long press } xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); te_button_handle_t te_button = (te_button_handle_t)button_handle; te_button->config->event_mask = event_mask; te_button->config->arg = arg; xSemaphoreGive(s_te_btn_obj->mutex); return ESP_OK; } esp_err_t touch_button_set_callback(touch_button_handle_t button_handle, touch_button_callback_t button_callback) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(button_callback != NULL, ESP_ERR_INVALID_ARG); te_button_handle_t te_button = (te_button_handle_t)button_handle; xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); te_button->config->callback = button_callback; xSemaphoreGive(s_te_btn_obj->mutex); return ESP_OK; } esp_err_t touch_button_set_longpress(touch_button_handle_t button_handle, uint32_t threshold_time) { TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(threshold_time > 0, ESP_ERR_INVALID_ARG); te_button_handle_t te_button = (te_button_handle_t)button_handle; touch_elem_dispatch_t dispatch_method = te_button->config->dispatch_method; if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); } uint8_t timer_period = te_get_timer_period(); te_button->trigger_thr = threshold_time / timer_period; if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { xSemaphoreGive(s_te_btn_obj->mutex); } return ESP_OK; } const touch_button_message_t *touch_button_get_message(const touch_elem_message_t *element_message) { return (touch_button_message_t *)&element_message->child_msg; _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_button_message_t), "Message size overflow"); } static bool button_object_check_channel(touch_pad_t channel_num) { te_button_handle_list_t *item; SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { if (button_channel_check(item->button_handle, channel_num)) { return true; } } return false; } static esp_err_t button_object_set_threshold(void) { esp_err_t ret = ESP_OK; te_button_handle_list_t *item; SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { ret = button_set_threshold(item->button_handle); if (ret != ESP_OK) { break; } } return ret; } static void button_object_process_state(void) { te_button_handle_list_t *item; SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { if (waterproof_check_mask_handle(item->button_handle)) { button_reset_state(item->button_handle); continue; } button_proc_state(item->button_handle); } } static void button_object_update_state(touch_pad_t channel_num, te_state_t channel_state) { te_button_handle_list_t *item; SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { if (waterproof_check_mask_handle(item->button_handle)) { continue; } button_update_state(item->button_handle, channel_num, channel_state); } } static esp_err_t button_object_add_instance(te_button_handle_t button_handle) { te_button_handle_list_t *item = (te_button_handle_list_t *)calloc(1, sizeof(te_button_handle_list_t)); TE_CHECK(item != NULL, ESP_ERR_NO_MEM); item->button_handle = button_handle; xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); SLIST_INSERT_HEAD(&s_te_btn_obj->handle_list, item, next); xSemaphoreGive(s_te_btn_obj->mutex); return ESP_OK; } static esp_err_t button_object_remove_instance(te_button_handle_t button_handle) { esp_err_t ret = ESP_ERR_NOT_FOUND; te_button_handle_list_t *item; SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { if (button_handle == item->button_handle) { xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); SLIST_REMOVE(&s_te_btn_obj->handle_list, item, te_button_handle_list, next); xSemaphoreGive(s_te_btn_obj->mutex); free(item); ret = ESP_OK; break; } } return ret; } bool is_button_object_handle(touch_elem_handle_t element_handle) { te_button_handle_list_t *item; xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { if (element_handle == item->button_handle) { xSemaphoreGive(s_te_btn_obj->mutex); return true; } } xSemaphoreGive(s_te_btn_obj->mutex); return false; } static bool button_channel_check(te_button_handle_t button_handle, touch_pad_t channel_num) { return (channel_num == button_handle->device->channel); } static esp_err_t button_set_threshold(te_button_handle_t button_handle) { return te_dev_set_threshold(button_handle->device); } static void button_update_state(te_button_handle_t button_handle, touch_pad_t channel_num, te_state_t channel_state) { te_dev_t *device = button_handle->device; if (channel_num != device->channel) { return; } device->state = channel_state; } static void button_reset_state(te_button_handle_t button_handle) { button_handle->trigger_cnt = 0; button_handle->current_state = TE_STATE_IDLE; button_handle->device->state = TE_STATE_IDLE; } static void button_event_give(te_button_handle_t button_handle) { touch_elem_message_t element_message; touch_button_message_t button_message = { .event = button_handle->event }; element_message.handle = (touch_elem_handle_t)button_handle; element_message.element_type = TOUCH_ELEM_TYPE_BUTTON; element_message.arg = button_handle->config->arg; memcpy(element_message.child_msg, &button_message, sizeof(button_message)); te_event_give(element_message); } static inline void button_dispatch(te_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method) { if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { button_event_give(button_handle); //Event queue } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { touch_button_message_t button_info; button_info.event = button_handle->event; button_handle->config->callback(button_handle, &button_info, button_handle->config->arg); //Event callback } } void button_enable_wakeup_calibration(te_button_handle_t button_handle, bool en) { button_handle->device->is_use_last_threshold = !en; } /** * @brief Button process * * This function will process the button state and maintain a button FSM: * IDLE ----> Press ----> Release ----> IDLE * * The state transition procedure is as follow: * (channel state ----> button state) * * TODO: add state transition diagram */ static void button_proc_state(te_button_handle_t button_handle) { uint32_t event_mask = button_handle->config->event_mask; touch_elem_dispatch_t dispatch_method = button_handle->config->dispatch_method; BaseType_t mux_ret = xSemaphoreTake(s_te_btn_obj->mutex, 0); if (mux_ret != pdPASS) { return; } button_handle->current_state = button_get_state(button_handle->device); if (button_handle->current_state == TE_STATE_PRESS) { if (button_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press ESP_LOGD(TE_DEBUG_TAG, "button press"); if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { button_handle->event = TOUCH_BUTTON_EVT_ON_PRESS; button_dispatch(button_handle, dispatch_method); } } else if (button_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_LongPress if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { if (++button_handle->trigger_cnt >= button_handle->trigger_thr) { ESP_LOGD(TE_DEBUG_TAG, "button longpress"); button_handle->event = TOUCH_BUTTON_EVT_ON_LONGPRESS; button_dispatch(button_handle, dispatch_method); button_handle->trigger_cnt = 0; } } } } else if (button_handle->current_state == TE_STATE_RELEASE) { if (button_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release ESP_LOGD(TE_DEBUG_TAG, "button release"); if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { button_handle->event = TOUCH_BUTTON_EVT_ON_RELEASE; button_dispatch(button_handle, dispatch_method); } } else if (button_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) button_reset_state(button_handle); //Reset the button state for the next time touch action detection } } else if (button_handle->current_state == TE_STATE_IDLE) { if (button_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) //Nothing } else if (button_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) //Nothing } } button_handle->last_state = button_handle->current_state; xSemaphoreGive(s_te_btn_obj->mutex); } static inline te_state_t button_get_state(te_dev_t *device) { te_state_t button_state; if (device->state == TE_STATE_PRESS) { button_state = TE_STATE_PRESS; } else if (device->state == TE_STATE_RELEASE) { button_state = TE_STATE_RELEASE; } else { button_state = TE_STATE_IDLE; } return button_state; } ================================================ FILE: touch_element/touch_element.c ================================================ /* * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/queue.h" #include "esp_sleep.h" #include "esp_timer.h" #include "esp_check.h" #include "esp_intr_alloc.h" #include "driver/rtc_io.h" #include "esp_private/rtc_ctrl.h" #include "esp_private/gpio.h" #include "soc/rtc_cntl_reg.h" #include "esp_private/touch_sensor_legacy_hal.h" #include "esp_private/touch_element_private.h" #include "esp_rom_sys.h" #define TE_CLASS_ITEM(cls, cls_type, cls_item) ((&((cls)[cls_type]))->cls_item) #define TE_CLASS_FOREACH(cls_var, cls_start, cls_end) \ for ((cls_var) = (cls_start); \ (cls_var) < (cls_end); \ (cls_var)++) #define TE_CLS_METHODS_INITIALIZER(cls, cls_start, cls_end) do { \ typeof(cls_start) cls_method; \ TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ TE_CLASS_ITEM(cls, cls_method, handle) = NULL; \ } \ } while (0) #define TE_CLASS_FOREACH_CHECK_CHANNEL(cls, cls_start, cls_end, channel) ({ \ bool ret = false; \ typeof(cls_start) cls_method; \ TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ ret |= TE_CLASS_ITEM(cls, cls_method, check_channel(channel)); \ } \ } \ ret; \ }) #define TE_CLASS_FOREACH_SET_THRESHOLD(cls, cls_start, cls_end) do { \ typeof(cls_start) cls_method; \ TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ TE_CLASS_ITEM(cls, cls_method, set_threshold()); \ } \ } \ } while (0) #define TE_CLASS_FOREACH_PROCESS_STATE(cls, cls_start, cls_end) do { \ typeof(cls_start) cls_method; \ TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ TE_CLASS_ITEM(cls, cls_method, process_state()); \ } \ } \ } while (0) #define TE_CLASS_FOREACH_UPDATE_STATE(cls, cls_start, cls_end, channel, state) do {\ typeof(cls_start) cls_method; \ TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ TE_CLASS_ITEM(cls, cls_method, update_state(channel, state)); \ } \ } \ } while (0) #define TE_PROCESSING_PERIOD(obj) ((obj)->global_config->software.processing_period) #define TE_WATERPROOF_DIVIDER(obj) ((obj)->global_config->software.waterproof_threshold_divider) #define TOUCH_GET_IO_NUM(channel) (touch_sensor_channel_io_map[channel]) typedef enum { TE_INTR_PRESS = 0, //Touch sensor press interrupt(TOUCH_LL_INTR_MASK_DONE) TE_INTR_RELEASE, //Touch sensor release interrupt(TOUCH_LL_INTR_MASK_DONE) TE_INTR_TIMEOUT, //Touch sensor scan timeout interrupt(TOUCH_LL_INTR_MASK_TIMEOUT) TE_INTR_SCAN_DONE, //Touch sensor scan done interrupt(TOUCH_LL_INTR_MASK_SCAN_DONE), now just use for setting threshold TE_INTR_MAX } te_intr_t; typedef struct { te_intr_t intr_type; //channel interrupt type te_state_t channel_state; //channel state touch_pad_t channel_num; //channel index } te_intr_msg_t; typedef struct { te_object_methods_t object_methods[TE_CLS_TYPE_MAX]; //Class(object) methods touch_elem_global_config_t *global_config; //Global initialization te_waterproof_handle_t waterproof_handle; //Waterproof configuration te_sleep_handle_t sleep_handle; esp_timer_handle_t proc_timer; //Processing timer handle QueueHandle_t event_msg_queue; //Application event message queue (for user) QueueHandle_t intr_msg_queue; //Interrupt message (for internal) SemaphoreHandle_t mutex; //Global resource mutex bool is_set_threshold; //Threshold configuration state bit uint32_t denoise_channel_raw; //De-noise channel(TO) raw signal } te_obj_t; static te_obj_t *s_te_obj = NULL; RTC_FAST_ATTR uint32_t threshold_shadow[TOUCH_PAD_MAX - 1] = {0}; /* Store IO number corresponding to the Touch Sensor channel number. */ /* Note: T0 is an internal channel that does not have a corresponding external GPIO. */ const int touch_sensor_channel_io_map[TOUCH_LL_CHAN_NUM] = { -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; /** * Internal de-noise channel(Touch channel 0) equivalent capacitance table, depends on hardware design * * Units: pF */ static const float denoise_channel_equ_cap[TOUCH_PAD_DENOISE_CAP_MAX] = {5.0f, 6.4f, 7.8f, 9.2f, 10.6f, 12.0f, 13.4f, 14.8f}; /** * Waterproof shield channel(Touch channel 14) equivalent capacitance table, depends on hardware design * * Units: pF */ static const float shield_channel_ref_cap[TOUCH_PAD_SHIELD_DRV_MAX] = {40.0f, 80.0f, 120.0f, 160.0f, 200.0f, 240.0f, 280.0f, 320.0f}; /* -------------------------------------------- Internal shared methods --------------------------------------------- */ /* ------------------------------------------------- */ /* ------------------------------------------------- System methods ------------------------------------------------- */ static esp_err_t te_hw_init(const touch_elem_hw_config_t *hardware_init); static esp_err_t te_sw_init(const touch_elem_sw_config_t *software_init); static inline float te_get_internal_equ_cap(touch_pad_denoise_cap_t denoise_level); static float te_channel_get_equ_cap(touch_pad_t channel_num); static uint32_t te_read_raw_signal(touch_pad_t channel_num); static void te_intr_cb(void *arg); static void te_proc_timer_cb(void *arg); static inline esp_err_t te_object_set_threshold(void); static inline void te_object_process_state(void); static inline void te_object_update_state(te_intr_msg_t te_intr_msg); /* ----------------------------------------------- Waterproof methods ----------------------------------------------- */ static inline bool waterproof_check_state(void); static inline bool waterproof_shield_check_state(void); static inline bool waterproof_guard_check_state(void); static bool waterproof_channel_check(touch_pad_t channel_num); static void waterproof_guard_set_threshold(void); static void waterproof_guard_update_state(touch_pad_t current_channel, te_state_t current_state); static touch_pad_shield_driver_t waterproof_get_shield_level(touch_pad_t guard_channel_num); /* ------------------------------------------------------------------------------------------------------------------ */ esp_err_t touch_element_install(const touch_elem_global_config_t *global_config) { TE_CHECK(s_te_obj == NULL, ESP_ERR_INVALID_STATE); TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); s_te_obj = (te_obj_t *)calloc(1, sizeof(te_obj_t)); TE_CHECK(s_te_obj != NULL, ESP_ERR_NO_MEM); esp_err_t ret = ESP_ERR_NO_MEM; s_te_obj->global_config = (touch_elem_global_config_t *)calloc(1, sizeof(touch_elem_global_config_t)); s_te_obj->mutex = xSemaphoreCreateMutex(); TE_CHECK_GOTO(s_te_obj->global_config != NULL && s_te_obj->mutex != NULL, cleanup); xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); TE_CLS_METHODS_INITIALIZER(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); ret = te_hw_init(&global_config->hardware); if (ret != ESP_OK) { abort(); } ret = te_sw_init(&global_config->software); if (ret != ESP_OK) { xSemaphoreGive(s_te_obj->mutex); goto cleanup; } xSemaphoreGive(s_te_obj->mutex); return ESP_OK; cleanup: TE_FREE_AND_NULL(s_te_obj->global_config); if (s_te_obj->mutex != NULL) { vSemaphoreDelete(s_te_obj->mutex); } TE_FREE_AND_NULL(s_te_obj); return ret; } esp_err_t touch_element_start(void) { TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); esp_err_t ret = ESP_OK; uint16_t inited_channel_mask; do { xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); touch_ll_get_channel_mask(&inited_channel_mask); if (inited_channel_mask == 0x0) { ESP_LOGE(TE_TAG, "Can not find Touch Sensor channel that has been initialized"); ret = ESP_ERR_INVALID_STATE; break; } s_te_obj->is_set_threshold = false; //Threshold configuration will be set on touch sense start ret = esp_timer_start_periodic(s_te_obj->proc_timer, TE_PROCESSING_PERIOD(s_te_obj) * 1000); if (ret != ESP_OK) { break; } touch_ll_intr_enable((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_SCAN_DONE); //Use scan done interrupt to set threshold touch_ll_start_fsm(); xQueueReset(s_te_obj->event_msg_queue); xQueueReset(s_te_obj->intr_msg_queue); xSemaphoreGive(s_te_obj->mutex); return ESP_OK; } while (0); ESP_LOGE(TE_TAG, "Touch interface start failed:(%s)", __FUNCTION__ ); xSemaphoreGive(s_te_obj->mutex); return ret; } esp_err_t touch_element_stop(void) { TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); esp_err_t ret = ESP_OK; xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); touch_ll_stop_fsm(); touch_ll_intr_disable((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_SCAN_DONE); ret = esp_timer_stop(s_te_obj->proc_timer); if (ret != ESP_OK) { return ret; } xSemaphoreGive(s_te_obj->mutex); return ESP_OK; } //TODO: add a new api that output system's run-time state void touch_element_uninstall(void) { xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); if (s_te_obj == NULL) { xSemaphoreGive(s_te_obj->mutex); return; } esp_err_t ret = ESP_OK; touch_hal_deinit(); ret = esp_timer_delete(s_te_obj->proc_timer); if (ret != ESP_OK) { abort(); } touch_ll_intr_disable((touch_pad_intr_mask_t)(TOUCH_LL_INTR_MASK_ACTIVE | TOUCH_LL_INTR_MASK_INACTIVE | TOUCH_LL_INTR_MASK_TIMEOUT)); ret = rtc_isr_deregister(te_intr_cb, NULL); if (ret != ESP_OK) { abort(); } vQueueDelete(s_te_obj->event_msg_queue); vQueueDelete(s_te_obj->intr_msg_queue); xSemaphoreGive(s_te_obj->mutex); vSemaphoreDelete(s_te_obj->mutex); free(s_te_obj->global_config); s_te_obj->global_config = NULL; free(s_te_obj); s_te_obj = NULL; } esp_err_t touch_element_message_receive(touch_elem_message_t *element_message, uint32_t ticks_to_wait) { //TODO: Use the generic data struct to refactor this api TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(element_message != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(s_te_obj->event_msg_queue != NULL, ESP_ERR_INVALID_STATE); int ret = xQueueReceive(s_te_obj->event_msg_queue, element_message, ticks_to_wait); return (ret == pdTRUE) ? ESP_OK : ESP_ERR_TIMEOUT; } static uint32_t te_read_raw_signal(touch_pad_t channel_num) { uint32_t raw_signal = 0; touch_pad_sleep_channel_t sleep_channel_info; touch_hal_sleep_channel_get_config(&sleep_channel_info); if (channel_num != sleep_channel_info.touch_num) { raw_signal = touch_ll_read_raw_data(channel_num); } else { touch_ll_sleep_read_data(&raw_signal); } return raw_signal; } uint32_t te_read_smooth_signal(touch_pad_t channel_num) { uint32_t smooth_signal = 0; touch_pad_sleep_channel_t sleep_channel_info; touch_hal_sleep_channel_get_config(&sleep_channel_info); if (channel_num != sleep_channel_info.touch_num) { touch_ll_filter_read_smooth(channel_num, &smooth_signal); } else { touch_ll_sleep_read_smooth(&smooth_signal); } return smooth_signal; } esp_err_t te_event_give(touch_elem_message_t te_message) { //TODO: add queue overwrite here when the queue is full int ret = xQueueSend(s_te_obj->event_msg_queue, &te_message, 0); if (ret != pdTRUE) { ESP_LOGE(TE_TAG, "event queue send failed, event message queue is full"); return ESP_ERR_TIMEOUT; } return ESP_OK; } uint32_t te_get_threshold(touch_pad_t channel_num) { uint32_t threshold = 0; touch_pad_sleep_channel_t sleep_channel_info; touch_hal_sleep_channel_get_config(&sleep_channel_info); if (channel_num != sleep_channel_info.touch_num) { touch_ll_get_threshold(channel_num, &threshold); } else { touch_ll_sleep_get_threshold(&threshold); } return threshold; } bool te_is_touch_dsleep_wakeup(void) { soc_reset_reason_t reset_reason = esp_rom_get_reset_reason(0); if (reset_reason != RESET_REASON_CORE_DEEP_SLEEP) { return false; } #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) return !!(esp_sleep_get_wakeup_causes() & BIT(ESP_SLEEP_WAKEUP_TOUCHPAD)); #else return esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD; #endif } touch_pad_t te_get_sleep_channel(void) { touch_pad_sleep_channel_t sleep_channel_info; touch_hal_sleep_channel_get_config(&sleep_channel_info); return sleep_channel_info.touch_num; } /** * @brief Touch sensor interrupt service routine * * This function is touch sensor ISR, all the touch * sensor channel state will be updated here. */ static void te_intr_cb(void *arg) { TE_UNUSED(arg); static int scan_done_cnt = 0; static uint32_t touch_pre_trig_status = 0; int task_awoken = pdFALSE; te_intr_msg_t te_intr_msg = {}; /*< Figure out which touch sensor channel is triggered and the trigger type */ uint32_t intr_mask = touch_ll_read_intr_status_mask(); #if CONFIG_IDF_TARGET_ESP32S2 /*< Workaround: For ESP32S2, the scan done interrupt may occur earlier, so we need to ignore the fake scan done interrupt */ if (intr_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { uint32_t curr_pad = touch_ll_get_current_meas_channel(); uint16_t ch_mask = 0; touch_ll_get_channel_mask(&ch_mask); /* If the current channel is not the last channel, it means the scan done interrupt is fake */ if (__builtin_clz((uint32_t)ch_mask) != __builtin_clz(BIT(curr_pad))) { touch_ll_intr_clear((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_SCAN_DONE); intr_mask &= ~TOUCH_LL_INTR_MASK_SCAN_DONE; } } #endif if (intr_mask == 0x0) { //For dummy interrupt return; } bool need_send_queue = true; uint8_t pad_num = 0; uint32_t touch_trig_status = 0; touch_ll_read_trigger_status_mask(&touch_trig_status); uint32_t touch_trig_diff = touch_trig_status ^ touch_pre_trig_status; while (touch_trig_diff) { if (touch_trig_diff & 0x1) { if (touch_trig_status & BIT(pad_num)) { if (s_te_obj->sleep_handle != NULL) { #ifdef CONFIG_PM_ENABLE esp_pm_lock_acquire(s_te_obj->sleep_handle->pm_lock); #endif } te_intr_msg.channel_state = TE_STATE_PRESS; te_intr_msg.intr_type = TE_INTR_PRESS; } else { te_intr_msg.channel_state = TE_STATE_RELEASE; te_intr_msg.intr_type = TE_INTR_RELEASE; } touch_pre_trig_status = touch_trig_status; te_intr_msg.channel_num = pad_num; } pad_num++; touch_trig_diff >>= 1; } if (intr_mask & TOUCH_LL_INTR_MASK_TIMEOUT) { te_intr_msg.channel_state = TE_STATE_IDLE; te_intr_msg.intr_type = TE_INTR_TIMEOUT; } else if (intr_mask & TOUCH_LL_INTR_MASK_SCAN_DONE) { te_intr_msg.channel_state = TE_STATE_IDLE; te_intr_msg.intr_type = TE_INTR_SCAN_DONE; need_send_queue = false; /*< Due to a hardware issue, all of the data read operation(read raw, read smooth, read benchmark) */ /*< must be after the second times of measure_done interrupt. */ if (++scan_done_cnt >= 5) { touch_ll_intr_disable((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_SCAN_DONE); //TODO: remove hal scan_done_cnt = 0; need_send_queue = true; } /*< De-noise channel signal must be read at the time between SCAN_DONE and next measurement beginning(sleep)!!! */ touch_ll_denoise_read_data(&s_te_obj->denoise_channel_raw); //Update de-noise signal } if (need_send_queue) { xQueueSendFromISR(s_te_obj->intr_msg_queue, &te_intr_msg, &task_awoken); } if (task_awoken == pdTRUE) { portYIELD_FROM_ISR(); } } /** * @brief esp-timer callback routine * * This function is an esp-timer daemon routine, all the touch sensor * application(button, slider, etc...) will be processed in here. * */ static void te_proc_timer_cb(void *arg) { TE_UNUSED(arg); te_intr_msg_t te_intr_msg; te_intr_msg.intr_type = TE_INTR_MAX; BaseType_t ret = xSemaphoreTake(s_te_obj->mutex, 0); if (ret != pdPASS) { return; } ret = xQueueReceive(s_te_obj->intr_msg_queue, &te_intr_msg, 0); if (ret == pdPASS) { if (te_intr_msg.intr_type == TE_INTR_PRESS || te_intr_msg.intr_type == TE_INTR_RELEASE) { te_object_update_state(te_intr_msg); if ((s_te_obj->sleep_handle != NULL) && (te_intr_msg.intr_type == TE_INTR_RELEASE)) { #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(s_te_obj->sleep_handle->pm_lock); #endif } } else if (te_intr_msg.intr_type == TE_INTR_SCAN_DONE) { if (s_te_obj->is_set_threshold != true) { s_te_obj->is_set_threshold = true; te_object_set_threshold(); //TODO: add set threshold error processing ESP_LOGD(TE_DEBUG_TAG, "Set threshold"); if (s_te_obj->sleep_handle != NULL) { #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(s_te_obj->sleep_handle->pm_lock); #endif } } if (waterproof_check_state()) { te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; if (waterproof_handle->is_shield_level_set != true) { waterproof_handle->is_shield_level_set = true; touch_pad_waterproof_t wp_conf; wp_conf.shield_driver = waterproof_get_shield_level(waterproof_handle->shield_channel); wp_conf.guard_ring_pad = (waterproof_guard_check_state() ? waterproof_handle->guard_device->channel : TOUCH_WATERPROOF_GUARD_NOUSE); touch_hal_waterproof_set_config(&wp_conf); touch_hal_waterproof_enable(); ESP_LOGD(TE_DEBUG_TAG, "Set waterproof shield level"); } } ESP_LOGD(TE_DEBUG_TAG, "read denoise channel %"PRIu32, s_te_obj->denoise_channel_raw); } else if (te_intr_msg.intr_type == TE_INTR_TIMEOUT) { //Timeout processing touch_ll_timer_force_done(); } } te_object_process_state(); xSemaphoreGive(s_te_obj->mutex); } void te_object_method_register(te_object_methods_t *object_methods, te_class_type_t object_type) { xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); TE_CLASS_ITEM(s_te_obj->object_methods, object_type, handle) = object_methods->handle; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, check_channel) = object_methods->check_channel; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, set_threshold) = object_methods->set_threshold; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, process_state) = object_methods->process_state; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, update_state) = object_methods->update_state; xSemaphoreGive(s_te_obj->mutex); } void te_object_method_unregister(te_class_type_t object_type) { xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); TE_CLASS_ITEM(s_te_obj->object_methods, object_type, handle) = NULL; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, check_channel) = NULL; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, set_threshold) = NULL; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, process_state) = NULL; TE_CLASS_ITEM(s_te_obj->object_methods, object_type, update_state) = NULL; xSemaphoreGive(s_te_obj->mutex); } /** * @brief Touch Sense channel check * * This function will check the input channel whether is * associated with the Touch Sense Object * * @return * - true: Channel has been initialized, pls adjust the input channel * - false: Channel has not been initialized, pass */ bool te_object_check_channel(const touch_pad_t *channel_array, uint8_t channel_sum) { touch_pad_t current_channel; for (int idx = 0; idx < channel_sum; idx++) { current_channel = channel_array[idx]; if (waterproof_channel_check(current_channel)) { goto INITIALIZED; } if (TE_CLASS_FOREACH_CHECK_CHANNEL(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX, current_channel)) { goto INITIALIZED; } } return false; INITIALIZED: ESP_LOGE(TE_TAG, "Current channel [%d] has been initialized:(%s)", current_channel, __FUNCTION__ ); return true; } static inline esp_err_t te_object_set_threshold(void) { if (waterproof_guard_check_state() == true) { //TODO: add to object methods waterproof_guard_set_threshold(); } TE_CLASS_FOREACH_SET_THRESHOLD(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); return ESP_OK; } static inline void te_object_process_state(void) { TE_CLASS_FOREACH_PROCESS_STATE(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); } static inline void te_object_update_state(te_intr_msg_t te_intr_msg) { if (waterproof_guard_check_state()) { waterproof_guard_update_state(te_intr_msg.channel_num, te_intr_msg.channel_state); } TE_CLASS_FOREACH_UPDATE_STATE(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX, te_intr_msg.channel_num, te_intr_msg.channel_state); } uint8_t te_get_timer_period(void) { return (TE_PROCESSING_PERIOD(s_te_obj)); } esp_err_t te_dev_init(te_dev_t **device, uint8_t device_num, te_dev_type_t type, const touch_pad_t *channel, const float *sens, float divider) { for (int idx = 0; idx < device_num; idx++) { device[idx]->channel = channel[idx]; device[idx]->sens = sens[idx] * divider; device[idx]->type = type; device[idx]->state = TE_STATE_IDLE; device[idx]->is_use_last_threshold = false; int touch_num = TOUCH_GET_IO_NUM(device[idx]->channel); esp_err_t ret = ESP_OK; #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)) ret = gpio_config_as_analog(touch_num); #else rtc_gpio_init(touch_num); rtc_gpio_set_direction(touch_num, RTC_GPIO_MODE_DISABLED); rtc_gpio_pulldown_dis(touch_num); rtc_gpio_pullup_dis(touch_num); #endif touch_hal_config(touch_num); touch_ll_set_channel_mask(BIT(touch_num)); TE_CHECK(ret == ESP_OK, ret); } return ESP_OK; } void te_dev_deinit(te_dev_t **device, uint8_t device_num) { for (int idx = 0; idx < device_num; idx++) { touch_ll_clear_channel_mask((1UL << device[idx]->channel)); } } static esp_err_t te_config_thresh(touch_pad_t channel_num, uint32_t threshold) { touch_pad_sleep_channel_t sleep_channel_info; touch_hal_sleep_channel_get_config(&sleep_channel_info); if (channel_num != sleep_channel_info.touch_num) { touch_ll_set_threshold(channel_num, threshold); } else { touch_ll_sleep_set_threshold(threshold); } return ESP_OK; } esp_err_t te_dev_set_threshold(te_dev_t *device) { esp_err_t ret = ESP_OK; uint32_t smo_val = 0; if (s_te_obj->sleep_handle && device->is_use_last_threshold) { if (te_is_touch_dsleep_wakeup()) { //Deep sleep wakeup reset ret = te_config_thresh(device->channel, s_te_obj->sleep_handle->non_volatile_threshold[device->channel - 1]); } else { //Other reset smo_val = te_read_smooth_signal(device->channel); ret = te_config_thresh(device->channel, device->sens * smo_val); uint32_t threshold = te_get_threshold(device->channel); s_te_obj->sleep_handle->non_volatile_threshold[device->channel - 1] = threshold; //Write threshold into RTC Fast Memory } } else { smo_val = te_read_smooth_signal(device->channel); ret = te_config_thresh(device->channel, device->sens * smo_val); } ESP_LOGD(TE_DEBUG_TAG, "channel: %"PRIu8", smo_val: %"PRIu32, (uint8_t)device->channel, smo_val); return ret; } /** * This function returns the s_te_obj whether is initialized * * @return * - true: initialized * - false: not initialized */ bool te_system_check_state(void) { return (s_te_obj != NULL); } static inline float te_get_internal_equ_cap(touch_pad_denoise_cap_t denoise_level) { return denoise_channel_equ_cap[denoise_level]; } /** * @brief Get channel equivalent capacitance * * This function calculates the equivalent capacitance of input channel by * using the Touch channel 0 equivalent capacitance. The formula is: * * Raw_N / Raw_0 = Cap_N / Cap_0 * * Note that Raw_N and Raw_0 are the raw data of touch channel N and touch channel 0 respectively, * Cap_N and Cap_0 are the equivalent capacitance of touch channel N and touch channel 0. * * @param[in] channel_num Input touch sensor channel * * @note The unit is pF * * @return Specified channel equivalent capacitance. */ static float te_channel_get_equ_cap(touch_pad_t channel_num) { //Fixme: add a mutex in here and prevent the system call this function TE_CHECK(channel_num >= TOUCH_PAD_NUM1 && channel_num < TOUCH_PAD_MAX, 0); uint32_t tn_raw, t0_raw; float tn_ref_cap, t0_ref_cap; touch_pad_denoise_t denoise_channel_conf; touch_hal_denoise_get_config(&denoise_channel_conf); tn_raw = te_read_raw_signal(channel_num); t0_raw = s_te_obj->denoise_channel_raw; t0_ref_cap = te_get_internal_equ_cap(denoise_channel_conf.cap_level); if (t0_raw == 0) { return 0; } tn_ref_cap = (float)tn_raw / t0_raw * t0_ref_cap; return tn_ref_cap; } /** * @brief Touch sensor driver default init [ESP32S2 only] * * 1. Channel measure time: Raw_value / RTC_FAST_CLK ==> Raw_value / 8000 000 * 2. Channel sleep time: TOUCH_PAD_SLEEP_CYCLE_DEFAULT / RTC_SLOW_CLK ==> 0xf / 90 000(default) = 0.16ms * 3. Channel charge voltage threshold(upper/lower): 2.7V upper voltage, 0.5V lower voltage, 0.5V attenuation voltage * 4. IDLE channel processing: Connecting to GND * 5. Interrupt type: ACTIVE, INACTIVE, TIMEOUT * * @note A touch sensor channel will spend the time = measure time + sleep time, RTC_FAST_CLK is 8M * */ static esp_err_t te_hw_init(const touch_elem_hw_config_t *hardware_init) { esp_err_t ret = ESP_OK; touch_hal_init(); touch_ll_set_fsm_mode(TOUCH_FSM_MODE_TIMER); touch_ll_set_sleep_time(hardware_init->sleep_cycle); touch_ll_set_meas_times(hardware_init->sample_count); touch_ll_set_voltage_high(hardware_init->upper_voltage); touch_ll_set_voltage_low(hardware_init->lower_voltage); touch_ll_set_voltage_attenuation(hardware_init->voltage_attenuation); touch_ll_set_idle_channel_connect(hardware_init->suspend_channel_polarity); uint32_t intr_mask = RTC_CNTL_TOUCH_ACTIVE_INT_ST_M | RTC_CNTL_TOUCH_INACTIVE_INT_ST_M | RTC_CNTL_TOUCH_TIMEOUT_INT_ST_M | RTC_CNTL_TOUCH_SCAN_DONE_INT_ST_M; ret = rtc_isr_register(te_intr_cb, NULL, intr_mask, 0); TE_CHECK(ret == ESP_OK, ret); touch_ll_intr_enable((touch_pad_intr_mask_t)(TOUCH_LL_INTR_MASK_ACTIVE | TOUCH_LL_INTR_MASK_SCAN_DONE | TOUCH_LL_INTR_MASK_INACTIVE | TOUCH_LL_INTR_MASK_TIMEOUT)); TE_CHECK(ret == ESP_OK, ret); /*< Internal de-noise configuration */ touch_pad_denoise_t denoise_config; denoise_config.grade = hardware_init->denoise_level; denoise_config.cap_level = hardware_init->denoise_equivalent_cap; touch_ll_set_slope((touch_pad_t)TOUCH_LL_DENOISE_CHANNEL, TOUCH_PAD_SLOPE_DEFAULT); touch_ll_set_tie_option((touch_pad_t)TOUCH_LL_DENOISE_CHANNEL, TOUCH_PAD_TIE_OPT_DEFAULT); touch_hal_denoise_set_config(&denoise_config); touch_hal_denoise_enable(); /*< benchmark filter configuration */ touch_filter_config_t filter_config; filter_config.smh_lvl = hardware_init->smooth_filter_mode; filter_config.mode = hardware_init->benchmark_filter_mode; filter_config.debounce_cnt = hardware_init->benchmark_debounce_count; filter_config.noise_thr = hardware_init->benchmark_calibration_threshold; filter_config.jitter_step = hardware_init->benchmark_jitter_step; touch_hal_filter_set_config(&filter_config); touch_ll_filter_enable(true); memcpy(&s_te_obj->global_config->hardware, hardware_init, sizeof(touch_elem_hw_config_t)); return ESP_OK; } static esp_err_t te_sw_init(const touch_elem_sw_config_t *software_init) { TE_CHECK(software_init->processing_period > 1, ESP_ERR_INVALID_ARG); TE_CHECK(software_init->waterproof_threshold_divider > 0, ESP_ERR_INVALID_ARG); TE_CHECK(software_init->intr_message_size >= (TOUCH_PAD_MAX - 1), ESP_ERR_INVALID_ARG); TE_CHECK(software_init->event_message_size > 0, ESP_ERR_INVALID_ARG); esp_err_t ret = ESP_ERR_NO_MEM; s_te_obj->intr_msg_queue = xQueueCreate(software_init->intr_message_size, sizeof(te_intr_msg_t)); s_te_obj->event_msg_queue = xQueueCreate(software_init->event_message_size, sizeof(touch_elem_message_t)); TE_CHECK_GOTO(s_te_obj->event_msg_queue != NULL && s_te_obj->intr_msg_queue != NULL, cleanup); const esp_timer_create_args_t te_proc_timer_args = { .name = "te_proc_timer_cb", .arg = NULL, .callback = &te_proc_timer_cb, .skip_unhandled_events = true, }; ret = esp_timer_create(&te_proc_timer_args, &s_te_obj->proc_timer); TE_CHECK_GOTO(ret == ESP_OK, cleanup); memcpy(&s_te_obj->global_config->software, software_init, sizeof(touch_elem_sw_config_t)); return ret; cleanup: if (s_te_obj->event_msg_queue != NULL) { vQueueDelete(s_te_obj->event_msg_queue); } if (s_te_obj->intr_msg_queue != NULL) { vQueueDelete(s_te_obj->intr_msg_queue); } return ret; } //TODO: add waterproof guard-lock hysteresis esp_err_t touch_element_waterproof_install(const touch_elem_waterproof_config_t *waterproof_config) { TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(waterproof_config != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(waterproof_config->guard_channel >= TOUCH_PAD_NUM0 && waterproof_config->guard_channel < TOUCH_PAD_MAX, ESP_ERR_INVALID_ARG); te_waterproof_handle_t waterproof_handle = (te_waterproof_handle_t)calloc(1, sizeof(struct te_waterproof_s)); TE_CHECK(waterproof_handle != NULL, ESP_ERR_NO_MEM); waterproof_handle->shield_channel = TOUCH_PAD_NUM14; esp_err_t ret = ESP_OK; if (waterproof_config->guard_channel != TOUCH_WATERPROOF_GUARD_NOUSE) { //Use guard sensor if (te_object_check_channel(&waterproof_config->guard_channel, 1)) { ret = ESP_ERR_INVALID_ARG; goto cleanup; } ret = ESP_ERR_NO_MEM; waterproof_handle->mask_handle = (touch_elem_handle_t *) calloc(TOUCH_PAD_MAX, sizeof(touch_elem_handle_t)); waterproof_handle->guard_device = (te_dev_t *)calloc(1, sizeof(te_dev_t)); TE_CHECK_GOTO(waterproof_handle->mask_handle != NULL && waterproof_handle->guard_device, cleanup); ret = te_dev_init(&waterproof_handle->guard_device, 1, TOUCH_ELEM_TYPE_BUTTON, &waterproof_config->guard_channel, &waterproof_config->guard_sensitivity, TE_WATERPROOF_DIVIDER(s_te_obj)); TE_CHECK_GOTO(ret == ESP_OK, cleanup); waterproof_handle->guard_device->state = TE_STATE_RELEASE; for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { waterproof_handle->mask_handle[idx] = NULL; } } else { //No use waterproof guard sensor waterproof_handle->guard_device = NULL; waterproof_handle->mask_handle = NULL; } waterproof_handle->is_shield_level_set = 0; //Set a state bit so as to configure the shield level at the run-time touch_pad_waterproof_t wp_conf; wp_conf.shield_driver = TOUCH_PAD_SHIELD_DRV_L0; //Set a default shield level wp_conf.guard_ring_pad = waterproof_config->guard_channel; touch_hal_waterproof_set_config(&wp_conf); touch_hal_waterproof_enable(); s_te_obj->waterproof_handle = waterproof_handle; //Fixme: add mutex return ret; cleanup: TE_FREE_AND_NULL(waterproof_handle->mask_handle); TE_FREE_AND_NULL(waterproof_handle->guard_device); TE_FREE_AND_NULL(waterproof_handle); return ret; } esp_err_t touch_element_waterproof_add(touch_elem_handle_t element_handle) { TE_CHECK(s_te_obj->waterproof_handle != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(s_te_obj->waterproof_handle->guard_device != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(element_handle != NULL, ESP_ERR_INVALID_ARG); te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { if (waterproof_handle->mask_handle[idx] == NULL) { waterproof_handle->mask_handle[idx] = element_handle; break; } } xSemaphoreGive(s_te_obj->mutex); return ESP_OK; } esp_err_t touch_element_waterproof_remove(touch_elem_handle_t element_handle) { TE_CHECK(s_te_obj->waterproof_handle != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(element_handle != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret = ESP_ERR_NOT_FOUND; te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { if (waterproof_handle->mask_handle[idx] == element_handle) { waterproof_handle->mask_handle[idx] = NULL; ret = ESP_OK; break; } } xSemaphoreGive(s_te_obj->mutex); return ret; } void touch_element_waterproof_uninstall(void) { xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); touch_ll_waterproof_enable(false); free(s_te_obj->waterproof_handle->guard_device); free(s_te_obj->waterproof_handle->mask_handle); free(s_te_obj->waterproof_handle); s_te_obj->waterproof_handle = NULL; xSemaphoreGive(s_te_obj->mutex); } static touch_pad_shield_driver_t waterproof_get_shield_level(touch_pad_t guard_channel_num) { touch_pad_shield_driver_t shield_level = TOUCH_PAD_SHIELD_DRV_L7; float guard_ref_cap = te_channel_get_equ_cap(guard_channel_num); for (int level = 0; level < TOUCH_PAD_SHIELD_DRV_MAX; level++) { if (guard_ref_cap <= shield_channel_ref_cap[level]) { shield_level = (touch_pad_shield_driver_t)level; break; } } return shield_level; } /** * This function returns the waterproof_handle whether is initialized * * @return * - true: initialized * - false: not initialized */ static inline bool waterproof_check_state(void) { return (s_te_obj->waterproof_handle != NULL); } static inline bool waterproof_shield_check_state(void) { return waterproof_check_state(); //Driver does not allow to disable shield sensor after waterproof enabling } static inline bool waterproof_guard_check_state(void) { if (waterproof_check_state() == false) { return false; } if (s_te_obj->waterproof_handle->guard_device == NULL || s_te_obj->waterproof_handle->mask_handle == NULL) { return false; } return true; } static bool waterproof_channel_check(touch_pad_t channel_num) { if (waterproof_check_state() == false) { return false; } te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; if (waterproof_shield_check_state()) { if (channel_num == waterproof_handle->shield_channel) { ESP_LOGE(TE_TAG, "TOUCH_PAD_NUM%"PRIu8" has been used for waterproof shield channel," " please change the touch sensor channel or disable waterproof", (uint8_t)channel_num); return true; } } if (waterproof_guard_check_state()) { if (channel_num == waterproof_handle->guard_device->channel) { ESP_LOGE(TE_TAG, "TOUCH_PAD_NUM%"PRIu8" has been used for waterproof guard channel," " please change the touch sensor channel or disable waterproof", (uint8_t)channel_num); return true; } } return false; } static void waterproof_guard_set_threshold(void) { if (waterproof_check_state() == false) { return; } if (waterproof_guard_check_state() == false) { return; } te_dev_set_threshold(s_te_obj->waterproof_handle->guard_device); } /** * This function will figure out current handle whether is a masked channel * while guard channel is triggered. * * @param[in] te_handle Touch sensor application handle * @return * - true current handle is a masked channel * - false current handle is not a masked channel */ bool waterproof_check_mask_handle(touch_elem_handle_t te_handle) { if (waterproof_check_state() == false) { return false; } if (waterproof_guard_check_state() == false) { return false; } te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; bool ret = false; if (waterproof_handle->guard_device->state == TE_STATE_PRESS) { for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { if (waterproof_handle->mask_handle[idx] == NULL) { break; } if (waterproof_handle->mask_handle[idx] == te_handle) { ret = true; } } } return ret; } static void waterproof_guard_update_state(touch_pad_t current_channel, te_state_t current_state) { te_dev_t *guard_device = s_te_obj->waterproof_handle->guard_device; if (current_channel == guard_device->channel) { guard_device->state = current_state; } ESP_LOGD(TE_DEBUG_TAG, "waterproof guard state update %d", guard_device->state); } esp_err_t touch_element_enable_light_sleep(const touch_elem_sleep_config_t *sleep_config) { TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(s_te_obj->sleep_handle == NULL, ESP_ERR_INVALID_STATE); uint16_t sample_count = 500; uint16_t sleep_cycle = 0x0f; if (sleep_config) { sample_count = sleep_config->sample_count; sleep_cycle = sleep_config->sleep_cycle; } s_te_obj->sleep_handle = calloc(1, sizeof(struct te_sleep_s)); TE_CHECK(s_te_obj->sleep_handle, ESP_ERR_NO_MEM); esp_err_t ret = ESP_OK; touch_hal_sleep_channel_set_work_time(sleep_cycle, sample_count); TE_CHECK_GOTO(esp_sleep_enable_touchpad_wakeup() == ESP_OK, cleanup); TE_CHECK_GOTO(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON) == ESP_OK, cleanup); s_te_obj->sleep_handle->non_volatile_threshold = threshold_shadow; #ifdef CONFIG_PM_ENABLE TE_CHECK_GOTO(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "touch_element", &s_te_obj->sleep_handle->pm_lock) == ESP_OK, cleanup); TE_CHECK_GOTO(esp_pm_lock_acquire(s_te_obj->sleep_handle->pm_lock) == ESP_OK, cleanup); #endif return ESP_OK; cleanup: #ifdef CONFIG_PM_ENABLE if (s_te_obj->sleep_handle->pm_lock != NULL) { if (esp_pm_lock_delete(s_te_obj->sleep_handle->pm_lock) != ESP_OK) { abort(); } } #endif TE_FREE_AND_NULL(s_te_obj->sleep_handle); return ret; } esp_err_t touch_element_disable_light_sleep(void) { TE_CHECK(s_te_obj->sleep_handle, ESP_ERR_INVALID_STATE); #ifdef CONFIG_PM_ENABLE if (s_te_obj->sleep_handle->pm_lock != NULL) { /* Sleep channel is going to uninstall, pm lock is not needed anymore, but we need to make sure that pm lock has been released before delete it. */ while (esp_pm_lock_release(s_te_obj->sleep_handle->pm_lock) == ESP_OK); esp_err_t ret = esp_pm_lock_delete(s_te_obj->sleep_handle->pm_lock); TE_CHECK(ret == ESP_OK, ret); s_te_obj->sleep_handle->pm_lock = NULL; } #endif esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TOUCHPAD); TE_FREE_AND_NULL(s_te_obj->sleep_handle); return ESP_OK; } esp_err_t touch_element_enable_deep_sleep(touch_elem_handle_t wakeup_elem_handle, const touch_elem_sleep_config_t *sleep_config) { TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(s_te_obj->sleep_handle == NULL, ESP_ERR_INVALID_STATE); TE_CHECK(wakeup_elem_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(sleep_config != NULL, ESP_ERR_INVALID_ARG); uint16_t sample_count = 500; uint16_t sleep_cycle = 0x0f; if (sleep_config) { sample_count = sleep_config->sample_count; sleep_cycle = sleep_config->sleep_cycle; } s_te_obj->sleep_handle = calloc(1, sizeof(struct te_sleep_s)); TE_CHECK(s_te_obj->sleep_handle, ESP_ERR_NO_MEM); esp_err_t ret = ESP_OK; touch_hal_sleep_channel_set_work_time(sleep_cycle, sample_count); TE_CHECK_GOTO(esp_sleep_enable_touchpad_wakeup() == ESP_OK, cleanup); TE_CHECK_GOTO(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON) == ESP_OK, cleanup); s_te_obj->sleep_handle->non_volatile_threshold = threshold_shadow; #ifdef CONFIG_PM_ENABLE TE_CHECK_GOTO(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "touch_element", &s_te_obj->sleep_handle->pm_lock) == ESP_OK, cleanup); TE_CHECK_GOTO(esp_pm_lock_acquire(s_te_obj->sleep_handle->pm_lock) == ESP_OK, cleanup); #endif //Only support one channel/element as the deep sleep wakeup channel/element TE_CHECK(is_button_object_handle(wakeup_elem_handle), ESP_ERR_NOT_SUPPORTED); s_te_obj->sleep_handle->wakeup_handle = wakeup_elem_handle; te_button_handle_t button_handle = wakeup_elem_handle; touch_hal_sleep_channel_enable(button_handle->device->channel, true); return ESP_OK; cleanup: #ifdef CONFIG_PM_ENABLE if (s_te_obj->sleep_handle->pm_lock != NULL) { if (esp_pm_lock_delete(s_te_obj->sleep_handle->pm_lock) != ESP_OK) { abort(); } } #endif TE_FREE_AND_NULL(s_te_obj->sleep_handle); return ret; } esp_err_t touch_element_disable_deep_sleep(void) { TE_CHECK(s_te_obj->sleep_handle, ESP_ERR_INVALID_STATE); #ifdef CONFIG_PM_ENABLE if (s_te_obj->sleep_handle->pm_lock != NULL) { /* Sleep channel is going to uninstall, pm lock is not needed anymore, but we need to make sure that pm lock has been released before delete it. */ while (esp_pm_lock_release(s_te_obj->sleep_handle->pm_lock) == ESP_OK); ret = esp_pm_lock_delete(s_te_obj->sleep_handle->pm_lock); TE_CHECK(ret == ESP_OK, ret); s_te_obj->sleep_handle->pm_lock = NULL; } #endif te_button_handle_t button_handle = s_te_obj->sleep_handle->wakeup_handle; touch_hal_sleep_channel_enable(button_handle->device->channel, false); esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TOUCHPAD); s_te_obj->sleep_handle->wakeup_handle = NULL; TE_FREE_AND_NULL(s_te_obj->sleep_handle); return ESP_OK; } esp_err_t touch_element_sleep_enable_wakeup_calibration(touch_elem_handle_t element_handle, bool en) { TE_CHECK(element_handle != NULL, ESP_ERR_INVALID_ARG); if (is_button_object_handle(element_handle)) { button_enable_wakeup_calibration(element_handle, en); } else if (is_slider_object_handle(element_handle)) { slider_enable_wakeup_calibration(element_handle, en); } else if (is_matrix_object_handle(element_handle)) { matrix_enable_wakeup_calibration(element_handle, en); } else { return ESP_ERR_NOT_FOUND; } return ESP_OK; } ================================================ FILE: touch_element/touch_matrix.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_log.h" #include "esp_private/touch_element_private.h" #define TE_MAT_POS_MAX (0xff) //!< Matrix button startup position typedef struct te_matrix_handle_list { te_matrix_handle_t matrix_handle; //Matrix handle SLIST_ENTRY(te_matrix_handle_list) next; //Matrix handle list entry } te_matrix_handle_list_t; typedef struct { SLIST_HEAD(te_matrix_handle_list_head, te_matrix_handle_list) handle_list; //Matrix handle (instance) list touch_matrix_global_config_t *global_config; //Matrix global configuration SemaphoreHandle_t mutex; //Matrix object mutex } te_matrix_obj_t; te_matrix_obj_t *s_te_mat_obj = NULL; /* ---------------------------------------- Matrix handle(instance) methods ----------------------------------------- */ static bool matrix_channel_check(te_matrix_handle_t matrix_handle, touch_pad_t channel_num); static esp_err_t matrix_set_threshold(te_matrix_handle_t matrix_handle); static inline te_state_t matrix_get_state(te_matrix_state_t x_axis_state, te_matrix_state_t y_axis_state); static void matrix_reset_state(te_matrix_handle_t matrix_handle); static void matrix_update_state(te_matrix_handle_t matrix_handle, touch_pad_t channel_num, te_state_t channel_state); static void matrix_update_position(te_matrix_handle_t matrix_handle, touch_matrix_position_t new_pos); static void matrix_proc_state(te_matrix_handle_t matrix_handle); static void matrix_event_give(te_matrix_handle_t matrix_handle); static inline void matrix_dispatch(te_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method); /* ------------------------------------------ Matrix object(class) methods ------------------------------------------ */ static esp_err_t matrix_object_add_instance(te_matrix_handle_t matrix_handle); static esp_err_t matrix_object_remove_instance(te_matrix_handle_t matrix_handle); static bool matrix_object_check_channel(touch_pad_t channel_num); static esp_err_t matrix_object_set_threshold(void); static void matrix_object_process_state(void); static void matrix_object_update_state(touch_pad_t channel_num, te_state_t channel_state); /* ------------------------------------------------------------------------------------------------------------------ */ esp_err_t touch_matrix_install(const touch_matrix_global_config_t *global_config) { TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); //Fixme: Make it thread-safe s_te_mat_obj = (te_matrix_obj_t *)calloc(1, sizeof(te_matrix_obj_t)); TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_NO_MEM); s_te_mat_obj->global_config = (touch_matrix_global_config_t *)calloc(1, sizeof(touch_matrix_global_config_t)); s_te_mat_obj->mutex = xSemaphoreCreateMutex(); TE_CHECK_GOTO(s_te_mat_obj->global_config != NULL && s_te_mat_obj->mutex != NULL, cleanup); xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); SLIST_INIT(&s_te_mat_obj->handle_list); memcpy(s_te_mat_obj->global_config, global_config, sizeof(touch_matrix_global_config_t)); te_object_methods_t matrix_methods = { .handle = s_te_mat_obj, .check_channel = matrix_object_check_channel, .set_threshold = matrix_object_set_threshold, .process_state = matrix_object_process_state, .update_state = matrix_object_update_state }; te_object_method_register(&matrix_methods, TE_CLS_TYPE_MATRIX); xSemaphoreGive(s_te_mat_obj->mutex); return ESP_OK; cleanup: TE_FREE_AND_NULL(s_te_mat_obj->global_config); if (s_te_mat_obj->mutex != NULL) { vSemaphoreDelete(s_te_mat_obj->mutex); } TE_FREE_AND_NULL(s_te_mat_obj); return ESP_ERR_NO_MEM; } void touch_matrix_uninstall(void) { xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); if (s_te_mat_obj == NULL) { xSemaphoreGive(s_te_mat_obj->mutex); return; } te_object_method_unregister(TE_CLS_TYPE_MATRIX); free(s_te_mat_obj->global_config); s_te_mat_obj->global_config = NULL; while (!SLIST_EMPTY(&s_te_mat_obj->handle_list)) { SLIST_REMOVE_HEAD(&s_te_mat_obj->handle_list, next); } xSemaphoreGive(s_te_mat_obj->mutex); vSemaphoreDelete(s_te_mat_obj->mutex); free(s_te_mat_obj); s_te_mat_obj = NULL; } esp_err_t touch_matrix_create(const touch_matrix_config_t *matrix_config, touch_matrix_handle_t *matrix_handle) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL && matrix_config != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(matrix_config->x_channel_array != NULL && matrix_config->y_channel_array != NULL && matrix_config->x_sensitivity_array != NULL && matrix_config->y_sensitivity_array != NULL && matrix_config->x_channel_num > 1 && matrix_config->x_channel_num < TOUCH_PAD_MAX && matrix_config->y_channel_num > 1 && matrix_config->y_channel_num < TOUCH_PAD_MAX, ESP_ERR_INVALID_ARG); TE_CHECK(te_object_check_channel(matrix_config->x_channel_array, matrix_config->x_channel_num) == false && te_object_check_channel(matrix_config->y_channel_array, matrix_config->y_channel_num) == false, ESP_ERR_INVALID_ARG); te_matrix_handle_t te_matrix = (te_matrix_handle_t)calloc(1, sizeof(struct te_matrix_s)); TE_CHECK(te_matrix != NULL, ESP_ERR_NO_MEM); esp_err_t ret = ESP_ERR_NO_MEM; te_matrix->config = (te_matrix_handle_config_t *)calloc(1, sizeof(te_matrix_handle_config_t)); te_matrix->device = (te_dev_t **)calloc(matrix_config->x_channel_num + matrix_config->y_channel_num, sizeof(te_dev_t *)); TE_CHECK_GOTO(te_matrix->config != NULL && te_matrix->device != NULL, cleanup); for (int idx = 0; idx < matrix_config->x_channel_num + matrix_config->y_channel_num; idx++) { te_matrix->device[idx] = (te_dev_t *)calloc(1, sizeof(te_dev_t)); if (te_matrix->device[idx] == NULL) { ret = ESP_ERR_NO_MEM; goto cleanup; } } /*< Initialize x-axis */ ret = te_dev_init(&te_matrix->device[0], matrix_config->x_channel_num, TOUCH_ELEM_TYPE_MATRIX, matrix_config->x_channel_array, matrix_config->x_sensitivity_array, TE_DEFAULT_THRESHOLD_DIVIDER(s_te_mat_obj)); TE_CHECK_GOTO(ret == ESP_OK, cleanup); /*< Initialize y-axis */ ret = te_dev_init(&te_matrix->device[matrix_config->x_channel_num], matrix_config->y_channel_num, TOUCH_ELEM_TYPE_MATRIX, matrix_config->y_channel_array, matrix_config->y_sensitivity_array, TE_DEFAULT_THRESHOLD_DIVIDER(s_te_mat_obj)); TE_CHECK_GOTO(ret == ESP_OK, cleanup); te_matrix->config->event_mask = TOUCH_ELEM_EVENT_NONE; te_matrix->config->dispatch_method = TOUCH_ELEM_DISP_MAX; te_matrix->config->callback = NULL; te_matrix->config->arg = NULL; te_matrix->current_state = TE_STATE_IDLE; te_matrix->last_state = TE_STATE_IDLE; te_matrix->event = TOUCH_MATRIX_EVT_MAX; te_matrix->x_channel_num = matrix_config->x_channel_num; te_matrix->y_channel_num = matrix_config->y_channel_num; te_matrix->trigger_cnt = 0; te_matrix->trigger_thr = 0xffffffff; te_matrix->position.x_axis = TE_MAT_POS_MAX; te_matrix->position.y_axis = TE_MAT_POS_MAX; te_matrix->position.index = TE_MAT_POS_MAX; ret = matrix_object_add_instance(te_matrix); TE_CHECK_GOTO(ret == ESP_OK, cleanup); *matrix_handle = (touch_elem_handle_t) te_matrix; return ESP_OK; cleanup: TE_FREE_AND_NULL(te_matrix->config); if (te_matrix->device != NULL) { for (int idx = 0; idx < matrix_config->x_channel_num + matrix_config->y_channel_num; idx++) { TE_FREE_AND_NULL(te_matrix->device[idx]); } free(te_matrix->device); te_matrix->device = NULL; } TE_FREE_AND_NULL(te_matrix); return ret; } esp_err_t touch_matrix_delete(touch_matrix_handle_t matrix_handle) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); /*< Release touch sensor application resource */ esp_err_t ret = matrix_object_remove_instance(matrix_handle); TE_CHECK(ret == ESP_OK, ret); te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; /*< Release touch sensor device resource */ te_dev_deinit(te_matrix->device, te_matrix->x_channel_num + te_matrix->y_channel_num); for (int idx = 0; idx < te_matrix->x_channel_num + te_matrix->y_channel_num; idx++) { free(te_matrix->device[idx]); } free(te_matrix->config); free(te_matrix->device); free(te_matrix); return ESP_OK; } esp_err_t touch_matrix_set_dispatch_method(touch_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; te_matrix->config->dispatch_method = dispatch_method; xSemaphoreGive(s_te_mat_obj->mutex); return ESP_OK; } esp_err_t touch_matrix_subscribe_event(touch_matrix_handle_t matrix_handle, uint32_t event_mask, void *arg) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && !(event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) && !(event_mask & TOUCH_ELEM_EVENT_NONE)) { ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS event mask"); return ESP_ERR_INVALID_ARG; } if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { touch_matrix_set_longpress(matrix_handle, TE_DEFAULT_LONGPRESS_TIME(s_te_mat_obj)); //set the default time(1000ms) for long press } xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; te_matrix->config->event_mask = event_mask; te_matrix->config->arg = arg; xSemaphoreGive(s_te_mat_obj->mutex); return ESP_OK; } esp_err_t touch_matrix_set_callback(touch_matrix_handle_t matrix_handle, touch_matrix_callback_t matrix_callback) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(matrix_callback != NULL, ESP_ERR_INVALID_ARG); te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); te_matrix->config->callback = matrix_callback; xSemaphoreGive(s_te_mat_obj->mutex); return ESP_OK; } esp_err_t touch_matrix_set_longpress(touch_matrix_handle_t matrix_handle, uint32_t threshold_time) { TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(threshold_time > 0, ESP_ERR_INVALID_ARG); te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; touch_elem_dispatch_t dispatch_method = te_matrix->config->dispatch_method; if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); } uint8_t timer_period = te_get_timer_period(); te_matrix->trigger_thr = threshold_time / timer_period; if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { xSemaphoreGive(s_te_mat_obj->mutex); } return ESP_OK; } const touch_matrix_message_t *touch_matrix_get_message(const touch_elem_message_t *element_message) { return (touch_matrix_message_t *)&element_message->child_msg; _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_matrix_message_t), "Message size overflow"); } static bool matrix_object_check_channel(touch_pad_t channel_num) { te_matrix_handle_list_t *item; SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { if (matrix_channel_check(item->matrix_handle, channel_num)) { return true; } } return false; } static esp_err_t matrix_object_set_threshold(void) { esp_err_t ret = ESP_OK; te_matrix_handle_list_t *item; SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { ret = matrix_set_threshold(item->matrix_handle); if (ret != ESP_OK) { break; } } return ret; } static void matrix_object_process_state(void) { te_matrix_handle_list_t *item; SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { if (waterproof_check_mask_handle(item->matrix_handle)) { matrix_reset_state(item->matrix_handle); continue; } matrix_proc_state(item->matrix_handle); } } static void matrix_object_update_state(touch_pad_t channel_num, te_state_t channel_state) { te_matrix_handle_list_t *item; SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { if (waterproof_check_mask_handle(item->matrix_handle)) { continue; } matrix_update_state(item->matrix_handle, channel_num, channel_state); } } static esp_err_t matrix_object_add_instance(te_matrix_handle_t matrix_handle) { te_matrix_handle_list_t *item = (te_matrix_handle_list_t *)calloc(1, sizeof(te_matrix_handle_list_t)); TE_CHECK(item != NULL, ESP_ERR_NO_MEM); item->matrix_handle = matrix_handle; xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); SLIST_INSERT_HEAD(&s_te_mat_obj->handle_list, item, next); xSemaphoreGive(s_te_mat_obj->mutex); return ESP_OK; } static esp_err_t matrix_object_remove_instance(te_matrix_handle_t matrix_handle) { esp_err_t ret = ESP_ERR_NOT_FOUND; te_matrix_handle_list_t *item; SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { if (matrix_handle == item->matrix_handle) { xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); SLIST_REMOVE(&s_te_mat_obj->handle_list, item, te_matrix_handle_list, next); xSemaphoreGive(s_te_mat_obj->mutex); free(item); ret = ESP_OK; break; } } return ret; } bool is_matrix_object_handle(touch_elem_handle_t element_handle) { te_matrix_handle_list_t *item; xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { if (element_handle == item->matrix_handle) { xSemaphoreGive(s_te_mat_obj->mutex); return true; } } xSemaphoreGive(s_te_mat_obj->mutex); return false; } static bool matrix_channel_check(te_matrix_handle_t matrix_handle, touch_pad_t channel_num) { te_dev_t *device; for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { device = matrix_handle->device[idx]; if (device->channel == channel_num) { return true; } } return false; } static esp_err_t matrix_set_threshold(te_matrix_handle_t matrix_handle) { esp_err_t ret = ESP_OK; for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { ret |= te_dev_set_threshold(matrix_handle->device[idx]); } return ret; } static void matrix_update_state(te_matrix_handle_t matrix_handle, touch_pad_t channel_num, te_state_t channel_state) { te_dev_t *device; for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { device = matrix_handle->device[idx]; if (channel_num == device->channel) { device->state = channel_state; } } } static void matrix_reset_state(te_matrix_handle_t matrix_handle) { for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { matrix_handle->device[idx]->state = TE_STATE_IDLE; } matrix_handle->trigger_cnt = 0; matrix_handle->current_state = TE_STATE_IDLE; } static void matrix_event_give(te_matrix_handle_t matrix_handle) { touch_elem_message_t element_message; touch_matrix_message_t matrix_message = { .event = matrix_handle->event, .position = matrix_handle->position }; element_message.handle = (touch_elem_handle_t)matrix_handle; element_message.element_type = TOUCH_ELEM_TYPE_MATRIX; element_message.arg = matrix_handle->config->arg; memcpy(element_message.child_msg, &matrix_message, sizeof(matrix_message)); te_event_give(element_message); } static inline void matrix_dispatch(te_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method) { if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { matrix_event_give(matrix_handle); //Event queue } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { touch_matrix_message_t matrix_info; matrix_info.event = matrix_handle->event; matrix_info.position = matrix_handle->position; void *arg = matrix_handle->config->arg; matrix_handle->config->callback(matrix_handle, &matrix_info, arg); //Event callback } } void matrix_enable_wakeup_calibration(te_matrix_handle_t matrix_handle, bool en) { for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; ++idx) { matrix_handle->device[idx]->is_use_last_threshold = !en; } } /** * @brief Scan the matrix channel * * This function will output the press position and release position info * so as to determine which operation(press/release) is happening, since there * will get the invalid state if user operates multi-points at the same time. * */ static void matrix_scan_axis(te_matrix_handle_t matrix_handle, touch_matrix_position_t *press_pos, uint8_t *press_cnt, touch_matrix_position_t *release_pos, uint8_t *release_cnt) { press_pos->x_axis = TE_MAT_POS_MAX; press_pos->y_axis = TE_MAT_POS_MAX; release_pos->x_axis = TE_MAT_POS_MAX; release_pos->y_axis = TE_MAT_POS_MAX; for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { te_dev_t *device = matrix_handle->device[idx]; if (device->state == TE_STATE_PRESS) { if (idx < matrix_handle->x_channel_num) { press_pos->x_axis = idx; //Write down the x axis info } else { press_pos->y_axis = idx - matrix_handle->x_channel_num; //Write down the y axis info } (*press_cnt)++; } else if (device->state == TE_STATE_RELEASE) { if (idx < matrix_handle->x_channel_num) { release_pos->x_axis = idx; } else { release_pos->y_axis = idx - matrix_handle->x_channel_num; } (*release_cnt)++; } } } /** * @brief Pre-check and fix * * This function will pre-check and fix the invalid state, preparing for the * next detection. * */ static void matrix_pre_fixed(te_matrix_handle_t matrix_handle, touch_matrix_position_t *press_pos, uint8_t press_cnt, touch_matrix_position_t *release_pos, uint8_t release_cnt) { te_dev_t *device; te_matrix_state_t last_state = matrix_handle->current_state; touch_matrix_position_t last_pos = { .x_axis = matrix_handle->position.x_axis, .y_axis = matrix_handle->position.y_axis }; if (last_state == TE_STATE_IDLE) { if (release_cnt > 0) { /*< Release is not allowed while matrix is in IDLE state, */ /*< if that happened, reset it from Release into IDLE. */ for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { device = matrix_handle->device[idx]; if (device->state != TE_STATE_RELEASE) { continue; } device->state = TE_STATE_IDLE; //Reset to IDLE } } } else if (last_state == TE_STATE_PRESS) { if (release_cnt > 0) { /*< Release position must be the same as the last Press position, */ /*< if it is not, reset it into IDLE. */ if (release_pos->x_axis != TE_MAT_POS_MAX && release_pos->x_axis != last_pos.x_axis) { device = matrix_handle->device[release_pos->x_axis]; device->state = TE_STATE_IDLE; } if (release_pos->y_axis != TE_MAT_POS_MAX && release_pos->y_axis != last_pos.y_axis) { device = matrix_handle->device[release_pos->y_axis + matrix_handle->x_channel_num]; device->state = TE_STATE_IDLE; } } if (press_cnt > 2) { //TODO: remove or rewrite here /*< If the last state is Press and current press count more than 2, */ /*< there must be multi-touch occurred, reset all of the channels */ /*< into IDLE except the last position channels. */ if (press_pos->x_axis != TE_MAT_POS_MAX && press_pos->x_axis != last_pos.x_axis) { device = matrix_handle->device[press_pos->x_axis]; device->state = TE_STATE_IDLE; } if (press_pos->y_axis != TE_MAT_POS_MAX && press_pos->y_axis != last_pos.y_axis) { device = matrix_handle->device[press_pos->y_axis + matrix_handle->x_channel_num]; device->state = TE_STATE_IDLE; } } } } //TODO: refactor this ugly implementation static esp_err_t matrix_get_axis_state(touch_matrix_position_t *press_pos, uint8_t press_cnt, touch_matrix_position_t *release_pos, uint8_t release_cnt, te_matrix_state_t *x_axis_state, te_matrix_state_t *y_axis_state) { esp_err_t ret = ESP_OK; if (press_cnt >= 2) { if (press_pos->x_axis != TE_MAT_POS_MAX && press_pos->y_axis != TE_MAT_POS_MAX) { *x_axis_state = TE_STATE_PRESS; *y_axis_state = TE_STATE_PRESS; ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } } else if (release_cnt >= 2) { if (release_pos->x_axis != TE_MAT_POS_MAX && release_pos->y_axis != TE_MAT_POS_MAX) { *x_axis_state = TE_STATE_RELEASE; *y_axis_state = TE_STATE_RELEASE; ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } } else { ret = ESP_ERR_INVALID_STATE; } return ret; } /** * @brief Matrix button process * * This function will process the matrix button state and maintain a matrix FSM: * IDLE ----> Press ----> Release ----> IDLE * * The state transition procedure is as follow: * (channel state ----> x,y axis state ----> matrix button state) * * TODO: add state transition diagram */ static void matrix_proc_state(te_matrix_handle_t matrix_handle) { esp_err_t ret = ESP_OK; uint8_t press_cnt = 0; uint8_t release_cnt = 0; touch_matrix_position_t press_pos; touch_matrix_position_t release_pos; te_matrix_state_t x_axis_state = TE_STATE_IDLE; te_matrix_state_t y_axis_state = TE_STATE_IDLE; uint32_t event_mask = matrix_handle->config->event_mask; touch_elem_dispatch_t dispatch_method = matrix_handle->config->dispatch_method; BaseType_t mux_ret = xSemaphoreTake(s_te_mat_obj->mutex, 0); if (mux_ret != pdPASS) { return; } //TODO: refactor those functions /*< Scan the state of all the matrix buttons channel */ matrix_scan_axis(matrix_handle, &press_pos, &press_cnt, &release_pos, &release_cnt); /*< Pre check and fixed the invalid state */ matrix_pre_fixed(matrix_handle, &press_pos, press_cnt, &release_pos, release_cnt); /*< Figure out x,y axis state and take the position */ ret = matrix_get_axis_state(&press_pos, press_cnt, &release_pos, release_cnt, &x_axis_state, &y_axis_state); if (ret != ESP_OK) { //TODO: remove return xSemaphoreGive(s_te_mat_obj->mutex); return; } matrix_handle->current_state = matrix_get_state(x_axis_state, y_axis_state); if (matrix_handle->current_state == TE_STATE_PRESS) { if (matrix_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press matrix_update_position(matrix_handle, press_pos); ESP_LOGD(TE_DEBUG_TAG, "matrix press (%"PRIu8", %"PRIu8")", matrix_handle->position.x_axis, matrix_handle->position.y_axis); if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { matrix_handle->event = TOUCH_MATRIX_EVT_ON_PRESS; matrix_dispatch(matrix_handle, dispatch_method); } } else if (matrix_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_LongPress if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { if (++matrix_handle->trigger_cnt >= matrix_handle->trigger_thr) { ESP_LOGD(TE_DEBUG_TAG, "matrix longpress (%"PRIu8", %"PRIu8")", matrix_handle->position.x_axis, matrix_handle->position.y_axis); matrix_handle->event = TOUCH_MATRIX_EVT_ON_LONGPRESS; matrix_dispatch(matrix_handle, dispatch_method); matrix_handle->trigger_cnt = 0; } } } } else if (matrix_handle->current_state == TE_STATE_RELEASE) { if (matrix_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release ESP_LOGD(TE_DEBUG_TAG, "matrix release (%"PRIu8", %"PRIu8")", matrix_handle->position.x_axis, matrix_handle->position.y_axis); if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { matrix_handle->event = TOUCH_MATRIX_EVT_ON_RELEASE; matrix_dispatch(matrix_handle, dispatch_method); } } else if (matrix_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) matrix_reset_state(matrix_handle); } } else if (matrix_handle->current_state == TE_STATE_IDLE) { if (matrix_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) //Nothing } else if (matrix_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) //Move Pre-fix into here } } matrix_handle->last_state = matrix_handle->current_state; xSemaphoreGive(s_te_mat_obj->mutex); } static inline te_state_t matrix_get_state(te_matrix_state_t x_axis_state, te_matrix_state_t y_axis_state) { te_state_t matrix_state; if ((x_axis_state == TE_STATE_PRESS) && (y_axis_state == TE_STATE_PRESS)) { matrix_state = TE_STATE_PRESS; } else if ((x_axis_state == TE_STATE_RELEASE) && (y_axis_state == TE_STATE_RELEASE)) { matrix_state = TE_STATE_RELEASE; } else { matrix_state = TE_STATE_IDLE; } return matrix_state; } static void matrix_update_position(te_matrix_handle_t matrix_handle, touch_matrix_position_t new_pos) { matrix_handle->position.x_axis = new_pos.x_axis; matrix_handle->position.y_axis = new_pos.y_axis; matrix_handle->position.index = matrix_handle->position.x_axis * matrix_handle->y_channel_num + matrix_handle->position.y_axis; } ================================================ FILE: touch_element/touch_sensor_legacy_hal.c ================================================ /* * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_private/touch_sensor_legacy_ll.h" #include "esp_private/touch_sensor_legacy_hal.h" #include "touch_element/touch_sensor_legacy_types.h" static int s_sleep_cycle = -1; static int s_meas_times = -1; void touch_hal_init(void) { touch_ll_stop_fsm(); touch_ll_reset(); // Reset the touch sensor FSM. touch_ll_intr_disable((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_ALL); touch_ll_intr_clear((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_ALL); touch_ll_clear_channel_mask(TOUCH_PAD_BIT_MASK_ALL); touch_ll_clear_trigger_status_mask(); touch_ll_set_meas_times(TOUCH_PAD_MEASURE_CYCLE_DEFAULT); touch_ll_set_sleep_time(TOUCH_PAD_SLEEP_CYCLE_DEFAULT); /* Configure the touch-sensor power domain into self-bias since bandgap-bias * level is different under sleep-mode compared to running-mode. self-bias is * always on after chip startup. */ touch_ll_sleep_low_power(true); touch_ll_set_voltage_high(TOUCH_PAD_HIGH_VOLTAGE_THRESHOLD); touch_ll_set_voltage_low(TOUCH_PAD_LOW_VOLTAGE_THRESHOLD); touch_ll_set_voltage_attenuation(TOUCH_PAD_ATTEN_VOLTAGE_THRESHOLD); touch_ll_set_idle_channel_connect(TOUCH_PAD_IDLE_CH_CONNECT_DEFAULT); /* Clear touch channels to initialize the channel value (benchmark, raw_data). * Note: Should call it after enable clock gate. */ touch_ll_clkgate(true); // Enable clock gate for touch sensor. touch_ll_reset_benchmark(TOUCH_PAD_MAX); touch_ll_sleep_reset_benchmark(); } void touch_hal_deinit(void) { touch_ll_reset_benchmark(TOUCH_PAD_MAX); touch_ll_sleep_reset_benchmark(); touch_ll_stop_fsm(); touch_ll_clkgate(false); touch_ll_clear_channel_mask(TOUCH_PAD_BIT_MASK_ALL); touch_ll_clear_trigger_status_mask(); touch_ll_intr_disable((touch_pad_intr_mask_t)TOUCH_LL_INTR_MASK_ALL); touch_ll_timeout_disable(); touch_ll_waterproof_enable(false); touch_ll_denoise_enable(false); touch_pad_t prox_pad[TOUCH_LL_PROXIMITY_CHANNEL_NUM] = {[0 ... (TOUCH_LL_PROXIMITY_CHANNEL_NUM - 1)] = 0}; touch_ll_proximity_set_channel_num((const touch_pad_t *)prox_pad); touch_ll_sleep_set_channel_num(0); touch_ll_sleep_enable_proximity_sensing(false); touch_ll_reset(); // Reset the touch sensor FSM. } void touch_hal_config(touch_pad_t touch_num) { touch_ll_set_threshold(touch_num, TOUCH_PAD_THRESHOLD_MAX); touch_ll_set_slope(touch_num, TOUCH_PAD_SLOPE_DEFAULT); touch_ll_set_tie_option(touch_num, TOUCH_PAD_TIE_OPT_DEFAULT); } void touch_hal_set_voltage(const touch_hal_volt_t *volt) { touch_ll_set_voltage_high(volt->refh); touch_ll_set_voltage_low(volt->refl); touch_ll_set_voltage_attenuation(volt->atten); } void touch_hal_get_voltage(touch_hal_volt_t *volt) { touch_ll_get_voltage_high(&volt->refh); touch_ll_get_voltage_low(&volt->refl); touch_ll_get_voltage_attenuation(&volt->atten); } void touch_hal_set_meas_mode(touch_pad_t touch_num, const touch_hal_meas_mode_t *meas) { touch_ll_set_slope(touch_num, meas->slope); touch_ll_set_tie_option(touch_num, meas->tie_opt); } void touch_hal_get_meas_mode(touch_pad_t touch_num, touch_hal_meas_mode_t *meas) { touch_ll_get_slope(touch_num, &meas->slope); touch_ll_get_tie_option(touch_num, &meas->tie_opt); } void touch_hal_filter_set_config(const touch_filter_config_t *filter_info) { touch_ll_filter_set_filter_mode(filter_info->mode); touch_ll_filter_set_debounce(filter_info->debounce_cnt); touch_ll_filter_set_noise_thres(filter_info->noise_thr); touch_ll_filter_set_jitter_step(filter_info->jitter_step); touch_ll_filter_set_smooth_mode(filter_info->smh_lvl); } void touch_hal_filter_get_config(touch_filter_config_t *filter_info) { touch_ll_filter_get_filter_mode(&filter_info->mode); touch_ll_filter_get_debounce(&filter_info->debounce_cnt); touch_ll_filter_get_noise_thres(&filter_info->noise_thr); touch_ll_filter_get_jitter_step(&filter_info->jitter_step); touch_ll_filter_get_smooth_mode(&filter_info->smh_lvl); } void touch_hal_denoise_set_config(const touch_pad_denoise_t *denoise) { touch_ll_denoise_set_cap_level(denoise->cap_level); touch_ll_denoise_set_grade(denoise->grade); } void touch_hal_denoise_get_config(touch_pad_denoise_t *denoise) { touch_ll_denoise_get_cap_level(&denoise->cap_level); touch_ll_denoise_get_grade(&denoise->grade); } void touch_hal_denoise_enable(void) { touch_ll_clear_channel_mask(1U << TOUCH_LL_DENOISE_CHANNEL); touch_ll_denoise_enable(true); } void touch_hal_waterproof_set_config(const touch_pad_waterproof_t *waterproof) { touch_ll_waterproof_set_guard_pad(waterproof->guard_ring_pad); touch_ll_waterproof_set_shield_driver(waterproof->shield_driver); } void touch_hal_waterproof_get_config(touch_pad_waterproof_t *waterproof) { touch_ll_waterproof_get_guard_pad(&waterproof->guard_ring_pad); touch_ll_waterproof_get_shield_driver(&waterproof->shield_driver); } void touch_hal_waterproof_enable(void) { touch_ll_clear_channel_mask(1U << TOUCH_LL_SHIELD_CHANNEL); touch_ll_waterproof_enable(true); } bool touch_hal_enable_proximity(touch_pad_t touch_num, bool enabled) { int i = 0; touch_pad_t ch_num[TOUCH_LL_PROXIMITY_CHANNEL_NUM] = {0}; touch_ll_proximity_get_channel_num(ch_num); if (enabled) { for (i = 0; i < TOUCH_LL_PROXIMITY_CHANNEL_NUM; i++) { if (ch_num[i] == TOUCH_PAD_NUM0 || ch_num[i] >= TOUCH_PAD_MAX || ch_num[i] == touch_num) { ch_num[i] = touch_num; break; } } if (i == TOUCH_LL_PROXIMITY_CHANNEL_NUM) { return false; } } else { for (i = 0; i < TOUCH_LL_PROXIMITY_CHANNEL_NUM; i++) { if (ch_num[i] == touch_num) { ch_num[i] = TOUCH_PAD_NUM0; break; } } } touch_ll_proximity_set_channel_num(ch_num); return true; } void touch_hal_sleep_channel_enable(touch_pad_t pad_num, bool enable) { if (enable) { touch_ll_sleep_set_channel_num(pad_num); touch_ll_sleep_set_threshold(TOUCH_PAD_THRESHOLD_MAX); touch_ll_sleep_reset_benchmark(); } else { touch_ll_sleep_set_channel_num(TOUCH_PAD_NUM0); } } void touch_hal_sleep_channel_get_config(touch_pad_sleep_channel_t *slp_config) { touch_ll_sleep_get_channel_num((uint32_t *)&slp_config->touch_num); slp_config->en_proximity = touch_ll_sleep_is_proximity_enabled(); } void touch_hal_sleep_channel_set_work_time(uint16_t sleep_cycle, uint16_t meas_times) { s_sleep_cycle = (int)sleep_cycle; s_meas_times = (int)meas_times; } void touch_hal_sleep_channel_get_work_time(uint16_t *sleep_cycle, uint16_t *meas_times) { if (s_meas_times < 0) { touch_ll_get_measure_times(meas_times); } else { *meas_times = (uint16_t)s_meas_times; } if (s_sleep_cycle < 0) { touch_ll_get_sleep_time(sleep_cycle); } else { *sleep_cycle = (uint16_t)s_sleep_cycle; } } ================================================ FILE: touch_element/touch_slider.c ================================================ /* * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_log.h" #include "esp_private/touch_element_private.h" #define TE_SLD_DEFAULT_QTF_THR(obj) ((obj)->global_config->quantify_lower_threshold) #define TE_SLD_DEFAULT_POS_FILTER_FACTOR(obj) ((obj)->global_config->position_filter_factor) #define TE_SLD_DEFAULT_CALCULATE_CHANNEL(obj) ((obj)->global_config->calculate_channel_count) #define TE_SLD_DEFAULT_BCM_UPDATE_TIME(obj) ((obj)->global_config->benchmark_update_time) #define TE_SLD_DEFAULT_FILTER_RESET_TIME(obj) ((obj)->global_config->filter_reset_time) #define TE_SLD_DEFAULT_POS_FILTER_SIZE(obj) ((obj)->global_config->position_filter_size) typedef struct te_slider_handle_list { te_slider_handle_t slider_handle; //Slider handle SLIST_ENTRY(te_slider_handle_list) next; //Slider handle list entry } te_slider_handle_list_t; typedef struct { SLIST_HEAD(te_slider_handle_list_head, te_slider_handle_list) handle_list; //Slider handle (instance) list touch_slider_global_config_t *global_config; //Slider global configuration SemaphoreHandle_t mutex; //Slider object mutex } te_slider_obj_t; te_slider_obj_t *s_te_sld_obj = NULL; /* ---------------------------------------- Slider handle(instance) methods ----------------------------------------- */ static bool slider_channel_check(te_slider_handle_t slider_handle, touch_pad_t channel_num); static esp_err_t slider_set_threshold(te_slider_handle_t slider_handle); static inline te_state_t slider_get_state(te_dev_t **device, int device_num); static void slider_reset_state(te_slider_handle_t slider_handle); static void slider_update_position(te_slider_handle_t slider_handle); static void slider_reset_position(te_slider_handle_t slider_handle); static void slider_update_benchmark(te_slider_handle_t slider_handle); static void slider_update_state(te_slider_handle_t slider_handle, touch_pad_t channel_num, te_state_t channel_state); static void slider_proc_state(te_slider_handle_t slider_handle); static void slider_event_give(te_slider_handle_t slider_handle); static inline void slider_dispatch(te_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method); /* ------------------------------------------ Slider object(class) methods ------------------------------------------ */ static esp_err_t slider_object_add_instance(te_slider_handle_t slider_handle); static esp_err_t slider_object_remove_instance(te_slider_handle_t slider_handle); static bool slider_object_check_channel(touch_pad_t channel_num); static esp_err_t slider_object_set_threshold(void); static void slider_object_process_state(void); static void slider_object_update_state(touch_pad_t channel_num, te_state_t channel_state); /* ------------------------------------------------------------------------------------------------------------------ */ esp_err_t touch_slider_install(const touch_slider_global_config_t *global_config) { TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); //Fixme: Make it thread-safe s_te_sld_obj = (te_slider_obj_t *)calloc(1, sizeof(te_slider_obj_t)); TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_NO_MEM); s_te_sld_obj->global_config = (touch_slider_global_config_t *)calloc(1, sizeof(touch_slider_global_config_t)); s_te_sld_obj->mutex = xSemaphoreCreateMutex(); TE_CHECK_GOTO(s_te_sld_obj->global_config != NULL && s_te_sld_obj->mutex != NULL, cleanup); xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); SLIST_INIT(&s_te_sld_obj->handle_list); memcpy(s_te_sld_obj->global_config, global_config, sizeof(touch_slider_global_config_t)); te_object_methods_t slider_methods = { .handle = s_te_sld_obj, .check_channel = slider_object_check_channel, .set_threshold = slider_object_set_threshold, .process_state = slider_object_process_state, .update_state = slider_object_update_state }; te_object_method_register(&slider_methods, TE_CLS_TYPE_SLIDER); xSemaphoreGive(s_te_sld_obj->mutex); return ESP_OK; cleanup: TE_FREE_AND_NULL(s_te_sld_obj->global_config); if (s_te_sld_obj->mutex != NULL) { vSemaphoreDelete(s_te_sld_obj->mutex); } TE_FREE_AND_NULL(s_te_sld_obj); return ESP_ERR_NO_MEM; } void touch_slider_uninstall(void) { xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); if (s_te_sld_obj == NULL) { xSemaphoreGive(s_te_sld_obj->mutex); return; } te_object_method_unregister(TE_CLS_TYPE_SLIDER); free(s_te_sld_obj->global_config); s_te_sld_obj->global_config = NULL; while (!SLIST_EMPTY(&s_te_sld_obj->handle_list)) { SLIST_REMOVE_HEAD(&s_te_sld_obj->handle_list, next); } xSemaphoreGive(s_te_sld_obj->mutex); vSemaphoreDelete(s_te_sld_obj->mutex); free(s_te_sld_obj); s_te_sld_obj = NULL; } esp_err_t touch_slider_create(const touch_slider_config_t *slider_config, touch_slider_handle_t *slider_handle) { TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(slider_handle != NULL && slider_config != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(slider_config->channel_num > 2 && slider_config->channel_num < TOUCH_PAD_MAX && slider_config->channel_array != NULL && slider_config->sensitivity_array != NULL && slider_config->position_range > slider_config->channel_num, ESP_ERR_INVALID_ARG); TE_CHECK(te_object_check_channel(slider_config->channel_array, slider_config->channel_num) == false, ESP_ERR_INVALID_ARG); te_slider_handle_t te_slider = (te_slider_handle_t)calloc(1, sizeof(struct te_slider_s)); TE_CHECK(te_slider != NULL, ESP_ERR_NO_MEM); esp_err_t ret = ESP_ERR_NO_MEM; te_slider->config = (te_slider_handle_config_t *)calloc(1, sizeof(te_slider_handle_config_t)); te_slider->pos_filter_window = calloc(TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj), sizeof(uint8_t)); te_slider->device = (te_dev_t **)calloc(slider_config->channel_num, sizeof(te_dev_t *)); te_slider->channel_bcm = (uint32_t *)calloc(slider_config->channel_num, sizeof(uint32_t)); te_slider->quantify_signal_array = (float *)calloc(slider_config->channel_num, sizeof(float)); TE_CHECK_GOTO(te_slider->config != NULL && te_slider->pos_filter_window != NULL && te_slider->device != NULL && te_slider->channel_bcm && te_slider->quantify_signal_array, cleanup); for (int idx = 0; idx < slider_config->channel_num; idx++) { te_slider->device[idx] = (te_dev_t *)calloc(1, sizeof(te_dev_t)); if (te_slider->device[idx] == NULL) { ret = ESP_ERR_NO_MEM; goto cleanup; } } ret = te_dev_init(te_slider->device, slider_config->channel_num, TOUCH_ELEM_TYPE_SLIDER, slider_config->channel_array, slider_config->sensitivity_array, TE_DEFAULT_THRESHOLD_DIVIDER(s_te_sld_obj)); TE_CHECK_GOTO(ret == ESP_OK, cleanup); te_slider->config->event_mask = TOUCH_ELEM_EVENT_NONE; te_slider->config->dispatch_method = TOUCH_ELEM_DISP_MAX; te_slider->config->callback = NULL; te_slider->config->arg = NULL; te_slider->channel_bcm_update_cnt = TE_SLD_DEFAULT_BCM_UPDATE_TIME(s_te_sld_obj); //update at first time te_slider->filter_reset_cnt = TE_SLD_DEFAULT_FILTER_RESET_TIME(s_te_sld_obj); //reset at first time te_slider->channel_sum = slider_config->channel_num; te_slider->position_range = slider_config->position_range; te_slider->position_scale = (float)(slider_config->position_range) / (slider_config->channel_num - 1); te_slider->current_state = TE_STATE_IDLE; te_slider->last_state = TE_STATE_IDLE; te_slider->event = TOUCH_SLIDER_EVT_MAX; te_slider->position = 0; te_slider->last_position = 0; te_slider->pos_window_idx = 0; te_slider->is_first_sample = true; ret = slider_object_add_instance(te_slider); TE_CHECK_GOTO(ret == ESP_OK, cleanup); *slider_handle = (touch_elem_handle_t)te_slider; return ESP_OK; cleanup: TE_FREE_AND_NULL(te_slider->config); TE_FREE_AND_NULL(te_slider->pos_filter_window); TE_FREE_AND_NULL(te_slider->channel_bcm); TE_FREE_AND_NULL(te_slider->quantify_signal_array); if (te_slider->device != NULL) { for (int idx = 0; idx < slider_config->channel_num; idx++) { TE_FREE_AND_NULL(te_slider->device[idx]); } free(te_slider->device); te_slider->device = NULL; } TE_FREE_AND_NULL(te_slider); return ret; } esp_err_t touch_slider_delete(touch_slider_handle_t slider_handle) { TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); /*< Release touch sensor application resource */ esp_err_t ret = slider_object_remove_instance(slider_handle); TE_CHECK(ret == ESP_OK, ret); te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; /*< Release touch sensor device resource */ te_dev_deinit(te_slider->device, te_slider->channel_sum); for (int idx = 0; idx < te_slider->channel_sum; idx++) { free(te_slider->device[idx]); } free(te_slider->config); free(te_slider->quantify_signal_array); free(te_slider->pos_filter_window); free(te_slider->channel_bcm); free(te_slider->device); free(te_slider); return ESP_OK; } esp_err_t touch_slider_set_dispatch_method(touch_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method) { TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; te_slider->config->dispatch_method = dispatch_method; xSemaphoreGive(s_te_sld_obj->mutex); return ESP_OK; } esp_err_t touch_slider_subscribe_event(touch_slider_handle_t slider_handle, uint32_t event_mask, void *arg) { TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && !(event_mask & TOUCH_ELEM_EVENT_NONE) && !(event_mask & TOUCH_ELEM_EVENT_ON_CALCULATION)) { ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_CALCULATION event mask"); return ESP_ERR_INVALID_ARG; } xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; te_slider->config->event_mask = event_mask; te_slider->config->arg = arg; xSemaphoreGive(s_te_sld_obj->mutex); return ESP_OK; } esp_err_t touch_slider_set_callback(touch_slider_handle_t slider_handle, touch_slider_callback_t slider_callback) { TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); TE_CHECK(slider_callback != NULL, ESP_ERR_INVALID_ARG); te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); te_slider->config->callback = slider_callback; xSemaphoreGive(s_te_sld_obj->mutex); return ESP_OK; } const touch_slider_message_t *touch_slider_get_message(const touch_elem_message_t *element_message) { return (touch_slider_message_t *)&element_message->child_msg; _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_slider_message_t), "Message size overflow"); } static bool slider_object_check_channel(touch_pad_t channel_num) { te_slider_handle_list_t *item; SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { if (slider_channel_check(item->slider_handle, channel_num)) { return true; } } return false; } static esp_err_t slider_object_set_threshold(void) { esp_err_t ret = ESP_OK; te_slider_handle_list_t *item; SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { ret = slider_set_threshold(item->slider_handle); if (ret != ESP_OK) { break; } } return ret; } // workaround for compilation error on xtensa-esp32s3-elf-gcc (crosstool-NG esp-2022r1-RC1) 11.2.0 (IDF-5725) __attribute__((optimize("-Os"))) static void slider_object_process_state(void) { te_slider_handle_list_t *item; SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { if (waterproof_check_mask_handle(item->slider_handle)) { slider_reset_state(item->slider_handle); slider_reset_position(item->slider_handle); continue; } slider_proc_state(item->slider_handle); } } static void slider_object_update_state(touch_pad_t channel_num, te_state_t channel_state) { te_slider_handle_list_t *item; SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { if (waterproof_check_mask_handle(item->slider_handle)) { continue; } slider_update_state(item->slider_handle, channel_num, channel_state); } } static esp_err_t slider_object_add_instance(te_slider_handle_t slider_handle) { te_slider_handle_list_t *item = (te_slider_handle_list_t *)calloc(1, sizeof(te_slider_handle_list_t)); TE_CHECK(item != NULL, ESP_ERR_NO_MEM); item->slider_handle = slider_handle; xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); SLIST_INSERT_HEAD(&s_te_sld_obj->handle_list, item, next); xSemaphoreGive(s_te_sld_obj->mutex); return ESP_OK; } static esp_err_t slider_object_remove_instance(te_slider_handle_t slider_handle) { esp_err_t ret = ESP_ERR_NOT_FOUND; te_slider_handle_list_t *item; SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { if (slider_handle == item->slider_handle) { xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); SLIST_REMOVE(&s_te_sld_obj->handle_list, item, te_slider_handle_list, next); xSemaphoreGive(s_te_sld_obj->mutex); free(item); ret = ESP_OK; break; } } return ret; } bool is_slider_object_handle(touch_elem_handle_t element_handle) { te_slider_handle_list_t *item; xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { if (element_handle == item->slider_handle) { xSemaphoreGive(s_te_sld_obj->mutex); return true; } } xSemaphoreGive(s_te_sld_obj->mutex); return false; } static bool slider_channel_check(te_slider_handle_t slider_handle, touch_pad_t channel_num) { te_dev_t *device; for (int idx = 0; idx < slider_handle->channel_sum; idx++) { device = slider_handle->device[idx]; if (device->channel == channel_num) { return true; } } return false; } static esp_err_t slider_set_threshold(te_slider_handle_t slider_handle) { esp_err_t ret = ESP_OK; for (int idx = 0; idx < slider_handle->channel_sum; idx++) { ret |= te_dev_set_threshold(slider_handle->device[idx]); } slider_update_benchmark(slider_handle); //Update benchmark at startup return ret; } static void slider_update_benchmark(te_slider_handle_t slider_handle) { for (int idx = 0; idx < slider_handle->channel_sum; idx++) { uint32_t bcm_val; te_dev_t *device = slider_handle->device[idx]; bcm_val = te_read_smooth_signal(device->channel); slider_handle->channel_bcm[idx] = bcm_val; } } static void slider_update_state(te_slider_handle_t slider_handle, touch_pad_t channel_num, te_state_t channel_state) { te_dev_t *device; for (int idx = 0; idx < slider_handle->channel_sum; idx++) { device = slider_handle->device[idx]; if (channel_num == device->channel) { device->state = channel_state; } } } static void slider_reset_state(te_slider_handle_t slider_handle) { for (int idx = 0; idx < slider_handle->channel_sum; idx++) { slider_handle->device[idx]->state = TE_STATE_IDLE; } slider_handle->current_state = TE_STATE_IDLE; } static void slider_event_give(te_slider_handle_t slider_handle) { touch_elem_message_t element_message; touch_slider_message_t slider_message = { .event = slider_handle->event, .position = slider_handle->position }; element_message.handle = (touch_elem_handle_t)slider_handle; element_message.element_type = TOUCH_ELEM_TYPE_SLIDER; element_message.arg = slider_handle->config->arg; memcpy(element_message.child_msg, &slider_message, sizeof(slider_message)); te_event_give(element_message); } static inline void slider_dispatch(te_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method) { if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { slider_event_give(slider_handle); //Event queue } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { touch_slider_message_t slider_info; slider_info.event = slider_handle->event; slider_info.position = slider_handle->position; void *arg = slider_handle->config->arg; slider_handle->config->callback(slider_handle, &slider_info, arg); //Event callback } } void slider_enable_wakeup_calibration(te_slider_handle_t slider_handle, bool en) { for (int idx = 0; idx < slider_handle->channel_sum; ++idx) { slider_handle->device[idx]->is_use_last_threshold = !en; } } /** * @brief Slider process * * This function will process the slider state and maintain a slider FSM: * IDLE ----> Press ----> Release ----> IDLE * * The state transition procedure is as follow: * (channel state ----> slider state) * * TODO: add state transition diagram */ static void slider_proc_state(te_slider_handle_t slider_handle) { uint32_t event_mask = slider_handle->config->event_mask; touch_elem_dispatch_t dispatch_method = slider_handle->config->dispatch_method; BaseType_t mux_ret = xSemaphoreTake(s_te_sld_obj->mutex, 0); if (mux_ret != pdPASS) { return; } slider_handle->current_state = slider_get_state(slider_handle->device, slider_handle->channel_sum); if (slider_handle->current_state == TE_STATE_PRESS) { slider_handle->channel_bcm_update_cnt = 0; // Reset benchmark update counter slider_update_position(slider_handle); if (slider_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press ESP_LOGD(TE_DEBUG_TAG, "slider press"); if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { slider_handle->event = TOUCH_SLIDER_EVT_ON_PRESS; slider_dispatch(slider_handle, dispatch_method); } } else if (slider_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_Calculation ESP_LOGD(TE_DEBUG_TAG, "slider calculation"); if (event_mask & TOUCH_ELEM_EVENT_ON_CALCULATION) { slider_handle->event = TOUCH_SLIDER_EVT_ON_CALCULATION; slider_dispatch(slider_handle, dispatch_method); } } } else if (slider_handle->current_state == TE_STATE_RELEASE) { if (slider_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release ESP_LOGD(TE_DEBUG_TAG, "slider release"); if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { slider_handle->event = TOUCH_SLIDER_EVT_ON_RELEASE; slider_dispatch(slider_handle, dispatch_method); } } else if (slider_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) slider_reset_state(slider_handle);//Reset the slider state for the next time touch action detection } } else if (slider_handle->current_state == TE_STATE_IDLE) { if (slider_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) //Nothing } else if (slider_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) if (++slider_handle->channel_bcm_update_cnt >= TE_SLD_DEFAULT_BCM_UPDATE_TIME(s_te_sld_obj)) { //Update channel benchmark slider_handle->channel_bcm_update_cnt = 0; slider_update_benchmark(slider_handle); ESP_LOGD(TE_DEBUG_TAG, "slider bcm update"); } if (++slider_handle->filter_reset_cnt >= TE_SLD_DEFAULT_FILTER_RESET_TIME(s_te_sld_obj)) { slider_reset_position(slider_handle); //Reset slider filter so as to speed up next time position calculation } } } slider_handle->last_state = slider_handle->current_state; xSemaphoreGive(s_te_sld_obj->mutex); } static inline te_state_t slider_get_state(te_dev_t **device, int device_num) { /*< Scan the state of all the slider channel and calculate the number of them if the state is Press*/ uint8_t press_cnt = 0; uint8_t idle_cnt = 0; for (int idx = 0; idx < device_num; idx++) { //Calculate how many channel is pressed if (device[idx]->state == TE_STATE_PRESS) { press_cnt++; } else if (device[idx]->state == TE_STATE_IDLE) { idle_cnt++; } } if (press_cnt > 0) { return TE_STATE_PRESS; } else if (idle_cnt == device_num) { return TE_STATE_IDLE; } else { return TE_STATE_RELEASE; } } /** * @brief Slider channel difference-rate re-quantization * * This function will re-quantifies the touch sensor slider channel difference-rate * so as to make the different size of touch pad in PCB has the same difference value * */ static inline void slider_quantify_signal(te_slider_handle_t slider_handle) { float weight_sum = 0; for (int idx = 0; idx < slider_handle->channel_sum; idx++) { te_dev_t *device = slider_handle->device[idx]; weight_sum += device->sens; uint32_t current_signal = te_read_smooth_signal(device->channel); int ans = current_signal - slider_handle->channel_bcm[idx]; float diff_rate = (float)ans / slider_handle->channel_bcm[idx]; slider_handle->quantify_signal_array[idx] = diff_rate / device->sens; if (slider_handle->quantify_signal_array[idx] < TE_SLD_DEFAULT_QTF_THR(s_te_sld_obj)) { slider_handle->quantify_signal_array[idx] = 0; } } for (int idx = 0; idx < slider_handle->channel_sum; idx++) { te_dev_t *device = slider_handle->device[idx]; slider_handle->quantify_signal_array[idx] = slider_handle->quantify_signal_array[idx] * weight_sum / device->sens; } } /** * @brief Calculate max sum subarray * * This function will figure out the max sum subarray from the * input array, return the max sum and max sum start index * */ static inline float slider_search_max_subarray(const float *array, int array_size, int *max_array_idx) { *max_array_idx = 0; float max_array_sum = 0; float current_array_sum = 0; for (int idx = 0; idx <= (array_size - TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj)); idx++) { for (int x = idx; x < idx + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); x++) { current_array_sum += array[x]; } if (max_array_sum < current_array_sum) { max_array_sum = current_array_sum; *max_array_idx = idx; } current_array_sum = 0; } return max_array_sum; } /** * @brief Calculate zero number * * This function will figure out the number of non-zero items from * the subarray */ static inline uint8_t slider_get_non_zero_num(const float *array, uint8_t array_idx) { uint8_t zero_cnt = 0; for (int idx = array_idx; idx < array_idx + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); idx++) { zero_cnt += (array[idx] > 0) ? 1 : 0; } return zero_cnt; } static inline uint32_t slider_calculate_position(te_slider_handle_t slider_handle, int subarray_index, float subarray_sum, int non_zero_num) { int range = slider_handle->position_range; int array_size = slider_handle->channel_sum; float scale = slider_handle->position_scale; const float *array = slider_handle->quantify_signal_array; uint32_t position = 0; if (non_zero_num == 0) { position = slider_handle->position; } else if (non_zero_num == 1) { for (int index = subarray_index; index < subarray_index + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); index++) { if (0 != array[index]) { if (index == array_size - 1) { position = range; } else { position = (uint32_t)((float)index * scale); } break; } } } else { for (int idx = subarray_index; idx < subarray_index + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); idx++) { position += ((float)idx * array[idx]); } position = position * scale / subarray_sum; } return position; } static uint32_t slider_filter_average(te_slider_handle_t slider_handle, uint32_t current_position) { uint32_t position_average = 0; if (slider_handle->is_first_sample) { for (int win_idx = 0; win_idx < TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj); win_idx++) { slider_handle->pos_filter_window[win_idx] = current_position; //Preload filter buffer } slider_handle->is_first_sample = false; } else { slider_handle->pos_filter_window[slider_handle->pos_window_idx++] = current_position; //Moving average filter if (slider_handle->pos_window_idx >= TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj)) { slider_handle->pos_window_idx = 0; } } for (int win_idx = 0; win_idx < TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj); win_idx++) { //Moving average filter position_average += slider_handle->pos_filter_window[win_idx]; } position_average = (uint32_t)((float)position_average / TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj) + 0.5F); return position_average; } static inline uint32_t slider_filter_iir(uint32_t in_now, uint32_t out_last, uint32_t k) { if (k == 0) { return in_now; } else { uint32_t out_now = (in_now + (k - 1) * out_last) / k; return out_now; } } /** * @brief touch sensor slider position update * * This function is the core algorithm about touch sensor slider * position update, mainly has several steps: * 1. Re-quantization * 2. Figure out changed channel * 3. Calculate position * 4. Filter * */ static void slider_update_position(te_slider_handle_t slider_handle) { int max_array_idx = 0; float max_array_sum; uint8_t non_zero_num; uint32_t current_position; slider_quantify_signal(slider_handle); max_array_sum = slider_search_max_subarray(slider_handle->quantify_signal_array, slider_handle->channel_sum, &max_array_idx); non_zero_num = slider_get_non_zero_num(slider_handle->quantify_signal_array, max_array_idx); current_position = slider_calculate_position(slider_handle, max_array_idx, max_array_sum, non_zero_num); uint32_t position_average = slider_filter_average(slider_handle, current_position); slider_handle->last_position = slider_handle->last_position == 0 ? (position_average << 4) : slider_handle->last_position; slider_handle->last_position = slider_filter_iir((position_average << 4), slider_handle->last_position, TE_SLD_DEFAULT_POS_FILTER_FACTOR(s_te_sld_obj)); slider_handle->position = ((slider_handle->last_position + 8) >> 4); //(x + 8) >> 4 ----> (x + 8) / 16 ----> x/16 + 0.5 } static void slider_reset_position(te_slider_handle_t slider_handle) { slider_handle->is_first_sample = true; slider_handle->last_position = 0; slider_handle->pos_window_idx = 0; } ================================================ FILE: unit-test-app/CMakeLists.txt ================================================ ================================================ FILE: unit-test-app/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: unit-test-app/README.md ================================================ # ESP-IDF Legacy Unit-Test-App Older version of ESP-IDF, all Unity tests were built using a common app, this project's unit-test-app. ESP-IDF has since migrated to a more modular approach, where each component has its own set of test apps. These test apps are built for the specified sdkconfigs using [idf-build-apps](https://github.com/espressif/idf-build-apps) and running the tests are automated with [pytest-embedded](https://github.com/espressif/pytest-embedded). This component has the unit-test-app as an example, to allow for easy integration by creating a project from it with: `idf.py create-project-from-example "espressif/unit-test-app:unit-test-app` For instructions on how to use the unit-test-app please see the [example README.md](examples/unit-test-app/README.md). ================================================ FILE: unit-test-app/examples/unit-test-app/CMakeLists.txt ================================================ # The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) if(EXISTS "$ENV{IDF_PATH}/tools/test_apps/components") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components") else() # Backwards compatibility for building with older IDF versions list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") endif() include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(unit-test-app) ================================================ FILE: unit-test-app/examples/unit-test-app/README.md ================================================ # Unit Test App This app can be built with the unit tests for a specific component. Unit tests are in `test` subdirectories of respective components. # Building Unit Test App ## CMake * Follow the setup instructions in the top-level esp-idf README. * Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory. * Change into `unit-test-app` directory * `idf.py menuconfig` to configure the Unit Test App. * `idf.py -T -T ... build` with `component` set to names of the components to be included in the test app. Or `idf.py -T all build` to build the test app with all the tests for components having `test` subdirectory. * Follow the printed instructions to flash, or run `idf.py -p PORT flash`. * Unit test have a few preset sdkconfigs. It provides command `idf.py ut-clean-config_name` and `idf.py ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `idf.py -T all ut-build-default` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder. * You may extract the test cases presented in the built elf file by calling `ElfUnitTestParser.py `. # Flash Size The unit test partition table assumes a 4MB flash size. When testing `-T all`, this additional factory app partition size is required. If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `-T ...` or instead of `-T all` tests don't fit in a smaller factory app partition (exact size will depend on configured options). # Running Unit Tests The unit test loader will prompt by showing a menu of available tests to run: * Type a number to run a single test. * `*` to run all tests. * `[tagname]` to run tests with "tag" * `![tagname]` to run tests without "tag" (`![ignore]` is very useful as it runs all CI-enabled tests.) * `"test name here"` to run test with given name ================================================ FILE: unit-test-app/examples/unit-test-app/configs/.gitkeep ================================================ ================================================ FILE: unit-test-app/examples/unit-test-app/idf_ext.py ================================================ import copy import glob import os import os.path import re import shutil def action_extensions(base_actions, project_path=os.getcwd()): """ Describes extensions for unit tests. This function expects that actions "all" and "reconfigure" """ PROJECT_NAME = 'unit-test-app' # List of unit-test-app configurations. # Each file in configs/ directory defines a configuration. The format is the # same as sdkconfig file. Configuration is applied on top of sdkconfig.defaults # file from the project directory CONFIG_NAMES = os.listdir(os.path.join(project_path, 'configs')) # Build (intermediate) and output (artifact) directories BUILDS_DIR = os.path.join(project_path, 'builds') BINARIES_DIR = os.path.join(project_path, 'output') def parse_file_to_dict(path, regex): """ Parse the config file at 'path' Returns a dict of name:value. """ compiled_regex = re.compile(regex) result = {} with open(path) as f: for line in f: m = compiled_regex.match(line) if m: result[m.group(1)] = m.group(2) return result def parse_config(path): """ Expected format with default regex is "key=value" """ return parse_file_to_dict(path, r'^([^=]+)=(.+)$') def ut_apply_config(ut_apply_config_name, ctx, args): config_name = re.match(r'ut-apply-config-(.*)', ut_apply_config_name).group(1) # Make sure that define_cache_entry is list args.define_cache_entry = list(args.define_cache_entry) new_cache_values = {} sdkconfig_set = list(filter(lambda s: 'SDKCONFIG=' in s, args.define_cache_entry)) sdkconfig_path = os.path.join(args.project_dir, 'sdkconfig') if sdkconfig_set: sdkconfig_path = sdkconfig_set[-1].split('=')[1] sdkconfig_path = os.path.abspath(sdkconfig_path) try: os.remove(sdkconfig_path) except OSError: pass if config_name in CONFIG_NAMES: # Parse the sdkconfig for components to be included/excluded and tests to be run config_path = os.path.join(project_path, 'configs', config_name) config = parse_config(config_path) target = config.get('CONFIG_IDF_TARGET', 'esp32').strip("'").strip('"') print('Reconfigure: config %s, target %s' % (config_name, target)) # Clean up and set idf-target base_actions['actions']['fullclean']['callback']('fullclean', ctx, args) new_cache_values['EXCLUDE_COMPONENTS'] = config.get('EXCLUDE_COMPONENTS', "''") new_cache_values['TEST_EXCLUDE_COMPONENTS'] = config.get('TEST_EXCLUDE_COMPONENTS', "''") new_cache_values['TEST_COMPONENTS'] = config.get('TEST_COMPONENTS', "''") new_cache_values['TESTS_ALL'] = int(new_cache_values['TEST_COMPONENTS'] == "''") new_cache_values['IDF_TARGET'] = target new_cache_values['SDKCONFIG_DEFAULTS'] = ';'.join([os.path.join(project_path, 'sdkconfig.defaults'), config_path]) args.define_cache_entry.extend(['%s=%s' % (k, v) for k, v in new_cache_values.items()]) reconfigure = base_actions['actions']['reconfigure']['callback'] reconfigure(None, ctx, args) # This target builds the configuration. It does not currently track dependencies, # but is good enough for CI builds if used together with clean-all-configs. # For local builds, use 'apply-config-NAME' target and then use normal 'all' # and 'flash' targets. def ut_build(ut_build_name, ctx, args): # Create a copy of the passed arguments to prevent arg modifications to accrue if # all configs are being built build_args = copy.copy(args) config_name = re.match(r'ut-build-(.*)', ut_build_name).group(1) if config_name in CONFIG_NAMES: build_args.build_dir = os.path.join(BUILDS_DIR, config_name) src = os.path.join(BUILDS_DIR, config_name) dest = os.path.join(BINARIES_DIR, config_name) try: os.makedirs(dest) except OSError: pass # Build, tweaking paths to sdkconfig and sdkconfig.defaults ut_apply_config('ut-apply-config-' + config_name, ctx, build_args) build_target = base_actions['actions']['all']['callback'] build_target('all', ctx, build_args) # Copy artifacts to the output directory shutil.copyfile( os.path.join(build_args.project_dir, 'sdkconfig'), os.path.join(dest, 'sdkconfig'), ) binaries = [PROJECT_NAME + x for x in ['.elf', '.bin', '.map']] for binary in binaries: shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary)) try: os.mkdir(os.path.join(dest, 'bootloader')) except OSError: pass shutil.copyfile( os.path.join(src, 'bootloader', 'bootloader.bin'), os.path.join(dest, 'bootloader', 'bootloader.bin'), ) for partition_table in glob.glob(os.path.join(src, 'partition_table', 'partition-table*.bin')): try: os.mkdir(os.path.join(dest, 'partition_table')) except OSError: pass shutil.copyfile( partition_table, os.path.join(dest, 'partition_table', os.path.basename(partition_table)), ) shutil.copyfile( os.path.join(src, 'flasher_args.json'), os.path.join(dest, 'flasher_args.json'), ) binaries = glob.glob(os.path.join(src, '*.bin')) binaries = [os.path.basename(s) for s in binaries] for binary in binaries: shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary)) def ut_clean(ut_clean_name, ctx, args): config_name = re.match(r'ut-clean-(.*)', ut_clean_name).group(1) if config_name in CONFIG_NAMES: shutil.rmtree(os.path.join(BUILDS_DIR, config_name), ignore_errors=True) shutil.rmtree(os.path.join(BINARIES_DIR, config_name), ignore_errors=True) def test_component_callback(ctx, global_args, tasks): """ Convert the values passed to the -T and -E parameter to corresponding cache entry definitions TESTS_ALL and TEST_COMPONENTS """ test_components = global_args.test_components test_exclude_components = global_args.test_exclude_components cache_entries = {} if test_components: if 'all' in test_components: cache_entries['TESTS_ALL'] = 1 cache_entries['TEST_COMPONENTS'] = "''" else: cache_entries['TESTS_ALL'] = 0 cache_entries['TEST_COMPONENTS'] = ' '.join(test_components) if test_exclude_components: cache_entries['TEST_EXCLUDE_COMPONENTS'] = ' '.join(test_exclude_components) if cache_entries: global_args.define_cache_entry = list(global_args.define_cache_entry) global_args.define_cache_entry.extend(['%s=%s' % (k, v) for k, v in cache_entries.items()]) # Add global options extensions = { 'global_options': [{ 'names': ['-T', '--test-components'], 'help': 'Specify the components to test.', 'scope': 'shared', 'multiple': True, }, { 'names': ['-E', '--test-exclude-components'], 'help': 'Specify the components to exclude from testing.', 'scope': 'shared', 'multiple': True, }], 'global_action_callbacks': [test_component_callback], 'actions': {}, } # This generates per-config targets (clean, build, apply-config). build_all_config_deps = [] clean_all_config_deps = [] for config in CONFIG_NAMES: config_build_action_name = 'ut-build-' + config config_clean_action_name = 'ut-clean-' + config config_apply_config_action_name = 'ut-apply-config-' + config extensions['actions'][config_build_action_name] = { 'callback': ut_build, 'help': 'Build unit-test-app with configuration provided in configs/NAME. ' + 'Build directory will be builds/%s/, ' % config_build_action_name + 'output binaries will be under output/%s/' % config_build_action_name, } extensions['actions'][config_clean_action_name] = { 'callback': ut_clean, 'help': 'Remove build and output directories for configuration %s.' % config_clean_action_name, } extensions['actions'][config_apply_config_action_name] = { 'callback': ut_apply_config, 'help': 'Generates configuration based on configs/%s in sdkconfig file.' % config_apply_config_action_name + 'After this, normal all/flash targets can be used. Useful for development/debugging.', } build_all_config_deps.append(config_build_action_name) clean_all_config_deps.append(config_clean_action_name) extensions['actions']['ut-build-all-configs'] = { 'callback': ut_build, 'help': 'Build all configurations defined in configs/ directory.', 'dependencies': build_all_config_deps, } extensions['actions']['ut-clean-all-configs'] = { 'callback': ut_clean, 'help': 'Remove build and output directories for all configurations defined in configs/ directory.', 'dependencies': clean_all_config_deps, } return extensions ================================================ FILE: unit-test-app/examples/unit-test-app/main/CMakeLists.txt ================================================ idf_component_register(SRCS "app_main.c" INCLUDE_DIRS "") ================================================ FILE: unit-test-app/examples/unit-test-app/main/app_main.c ================================================ #include "test_utils.h" void app_main(void) { test_main(); } ================================================ FILE: unit-test-app/examples/unit-test-app/partition_table_unit_test_app.csv ================================================ # Special partition table for unit test app # # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0xb000, 0x5000 otadata, data, ota, 0x10000, 0x2000 phy_init, data, phy, 0x12000, 0x1000 factory, 0, 0, 0x20000, 0x260000 ota_0, 0, ota_0, , 256K ota_1, 0, ota_1, , 256K # flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests flash_test, data, fat, , 528K nvs_key, data, nvs_keys, , 0x1000, encrypted # Note: still 1MB of a 4MB flash left free for some other purpose ================================================ FILE: unit-test-app/examples/unit-test-app/partition_table_unit_test_app_2m.csv ================================================ # Special partition table for unit test app # # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0xb000, 0x5000 otadata, data, ota, 0x10000, 0x2000 phy_init, data, phy, 0x12000, 0x1000 factory, 0, 0, 0x20000, 0x150000 ota_0, 0, ota_0, , 156K ota_1, 0, ota_1, , 156K # flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests flash_test, data, fat, , 220K nvs_key, data, nvs_keys, , 0x1000, encrypted # Note: occupied 1.85MB in the 2MB flash ================================================ FILE: unit-test-app/examples/unit-test-app/partition_table_unit_test_two_ota.csv ================================================ # Special partition table for unit test app_update # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, , 0x4000 otadata, data, ota, , 0x2000 phy_init, data, phy, , 0x1000 factory, 0, 0, , 0xB0000 ota_0, 0, ota_0, , 0xB0000 ota_1, 0, ota_1, , 0xB0000 test, 0, test, , 0xB0000 # flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests flash_test, data, fat, , 528K ================================================ FILE: unit-test-app/examples/unit-test-app/partition_table_unit_test_two_ota_2m.csv ================================================ # Special partition table for unit test app_update # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, , 0x4000 otadata, data, ota, , 0x2000 phy_init, data, phy, , 0x1000 factory, 0, 0, , 0x70000 ota_0, 0, ota_0, , 0x70000 ota_1, 0, ota_1, , 0x70000 test, 0, test, , 0x70000 # flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests flash_test, data, fat, , 128K ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults ================================================ CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app.csv" CONFIG_PARTITION_TABLE_FILENAME="partition_table_unit_test_app.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y CONFIG_HEAP_POISONING_COMPREHENSIVE=y CONFIG_SPI_FLASH_ENABLE_COUNTERS=y CONFIG_ESP_TASK_WDT_INIT=n CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y CONFIG_ADC_DISABLE_DAC=n CONFIG_COMPILER_WARN_WRITE_STRINGS=y CONFIG_SPI_MASTER_IN_IRAM=y CONFIG_EFUSE_VIRTUAL=y CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults.esp32 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_XTAL_FREQ_AUTO=y CONFIG_SPI_FLASH_SHARE_SPI1_BUS=y CONFIG_SPIRAM_BANKSWITCH_ENABLE=n ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults.esp32c2 ================================================ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app_2m.csv" CONFIG_PARTITION_TABLE_FILENAME="partition_table_unit_test_app_2m.csv" # 2MB partition table has a FAT partition too small for 4k sectors CONFIG_WL_SECTOR_SIZE_512=y ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults.esp32c3 ================================================ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults.esp32s2 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n ================================================ FILE: unit-test-app/examples/unit-test-app/sdkconfig.defaults.esp32s3 ================================================ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n ================================================ FILE: unit-test-app/examples/unit-test-app/tools/CreateSectionTable.py ================================================ # This file is used to process section data generated by `objdump -s` import re class Section(object): """ One Section of section table. contains info about section name, address and raw data """ SECTION_START_PATTERN = re.compile(b'Contents of section (.+?):') DATA_PATTERN = re.compile(b'([0-9a-f]{4,8})') def __init__(self, name, start_address, data): self.name = name self.start_address = start_address self.data = data def __contains__(self, item): """ check if the section name and address match this section """ if (item['section'] == self.name or item['section'] == 'any') \ and (self.start_address <= item['address'] < (self.start_address + len(self.data))): return True else: return False def __getitem__(self, item): """ process slice. convert absolute address to relative address in current section and return slice result """ if isinstance(item, int): return self.data[item - self.start_address] elif isinstance(item, slice): start = item.start if item.start is None else item.start - self.start_address stop = item.stop if item.stop is None else item.stop - self.start_address return self.data[start:stop] return self.data[item] def __str__(self): return '%s [%08x - %08x]' % (self.name, self.start_address, self.start_address + len(self.data)) __repr__ = __str__ @classmethod def parse_raw_data(cls, raw_data): """ process raw data generated by `objdump -s`, create section and return un-processed lines :param raw_data: lines of raw data generated by `objdump -s` :return: one section, un-processed lines """ name = '' data = '' start_address = 0 # first find start line for i, line in enumerate(raw_data): if b'Contents of section ' in line: # do strcmp first to speed up match = cls.SECTION_START_PATTERN.search(line) if match is not None: name = match.group(1) raw_data = raw_data[i + 1:] break else: # do some error handling raw_data = [b''] # add a dummy first data line def process_data_line(line_to_process): # first remove the ascii part hex_part = line_to_process.split(b' ')[0] # process rest part data_list = cls.DATA_PATTERN.findall(hex_part) try: _address = int(data_list[0], base=16) except IndexError: _address = -1 def hex_to_str(hex_data): if len(hex_data) % 2 == 1: hex_data = b'0' + hex_data # append zero at the beginning _length = len(hex_data) return ''.join([chr(int(hex_data[_i:_i + 2], base=16)) for _i in range(0, _length, 2)]) return _address, ''.join([hex_to_str(x) for x in data_list[1:]]) # handle first line: address, _data = process_data_line(raw_data[0]) if address != -1: start_address = address data += _data raw_data = raw_data[1:] for i, line in enumerate(raw_data): address, _data = process_data_line(line) if address == -1: raw_data = raw_data[i:] break else: data += _data else: # do error handling raw_data = [] section = cls(name, start_address, data) if start_address != -1 else None unprocessed_data = None if len(raw_data) == 0 else raw_data return section, unprocessed_data class SectionTable(object): """ elf section table """ def __init__(self, file_name): with open(file_name, 'rb') as f: raw_data = f.readlines() self.table = [] while raw_data: section, raw_data = Section.parse_raw_data(raw_data) self.table.append(section) def get_unsigned_int(self, section, address, size=4, endian='LE'): """ get unsigned int from section table :param section: section name; use "any" will only match with address :param address: start address :param size: size in bytes :param endian: LE or BE :return: int or None """ if address % 4 != 0 or size % 4 != 0: print('warning: try to access without 4 bytes aligned') key = {'address': address, 'section': section} for section in self.table: if key in section: tmp = section[address:address + size] value = 0 for i in range(size): if endian == 'LE': value += ord(tmp[i]) << (i * 8) elif endian == 'BE': value += ord(tmp[i]) << ((size - i - 1) * 8) else: print('only support LE or BE for parameter endian') assert False break else: value = None return value def get_string(self, section, address): """ get string ('\0' terminated) from section table :param section: section name; use "any" will only match with address :param address: start address :return: string or None """ value = None key = {'address': address, 'section': section} for section in self.table: if key in section: value = section[address:] for i, c in enumerate(value): if c == '\0': value = value[:i] break break return value ================================================ FILE: unit-test-app/examples/unit-test-app/tools/ElfUnitTestParser.py ================================================ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import argparse import os import subprocess import sys from typing import Dict, List import yaml try: import CreateSectionTable except ImportError: sys.path.append(os.path.expandvars(os.path.join('$IDF_PATH', 'tools', 'unit-test-app', 'tools'))) import CreateSectionTable def get_target_objdump(idf_target: str) -> str: toolchain_for_target = { 'esp32': 'xtensa-esp32-elf-', 'esp32s2': 'xtensa-esp32s2-elf-', 'esp32s3': 'xtensa-esp32s3-elf-', 'esp32c2': 'riscv32-esp-elf-', 'esp32c3': 'riscv32-esp-elf-', } return toolchain_for_target.get(idf_target, 'riscv32-esp-elf-') + 'objdump' def parse_elf_test_cases(elf_file: str, idf_target: str) -> List[Dict]: objdump = get_target_objdump(idf_target) try: subprocess.check_output('{} -s {} > section_table.tmp'.format(objdump, elf_file), shell=True) table = CreateSectionTable.SectionTable('section_table.tmp') except subprocess.CalledProcessError: raise Exception('Can\'t resolve elf file. File not found.') finally: os.remove('section_table.tmp') bin_test_cases = [] try: subprocess.check_output('{} -t {} | grep test_desc > case_address.tmp'.format(objdump, elf_file), shell=True) with open('case_address.tmp', 'rb') as input_f: for line in input_f: # process symbol table like: "3ffb4310 l O .dram0.data 00000018 test_desc_33$5010" sections = line.split() test_addr = int(sections[0], 16) section = sections[3] name_addr = table.get_unsigned_int(section, test_addr, 4) desc_addr = table.get_unsigned_int(section, test_addr + 4, 4) tc = { 'name': table.get_string('any', name_addr), 'desc': table.get_string('any', desc_addr), 'function_count': table.get_unsigned_int(section, test_addr + 20, 4), } bin_test_cases.append(tc) except subprocess.CalledProcessError: raise Exception('Test cases not found') finally: os.remove('case_address.tmp') return bin_test_cases if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('elf_file', help='Elf file to parse') parser.add_argument('-t', '--idf_target', type=str, default=os.environ.get('IDF_TARGET', ''), help='Target of the elf, e.g. esp32s2') parser.add_argument('-o', '--output_file', type=str, default='elf_test_cases.yml', help='Target of the elf, e.g. esp32s2') args = parser.parse_args() assert args.idf_target test_cases = parse_elf_test_cases(args.elf_file, args.idf_target) with open(args.output_file, 'w') as out_file: yaml.dump(test_cases, out_file, default_flow_style=False) ================================================ FILE: unit-test-app/idf_component.yml ================================================ version: "1.0.0" description: "Legacy ESP-IDF unit test app" url: https://github.com/espressif/idf-extra-components/tree/master/unit-test-app ================================================ FILE: zlib/.build-test-rules.yml ================================================ ================================================ FILE: zlib/CMakeLists.txt ================================================ idf_component_register(INCLUDE_DIRS zlib SRC_DIRS zlib) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-int) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-return-type) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-format-extra-args) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-parameter) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough) target_compile_definitions(${COMPONENT_LIB} PRIVATE PPM_SUPPORTED) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_MATH_H) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_CTYPE_H) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_UNISTD_H) target_compile_definitions(${COMPONENT_LIB} PRIVATE HAVE_ERRNO_H) ================================================ FILE: zlib/LICENSE ================================================ Copyright notice: (C) 1995-2022 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu ================================================ FILE: zlib/README.md ================================================ # Zlib compression and decompression Library [![Component Registry](https://components.espressif.com/components/espressif/zlib/badge.svg)](https://components.espressif.com/components/espressif/zlib) This is an IDF component for zlib library. For usage instructions, please refer to the official documentation: https://www.zlib.net/manual.html ================================================ FILE: zlib/idf_component.yml ================================================ version: "1.3.2" description: zlib C library url: https://github.com/espressif/idf-extra-components/tree/master/zlib repository: "https://github.com/espressif/idf-extra-components.git" documentation: "https://www.zlib.net/manual.html" issues: "https://github.com/espressif/idf-extra-components/issues" # URL of the issue tracker dependencies: idf: ">=4.4" sbom: manifests: - path: sbom_zlib.yml dest: zlib ================================================ FILE: zlib/sbom_zlib.yml ================================================ name: zlib version: 1.3.2 cpe: cpe:2.3:a:zlib:zlib:{}:*:*:*:*:*:*:* supplier: 'Organization: zlib ' description: A massively spiffy yet delicately unobtrusive compression library url: https://github.com/madler/zlib hash: da607da739fa6047df13e66a2af6b8bec7c2a498 ================================================ FILE: zlib/test_apps/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) project(zlib_test) ================================================ FILE: zlib/test_apps/main/CMakeLists.txt ================================================ idf_component_register(SRCS "zlib_test.c" INCLUDE_DIRS "." PRIV_REQUIRES unity) ================================================ FILE: zlib/test_apps/main/idf_component.yml ================================================ dependencies: espressif/zlib: version: "*" override_path: "../.." ================================================ FILE: zlib/test_apps/main/zlib_test.c ================================================ #include void app_main(void) { }