Merge remote-tracking branch 'origin/dev' into release-candidate
This commit is contained in:
		
						commit
						c5fd214f80
					
				
							
								
								
									
										24
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -71,14 +71,6 @@ jobs: | ||||
|         run: | | ||||
|           tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts | ||||
| 
 | ||||
|       - name: 'Rebuild Assets' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             make assets_rebuild assets_manifest | ||||
|             git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
| @ -86,7 +78,7 @@ jobs: | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               make updater_package TARGET=${TARGET} ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|               ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` --with-updater updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|             done | ||||
| 
 | ||||
|       - name: 'Move upload files' | ||||
| @ -97,7 +89,7 @@ jobs: | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               mv dist/${TARGET}/* artifacts/ | ||||
|               mv dist/${TARGET}-*/* artifacts/ | ||||
|             done | ||||
| 
 | ||||
|       - name: 'Bundle self-update package' | ||||
| @ -124,7 +116,7 @@ jobs: | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             make -C assets copro_bundle | ||||
|             ./fbt copro_dist | ||||
|             tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
| @ -208,14 +200,6 @@ jobs: | ||||
|           echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV | ||||
|           echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV | ||||
| 
 | ||||
|       - name: 'Rebuild Assets' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             make assets_rebuild assets_manifest | ||||
|             git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
| @ -223,5 +207,5 @@ jobs: | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               make TARGET=${TARGET} DEBUG=0 COMPACT=1 | ||||
|               ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` --with-updater updater_package DEBUG=0 COMPACT=1 | ||||
|             done | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -47,7 +47,7 @@ jobs: | ||||
|         id: syntax_check | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: SET_GH_OUTPUT=1 make lint | ||||
|           run: SET_GH_OUTPUT=1 ./fbt lint | ||||
| 
 | ||||
|       - name: Report code formatting errors | ||||
|         if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request | ||||
|  | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -39,3 +39,11 @@ dist | ||||
| 
 | ||||
| # kde | ||||
| .directory | ||||
| 
 | ||||
| # SCons | ||||
| .sconsign.dblite | ||||
| # SCons build dir | ||||
| build/ | ||||
| 
 | ||||
| # Toolchain | ||||
| toolchain*/ | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -22,3 +22,6 @@ | ||||
| [submodule "lib/microtar"] | ||||
| 	path = lib/microtar | ||||
| 	url = https://github.com/amachronic/microtar.git | ||||
| [submodule "lib/scons"] | ||||
| 	path = lib/scons | ||||
| 	url = https://github.com/SCons/scons.git | ||||
|  | ||||
							
								
								
									
										2
									
								
								Brewfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Brewfile
									
									
									
									
									
								
							| @ -1,8 +1,6 @@ | ||||
| cask "gcc-arm-embedded" | ||||
| brew "protobuf" | ||||
| brew "gdb" | ||||
| brew "heatshrink" | ||||
| brew "open-ocd" | ||||
| brew "clang-format" | ||||
| brew "dfu-util" | ||||
| brew "imagemagick" | ||||
|  | ||||
| @ -48,7 +48,7 @@ Almost everything in flipper firmware is built around this concept. | ||||
| # C coding style | ||||
| 
 | ||||
| - Tab is 4 spaces | ||||
| - Use `make format` to reformat source code and check style guide before commit | ||||
| - Use `fbt format` to reformat source code and check style guide before commit | ||||
| 
 | ||||
| ## Naming | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										176
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,155 +1,21 @@ | ||||
| PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||
| 
 | ||||
| include			$(PROJECT_ROOT)/make/git.mk | ||||
| include			$(PROJECT_ROOT)/assets/copro.mk | ||||
| 
 | ||||
| PROJECT_SOURCE_DIRECTORIES := \
 | ||||
| 	$(PROJECT_ROOT)/applications \
 | ||||
| 	$(PROJECT_ROOT)/core \
 | ||||
| 	$(PROJECT_ROOT)/firmware/targets \
 | ||||
| 	$(PROJECT_ROOT)/lib/app-template \
 | ||||
| 	$(PROJECT_ROOT)/lib/app-scened-template \
 | ||||
| 	$(PROJECT_ROOT)/lib/common-api \
 | ||||
| 	$(PROJECT_ROOT)/lib/drivers \
 | ||||
| 	$(PROJECT_ROOT)/lib/flipper_file \
 | ||||
| 	$(PROJECT_ROOT)/lib/infrared \
 | ||||
| 	$(PROJECT_ROOT)/lib/nfc_protocols \
 | ||||
| 	$(PROJECT_ROOT)/lib/ST25RFAL002 \
 | ||||
| 	$(PROJECT_ROOT)/lib/onewire \
 | ||||
| 	$(PROJECT_ROOT)/lib/qrcode \
 | ||||
| 	$(PROJECT_ROOT)/lib/subghz \
 | ||||
| 	$(PROJECT_ROOT)/lib/toolbox \
 | ||||
| 	$(PROJECT_ROOT)/lib/u8g2 | ||||
| 
 | ||||
| NPROCS := 3 | ||||
| OS := $(shell uname -s) | ||||
| 
 | ||||
| ifeq ($(OS), Linux) | ||||
| NPROCS := $(shell grep -c ^processor /proc/cpuinfo) | ||||
| else ifeq ($(OS), Darwin) | ||||
| NPROCS := $(shell sysctl -n hw.ncpu) | ||||
| endif | ||||
| 
 | ||||
| include	$(PROJECT_ROOT)/make/defaults.mk | ||||
| 
 | ||||
| .PHONY: all | ||||
| all: firmware_all | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy -t $(TARGET) -p firmware -s $(DIST_SUFFIX) | ||||
| 
 | ||||
| .PHONY: whole | ||||
| whole: flash_radio firmware_flash | ||||
| 
 | ||||
| .PHONY: clean | ||||
| clean: firmware_clean updater_clean | ||||
| 	@rm -rf $(PROJECT_ROOT)/dist/$(TARGET) | ||||
| 
 | ||||
| .PHONY: flash | ||||
| flash: firmware_flash | ||||
| 
 | ||||
| .PHONY: debug | ||||
| debug: | ||||
| 	@$(MAKE) -C firmware -j$(NPROCS) debug | ||||
| 
 | ||||
| .PHONY: debug_other | ||||
| debug_other: | ||||
| 	@$(MAKE) -C firmware -j$(NPROCS) debug_other | ||||
| 
 | ||||
| .PHONY: blackmagic | ||||
| blackmagic: | ||||
| 	@$(MAKE) -C firmware -j$(NPROCS) blackmagic | ||||
| 
 | ||||
| .PHONY: wipe | ||||
| wipe: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py wipe | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: firmware_all | ||||
| firmware_all: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) all | ||||
| 
 | ||||
| .PHONY: firmware_clean | ||||
| firmware_clean: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) clean | ||||
| 
 | ||||
| .PHONY: firmware_flash | ||||
| firmware_flash: | ||||
| ifeq ($(FORCE), 1) | ||||
| 	@rm $(PROJECT_ROOT)/firmware/.obj/f*-firmware/flash || true | ||||
| endif | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) flash | ||||
| 
 | ||||
| .PHONY: updater | ||||
| updater: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) RAM_EXEC=1 all | ||||
| 
 | ||||
| .PHONY: updater_clean | ||||
| updater_clean: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) RAM_EXEC=1 clean | ||||
| 
 | ||||
| .PHONY: updater_debug | ||||
| updater_debug: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) RAM_EXEC=1 debug | ||||
| 
 | ||||
| .PHONY: updater_package_bin | ||||
| updater_package_bin: firmware_all updater | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy -t $(TARGET) -p firmware updater -s $(DIST_SUFFIX) --bundlever "$(VERSION_STRING)" | ||||
| 
 | ||||
| .PHONY: updater_package | ||||
| updater_package: firmware_all updater assets_manifest | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy \
 | ||||
| 	-t $(TARGET) -p firmware updater \
 | ||||
| 	-s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources \
 | ||||
| 	--bundlever "$(VERSION_STRING)" \
 | ||||
| 	--radio $(COPRO_STACK_BIN_PATH) \
 | ||||
| 	--radiotype $(COPRO_STACK_TYPE) \
 | ||||
| 	$(COPRO_DISCLAIMER) \
 | ||||
| 	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) | ||||
| 
 | ||||
| .PHONY: assets_manifest | ||||
| assets_manifest: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/assets manifest | ||||
| 
 | ||||
| .PHONY: assets_rebuild | ||||
| assets_rebuild: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/assets clean all | ||||
| 
 | ||||
| .PHONY: flash_radio | ||||
| flash_radio: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2radio $(COPRO_STACK_BIN_PATH) --addr=$(COPRO_STACK_ADDR) | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: flash_radio_fus | ||||
| flash_radio_fus: | ||||
| 	@echo | ||||
| 	@echo "================   DON'T DO IT    ================" | ||||
| 	@echo "= Flashing FUS is going to erase secure enclave  =" | ||||
| 	@echo "= You will lose ability to use encrypted assets  =" | ||||
| 	@echo "=       type 'find / -exec rm -rf {} \;'         =" | ||||
| 	@echo "=     In case if you still want to continue      =" | ||||
| 	@echo "================    JUST DON'T    ================" | ||||
| 	@echo | ||||
| 
 | ||||
| .PHONY: flash_radio_fus_please_i_m_not_going_to_complain | ||||
| flash_radio_fus_please_i_m_not_going_to_complain: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: lint | ||||
| lint: | ||||
| 	@echo "Checking source code formatting" | ||||
| 	@$(PROJECT_ROOT)/scripts/lint.py check $(PROJECT_SOURCE_DIRECTORIES) | ||||
| 
 | ||||
| .PHONY: format | ||||
| format: | ||||
| 	@echo "Reformating sources code" | ||||
| 	@$(PROJECT_ROOT)/scripts/lint.py format $(PROJECT_SOURCE_DIRECTORIES) | ||||
| 
 | ||||
| .PHONY: guruguru | ||||
| guruguru: | ||||
| 	@echo "ぐるぐる回る" | ||||
| 	@$(PROJECT_ROOT)/scripts/guruguru.py $(PROJECT_ROOT) | ||||
| 
 | ||||
| .PHONY: generate_compile_db | ||||
| generate_compile_db: | ||||
| 	@$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) generate_compile_db | ||||
| $(info +-------------------------------------------------+) | ||||
| $(info |                                                 |) | ||||
| $(info |      Hello, this is Flipper team speaking!      |) | ||||
| $(info |                                                 |) | ||||
| $(info |       We've migrated to new build system        |) | ||||
| $(info |          It's nice and based on scons           |) | ||||
| $(info |                                                 |) | ||||
| $(info |      Crash course:                              |) | ||||
| $(info |                                                 |) | ||||
| $(info |        `./fbt`                                  |) | ||||
| $(info |        `./fbt flash`                            |) | ||||
| $(info |        `./fbt debug`                            |) | ||||
| $(info |                                                 |) | ||||
| $(info |      More details in documentation/fbt.md       |) | ||||
| $(info |                                                 |) | ||||
| $(info |      Also Please leave your feedback here:      |) | ||||
| $(info |           https://flipp.dev/4RDu                |) | ||||
| $(info |                      or                         |) | ||||
| $(info |           https://flipp.dev/2XM8                |) | ||||
| $(info |                                                 |) | ||||
| $(info +-------------------------------------------------+) | ||||
							
								
								
									
										28
									
								
								ReadMe.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								ReadMe.md
									
									
									
									
									
								
							| @ -25,6 +25,12 @@ Flipper Zero's firmware consists of two components: | ||||
| 
 | ||||
| They both must be flashed in order described. | ||||
| 
 | ||||
| ## With offline update package | ||||
| 
 | ||||
| `./fbt --with-updater updater_package` | ||||
| 
 | ||||
| Copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.  | ||||
| 
 | ||||
| ## With STLink | ||||
| 
 | ||||
| ### Core1 Firmware | ||||
| @ -36,17 +42,7 @@ Prerequisites: | ||||
| - [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) | ||||
| - openocd | ||||
| 
 | ||||
| One liner: `make flash` | ||||
| 
 | ||||
| ### Core2 flashing procedures | ||||
| 
 | ||||
| Prerequisites: | ||||
| 
 | ||||
| - Linux / macOS | ||||
| - Terminal | ||||
| - STM32_Programmer_CLI (v2.5.0) added to $PATH | ||||
| 
 | ||||
| One liner: `make flash_radio` | ||||
| One liner: `./fbt firmware_flash` | ||||
| 
 | ||||
| ## With USB DFU  | ||||
| 
 | ||||
| @ -56,7 +52,6 @@ One liner: `make flash_radio` | ||||
|  - Press and hold `← Left` + `↩ Back` for reset  | ||||
|  - Release `↩ Back` and keep holding `← Left` until blue LED lights up | ||||
|  - Release `← Left` | ||||
| <!--  --> | ||||
| 
 | ||||
| 3. Run `dfu-util -D full.dfu -a 0` | ||||
| 
 | ||||
| @ -74,7 +69,7 @@ One liner: `make flash_radio` | ||||
| ## Compile everything | ||||
| 
 | ||||
| ```sh | ||||
| docker-compose exec dev make | ||||
| docker-compose exec dev ./fbt | ||||
| ``` | ||||
| 
 | ||||
| Check `dist/` for build outputs. | ||||
| @ -85,6 +80,8 @@ If compilation fails, make sure all submodules are all initialized. Either clone | ||||
| 
 | ||||
| # Build on Linux/macOS | ||||
| 
 | ||||
| Check out `documentation/fbt.md` for details on building and flashing firmware.  | ||||
| 
 | ||||
| ## macOS Prerequisites | ||||
| 
 | ||||
| Make sure you have [brew](https://brew.sh) and install all the dependencies: | ||||
| @ -127,7 +124,7 @@ heatshrink has to be compiled [from sources](https://github.com/atomicobject/hea | ||||
| ## Compile everything | ||||
| 
 | ||||
| ```sh | ||||
| make | ||||
| ./fbt | ||||
| ``` | ||||
| 
 | ||||
| Check `dist/` for build outputs. | ||||
| @ -138,7 +135,7 @@ Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. | ||||
| 
 | ||||
| Connect your device via ST-Link and run: | ||||
| ```sh | ||||
| make whole | ||||
| ./fbt firmware_flash | ||||
| ``` | ||||
| 
 | ||||
| # Links | ||||
| @ -158,7 +155,6 @@ make whole | ||||
| - `documentation`   - Documentation generation system configs and input files | ||||
| - `firmware`        - Firmware source code | ||||
| - `lib`             - Our and 3rd party libraries, drivers and etc... | ||||
| - `make`            - Make helpers | ||||
| - `scripts`         - Supplementary scripts and python libraries home | ||||
| 
 | ||||
| Also pay attention to `ReadMe.md` files inside of those directories. | ||||
|  | ||||
							
								
								
									
										150
									
								
								SConstruct
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								SConstruct
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | ||||
| # | ||||
| # Main Fipper Build System entry point | ||||
| # | ||||
| # This file is evaluated by scons (the build system) every time fbt is invoked. | ||||
| # Scons constructs all referenced environments & their targets' dependency | ||||
| # trees on startup. So, to keep startup time as low as possible, we're hiding | ||||
| # construction of certain targets behind command-line options. | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| DefaultEnvironment(tools=[]) | ||||
| # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) | ||||
| 
 | ||||
| 
 | ||||
| # This environment is created only for loading options & validating file/dir existance | ||||
| fbt_variables = SConscript("site_scons/commandline.scons") | ||||
| cmd_environment = Environment(tools=[], variables=fbt_variables) | ||||
| Help(fbt_variables.GenerateHelpText(cmd_environment)) | ||||
| 
 | ||||
| 
 | ||||
| # Building basic environment - tools, utility methods, cross-compilation | ||||
| # settings, gcc flags for Cortex-M4, basic builders and more | ||||
| coreenv = SConscript( | ||||
|     "site_scons/environ.scons", | ||||
|     exports={"VAR_ENV": cmd_environment}, | ||||
| ) | ||||
| SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) | ||||
| 
 | ||||
| # Store root dir in environment for certain tools | ||||
| coreenv["ROOT_DIR"] = Dir(".") | ||||
| 
 | ||||
| # Create a separate "dist" environment and add construction envs to it | ||||
| distenv = coreenv.Clone( | ||||
|     tools=["fbt_dist", "openocd"], | ||||
|     GDBOPTS="-ex 'target extended-remote | ${OPENOCD} -c \"gdb_port pipe\" ${OPENOCD_OPTS}' " | ||||
|     '-ex "set confirm off" ', | ||||
|     ENV=os.environ, | ||||
| ) | ||||
| 
 | ||||
| firmware_out = distenv.AddFwProject( | ||||
|     base_env=coreenv, | ||||
|     fw_type="firmware", | ||||
|     fw_env_key="FW_ENV", | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| # If enabled, initialize updater-related targets | ||||
| if GetOption("fullenv"): | ||||
|     updater_out = distenv.AddFwProject( | ||||
|         base_env=coreenv, | ||||
|         fw_type="updater", | ||||
|         fw_env_key="UPD_ENV", | ||||
|     ) | ||||
| 
 | ||||
|     # Target for self-update package | ||||
|     dist_arguments = [ | ||||
|         "-r", | ||||
|         '"${ROOT_DIR.abspath}/assets/resources"', | ||||
|         "--bundlever", | ||||
|         '"${UPDATE_VERSION_STRING}"', | ||||
|         "--radio", | ||||
|         '"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"', | ||||
|         "--radiotype", | ||||
|         "${COPRO_STACK_TYPE}", | ||||
|         "${COPRO_DISCLAIMER}", | ||||
|         "--obdata", | ||||
|         '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"', | ||||
|     ] | ||||
|     if distenv["UPDATE_SPLASH"]: | ||||
|         dist_arguments += [ | ||||
|             "--splash", | ||||
|             distenv.subst("assets/slideshow/$UPDATE_SPLASH"), | ||||
|         ] | ||||
|     selfupdate_dist = distenv.DistBuilder( | ||||
|         "selfupdate.pseudo", | ||||
|         (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]), | ||||
|         DIST_EXTRA=dist_arguments, | ||||
|     ) | ||||
|     distenv.Pseudo("selfupdate.pseudo") | ||||
|     AlwaysBuild(selfupdate_dist) | ||||
|     Alias("updater_package", selfupdate_dist) | ||||
| 
 | ||||
|     # Updater debug | ||||
|     debug_updater_elf = distenv.AddDebugTarget(updater_out, False) | ||||
|     Alias("updater_debug", debug_updater_elf) | ||||
| 
 | ||||
| 
 | ||||
| # Target for copying & renaming binaries to dist folder | ||||
| basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"]) | ||||
| distenv.Pseudo("dist.pseudo") | ||||
| AlwaysBuild(basic_dist) | ||||
| Alias("fw_dist", basic_dist) | ||||
| Default(basic_dist) | ||||
| 
 | ||||
| # Target for bundling core2 package for qFlipper | ||||
| copro_dist = distenv.CoproBuilder( | ||||
|     Dir("assets/core2_firmware"), | ||||
|     [], | ||||
| ) | ||||
| AlwaysBuild(copro_dist) | ||||
| Alias("copro_dist", copro_dist) | ||||
| 
 | ||||
| 
 | ||||
| # Debugging firmware | ||||
| 
 | ||||
| debug_fw_elf = distenv.AddDebugTarget(firmware_out) | ||||
| Alias("debug", debug_fw_elf) | ||||
| 
 | ||||
| 
 | ||||
| # Debug alien elf | ||||
| debug_other = distenv.GDBPy( | ||||
|     "debugother.pseudo", | ||||
|     None, | ||||
|     GDBPYOPTS= | ||||
|     # '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' | ||||
|     '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' | ||||
| ) | ||||
| distenv.Pseudo("debugother.pseudo") | ||||
| AlwaysBuild(debug_other) | ||||
| Alias("debug_other", debug_other) | ||||
| 
 | ||||
| 
 | ||||
| # Just start OpenOCD | ||||
| openocd = distenv.OOCDCommand("openocd.pseudo", []) | ||||
| distenv.Pseudo("openocd.pseudo") | ||||
| AlwaysBuild(openocd) | ||||
| Alias("openocd", openocd) | ||||
| 
 | ||||
| 
 | ||||
| # Linter | ||||
| lint_check = distenv.Command( | ||||
|     "lint.check.pseudo", | ||||
|     [], | ||||
|     "${PYTHON3} scripts/lint.py check $LINT_SOURCES", | ||||
|     LINT_SOURCES=firmware_out["LINT_SOURCES"], | ||||
| ) | ||||
| distenv.Pseudo("lint.check.pseudo") | ||||
| AlwaysBuild(lint_check) | ||||
| Alias("lint", lint_check) | ||||
| 
 | ||||
| 
 | ||||
| lint_format = distenv.Command( | ||||
|     "lint.format.pseudo", | ||||
|     [], | ||||
|     "${PYTHON3} scripts/lint.py format $LINT_SOURCES", | ||||
|     LINT_SOURCES=firmware_out["LINT_SOURCES"], | ||||
| ) | ||||
| distenv.Pseudo("lint.format.pseudo") | ||||
| AlwaysBuild(lint_format) | ||||
| Alias("format", lint_format) | ||||
| @ -16,7 +16,6 @@ | ||||
| - `ibutton`             - iButton application, onewire keys and more | ||||
| - `input`               - Input service | ||||
| - `infrared`            - Infrared application, controls your IR devices | ||||
| - `infrared_monitor`    - Infrared debug tool | ||||
| - `lfrfid`              - LF RFID application | ||||
| - `lfrfid_debug`        - LF RFID debug tool | ||||
| - `loader`              - Application loader service | ||||
| @ -36,6 +35,4 @@ | ||||
| - `u2f`                 - U2F Application | ||||
| - `updater`             - Update service & application | ||||
| 
 | ||||
| - `application.c`       - Firmware application list source | ||||
| - `application.h`       - Firmware application list header | ||||
| - `application.mk`      - Makefile helper | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| #include <gui/modules/empty_screen.h> | ||||
| #include <m-string.h> | ||||
| #include <furi_hal_version.h> | ||||
| #include <furi_hal_bt.h> | ||||
| 
 | ||||
| typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); | ||||
| 
 | ||||
| @ -57,7 +58,7 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* | ||||
| static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||
|     DialogMessageButton result; | ||||
| 
 | ||||
|     dialog_message_set_icon(message, &I_Certification1_103x23, 12, 12); | ||||
|     dialog_message_set_icon(message, &I_Certification1_103x56, 13, 0); | ||||
|     result = dialog_message_show(dialogs, message); | ||||
|     dialog_message_set_icon(message, NULL, 0, 0); | ||||
| 
 | ||||
| @ -67,7 +68,7 @@ static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* mess | ||||
| static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||
|     DialogMessageButton result; | ||||
| 
 | ||||
|     dialog_message_set_icon(message, &I_Certification2_119x30, 4, 9); | ||||
|     dialog_message_set_icon(message, &I_Certification2_98x33, 15, 10); | ||||
|     result = dialog_message_show(dialogs, message); | ||||
|     dialog_message_set_icon(message, NULL, 0, 0); | ||||
| 
 | ||||
| @ -111,18 +112,23 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     const Version* ver = furi_hal_version_get_firmware_version(); | ||||
|     const BleGlueC2Info* c2_ver = NULL; | ||||
| #ifdef SRV_BT | ||||
|     c2_ver = ble_glue_get_c2_info(); | ||||
| #endif | ||||
| 
 | ||||
|     if(!ver) { | ||||
|         string_cat_printf(buffer, "No info\n"); | ||||
|     } else { | ||||
|         string_cat_printf( | ||||
|             buffer, | ||||
|             "%s [%s]\n%s%s [%s]\n[%d] %s", | ||||
|             "%s [%s]\n%s%s [%s] %s\n[%d] %s", | ||||
|             version_get_version(ver), | ||||
|             version_get_builddate(ver), | ||||
|             version_get_dirty_flag(ver) ? "[!] " : "", | ||||
|             version_get_githash(ver), | ||||
|             version_get_gitbranchnum(ver), | ||||
|             c2_ver ? c2_ver->StackTypeString : "<none>", | ||||
|             version_get_target(ver), | ||||
|             version_get_gitbranch(ver)); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										13
									
								
								applications/about/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/about/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| App( | ||||
|     appid="about", | ||||
|     name="About", | ||||
|     apptype=FlipperAppType.SETTINGS, | ||||
|     entry_point="about_settings_app", | ||||
|     cdefines=["APP_ABOUT"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     stack_size=1 * 1024, | ||||
|     order=1000, | ||||
| ) | ||||
							
								
								
									
										10
									
								
								applications/accessor/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								applications/accessor/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| App( | ||||
|     appid="accessor", | ||||
|     name="Accessor", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="accessor_app", | ||||
|     cdefines=["APP_ACCESSOR"], | ||||
|     requires=["gui"], | ||||
|     stack_size=4 * 1024, | ||||
|     order=40, | ||||
| ) | ||||
| @ -1,558 +0,0 @@ | ||||
| #include "applications.h" | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| // Services
 | ||||
| extern int32_t rpc_srv(void* p); | ||||
| extern int32_t bt_srv(void* p); | ||||
| extern int32_t cli_srv(void* p); | ||||
| extern int32_t dialogs_srv(void* p); | ||||
| extern int32_t dolphin_srv(void* p); | ||||
| extern int32_t gui_srv(void* p); | ||||
| extern int32_t input_srv(void* p); | ||||
| extern int32_t loader_srv(void* p); | ||||
| extern int32_t notification_srv(void* p); | ||||
| extern int32_t power_srv(void* p); | ||||
| extern int32_t storage_srv(void* p); | ||||
| extern int32_t desktop_srv(void* p); | ||||
| extern int32_t updater_srv(void* p); | ||||
| 
 | ||||
| // Apps
 | ||||
| extern int32_t accessor_app(void* p); | ||||
| extern int32_t archive_app(void* p); | ||||
| extern int32_t bad_usb_app(void* p); | ||||
| extern int32_t u2f_app(void* p); | ||||
| extern int32_t uart_echo_app(void* p); | ||||
| extern int32_t blink_test_app(void* p); | ||||
| extern int32_t bt_debug_app(void* p); | ||||
| extern int32_t delay_test_app(void* p); | ||||
| extern int32_t display_test_app(void* p); | ||||
| extern int32_t gpio_app(void* p); | ||||
| extern int32_t ibutton_app(void* p); | ||||
| extern int32_t infrared_app(void* p); | ||||
| extern int32_t infrared_monitor_app(void* p); | ||||
| extern int32_t keypad_test_app(void* p); | ||||
| extern int32_t lfrfid_app(void* p); | ||||
| extern int32_t lfrfid_debug_app(void* p); | ||||
| extern int32_t nfc_app(void* p); | ||||
| extern int32_t passport_app(void* p); | ||||
| extern int32_t scened_app(void* p); | ||||
| extern int32_t storage_test_app(void* p); | ||||
| extern int32_t subghz_app(void* p); | ||||
| extern int32_t usb_mouse_app(void* p); | ||||
| extern int32_t usb_test_app(void* p); | ||||
| extern int32_t vibro_test_app(void* p); | ||||
| extern int32_t bt_hid_app(void* p); | ||||
| extern int32_t battery_test_app(void* p); | ||||
| extern int32_t text_box_test_app(void* p); | ||||
| extern int32_t file_browser_app(void* p); | ||||
| 
 | ||||
| // Plugins
 | ||||
| extern int32_t music_player_app(void* p); | ||||
| extern int32_t snake_game_app(void* p); | ||||
| 
 | ||||
| // On system start hooks declaration
 | ||||
| extern void bt_on_system_start(); | ||||
| extern void crypto_on_system_start(); | ||||
| extern void ibutton_on_system_start(); | ||||
| extern void infrared_on_system_start(); | ||||
| extern void lfrfid_on_system_start(); | ||||
| extern void music_player_on_system_start(); | ||||
| extern void nfc_on_system_start(); | ||||
| extern void storage_on_system_start(); | ||||
| extern void subghz_on_system_start(); | ||||
| extern void power_on_system_start(); | ||||
| extern void unit_tests_on_system_start(); | ||||
| extern void updater_on_system_start(); | ||||
| 
 | ||||
| // Settings
 | ||||
| extern int32_t notification_settings_app(void* p); | ||||
| extern int32_t storage_settings_app(void* p); | ||||
| extern int32_t bt_settings_app(void* p); | ||||
| extern int32_t desktop_settings_app(void* p); | ||||
| extern int32_t about_settings_app(void* p); | ||||
| extern int32_t power_settings_app(void* p); | ||||
| extern int32_t system_settings_app(void* p); | ||||
| 
 | ||||
| const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| /* Services */ | ||||
| #ifdef SRV_RPC | ||||
|     {.app = rpc_srv, | ||||
|      .name = "RpcSrv", | ||||
|      .stack_size = 1024 * 4, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_srv, | ||||
|      .name = "BtSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_CLI | ||||
|     {.app = cli_srv, | ||||
|      .name = "CliSrv", | ||||
|      .stack_size = 4096, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DIALOGS | ||||
|     {.app = dialogs_srv, | ||||
|      .name = "DialogsSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DOLPHIN | ||||
|     {.app = dolphin_srv, | ||||
|      .name = "DolphinSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DESKTOP | ||||
| #ifdef SRV_UPDATER | ||||
| #error SRV_UPDATER and SRV_DESKTOP are mutually exclusive! | ||||
| #endif | ||||
|     {.app = desktop_srv, | ||||
|      .name = "DesktopSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GUI | ||||
|     {.app = gui_srv, | ||||
|      .name = "GuiSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_INPUT | ||||
|     {.app = input_srv, | ||||
|      .name = "InputSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_LOADER | ||||
|     {.app = loader_srv, | ||||
|      .name = "LoaderSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_NOTIFICATION | ||||
|     {.app = notification_srv, | ||||
|      .name = "NotificationSrv", | ||||
|      .stack_size = 1536, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     {.app = power_srv, | ||||
|      .name = "PowerSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_srv, | ||||
|      .name = "StorageSrv", | ||||
|      .stack_size = 3072, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_UPDATER | ||||
| #ifdef SRV_DESKTOP | ||||
| #error SRV_UPDATER and SRV_DESKTOP are mutually exclusive! | ||||
| #endif | ||||
|     {.app = updater_srv, | ||||
|      .name = "UpdaterSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_SERVICES_COUNT = COUNT_OF(FLIPPER_SERVICES); | ||||
| 
 | ||||
| const FlipperApplication FLIPPER_SYSTEM_APPS[] = { | ||||
| #ifdef APP_UPDATER | ||||
| #ifdef SRV_UPDATER | ||||
| #error APP_UPDATER and SRV_UPDATER are mutually exclusive! | ||||
| #endif | ||||
|     {.app = updater_srv, | ||||
|      .name = "UpdaterApp", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_SYSTEM_APPS_COUNT = COUNT_OF(FLIPPER_SYSTEM_APPS); | ||||
| 
 | ||||
| // Main menu APP
 | ||||
| const FlipperApplication FLIPPER_APPS[] = { | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     {.app = subghz_app, | ||||
|      .name = "Sub-GHz", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_Sub1ghz_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_app, | ||||
|      .name = "125 kHz RFID", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_125khz_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     {.app = nfc_app, | ||||
|      .name = "NFC", | ||||
|      .stack_size = 4096, | ||||
|      .icon = &A_NFC_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_INFRARED | ||||
|     {.app = infrared_app, | ||||
|      .name = "Infrared", | ||||
|      .stack_size = 1024 * 3, | ||||
|      .icon = &A_Infrared_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_GPIO | ||||
|     {.app = gpio_app, | ||||
|      .name = "GPIO", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_GPIO_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, | ||||
|      .name = "iButton", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_iButton_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_BAD_USB | ||||
|     {.app = bad_usb_app, | ||||
|      .name = "Bad USB", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_BadUsb_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_U2F | ||||
|     {.app = u2f_app, | ||||
|      .name = "U2F", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_U2F_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_APPS_COUNT = COUNT_OF(FLIPPER_APPS); | ||||
| 
 | ||||
| // On system start hooks
 | ||||
| const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||
|     crypto_on_system_start, | ||||
| 
 | ||||
| #ifdef APP_INFRARED | ||||
|     infrared_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_MUSIC_PLAYER | ||||
|     music_player_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     nfc_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     subghz_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     lfrfid_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     ibutton_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     bt_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     power_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     storage_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     unit_tests_on_system_start, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UPDATER | ||||
|     updater_on_system_start, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_ON_SYSTEM_START_COUNT = COUNT_OF(FLIPPER_ON_SYSTEM_START); | ||||
| 
 | ||||
| // Plugin menu
 | ||||
| const FlipperApplication FLIPPER_PLUGINS[] = { | ||||
| #ifdef APP_BLE_HID | ||||
|     {.app = bt_hid_app, | ||||
|      .name = "Bluetooth Remote", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_MUSIC_PLAYER | ||||
|     {.app = music_player_app, | ||||
|      .name = "Music Player", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_Plugins_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SNAKE_GAME | ||||
|     {.app = snake_game_app, | ||||
|      .name = "Snake Game", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_Plugins_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_PLUGINS_COUNT = COUNT_OF(FLIPPER_PLUGINS); | ||||
| 
 | ||||
| // Plugin menu
 | ||||
| const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
| #ifdef APP_BLINK | ||||
|     {.app = blink_test_app, | ||||
|      .name = "Blink Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_VIBRO_TEST | ||||
|     {.app = vibro_test_app, | ||||
|      .name = "Vibro Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_KEYPAD_TEST | ||||
|     {.app = keypad_test_app, | ||||
|      .name = "Keypad Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_ACCESSOR | ||||
|     {.app = accessor_app, | ||||
|      .name = "Accessor", | ||||
|      .stack_size = 4096, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_USB_TEST | ||||
|     {.app = usb_test_app, | ||||
|      .name = "USB Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_USB_MOUSE | ||||
|     {.app = usb_mouse_app, | ||||
|      .name = "USB Mouse Demo", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UART_ECHO | ||||
|     {.app = uart_echo_app, | ||||
|      .name = "Uart Echo", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_INFRARED_MONITOR | ||||
|     {.app = infrared_monitor_app, | ||||
|      .name = "Infrared Monitor", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_debug_app, | ||||
|      .name = "LF-RFID Debug", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_debug_app, | ||||
|      .name = "Bluetooth Debug", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     {.app = delay_test_app, | ||||
|      .name = "Delay Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_DISPLAY_TEST | ||||
|     {.app = display_test_app, | ||||
|      .name = "Display Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_FILE_BROWSER_TEST | ||||
|     {.app = file_browser_app, | ||||
|      .name = "File Browser test", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_BadUsb_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_BATTERY_TEST | ||||
|     {.app = battery_test_app, | ||||
|      .name = "Battery Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_TEXT_BOX_TEST | ||||
|     {.app = text_box_test_app, | ||||
|      .name = "Text Box Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_DEBUG_APPS_COUNT = COUNT_OF(FLIPPER_DEBUG_APPS); | ||||
| 
 | ||||
| #ifdef APP_ARCHIVE | ||||
| const FlipperApplication FLIPPER_ARCHIVE = { | ||||
|     .app = archive_app, | ||||
|     .name = "Archive", | ||||
|     .stack_size = 4096, | ||||
|     .icon = &A_FileManager_14, | ||||
|     .flags = FlipperApplicationFlagDefault}; | ||||
| #endif | ||||
| 
 | ||||
| // Settings menu
 | ||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_settings_app, | ||||
|      .name = "Bluetooth", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_NOTIFICATION | ||||
|     {.app = notification_settings_app, | ||||
|      .name = "LCD and Notifications", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_settings_app, | ||||
|      .name = "Storage", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     {.app = power_settings_app, | ||||
|      .name = "Power", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagInsomniaSafe}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DESKTOP | ||||
|     {.app = desktop_settings_app, | ||||
|      .name = "Desktop", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_PASSPORT | ||||
|     {.app = passport_app, | ||||
|      .name = "Passport", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GUI | ||||
|     {.app = system_settings_app, | ||||
|      .name = "System", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_ABOUT | ||||
|     {.app = about_settings_app, | ||||
|      .name = "About", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_SETTINGS_APPS_COUNT = COUNT_OF(FLIPPER_SETTINGS_APPS); | ||||
| @ -1,362 +0,0 @@ | ||||
| APP_DIR		= $(PROJECT_ROOT)/applications | ||||
| LIB_DIR		= $(PROJECT_ROOT)/lib | ||||
| 
 | ||||
| CFLAGS		+= -I$(APP_DIR) | ||||
| C_SOURCES	+= $(shell find $(APP_DIR) -name "*.c") | ||||
| CPP_SOURCES	+= $(shell find $(APP_DIR) -name "*.cpp") | ||||
| 
 | ||||
| RAM_EXEC ?= 0 | ||||
| ifeq ($(RAM_EXEC), 1) | ||||
| APP_RELEASE	= 0 | ||||
| SRV_GUI 	= 1 | ||||
| SRV_INPUT 	= 1 | ||||
| SRV_NOTIFICATION	= 1 | ||||
| SRV_STORAGE	= 1 | ||||
| SRV_UPDATER	= 1 | ||||
| APP_UPDATER	= 0 | ||||
| endif | ||||
| 
 | ||||
| APP_RELEASE ?= 1 | ||||
| ifeq ($(APP_RELEASE), 1) | ||||
| # Services
 | ||||
| SRV_BT		= 1 | ||||
| SRV_CLI		= 1 | ||||
| SRV_DIALOGS	= 1 | ||||
| SRV_DOLPHIN	= 1 | ||||
| SRV_GUI		= 1 | ||||
| SRV_INPUT	= 1 | ||||
| SRV_LOADER	= 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_POWER	= 1 | ||||
| SRV_RPC 	= 1 | ||||
| SRV_STORAGE	= 1 | ||||
| 
 | ||||
| # Apps
 | ||||
| SRV_DESKTOP	= 1 | ||||
| APP_ARCHIVE	= 1 | ||||
| APP_GPIO 	= 1 | ||||
| APP_IBUTTON	= 1 | ||||
| APP_INFRARED	= 1 | ||||
| APP_LF_RFID	= 1 | ||||
| APP_NFC		= 1 | ||||
| APP_SUBGHZ	= 1 | ||||
| APP_ABOUT	= 1 | ||||
| APP_PASSPORT = 1 | ||||
| APP_UPDATER = 1 | ||||
| 
 | ||||
| # Plugins
 | ||||
| APP_MUSIC_PLAYER = 1 | ||||
| APP_SNAKE_GAME = 1 | ||||
| 
 | ||||
| # Debug
 | ||||
| APP_ACCESSOR = 1 | ||||
| APP_BLINK = 1 | ||||
| APP_INFRARED_MONITOR = 1 | ||||
| APP_KEYPAD_TEST = 1 | ||||
| APP_SD_TEST	= 1 | ||||
| APP_VIBRO_TEST = 1 | ||||
| APP_USB_TEST = 1 | ||||
| APP_DISPLAY_TEST = 1 | ||||
| APP_BLE_HID = 1 | ||||
| APP_USB_MOUSE = 1 | ||||
| APP_BAD_USB = 1 | ||||
| APP_U2F = 1 | ||||
| APP_UART_ECHO = 1 | ||||
| APP_FILE_BROWSER_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| # Applications
 | ||||
| # that will be shown in menu
 | ||||
| # Prefix with APP_*
 | ||||
| 
 | ||||
| 
 | ||||
| APP_INFRARED_MONITOR	?= 0 | ||||
| ifeq ($(APP_INFRARED_MONITOR), 1) | ||||
| CFLAGS		+= -DAPP_INFRARED_MONITOR | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_UNIT_TESTS ?= 0 | ||||
| ifeq ($(APP_UNIT_TESTS), 1) | ||||
| CFLAGS		+= -DAPP_UNIT_TESTS | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_ARCHIVE ?= 0 | ||||
| ifeq ($(APP_ARCHIVE), 1) | ||||
| CFLAGS		+= -DAPP_ARCHIVE | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_BLINK ?= 0 | ||||
| ifeq ($(APP_BLINK), 1) | ||||
| CFLAGS		+= -DAPP_BLINK | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_SUBGHZ ?= 0 | ||||
| ifeq ($(APP_SUBGHZ), 1) | ||||
| CFLAGS		+= -DAPP_SUBGHZ | ||||
| SRV_GUI		= 1 | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_ABOUT ?= 0 | ||||
| ifeq ($(APP_ABOUT), 1) | ||||
| CFLAGS		+= -DAPP_ABOUT | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_PASSPORT ?= 0 | ||||
| ifeq ($(APP_PASSPORT), 1) | ||||
| CFLAGS		+= -DAPP_PASSPORT | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_LF_RFID ?= 0 | ||||
| ifeq ($(APP_LF_RFID), 1) | ||||
| CFLAGS		+= -DAPP_LF_RFID | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_NFC ?= 0 | ||||
| ifeq ($(APP_NFC), 1) | ||||
| CFLAGS		+= -DAPP_NFC | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_INFRARED ?= 0 | ||||
| ifeq ($(APP_INFRARED), 1) | ||||
| CFLAGS		+= -DAPP_INFRARED | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_VIBRO_TEST ?= 0 | ||||
| ifeq ($(APP_VIBRO_TEST), 1) | ||||
| CFLAGS		+= -DAPP_VIBRO_TEST | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_USB_TEST ?= 0 | ||||
| ifeq ($(APP_USB_TEST), 1) | ||||
| CFLAGS		+= -DAPP_USB_TEST | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_UART_ECHO ?= 0 | ||||
| ifeq ($(APP_UART_ECHO), 1) | ||||
| CFLAGS		+= -DAPP_UART_ECHO | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_DISPLAY_TEST ?= 0 | ||||
| ifeq ($(APP_DISPLAY_TEST), 1) | ||||
| CFLAGS		+= -DAPP_DISPLAY_TEST | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_TEXT_BOX_TEST ?= 0 | ||||
| ifeq ($(APP_TEXT_BOX_TEST), 1) | ||||
| CFLAGS		+= -DAPP_TEXT_BOX_TEST | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BATTERY_TEST ?= 0 | ||||
| ifeq ($(APP_BATTERY_TEST), 1) | ||||
| CFLAGS		+= -DAPP_BATTERY_TEST | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_USB_MOUSE ?= 0 | ||||
| ifeq ($(APP_USB_MOUSE), 1) | ||||
| CFLAGS		+= -DAPP_USB_MOUSE | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BAD_USB ?= 0 | ||||
| ifeq ($(APP_BAD_USB), 1) | ||||
| CFLAGS		+= -DAPP_BAD_USB | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_U2F ?= 0 | ||||
| ifeq ($(APP_U2F), 1) | ||||
| CFLAGS		+= -DAPP_U2F | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BLE_HID ?=0 | ||||
| ifeq ($(APP_BLE_HID), 1) | ||||
| CFLAGS		+= -DAPP_BLE_HID | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(APP_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DAPP_KEYPAD_TEST | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| APP_FILE_BROWSER_TEST ?= 0 | ||||
| ifeq ($(APP_FILE_BROWSER_TEST), 1) | ||||
| CFLAGS		+= -DAPP_FILE_BROWSER_TEST | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_ACCESSOR ?= 0 | ||||
| ifeq ($(APP_ACCESSOR), 1) | ||||
| CFLAGS		+= -DAPP_ACCESSOR | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_GPIO ?= 0 | ||||
| ifeq ($(APP_GPIO), 1) | ||||
| CFLAGS		+= -DAPP_GPIO | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_MUSIC_PLAYER ?= 0 | ||||
| ifeq ($(APP_MUSIC_PLAYER), 1) | ||||
| CFLAGS		+= -DAPP_MUSIC_PLAYER | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| APP_SNAKE_GAME ?= 0 | ||||
| ifeq ($(APP_SNAKE_GAME), 1) | ||||
| CFLAGS		+= -DAPP_SNAKE_GAME | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| APP_IBUTTON ?= 0 | ||||
| ifeq ($(APP_IBUTTON), 1) | ||||
| CFLAGS		+= -DAPP_IBUTTON | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| APP_UPDATER ?= 0 | ||||
| ifeq ($(APP_UPDATER), 1) | ||||
| CFLAGS		+= -DAPP_UPDATER | ||||
| SRV_GUI		= 1 | ||||
| SRV_STORAGE = 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_INPUT   = 1 | ||||
| endif | ||||
| 
 | ||||
| # Services
 | ||||
| # that will start with OS
 | ||||
| # Prefix with SRV_*
 | ||||
| 
 | ||||
| 
 | ||||
| SRV_BT ?= 0 | ||||
| ifeq ($(SRV_BT), 1) | ||||
| CFLAGS		+= -DSRV_BT | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_DESKTOP ?= 0 | ||||
| ifeq ($(SRV_DESKTOP), 1) | ||||
| CFLAGS		+= -DSRV_DESKTOP | ||||
| SRV_DOLPHIN	= 1 | ||||
| SRV_STORAGE	= 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_UPDATER ?= 0 | ||||
| ifeq ($(SRV_UPDATER), 1) | ||||
| CFLAGS		+= -DSRV_UPDATER | ||||
| SRV_STORAGE	= 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_DOLPHIN ?= 0 | ||||
| ifeq ($(SRV_DOLPHIN), 1) | ||||
| CFLAGS		+= -DSRV_DOLPHIN | ||||
| SRV_DOLPHIN_STATE_DEBUG ?= 0 | ||||
| ifeq ($(SRV_DOLPHIN_STATE_DEBUG), 1) | ||||
| CFLAGS		+= -DSRV_DOLPHIN_STATE_DEBUG | ||||
| endif | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_POWER ?= 0 | ||||
| ifeq ($(SRV_POWER), 1) | ||||
| CFLAGS		+= -DSRV_POWER | ||||
| SRV_GUI		= 1 | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_RPC ?= 0 | ||||
| ifeq ($(SRV_RPC), 1) | ||||
| CFLAGS		+= -DSRV_RPC | ||||
| ifeq ($(SRV_RPC_DEBUG), 1) | ||||
| CFLAGS		+= -DSRV_RPC_DEBUG | ||||
| endif | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_LOADER ?= 0 | ||||
| ifeq ($(SRV_LOADER), 1) | ||||
| CFLAGS		+= -DSRV_LOADER | ||||
| SRV_GUI		= 1 | ||||
| # Loader autostart hook
 | ||||
| LOADER_AUTOSTART ?= "" | ||||
| ifneq ($(strip $(LOADER_AUTOSTART)), "") | ||||
| CFLAGS		+= -DLOADER_AUTOSTART="\"$(LOADER_AUTOSTART)\"" | ||||
| endif | ||||
| # Loader autostart hook END
 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_DIALOGS ?= 0 | ||||
| ifeq ($(SRV_DIALOGS), 1) | ||||
| CFLAGS		+= -DSRV_DIALOGS | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_GUI	?= 0 | ||||
| ifeq ($(SRV_GUI), 1) | ||||
| CFLAGS		+= -DSRV_GUI | ||||
| SRV_INPUT	= 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_INPUT	?= 0 | ||||
| ifeq ($(SRV_INPUT), 1) | ||||
| CFLAGS		+= -DSRV_INPUT | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_CLI ?= 0 | ||||
| ifeq ($(SRV_CLI), 1) | ||||
| CFLAGS		+= -DSRV_CLI | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_NOTIFICATION ?= 0 | ||||
| ifeq ($(SRV_NOTIFICATION), 1) | ||||
| CFLAGS		+= -DSRV_NOTIFICATION | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_STORAGE ?= 0 | ||||
| ifeq ($(SRV_STORAGE), 1) | ||||
| CFLAGS		+= -DSRV_STORAGE | ||||
| endif | ||||
							
								
								
									
										11
									
								
								applications/archive/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								applications/archive/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| App( | ||||
|     appid="archive", | ||||
|     name="Archive", | ||||
|     apptype=FlipperAppType.ARCHIVE, | ||||
|     entry_point="archive_app", | ||||
|     cdefines=["APP_ARCHIVE"], | ||||
|     requires=["gui"], | ||||
|     stack_size=4 * 1024, | ||||
|     icon="A_FileManager_14", | ||||
|     order=0, | ||||
| ) | ||||
							
								
								
									
										14
									
								
								applications/bad_usb/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								applications/bad_usb/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| App( | ||||
|     appid="bad_usb", | ||||
|     name="Bad USB", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     entry_point="bad_usb_app", | ||||
|     cdefines=["APP_BAD_USB"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     stack_size=2 * 1024, | ||||
|     icon="A_BadUsb_14", | ||||
|     order=70, | ||||
| ) | ||||
| @ -440,9 +440,9 @@ static void bad_usb_hid_state_callback(bool state, void* context) { | ||||
|     BadUsbScript* bad_usb = context; | ||||
| 
 | ||||
|     if(state == true) | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(bad_usb->thread), WorkerEvtConnect); | ||||
|         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect); | ||||
|     else | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(bad_usb->thread), WorkerEvtDisconnect); | ||||
|         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); | ||||
| } | ||||
| 
 | ||||
| static int32_t bad_usb_worker(void* context) { | ||||
| @ -483,8 +483,8 @@ static int32_t bad_usb_worker(void* context) { | ||||
|             bad_usb->st.state = worker_state; | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 | ||||
|             uint32_t flags = | ||||
|                 osThreadFlagsWait(WorkerEvtEnd | WorkerEvtConnect, osFlagsWaitAny, osWaitForever); | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtConnect, osFlagsWaitAny, osWaitForever); | ||||
|             furi_check((flags & osFlagsError) == 0); | ||||
|             if(flags & WorkerEvtEnd) { | ||||
|                 break; | ||||
| @ -494,7 +494,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
|             bad_usb->st.state = worker_state; | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 | ||||
|             uint32_t flags = osThreadFlagsWait( | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, | ||||
|                 osFlagsWaitAny, | ||||
|                 osWaitForever); | ||||
| @ -518,7 +518,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
| 
 | ||||
|         } else if(worker_state == BadUsbStateRunning) { // State: running
 | ||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||
|             uint32_t flags = osThreadFlagsWait( | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, osFlagsWaitAny, delay_cur); | ||||
|             delay_val -= delay_cur; | ||||
|             if(!(flags & osFlagsError)) { | ||||
| @ -561,7 +561,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
|         } else if( | ||||
|             (worker_state == BadUsbStateFileError) || | ||||
|             (worker_state == BadUsbStateScriptError)) { // State: error
 | ||||
|             uint32_t flags = osThreadFlagsWait( | ||||
|             uint32_t flags = furi_thread_flags_wait( | ||||
|                 WorkerEvtEnd, osFlagsWaitAny, osWaitForever); // Waiting for exit command
 | ||||
|             furi_check((flags & osFlagsError) == 0); | ||||
|             if(flags & WorkerEvtEnd) { | ||||
| @ -605,7 +605,7 @@ BadUsbScript* bad_usb_script_open(string_t file_path) { | ||||
| 
 | ||||
| void bad_usb_script_close(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(bad_usb->thread), WorkerEvtEnd); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtEnd); | ||||
|     furi_thread_join(bad_usb->thread); | ||||
|     furi_thread_free(bad_usb->thread); | ||||
|     string_clear(bad_usb->file_path); | ||||
| @ -614,7 +614,7 @@ void bad_usb_script_close(BadUsbScript* bad_usb) { | ||||
| 
 | ||||
| void bad_usb_script_toggle(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(bad_usb->thread), WorkerEvtToggle); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); | ||||
| } | ||||
| 
 | ||||
| BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) { | ||||
|  | ||||
							
								
								
									
										66
									
								
								applications/bt/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								applications/bt/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| App( | ||||
|     appid="bt", | ||||
|     name="BtSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="bt_srv", | ||||
|     cdefines=["SRV_BT"], | ||||
|     requires=[ | ||||
|         "cli", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     provides=[ | ||||
|         "bt_start", | ||||
|         "bt_settings", | ||||
|         "bt_debug", | ||||
|     ], | ||||
|     stack_size=1 * 1024, | ||||
|     order=20, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="bt_start", | ||||
|     apptype=FlipperAppType.STARTUP, | ||||
|     entry_point="bt_on_system_start", | ||||
|     order=70, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="bt_settings", | ||||
|     name="Bluetooth", | ||||
|     apptype=FlipperAppType.SETTINGS, | ||||
|     entry_point="bt_settings_app", | ||||
|     stack_size=1 * 1024, | ||||
|     requires=[ | ||||
|         "bt", | ||||
|         "gui", | ||||
|     ], | ||||
|     order=10, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="bt_debug", | ||||
|     name="Bluetooth Debug", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="bt_debug_app", | ||||
|     stack_size=1 * 1024, | ||||
|     requires=[ | ||||
|         "bt", | ||||
|         "gui", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     order=110, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="bt_hid", | ||||
|     name="Bluetooth Remote", | ||||
|     apptype=FlipperAppType.PLUGIN, | ||||
|     entry_point="bt_hid_app", | ||||
|     stack_size=1 * 1024, | ||||
|     cdefines=["APP_BLE_HID"], | ||||
|     requires=[ | ||||
|         "bt", | ||||
|         "gui", | ||||
|     ], | ||||
|     order=10, | ||||
| ) | ||||
							
								
								
									
										9
									
								
								applications/cli/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								applications/cli/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| App( | ||||
|     appid="cli", | ||||
|     name="CliSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="cli_srv", | ||||
|     cdefines=["SRV_CLI"], | ||||
|     stack_size=4 * 1024, | ||||
|     order=30, | ||||
| ) | ||||
| @ -255,19 +255,19 @@ void cli_command_ps(Cli* cli, string_t args, void* context) { | ||||
|     UNUSED(context); | ||||
| 
 | ||||
|     const uint8_t threads_num_max = 32; | ||||
|     osThreadId_t threads_id[threads_num_max]; | ||||
|     uint8_t thread_num = osThreadEnumerate(threads_id, threads_num_max); | ||||
|     FuriThreadId threads_ids[threads_num_max]; | ||||
|     uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); | ||||
|     printf( | ||||
|         "%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free"); | ||||
|     for(uint8_t i = 0; i < thread_num; i++) { | ||||
|         TaskControlBlock* tcb = (TaskControlBlock*)threads_id[i]; | ||||
|         TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; | ||||
|         printf( | ||||
|             "%-20s 0x%-12lx %-8d %-8ld %-8ld\r\n", | ||||
|             osThreadGetName(threads_id[i]), | ||||
|             furi_thread_get_name(threads_ids[i]), | ||||
|             (uint32_t)tcb->pxStack, | ||||
|             memmgr_heap_get_thread_memory(threads_id[i]), | ||||
|             memmgr_heap_get_thread_memory(threads_ids[i]), | ||||
|             (uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t), | ||||
|             osThreadGetStackSpace(threads_id[i])); | ||||
|             furi_thread_get_stack_space(threads_ids[i])); | ||||
|     } | ||||
|     printf("\r\nTotal: %d", thread_num); | ||||
| } | ||||
|  | ||||
| @ -79,7 +79,7 @@ static void cli_vcp_init() { | ||||
| } | ||||
| 
 | ||||
| static void cli_vcp_deinit() { | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStop); | ||||
|     furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop); | ||||
|     furi_thread_join(vcp->thread); | ||||
|     furi_thread_free(vcp->thread); | ||||
|     vcp->thread = NULL; | ||||
| @ -102,7 +102,8 @@ static int32_t vcp_worker(void* context) { | ||||
|     vcp->running = true; | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t flags = osThreadFlagsWait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t flags = | ||||
|             furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         furi_assert((flags & osFlagsError) == 0); | ||||
| 
 | ||||
|         // VCP session opened
 | ||||
| @ -232,7 +233,7 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|         FURI_LOG_D(TAG, "rx %u ", batch_size); | ||||
| #endif | ||||
|         if(len == 0) break; | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamRx); | ||||
|         furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); | ||||
|         size -= len; | ||||
|         buffer += len; | ||||
|         rx_cnt += len; | ||||
| @ -261,7 +262,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { | ||||
|         if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN; | ||||
| 
 | ||||
|         xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever); | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx); | ||||
|         furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|         FURI_LOG_D(TAG, "tx %u", batch_size); | ||||
| #endif | ||||
| @ -283,7 +284,7 @@ static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) { | ||||
| static void vcp_state_callback(void* context, uint8_t state) { | ||||
|     UNUSED(context); | ||||
|     if(state == 0) { | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); | ||||
|         furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -293,21 +294,21 @@ static void vcp_on_cdc_control_line(void* context, uint8_t state) { | ||||
|     bool dtr = state & (1 << 0); | ||||
| 
 | ||||
|     if(dtr == true) { | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtConnect); | ||||
|         furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect); | ||||
|     } else { | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); | ||||
|         furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void vcp_on_cdc_rx(void* context) { | ||||
|     UNUSED(context); | ||||
|     uint32_t ret = osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtRx); | ||||
|     uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx); | ||||
|     furi_check((ret & osFlagsError) == 0); | ||||
| } | ||||
| 
 | ||||
| static void vcp_on_cdc_tx_complete(void* context) { | ||||
|     UNUSED(context); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx); | ||||
|     furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx); | ||||
| } | ||||
| 
 | ||||
| static bool cli_vcp_is_connected(void) { | ||||
|  | ||||
							
								
								
									
										6
									
								
								applications/crypto/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								applications/crypto/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| App( | ||||
|     appid="crypto_start", | ||||
|     apptype=FlipperAppType.STARTUP, | ||||
|     entry_point="crypto_on_system_start", | ||||
|     order=10, | ||||
| ) | ||||
							
								
								
									
										115
									
								
								applications/debug_tools/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								applications/debug_tools/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| App( | ||||
|     appid="debug_apps", | ||||
|     name="Basic debug apps bundle", | ||||
|     apptype=FlipperAppType.METAPACKAGE, | ||||
|     provides=[ | ||||
|         "blink_test", | ||||
|         "vibro_test", | ||||
|         "keypad_test", | ||||
|         "usb_test", | ||||
|         "usb_mouse", | ||||
|         "uart_echo", | ||||
|         "display_test", | ||||
|         "text_box_test", | ||||
|         "file_browser_test", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="blink_test", | ||||
|     name="Blink Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="blink_test_app", | ||||
|     cdefines=["APP_BLINK"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=10, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="vibro_test", | ||||
|     name="Vibro Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="vibro_test_app", | ||||
|     cdefines=["APP_VIBRO_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=20, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="keypad_test", | ||||
|     name="Keypad Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="keypad_test_app", | ||||
|     cdefines=["APP_KEYPAD_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=30, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="usb_test", | ||||
|     name="USB Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="usb_test_app", | ||||
|     cdefines=["APP_USB_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=50, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="usb_mouse", | ||||
|     name="USB Mouse Demo", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="usb_mouse_app", | ||||
|     cdefines=["APP_USB_MOUSE"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=60, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="uart_echo", | ||||
|     name="UART Echo", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="uart_echo_app", | ||||
|     cdefines=["APP_UART_ECHO"], | ||||
|     requires=["gui"], | ||||
|     stack_size=2 * 1024, | ||||
|     order=70, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="display_test", | ||||
|     name="Display Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="display_test_app", | ||||
|     cdefines=["APP_DISPLAY_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=120, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="text_box_test", | ||||
|     name="Text Box Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="text_box_test_app", | ||||
|     cdefines=["APP_TEXT_BOX_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=140, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="file_browser_test", | ||||
|     name="File Browser Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="file_browser_app", | ||||
|     cdefines=["APP_FILE_BROWSER_TEST"], | ||||
|     requires=["gui"], | ||||
|     stack_size=2 * 1024, | ||||
|     order=150, | ||||
| ) | ||||
| @ -97,7 +97,7 @@ static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { | ||||
| 
 | ||||
|     if(ev == UartIrqEventRXNE) { | ||||
|         xStreamBufferSendFromISR(app->rx_stream, &data, 1, &xHigherPriorityTaskWoken); | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(app->worker_thread), WorkerEventRx); | ||||
|         furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx); | ||||
|         portYIELD_FROM_ISR(xHigherPriorityTaskWoken); | ||||
|     } | ||||
| } | ||||
| @ -149,7 +149,8 @@ static int32_t uart_echo_worker(void* context) { | ||||
|     UartEchoApp* app = context; | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t events = osThreadFlagsWait(WORKER_EVENTS_MASK, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t events = | ||||
|             furi_thread_flags_wait(WORKER_EVENTS_MASK, osFlagsWaitAny, osWaitForever); | ||||
|         furi_check((events & osFlagsError) == 0); | ||||
| 
 | ||||
|         if(events & WorkerEventStop) break; | ||||
| @ -234,7 +235,7 @@ static UartEchoApp* uart_echo_app_alloc() { | ||||
| static void uart_echo_app_free(UartEchoApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(app->worker_thread), WorkerEventStop); | ||||
|     furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); | ||||
|     furi_thread_join(app->worker_thread); | ||||
|     furi_thread_free(app->worker_thread); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										30
									
								
								applications/desktop/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/desktop/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| App( | ||||
|     appid="desktop", | ||||
|     name="DesktopSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="desktop_srv", | ||||
|     cdefines=["SRV_DESKTOP"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dolphin", | ||||
|         "storage", | ||||
|         "input", | ||||
|     ], | ||||
|     provides=["desktop_settings"], | ||||
|     conflicts=["updater"], | ||||
|     stack_size=2 * 1024, | ||||
|     order=60, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="desktop_settings", | ||||
|     name="Desktop", | ||||
|     apptype=FlipperAppType.SETTINGS, | ||||
|     entry_point="desktop_settings_app", | ||||
|     requires=[ | ||||
|         "desktop", | ||||
|         "gui", | ||||
|     ], | ||||
|     stack_size=1 * 1024, | ||||
|     order=50, | ||||
| ) | ||||
| @ -158,11 +158,11 @@ Desktop* desktop_alloc() { | ||||
| 
 | ||||
|     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||
|     desktop->debug_view = desktop_debug_alloc(); | ||||
|     desktop->first_start_view = desktop_first_start_alloc(); | ||||
|     desktop->hw_mismatch_popup = popup_alloc(); | ||||
|     desktop->locked_view = desktop_view_locked_alloc(); | ||||
|     desktop->pin_input_view = desktop_view_pin_input_alloc(); | ||||
|     desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); | ||||
|     desktop->slideshow_view = desktop_view_slideshow_alloc(); | ||||
| 
 | ||||
|     desktop->main_view_stack = view_stack_alloc(); | ||||
|     desktop->main_view = desktop_main_alloc(); | ||||
| @ -193,10 +193,6 @@ Desktop* desktop_alloc() { | ||||
|         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdFirstStart, | ||||
|         desktop_first_start_get_view(desktop->first_start_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdHwMismatch, | ||||
| @ -209,6 +205,10 @@ Desktop* desktop_alloc() { | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdPinInput, | ||||
|         desktop_view_pin_input_get_view(desktop->pin_input_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdSlideshow, | ||||
|         desktop_view_slideshow_get_view(desktop->slideshow_view)); | ||||
| 
 | ||||
|     // Lock icon
 | ||||
|     desktop->lock_viewport = view_port_alloc(); | ||||
| @ -258,7 +258,6 @@ void desktop_free(Desktop* desktop) { | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdFirstStart); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); | ||||
| @ -274,7 +273,6 @@ void desktop_free(Desktop* desktop) { | ||||
|     desktop_lock_menu_free(desktop->lock_menu); | ||||
|     desktop_view_locked_free(desktop->locked_view); | ||||
|     desktop_debug_free(desktop->debug_view); | ||||
|     desktop_first_start_free(desktop->first_start_view); | ||||
|     popup_free(desktop->hw_mismatch_popup); | ||||
|     desktop_view_pin_timeout_free(desktop->pin_timeout_view); | ||||
| 
 | ||||
| @ -290,9 +288,9 @@ void desktop_free(Desktop* desktop) { | ||||
|     free(desktop); | ||||
| } | ||||
| 
 | ||||
| static bool desktop_is_first_start() { | ||||
| static bool desktop_check_file_flag(const char* flag_path) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     bool exists = storage_common_stat(storage, "/int/first_start", NULL) == FSE_OK; | ||||
|     bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     return exists; | ||||
| @ -320,8 +318,8 @@ int32_t desktop_srv(void* p) { | ||||
|         desktop_lock(desktop); | ||||
|     } | ||||
| 
 | ||||
|     if(desktop_is_first_start()) { | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||
|     if(desktop_check_file_flag("/int/slideshow")) { | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow); | ||||
|     } | ||||
| 
 | ||||
|     if(!furi_hal_version_do_i_belong_here()) { | ||||
|  | ||||
| @ -6,9 +6,9 @@ | ||||
| #include "views/desktop_view_pin_input.h" | ||||
| #include "views/desktop_view_locked.h" | ||||
| #include "views/desktop_view_main.h" | ||||
| #include "views/desktop_view_first_start.h" | ||||
| #include "views/desktop_view_lock_menu.h" | ||||
| #include "views/desktop_view_debug.h" | ||||
| #include "views/desktop_view_slideshow.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| @ -28,10 +28,10 @@ typedef enum { | ||||
|     DesktopViewIdLockMenu, | ||||
|     DesktopViewIdLocked, | ||||
|     DesktopViewIdDebug, | ||||
|     DesktopViewIdFirstStart, | ||||
|     DesktopViewIdHwMismatch, | ||||
|     DesktopViewIdPinInput, | ||||
|     DesktopViewIdPinTimeout, | ||||
|     DesktopViewIdSlideshow, | ||||
|     DesktopViewIdTotal, | ||||
| } DesktopViewId; | ||||
| 
 | ||||
| @ -43,13 +43,13 @@ struct Desktop { | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     SceneManager* scene_manager; | ||||
| 
 | ||||
|     DesktopFirstStartView* first_start_view; | ||||
|     Popup* hw_mismatch_popup; | ||||
|     DesktopLockMenuView* lock_menu; | ||||
|     DesktopDebugView* debug_view; | ||||
|     DesktopViewLocked* locked_view; | ||||
|     DesktopMainView* main_view; | ||||
|     DesktopViewPinTimeout* pin_timeout_view; | ||||
|     DesktopSlideshowView* slideshow_view; | ||||
| 
 | ||||
|     ViewStack* main_view_stack; | ||||
|     ViewStack* locked_view_stack; | ||||
|  | ||||
							
								
								
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| #include "slideshow.h" | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <storage/storage.h> | ||||
| #include <gui/icon.h> | ||||
| #include <gui/icon_i.h> | ||||
| #include <furi/dangerous_defines.h> | ||||
| 
 | ||||
| #define SLIDESHOW_MAGIC 0x72676468 | ||||
| #define SLIDESHOW_MAX_SUPPORTED_VERSION 1 | ||||
| 
 | ||||
| struct Slideshow { | ||||
|     Icon icon; | ||||
|     uint32_t current_frame; | ||||
| }; | ||||
| 
 | ||||
| #pragma pack(push, 1) | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t magic; | ||||
|     uint8_t version; | ||||
|     uint8_t width; | ||||
|     uint8_t height; | ||||
|     uint8_t frame_count; | ||||
| } SlideshowFileHeader; | ||||
| _Static_assert(sizeof(SlideshowFileHeader) == 8, "Incorrect SlideshowFileHeader size"); | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint16_t size; | ||||
| } SlideshowFrameHeader; | ||||
| _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeader size"); | ||||
| 
 | ||||
| #pragma pack(pop) | ||||
| 
 | ||||
| Slideshow* slideshow_alloc() { | ||||
|     Slideshow* ret = malloc(sizeof(Slideshow)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void slideshow_free(Slideshow* slideshow) { | ||||
|     Icon* icon = &slideshow->icon; | ||||
|     if(icon) { | ||||
|         for(int frame_idx = 0; frame_idx < icon->frame_count; ++frame_idx) { | ||||
|             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||
|             free(frame_data); | ||||
|         } | ||||
|         free((uint8_t**)icon->frames); | ||||
|     } | ||||
|     free(slideshow); | ||||
| } | ||||
| 
 | ||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     File* slideshow_file = storage_file_alloc(storage); | ||||
|     bool load_success = false; | ||||
|     do { | ||||
|         if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|             break; | ||||
|         } | ||||
|         SlideshowFileHeader header; | ||||
|         if((storage_file_read(slideshow_file, &header, sizeof(header)) != sizeof(header)) || | ||||
|            (header.magic != SLIDESHOW_MAGIC) || | ||||
|            (header.version > SLIDESHOW_MAX_SUPPORTED_VERSION)) { | ||||
|             break; | ||||
|         } | ||||
|         Icon* icon = &slideshow->icon; | ||||
|         FURI_CONST_ASSIGN(icon->frame_count, header.frame_count); | ||||
|         FURI_CONST_ASSIGN(icon->width, header.width); | ||||
|         FURI_CONST_ASSIGN(icon->height, header.height); | ||||
|         icon->frames = malloc(header.frame_count * sizeof(uint8_t*)); | ||||
|         for(int frame_idx = 0; frame_idx < header.frame_count; ++frame_idx) { | ||||
|             SlideshowFrameHeader frame_header; | ||||
|             if(storage_file_read(slideshow_file, &frame_header, sizeof(frame_header)) != | ||||
|                sizeof(frame_header)) { | ||||
|                 break; | ||||
|             } | ||||
|             FURI_CONST_ASSIGN_PTR(icon->frames[frame_idx], malloc(frame_header.size)); | ||||
|             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||
|             if(storage_file_read(slideshow_file, frame_data, frame_header.size) != | ||||
|                frame_header.size) { | ||||
|                 break; | ||||
|             } | ||||
|             load_success = (frame_idx + 1) == header.frame_count; | ||||
|         } | ||||
|     } while(false); | ||||
|     storage_file_free(slideshow_file); | ||||
|     furi_record_close("storage"); | ||||
|     return load_success; | ||||
| } | ||||
| 
 | ||||
| bool slideshow_advance(Slideshow* slideshow) { | ||||
|     uint8_t next_frame = slideshow->current_frame + 1; | ||||
|     if(next_frame < slideshow->icon.frame_count) { | ||||
|         slideshow->current_frame = next_frame; | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void slideshow_goback(Slideshow* slideshow) { | ||||
|     if(slideshow->current_frame > 0) { | ||||
|         slideshow->current_frame--; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y) { | ||||
|     furi_assert(slideshow->current_frame < slideshow->icon.frame_count); | ||||
|     canvas_draw_bitmap( | ||||
|         canvas, | ||||
|         x, | ||||
|         y, | ||||
|         slideshow->icon.width, | ||||
|         slideshow->icon.height, | ||||
|         slideshow->icon.frames[slideshow->current_frame]); | ||||
| } | ||||
							
								
								
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/canvas.h> | ||||
| 
 | ||||
| typedef struct Slideshow Slideshow; | ||||
| 
 | ||||
| Slideshow* slideshow_alloc(); | ||||
| 
 | ||||
| void slideshow_free(Slideshow* slideshow); | ||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||
| void slideshow_goback(Slideshow* slideshow); | ||||
| bool slideshow_advance(Slideshow* slideshow); | ||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | ||||
| @ -1,9 +1,9 @@ | ||||
| ADD_SCENE(desktop, main, Main) | ||||
| ADD_SCENE(desktop, lock_menu, LockMenu) | ||||
| ADD_SCENE(desktop, debug, Debug) | ||||
| ADD_SCENE(desktop, first_start, FirstStart) | ||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||
| ADD_SCENE(desktop, fault, Fault) | ||||
| ADD_SCENE(desktop, locked, Locked) | ||||
| ADD_SCENE(desktop, pin_input, PinInput) | ||||
| ADD_SCENE(desktop, pin_timeout, PinTimeout) | ||||
| ADD_SCENE(desktop, slideshow, Slideshow) | ||||
|  | ||||
| @ -1,54 +0,0 @@ | ||||
| #include <power/power_service/power.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_view_first_start.h" | ||||
| #include "../views/desktop_events.h" | ||||
| 
 | ||||
| void desktop_scene_first_start_callback(DesktopEvent event, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_first_start_on_enter(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     DesktopFirstStartView* first_start_view = desktop->first_start_view; | ||||
| 
 | ||||
|     desktop_first_start_set_callback( | ||||
|         first_start_view, desktop_scene_first_start_callback, desktop); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdFirstStart); | ||||
| } | ||||
| 
 | ||||
| bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     bool consumed = false; | ||||
|     Storage* storage = NULL; | ||||
|     Power* power = NULL; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopFirstStartCompleted: | ||||
|             storage = furi_record_open("storage"); | ||||
|             storage_common_remove(storage, "/int/first_start"); | ||||
|             furi_record_close("storage"); | ||||
|             scene_manager_previous_scene(desktop->scene_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopFirstStartPoweroff: | ||||
|             power = furi_record_open("power"); | ||||
|             power_off(power); | ||||
|             furi_record_close("power"); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_first_start_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
| } | ||||
							
								
								
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_view_slideshow.h" | ||||
| #include "../views/desktop_events.h" | ||||
| 
 | ||||
| void desktop_scene_slideshow_callback(DesktopEvent event, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_slideshow_on_enter(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     DesktopSlideshowView* slideshow_view = desktop->slideshow_view; | ||||
| 
 | ||||
|     desktop_view_slideshow_set_callback(slideshow_view, desktop_scene_slideshow_callback, desktop); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdSlideshow); | ||||
| } | ||||
| 
 | ||||
| bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     bool consumed = false; | ||||
|     Storage* storage = NULL; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopSlideshowCompleted: | ||||
|             storage = furi_record_open("storage"); | ||||
|             storage_common_remove(storage, "/int/slideshow"); | ||||
|             furi_record_close("storage"); | ||||
|             scene_manager_previous_scene(desktop->scene_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_slideshow_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
| } | ||||
| @ -26,9 +26,6 @@ typedef enum { | ||||
|     DesktopDebugEventSaveState, | ||||
|     DesktopDebugEventExit, | ||||
| 
 | ||||
|     DesktopFirstStartCompleted, | ||||
|     DesktopFirstStartPoweroff, | ||||
| 
 | ||||
|     DesktopLockMenuEventLock, | ||||
|     DesktopLockMenuEventPinLock, | ||||
|     DesktopLockMenuEventExit, | ||||
| @ -37,6 +34,8 @@ typedef enum { | ||||
|     DesktopAnimationEventNewIdleAnimation, | ||||
|     DesktopAnimationEventInteractAnimation, | ||||
| 
 | ||||
|     DesktopSlideshowCompleted, | ||||
| 
 | ||||
|     // Global events
 | ||||
|     DesktopGlobalBeforeAppStarted, | ||||
|     DesktopGlobalAfterAppFinished, | ||||
|  | ||||
| @ -46,7 +46,10 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|         canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|         ver = furi_hal_version_get_firmware_version(); | ||||
| 
 | ||||
|         const BleGlueC2Info* c2_ver = NULL; | ||||
| #ifdef SRV_BT | ||||
|         c2_ver = ble_glue_get_c2_info(); | ||||
| #endif | ||||
|         if(!ver) { | ||||
|             canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, "No info"); | ||||
|             return; | ||||
| @ -63,10 +66,11 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|         snprintf( | ||||
|             buffer, | ||||
|             sizeof(buffer), | ||||
|             "%s%s [%s]", | ||||
|             "%s%s [%s] %s", | ||||
|             version_get_dirty_flag(ver) ? "[!] " : "", | ||||
|             version_get_githash(ver), | ||||
|             version_get_gitbranchnum(ver)); | ||||
|             version_get_gitbranchnum(ver), | ||||
|             c2_ver ? c2_ver->StackTypeString : "<none>"); | ||||
|         canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|         snprintf( | ||||
|  | ||||
| @ -1,166 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_view_first_start.h" | ||||
| 
 | ||||
| #define DESKTOP_FIRST_START_POWEROFF_SHORT 5000 | ||||
| #define DESKTOP_FIRST_START_POWEROFF_LONG (60 * 60 * 1000) | ||||
| 
 | ||||
| struct DesktopFirstStartView { | ||||
|     View* view; | ||||
|     DesktopFirstStartViewCallback callback; | ||||
|     void* context; | ||||
|     osTimerId_t timer; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t page; | ||||
| } DesktopFirstStartViewModel; | ||||
| 
 | ||||
| static void desktop_first_start_draw(Canvas* canvas, void* model) { | ||||
|     DesktopFirstStartViewModel* m = model; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     uint8_t width = canvas_width(canvas); | ||||
|     uint8_t height = canvas_height(canvas); | ||||
|     const char* my_name = furi_hal_version_get_name_ptr(); | ||||
|     if(m->page == 0) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart0_70x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 75, 16 + STATUS_BAR_Y_SHIFT, "Hey m8,\npress > to\ncontinue"); | ||||
|     } else if(m->page == 1) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart1_59x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "First Of All,\n...      >"); | ||||
|     } else if(m->page == 2) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart2_59x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "Thank you\nfor your\nsupport! >"); | ||||
|     } else if(m->page == 3) { | ||||
|         canvas_draw_icon(canvas, width - 57, height - 45, &I_DolphinFirstStart3_57x48); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 16 + STATUS_BAR_Y_SHIFT, "Kickstarter\ncampaign\nwas INSANE! >"); | ||||
|     } else if(m->page == 4) { | ||||
|         canvas_draw_icon(canvas, width - 67, height - 51, &I_DolphinFirstStart4_67x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "Now\nallow me\nto introduce\nmyself >"); | ||||
|     } else if(m->page == 5) { | ||||
|         char buf[64]; | ||||
|         snprintf( | ||||
|             buf, | ||||
|             64, | ||||
|             "%s %s%s", | ||||
|             "I am", | ||||
|             my_name ? my_name : "Unknown", | ||||
|             ",\ncyberdolphin\nliving in your\npocket >"); | ||||
|         canvas_draw_icon(canvas, 0, height - 49, &I_DolphinFirstStart5_54x49); | ||||
|         elements_multiline_text_framed(canvas, 60, 13 + STATUS_BAR_Y_SHIFT, buf); | ||||
|     } else if(m->page == 6) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart6_58x54); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, | ||||
|             63, | ||||
|             13 + STATUS_BAR_Y_SHIFT, | ||||
|             "I can grow\nsmart'n'cool\nif you use me\noften >"); | ||||
|     } else if(m->page == 7) { | ||||
|         canvas_draw_icon(canvas, width - 61, height - 51, &I_DolphinFirstStart7_61x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "As long as\nyou read, write\nand emulate >"); | ||||
|     } else if(m->page == 8) { | ||||
|         canvas_draw_icon(canvas, width - 56, height - 51, &I_DolphinFirstStart8_56x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, | ||||
|             0, | ||||
|             13 + STATUS_BAR_Y_SHIFT, | ||||
|             "You can check\nmy level and\nmood in the\nPassport menu"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool desktop_first_start_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         DesktopFirstStartViewModel* model = view_get_model(instance->view); | ||||
|         if(event->key == InputKeyLeft) { | ||||
|             if(model->page > 0) model->page--; | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             uint32_t page = ++model->page; | ||||
|             if(page > 8) { | ||||
|                 instance->callback(DesktopFirstStartCompleted, instance->context); | ||||
|             } | ||||
|         } | ||||
|         view_commit_model(instance->view, true); | ||||
|     } | ||||
| 
 | ||||
|     if(event->key == InputKeyOk) { | ||||
|         if(event->type == InputTypePress) { | ||||
|             osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_SHORT); | ||||
|         } else if(event->type == InputTypeRelease) { | ||||
|             osTimerStop(instance->timer); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_timer_callback(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
|     instance->callback(DesktopFirstStartPoweroff, instance->context); | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_enter(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     furi_assert(instance->timer == NULL); | ||||
|     instance->timer = osTimerNew(desktop_first_start_timer_callback, osTimerOnce, instance, NULL); | ||||
| 
 | ||||
|     osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_LONG); | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_exit(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     osTimerStop(instance->timer); | ||||
|     osTimerDelete(instance->timer); | ||||
|     instance->timer = NULL; | ||||
| } | ||||
| 
 | ||||
| DesktopFirstStartView* desktop_first_start_alloc() { | ||||
|     DesktopFirstStartView* instance = malloc(sizeof(DesktopFirstStartView)); | ||||
|     instance->view = view_alloc(); | ||||
|     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopFirstStartViewModel)); | ||||
|     view_set_context(instance->view, instance); | ||||
|     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_first_start_draw); | ||||
|     view_set_input_callback(instance->view, desktop_first_start_input); | ||||
|     view_set_enter_callback(instance->view, desktop_first_start_enter); | ||||
|     view_set_exit_callback(instance->view, desktop_first_start_exit); | ||||
| 
 | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void desktop_first_start_free(DesktopFirstStartView* instance) { | ||||
|     furi_assert(instance); | ||||
| 
 | ||||
|     view_free(instance->view); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| View* desktop_first_start_get_view(DesktopFirstStartView* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->view; | ||||
| } | ||||
| 
 | ||||
| void desktop_first_start_set_callback( | ||||
|     DesktopFirstStartView* instance, | ||||
|     DesktopFirstStartViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(callback); | ||||
|     instance->callback = callback; | ||||
|     instance->context = context; | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||
| 
 | ||||
| typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context); | ||||
| 
 | ||||
| DesktopFirstStartView* desktop_first_start_alloc(); | ||||
| 
 | ||||
| void desktop_first_start_free(DesktopFirstStartView* main_view); | ||||
| 
 | ||||
| View* desktop_first_start_get_view(DesktopFirstStartView* main_view); | ||||
| 
 | ||||
| void desktop_first_start_set_callback( | ||||
|     DesktopFirstStartView* main_view, | ||||
|     DesktopFirstStartViewCallback callback, | ||||
|     void* context); | ||||
							
								
								
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_view_slideshow.h" | ||||
| #include "../helpers/slideshow.h" | ||||
| 
 | ||||
| struct DesktopSlideshowView { | ||||
|     View* view; | ||||
|     DesktopSlideshowViewCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t page; | ||||
|     Slideshow* slideshow; | ||||
| } DesktopSlideshowViewModel; | ||||
| 
 | ||||
| static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { | ||||
|     DesktopSlideshowViewModel* m = model; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     slideshow_draw(m->slideshow, canvas, 0, 0); | ||||
| } | ||||
| 
 | ||||
| static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|         bool end_slideshow = false; | ||||
|         switch(event->key) { | ||||
|         case InputKeyLeft: | ||||
|             slideshow_goback(model->slideshow); | ||||
|             break; | ||||
|         case InputKeyRight: | ||||
|         case InputKeyOk: | ||||
|             end_slideshow = !slideshow_advance(model->slideshow); | ||||
|             break; | ||||
|         case InputKeyBack: | ||||
|             end_slideshow = true; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         if(end_slideshow) { | ||||
|             instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|         } | ||||
|         view_commit_model(instance->view, true); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void desktop_view_slideshow_enter(void* context) { | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     model->slideshow = slideshow_alloc(); | ||||
|     if(!slideshow_load(model->slideshow, "/int/slideshow")) { | ||||
|         instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|     } | ||||
|     view_commit_model(instance->view, false); | ||||
| } | ||||
| 
 | ||||
| static void desktop_view_slideshow_exit(void* context) { | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     slideshow_free(model->slideshow); | ||||
|     view_commit_model(instance->view, false); | ||||
| } | ||||
| 
 | ||||
| DesktopSlideshowView* desktop_view_slideshow_alloc() { | ||||
|     DesktopSlideshowView* instance = malloc(sizeof(DesktopSlideshowView)); | ||||
|     instance->view = view_alloc(); | ||||
|     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopSlideshowViewModel)); | ||||
|     view_set_context(instance->view, instance); | ||||
|     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_view_slideshow_draw); | ||||
|     view_set_input_callback(instance->view, desktop_view_slideshow_input); | ||||
|     view_set_enter_callback(instance->view, desktop_view_slideshow_enter); | ||||
|     view_set_exit_callback(instance->view, desktop_view_slideshow_exit); | ||||
| 
 | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void desktop_view_slideshow_free(DesktopSlideshowView* instance) { | ||||
|     furi_assert(instance); | ||||
| 
 | ||||
|     view_free(instance->view); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| View* desktop_view_slideshow_get_view(DesktopSlideshowView* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->view; | ||||
| } | ||||
| 
 | ||||
| void desktop_view_slideshow_set_callback( | ||||
|     DesktopSlideshowView* instance, | ||||
|     DesktopSlideshowViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(callback); | ||||
|     instance->callback = callback; | ||||
|     instance->context = context; | ||||
| } | ||||
							
								
								
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopSlideshowView DesktopSlideshowView; | ||||
| 
 | ||||
| typedef void (*DesktopSlideshowViewCallback)(DesktopEvent event, void* context); | ||||
| 
 | ||||
| DesktopSlideshowView* desktop_view_slideshow_alloc(); | ||||
| 
 | ||||
| void desktop_view_slideshow_free(DesktopSlideshowView* main_view); | ||||
| 
 | ||||
| View* desktop_view_slideshow_get_view(DesktopSlideshowView* main_view); | ||||
| 
 | ||||
| void desktop_view_slideshow_set_callback( | ||||
|     DesktopSlideshowView* main_view, | ||||
|     DesktopSlideshowViewCallback callback, | ||||
|     void* context); | ||||
							
								
								
									
										10
									
								
								applications/dialogs/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								applications/dialogs/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| App( | ||||
|     appid="dialogs", | ||||
|     name="DialogsSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="dialogs_srv", | ||||
|     cdefines=["SRV_DIALOGS"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=40, | ||||
| ) | ||||
							
								
								
									
										23
									
								
								applications/dolphin/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								applications/dolphin/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| App( | ||||
|     appid="dolphin", | ||||
|     name="DolphinSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="dolphin_srv", | ||||
|     cdefines=["SRV_DOLPHIN"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=50, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="passport", | ||||
|     name="Passport", | ||||
|     apptype=FlipperAppType.SETTINGS, | ||||
|     entry_point="passport_app", | ||||
|     cdefines=["APP_PASSPORT"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dolphin", | ||||
|     ], | ||||
|     stack_size=1 * 1024, | ||||
|     order=60, | ||||
| ) | ||||
							
								
								
									
										57
									
								
								applications/extapps.scons
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								applications/extapps.scons
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| Import("ENV") | ||||
| 
 | ||||
| 
 | ||||
| from fbt.appmanifest import FlipperAppType | ||||
| 
 | ||||
| 
 | ||||
| appenv = ENV.Clone(tools=["fbt_extapps"]) | ||||
| 
 | ||||
| appenv.Replace( | ||||
|     LINKER_SCRIPT="application-ext", | ||||
|     STRIPFLAGS=[ | ||||
|         "--strip-debug", | ||||
|         "--strip-unneeded", | ||||
|         "-d", | ||||
|         "-g", | ||||
|         "-S", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| appenv.AppendUnique( | ||||
|     CCFLAGS=[ | ||||
|         "-Os", | ||||
|         "-ggdb3", | ||||
|         "-mword-relocations", | ||||
|         "-mlong-calls", | ||||
|         "-fno-common", | ||||
|         "-nostdlib", | ||||
|         "-fvisibility=hidden", | ||||
|     ], | ||||
|     LINKFLAGS=[ | ||||
|         "-r", | ||||
|         "-s", | ||||
|         # "-Bsymbolic", | ||||
|         "-nostartfiles", | ||||
|         "-mlong-calls", | ||||
|         "-fno-common", | ||||
|         "-nostdlib", | ||||
|         "-Wl,--gc-sections", | ||||
|         "-Wl,--no-export-dynamic", | ||||
|         "-fvisibility=hidden", | ||||
|         "-Wl,-e${APP_ENTRY}", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| extapps = [] | ||||
| for apptype in (FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL): | ||||
|     for app in appenv["APPBUILD"].get_apps_of_type(apptype): | ||||
|         extapps.append(appenv.BuildAppElf(app)) | ||||
| 
 | ||||
| # Ugly access to global option | ||||
| if extra_app_list := GetOption("extra_ext_apps"): | ||||
|     for extra_app in extra_app_list.split(","): | ||||
|         extapps.append(appenv.BuildAppElf(appenv["APPMGR"].get(extra_app))) | ||||
| 
 | ||||
| Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps) | ||||
| Return("extapps") | ||||
							
								
								
									
										11
									
								
								applications/gpio/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								applications/gpio/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| App( | ||||
|     appid="gpio", | ||||
|     name="GPIO", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     entry_point="gpio_app", | ||||
|     cdefines=["APP_GPIO"], | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     icon="A_GPIO_14", | ||||
|     order=50, | ||||
| ) | ||||
| @ -78,7 +78,7 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { | ||||
| 
 | ||||
|     if(ev == UartIrqEventRXNE) { | ||||
|         xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtRxDone); | ||||
|         furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); | ||||
|         portYIELD_FROM_ISR(xHigherPriorityTaskWoken); | ||||
|     } | ||||
| } | ||||
| @ -181,12 +181,13 @@ static int32_t usb_uart_worker(void* context) { | ||||
|         usb_uart_update_ctrl_lines(usb_uart); | ||||
|     } | ||||
| 
 | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||
| 
 | ||||
|     furi_thread_start(usb_uart->tx_thread); | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t events = osThreadFlagsWait(WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t events = | ||||
|             furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||
|         furi_check((events & osFlagsError) == 0); | ||||
|         if(events & WorkerEvtStop) break; | ||||
|         if(events & WorkerEvtRxDone) { | ||||
| @ -205,7 +206,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|         } | ||||
|         if(events & WorkerEvtCfgChange) { | ||||
|             if(usb_uart->cfg.vcp_ch != usb_uart->cfg_new.vcp_ch) { | ||||
|                 osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|                 furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|                 furi_thread_join(usb_uart->tx_thread); | ||||
| 
 | ||||
|                 usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); | ||||
| @ -217,7 +218,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|                 events |= WorkerEvtLineCfgSet; | ||||
|             } | ||||
|             if(usb_uart->cfg.uart_ch != usb_uart->cfg_new.uart_ch) { | ||||
|                 osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|                 furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|                 furi_thread_join(usb_uart->tx_thread); | ||||
| 
 | ||||
|                 usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); | ||||
| @ -266,7 +267,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|         furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeAnalog); | ||||
|     } | ||||
| 
 | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|     furi_thread_join(usb_uart->tx_thread); | ||||
|     furi_thread_free(usb_uart->tx_thread); | ||||
| 
 | ||||
| @ -288,7 +289,8 @@ static int32_t usb_uart_tx_thread(void* context) { | ||||
| 
 | ||||
|     uint8_t data[USB_CDC_PKT_LEN]; | ||||
|     while(1) { | ||||
|         uint32_t events = osThreadFlagsWait(WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t events = | ||||
|             furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||
|         furi_check((events & osFlagsError) == 0); | ||||
|         if(events & WorkerEvtTxStop) break; | ||||
|         if(events & WorkerEvtCdcRx) { | ||||
| @ -314,7 +316,7 @@ static void vcp_on_cdc_tx_complete(void* context) { | ||||
| 
 | ||||
| static void vcp_on_cdc_rx(void* context) { | ||||
|     UsbUartBridge* usb_uart = (UsbUartBridge*)context; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||
| } | ||||
| 
 | ||||
| static void vcp_state_callback(void* context, uint8_t state) { | ||||
| @ -325,13 +327,13 @@ static void vcp_state_callback(void* context, uint8_t state) { | ||||
| static void vcp_on_cdc_control_line(void* context, uint8_t state) { | ||||
|     UNUSED(state); | ||||
|     UsbUartBridge* usb_uart = (UsbUartBridge*)context; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtCtrlLineSet); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCtrlLineSet); | ||||
| } | ||||
| 
 | ||||
| static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) { | ||||
|     UNUSED(config); | ||||
|     UsbUartBridge* usb_uart = (UsbUartBridge*)context; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtLineCfgSet); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtLineCfgSet); | ||||
| } | ||||
| 
 | ||||
| UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { | ||||
| @ -351,7 +353,7 @@ UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { | ||||
| 
 | ||||
| void usb_uart_disable(UsbUartBridge* usb_uart) { | ||||
|     furi_assert(usb_uart); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtStop); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtStop); | ||||
|     furi_thread_join(usb_uart->thread); | ||||
|     furi_thread_free(usb_uart->thread); | ||||
|     free(usb_uart); | ||||
| @ -361,7 +363,7 @@ void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | ||||
|     furi_assert(usb_uart); | ||||
|     furi_assert(cfg); | ||||
|     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtCfgChange); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCfgChange); | ||||
| } | ||||
| 
 | ||||
| void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | ||||
|  | ||||
							
								
								
									
										13
									
								
								applications/gui/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/gui/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| App( | ||||
|     appid="gui", | ||||
|     name="GuiSrv", | ||||
|     apptype=FlipperAppType.SERVICE, | ||||
|     entry_point="gui_srv", | ||||
|     cdefines=["SRV_GUI"], | ||||
|     requires=[ | ||||
|         "input", | ||||
|         "notification", | ||||
|     ], | ||||
|     stack_size=2 * 1024, | ||||
|     order=70, | ||||
| ) | ||||
| @ -19,7 +19,7 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { | ||||
| 
 | ||||
| void gui_update(Gui* gui) { | ||||
|     furi_assert(gui); | ||||
|     osThreadFlagsSet(gui->thread, GUI_THREAD_FLAG_DRAW); | ||||
|     furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); | ||||
| } | ||||
| 
 | ||||
| void gui_input_events_callback(const void* value, void* ctx) { | ||||
| @ -29,7 +29,7 @@ void gui_input_events_callback(const void* value, void* ctx) { | ||||
|     Gui* gui = ctx; | ||||
| 
 | ||||
|     osMessageQueuePut(gui->input_queue, value, 0, osWaitForever); | ||||
|     osThreadFlagsSet(gui->thread, GUI_THREAD_FLAG_INPUT); | ||||
|     furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_INPUT); | ||||
| } | ||||
| 
 | ||||
| // Only Fullscreen supports vertical display for now
 | ||||
| @ -471,7 +471,7 @@ void gui_set_lockdown(Gui* gui, bool lockdown) { | ||||
| Gui* gui_alloc() { | ||||
|     Gui* gui = malloc(sizeof(Gui)); | ||||
|     // Thread ID
 | ||||
|     gui->thread = osThreadGetId(); | ||||
|     gui->thread_id = furi_thread_get_current_id(); | ||||
|     // Allocate mutex
 | ||||
|     gui->mutex = osMutexNew(NULL); | ||||
|     furi_check(gui->mutex); | ||||
| @ -500,7 +500,8 @@ int32_t gui_srv(void* p) { | ||||
|     furi_record_create("gui", gui); | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t flags = osThreadFlagsWait(GUI_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t flags = | ||||
|             furi_thread_flags_wait(GUI_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         // Process and dispatch input
 | ||||
|         if(flags & GUI_THREAD_FLAG_INPUT) { | ||||
|             // Process till queue become empty
 | ||||
| @ -512,7 +513,7 @@ int32_t gui_srv(void* p) { | ||||
|         // Process and dispatch draw call
 | ||||
|         if(flags & GUI_THREAD_FLAG_DRAW) { | ||||
|             // Clear flags that arrived on input step
 | ||||
|             osThreadFlagsClear(GUI_THREAD_FLAG_DRAW); | ||||
|             furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW); | ||||
|             gui_redraw(gui); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -57,7 +57,7 @@ ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); | ||||
| /** Gui structure */ | ||||
| struct Gui { | ||||
|     // Thread and lock
 | ||||
|     osThreadId_t thread; | ||||
|     FuriThreadId thread_id; | ||||
|     osMutexId_t mutex; | ||||
| 
 | ||||
|     // Layers and Canvas
 | ||||
|  | ||||
| @ -259,10 +259,10 @@ static int32_t browser_worker(void* context) { | ||||
|     string_t filename; | ||||
|     string_init(filename); | ||||
| 
 | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtConfigChange); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         uint32_t flags = furi_thread_flags_wait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         furi_assert((flags & osFlagsError) == 0); | ||||
| 
 | ||||
|         if(flags & WorkerEvtConfigChange) { | ||||
| @ -272,7 +272,7 @@ static int32_t browser_worker(void* context) { | ||||
|             } | ||||
|             idx_last_array_reset(browser->idx_last); | ||||
| 
 | ||||
|             osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||
|             furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); | ||||
|         } | ||||
| 
 | ||||
|         if(flags & WorkerEvtFolderEnter) { | ||||
| @ -369,7 +369,7 @@ BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, | ||||
| void file_browser_worker_free(BrowserWorker* browser) { | ||||
|     furi_assert(browser); | ||||
| 
 | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtStop); | ||||
|     furi_thread_join(browser->thread); | ||||
|     furi_thread_free(browser->thread); | ||||
| 
 | ||||
| @ -423,30 +423,30 @@ void file_browser_worker_set_config( | ||||
|     string_set(browser->path_next, path); | ||||
|     string_set_str(browser->filter_extension, filter_ext); | ||||
|     browser->skip_assets = skip_assets; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtConfigChange); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); | ||||
| } | ||||
| 
 | ||||
| void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { | ||||
|     furi_assert(browser); | ||||
|     string_set(browser->path_next, path); | ||||
|     browser->item_sel_idx = item_idx; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); | ||||
| } | ||||
| 
 | ||||
| void file_browser_worker_folder_exit(BrowserWorker* browser) { | ||||
|     furi_assert(browser); | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderExit); | ||||
| } | ||||
| 
 | ||||
| void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx) { | ||||
|     furi_assert(browser); | ||||
|     browser->item_sel_idx = item_idx; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderRefresh); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderRefresh); | ||||
| } | ||||
| 
 | ||||
| void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { | ||||
|     furi_assert(browser); | ||||
|     browser->load_offset = offset; | ||||
|     browser->load_count = count; | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); | ||||
|     furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtLoad); | ||||
| } | ||||
|  | ||||
| @ -20,10 +20,10 @@ typedef struct { | ||||
| static void loading_draw_callback(Canvas* canvas, void* _model) { | ||||
|     LoadingModel* model = (LoadingModel*)_model; | ||||
| 
 | ||||
|     uint8_t x = 7; | ||||
|     uint8_t y = 40; | ||||
|     uint8_t width = 49; | ||||
|     uint8_t height = 47; | ||||
|     uint8_t x = (canvas_width(canvas) - width) / 2; | ||||
|     uint8_t y = (canvas_height(canvas) - height) / 2; | ||||
| 
 | ||||
|     elements_bold_rounded_frame(canvas, x, y, width, height); | ||||
| 
 | ||||
|  | ||||
| @ -165,6 +165,25 @@ bool scene_manager_search_and_switch_to_previous_scene( | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool scene_manager_search_and_switch_to_previous_scene_one_of( | ||||
|     SceneManager* scene_manager, | ||||
|     const uint32_t* scene_ids, | ||||
|     size_t scene_ids_size) { | ||||
|     furi_assert(scene_manager); | ||||
|     furi_assert(scene_ids); | ||||
|     bool scene_found = false; | ||||
| 
 | ||||
|     for(size_t i = 0; i < scene_ids_size; ++i) { | ||||
|         const uint32_t scene_id = scene_ids[i]; | ||||
|         if(scene_manager_has_previous_scene(scene_manager, scene_id)) { | ||||
|             scene_manager_search_and_switch_to_previous_scene(scene_manager, scene_id); | ||||
|             scene_found = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return scene_found; | ||||
| } | ||||
| 
 | ||||
| bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) { | ||||
|     furi_assert(scene_manager); | ||||
|     bool scene_found = false; | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| @ -146,6 +147,19 @@ bool scene_manager_search_and_switch_to_previous_scene( | ||||
|     SceneManager* scene_manager, | ||||
|     uint32_t scene_id); | ||||
| 
 | ||||
| /** Search and switch to previous Scene, multiple choice
 | ||||
|  * | ||||
|  * @param      scene_manager    SceneManager instance | ||||
|  * @param      scene_ids        Array of scene IDs | ||||
|  * @param      scene_ids_size   Array of scene IDs size | ||||
|  * | ||||
|  * @return     true if one of previous scenes was found, false otherwise | ||||
|  */ | ||||
| bool scene_manager_search_and_switch_to_previous_scene_one_of( | ||||
|     SceneManager* scene_manager, | ||||
|     const uint32_t* scene_ids, | ||||
|     size_t scene_ids_size); | ||||
| 
 | ||||
| /** Clear Scene stack and switch to another Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  | ||||
							
								
								
									
										23
									
								
								applications/ibutton/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								applications/ibutton/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| App( | ||||
|     appid="ibutton", | ||||
|     name="iButton", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     entry_point="ibutton_app", | ||||
|     cdefines=["APP_IBUTTON"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     provides=["ibutton_start"], | ||||
|     icon="A_iButton_14", | ||||
|     stack_size=2 * 1024, | ||||
|     order=60, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="ibutton_start", | ||||
|     apptype=FlipperAppType.STARTUP, | ||||
|     entry_point="ibutton_on_system_start", | ||||
|     requires=["ibutton"], | ||||
|     order=60, | ||||
| ) | ||||
							
								
								
									
										23
									
								
								applications/infrared/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								applications/infrared/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| App( | ||||
|     appid="infrared", | ||||
|     name="Infrared", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     entry_point="infrared_app", | ||||
|     cdefines=["APP_INFRARED"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|         "dialogs", | ||||
|     ], | ||||
|     provides=["infrared_start"], | ||||
|     icon="A_Infrared_14", | ||||
|     stack_size=3 * 1024, | ||||
|     order=40, | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="infrared_start", | ||||
|     apptype=FlipperAppType.STARTUP, | ||||
|     entry_point="infrared_on_system_start", | ||||
|     requires=["infrared"], | ||||
|     order=20, | ||||
| ) | ||||
| @ -1,157 +0,0 @@ | ||||
| 
 | ||||
| #include "../infrared_app_signal.h" | ||||
| #include "infrared.h" | ||||
| #include "infrared/helpers/infrared_parser.h" | ||||
| #include "infrared_worker.h" | ||||
| #include "m-string.h" | ||||
| #include <flipper_format/flipper_format.h> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <furi_hal_infrared.h> | ||||
| 
 | ||||
| #define TAG "InfraredParser" | ||||
| 
 | ||||
| bool infrared_parser_save_signal( | ||||
|     FlipperFormat* ff, | ||||
|     const InfraredAppSignal& signal, | ||||
|     const std::string& name) { | ||||
|     furi_assert(ff); | ||||
|     furi_assert(!name.empty()); | ||||
| 
 | ||||
|     bool result = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_write_comment_cstr(ff, "")) break; | ||||
|         if(!flipper_format_write_string_cstr(ff, "name", name.c_str())) break; | ||||
|         if(signal.is_raw()) { | ||||
|             furi_assert(signal.get_raw_signal().timings_cnt <= MAX_TIMINGS_AMOUNT); | ||||
|             auto raw_signal = signal.get_raw_signal(); | ||||
|             if(!flipper_format_write_string_cstr(ff, "type", "raw")) break; | ||||
|             if(!flipper_format_write_uint32(ff, "frequency", &raw_signal.frequency, 1)) break; | ||||
|             if(!flipper_format_write_float(ff, "duty_cycle", &raw_signal.duty_cycle, 1)) break; | ||||
|             if(!flipper_format_write_uint32(ff, "data", raw_signal.timings, raw_signal.timings_cnt)) | ||||
|                 break; | ||||
|         } else { | ||||
|             auto parsed_signal = signal.get_message(); | ||||
|             const char* protocol_name = infrared_get_protocol_name(parsed_signal.protocol); | ||||
|             if(!flipper_format_write_string_cstr(ff, "type", "parsed")) break; | ||||
|             if(!flipper_format_write_string_cstr(ff, "protocol", protocol_name)) break; | ||||
|             if(!flipper_format_write_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) | ||||
|                 break; | ||||
|             if(!flipper_format_write_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4)) | ||||
|                 break; | ||||
|         } | ||||
|         result = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name) { | ||||
|     furi_assert(ff); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     string_t read_string; | ||||
|     string_init(read_string); | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_read_string(ff, "name", read_string)) break; | ||||
|         name = string_get_cstr(read_string); | ||||
|         if(!flipper_format_read_string(ff, "type", read_string)) break; | ||||
|         if(!string_cmp_str(read_string, "raw")) { | ||||
|             uint32_t* timings = nullptr; | ||||
|             uint32_t timings_cnt = 0; | ||||
|             uint32_t frequency = 0; | ||||
|             float duty_cycle = 0; | ||||
| 
 | ||||
|             if(!flipper_format_read_uint32(ff, "frequency", &frequency, 1)) break; | ||||
|             if(!flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1)) break; | ||||
|             if(!flipper_format_get_value_count(ff, "data", &timings_cnt)) break; | ||||
|             if(timings_cnt > MAX_TIMINGS_AMOUNT) break; | ||||
|             timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt); | ||||
|             if(flipper_format_read_uint32(ff, "data", timings, timings_cnt)) { | ||||
|                 signal.set_raw_signal(timings, timings_cnt, frequency, duty_cycle); | ||||
|                 result = true; | ||||
|             } | ||||
|             free(timings); | ||||
|         } else if(!string_cmp_str(read_string, "parsed")) { | ||||
|             InfraredMessage parsed_signal; | ||||
|             if(!flipper_format_read_string(ff, "protocol", read_string)) break; | ||||
|             parsed_signal.protocol = infrared_get_protocol_by_name(string_get_cstr(read_string)); | ||||
|             if(!flipper_format_read_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) break; | ||||
|             if(!flipper_format_read_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4)) break; | ||||
|             if(!infrared_parser_is_parsed_signal_valid(&parsed_signal)) break; | ||||
|             signal.set_message(&parsed_signal); | ||||
|             result = true; | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) "); | ||||
|         } | ||||
|     } while(0); | ||||
| 
 | ||||
|     string_clear(read_string); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal) { | ||||
|     furi_assert(signal); | ||||
|     bool result = true; | ||||
| 
 | ||||
|     if(!infrared_is_protocol_valid(signal->protocol)) { | ||||
|         FURI_LOG_E(TAG, "Unknown protocol"); | ||||
|         result = false; | ||||
|     } | ||||
| 
 | ||||
|     if(result) { | ||||
|         uint32_t address_length = infrared_get_protocol_address_length(signal->protocol); | ||||
|         uint32_t address_mask = (1LU << address_length) - 1; | ||||
|         if(signal->address != (signal->address & address_mask)) { | ||||
|             FURI_LOG_E( | ||||
|                 TAG, | ||||
|                 "Address is out of range (mask 0x%08lX): 0x%lX\r\n", | ||||
|                 address_mask, | ||||
|                 signal->address); | ||||
|             result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(result) { | ||||
|         uint32_t command_length = infrared_get_protocol_command_length(signal->protocol); | ||||
|         uint32_t command_mask = (1LU << command_length) - 1; | ||||
|         if(signal->command != (signal->command & command_mask)) { | ||||
|             FURI_LOG_E( | ||||
|                 TAG, | ||||
|                 "Command is out of range (mask 0x%08lX): 0x%lX\r\n", | ||||
|                 command_mask, | ||||
|                 signal->command); | ||||
|             result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool infrared_parser_is_raw_signal_valid( | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle, | ||||
|     uint32_t timings_cnt) { | ||||
|     bool result = true; | ||||
| 
 | ||||
|     if((frequency > INFRARED_MAX_FREQUENCY) || (frequency < INFRARED_MIN_FREQUENCY)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, | ||||
|             "Frequency is out of range (%lX - %lX): %lX", | ||||
|             INFRARED_MIN_FREQUENCY, | ||||
|             INFRARED_MAX_FREQUENCY, | ||||
|             frequency); | ||||
|         result = false; | ||||
|     } else if((duty_cycle <= 0) || (duty_cycle > 1)) { | ||||
|         FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)duty_cycle); | ||||
|         result = false; | ||||
|     } else if((timings_cnt <= 0) || (timings_cnt > MAX_TIMINGS_AMOUNT)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, "Timings amount is out of range (0 - %lX): %lX", MAX_TIMINGS_AMOUNT, timings_cnt); | ||||
|         result = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_parser.h | ||||
|   * Infrared: Helper file for conversion Flipper File Format | ||||
|   *     to Infrared signal class, and backwards | ||||
|   */ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "../infrared_app_signal.h" | ||||
| #include <flipper_format/flipper_format.h> | ||||
| #include <string> | ||||
| 
 | ||||
| /** Save Infrared signal into file
 | ||||
|  * | ||||
|  * @param ff - Flipper File Format instance | ||||
|  * @param signal - Infrared signal to save | ||||
|  * @param name - name for saved signal. Every | ||||
|  *      signal on disk has name. | ||||
|  */ | ||||
| bool infrared_parser_save_signal( | ||||
|     FlipperFormat* ff, | ||||
|     const InfraredAppSignal& signal, | ||||
|     const std::string& name); | ||||
| 
 | ||||
| /** Read Infrared signal from file
 | ||||
|  * | ||||
|  * @param ff - Flipper File Format instance | ||||
|  * @param signal - Infrared signal to read to | ||||
|  * @param name - name for saved signal. Every | ||||
|  *      signal in file has name. | ||||
|  */ | ||||
| bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name); | ||||
| 
 | ||||
| /** Validate parsed signal
 | ||||
|  * | ||||
|  * @signal - signal to validate | ||||
|  * @retval true if valid, false otherwise | ||||
|  */ | ||||
| bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal); | ||||
| 
 | ||||
| /** Validate raw signal
 | ||||
|  * | ||||
|  * @signal - signal to validate | ||||
|  * @retval true if valid, false otherwise | ||||
|  */ | ||||
| bool infrared_parser_is_raw_signal_valid( | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle, | ||||
|     uint32_t timings_cnt); | ||||
							
								
								
									
										399
									
								
								applications/infrared/infrared.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								applications/infrared/infrared.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,399 @@ | ||||
| #include "infrared_i.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static const NotificationSequence* infrared_notification_sequences[] = { | ||||
|     &sequence_success, | ||||
|     &sequence_set_only_green_255, | ||||
|     &sequence_reset_green, | ||||
|     &sequence_blink_cyan_10, | ||||
|     &sequence_blink_magenta_10, | ||||
|     &sequence_solid_yellow, | ||||
|     &sequence_reset_rgb}; | ||||
| 
 | ||||
| static void infrared_make_app_folder(Infrared* infrared) { | ||||
|     if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) { | ||||
|         dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool infrared_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     return scene_manager_handle_custom_event(infrared->scene_manager, event); | ||||
| } | ||||
| 
 | ||||
| static bool infrared_back_event_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     return scene_manager_handle_back_event(infrared->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static void infrared_tick_event_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     scene_manager_handle_tick_event(infrared->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static void infrared_find_vacant_remote_name(string_t name, const char* path) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
| 
 | ||||
|     string_t base_path; | ||||
|     string_init_set_str(base_path, path); | ||||
| 
 | ||||
|     if(string_end_with_str_p(base_path, INFRARED_APP_EXTENSION)) { | ||||
|         size_t filename_start = string_search_rchar(base_path, '/'); | ||||
|         string_left(base_path, filename_start); | ||||
|     } | ||||
| 
 | ||||
|     string_printf(base_path, "%s/%s%s", path, string_get_cstr(name), INFRARED_APP_EXTENSION); | ||||
| 
 | ||||
|     FS_Error status = storage_common_stat(storage, string_get_cstr(base_path), NULL); | ||||
| 
 | ||||
|     if(status == FSE_OK) { | ||||
|         /* If the suggested name is occupied, try another one (name2, name3, etc) */ | ||||
|         size_t dot = string_search_rchar(base_path, '.'); | ||||
|         string_left(base_path, dot); | ||||
| 
 | ||||
|         string_t path_temp; | ||||
|         string_init(path_temp); | ||||
| 
 | ||||
|         uint32_t i = 1; | ||||
|         do { | ||||
|             string_printf( | ||||
|                 path_temp, "%s%u%s", string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION); | ||||
|             status = storage_common_stat(storage, string_get_cstr(path_temp), NULL); | ||||
|         } while(status == FSE_OK); | ||||
| 
 | ||||
|         string_clear(path_temp); | ||||
| 
 | ||||
|         if(status == FSE_NOT_EXIST) { | ||||
|             string_cat_printf(name, "%u", i); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(base_path); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| static Infrared* infrared_alloc() { | ||||
|     Infrared* infrared = malloc(sizeof(Infrared)); | ||||
| 
 | ||||
|     string_init(infrared->file_path); | ||||
| 
 | ||||
|     InfraredAppState* app_state = &infrared->app_state; | ||||
|     app_state->is_learning_new_remote = false; | ||||
|     app_state->is_debug_enabled = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); | ||||
|     app_state->edit_target = InfraredEditTargetNone; | ||||
|     app_state->edit_mode = InfraredEditModeNone; | ||||
|     app_state->current_button_index = InfraredButtonIndexNone; | ||||
| 
 | ||||
|     infrared->scene_manager = scene_manager_alloc(&infrared_scene_handlers, infrared); | ||||
|     infrared->view_dispatcher = view_dispatcher_alloc(); | ||||
| 
 | ||||
|     infrared->gui = furi_record_open("gui"); | ||||
| 
 | ||||
|     ViewDispatcher* view_dispatcher = infrared->view_dispatcher; | ||||
|     view_dispatcher_attach_to_gui(view_dispatcher, infrared->gui, ViewDispatcherTypeFullscreen); | ||||
|     view_dispatcher_enable_queue(view_dispatcher); | ||||
|     view_dispatcher_set_event_callback_context(view_dispatcher, infrared); | ||||
|     view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback); | ||||
|     view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback); | ||||
|     view_dispatcher_set_tick_event_callback(view_dispatcher, infrared_tick_event_callback, 100); | ||||
| 
 | ||||
|     infrared->storage = furi_record_open("storage"); | ||||
|     infrared->dialogs = furi_record_open("dialogs"); | ||||
|     infrared->notifications = furi_record_open("notification"); | ||||
| 
 | ||||
|     infrared->worker = infrared_worker_alloc(); | ||||
|     infrared->remote = infrared_remote_alloc(); | ||||
|     infrared->received_signal = infrared_signal_alloc(); | ||||
|     infrared->brute_force = infrared_brute_force_alloc(); | ||||
| 
 | ||||
|     infrared->submenu = submenu_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         view_dispatcher, InfraredViewSubmenu, submenu_get_view(infrared->submenu)); | ||||
| 
 | ||||
|     infrared->text_input = text_input_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         view_dispatcher, InfraredViewTextInput, text_input_get_view(infrared->text_input)); | ||||
| 
 | ||||
|     infrared->dialog_ex = dialog_ex_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex)); | ||||
| 
 | ||||
|     infrared->button_menu = button_menu_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         view_dispatcher, InfraredViewButtonMenu, button_menu_get_view(infrared->button_menu)); | ||||
| 
 | ||||
|     infrared->popup = popup_alloc(); | ||||
|     view_dispatcher_add_view(view_dispatcher, InfraredViewPopup, popup_get_view(infrared->popup)); | ||||
| 
 | ||||
|     infrared->view_stack = view_stack_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack)); | ||||
| 
 | ||||
|     if(app_state->is_debug_enabled) { | ||||
|         infrared->debug_view = infrared_debug_view_alloc(); | ||||
|         view_dispatcher_add_view( | ||||
|             view_dispatcher, | ||||
|             InfraredViewDebugView, | ||||
|             infrared_debug_view_get_view(infrared->debug_view)); | ||||
|     } | ||||
| 
 | ||||
|     infrared->button_panel = button_panel_alloc(); | ||||
|     infrared->loading = loading_alloc(); | ||||
|     infrared->progress = infrared_progress_view_alloc(); | ||||
| 
 | ||||
|     return infrared; | ||||
| } | ||||
| 
 | ||||
| static void infrared_free(Infrared* infrared) { | ||||
|     furi_assert(infrared); | ||||
|     ViewDispatcher* view_dispatcher = infrared->view_dispatcher; | ||||
|     InfraredAppState* app_state = &infrared->app_state; | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu); | ||||
|     submenu_free(infrared->submenu); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewTextInput); | ||||
|     text_input_free(infrared->text_input); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx); | ||||
|     dialog_ex_free(infrared->dialog_ex); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewButtonMenu); | ||||
|     button_menu_free(infrared->button_menu); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewPopup); | ||||
|     popup_free(infrared->popup); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(view_dispatcher, InfraredViewStack); | ||||
|     view_stack_free(infrared->view_stack); | ||||
| 
 | ||||
|     if(app_state->is_debug_enabled) { | ||||
|         view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView); | ||||
|         infrared_debug_view_free(infrared->debug_view); | ||||
|     } | ||||
| 
 | ||||
|     button_panel_free(infrared->button_panel); | ||||
|     loading_free(infrared->loading); | ||||
|     infrared_progress_view_free(infrared->progress); | ||||
| 
 | ||||
|     view_dispatcher_free(view_dispatcher); | ||||
|     scene_manager_free(infrared->scene_manager); | ||||
| 
 | ||||
|     infrared_brute_force_free(infrared->brute_force); | ||||
|     infrared_signal_free(infrared->received_signal); | ||||
|     infrared_remote_free(infrared->remote); | ||||
|     infrared_worker_free(infrared->worker); | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     infrared->gui = NULL; | ||||
| 
 | ||||
|     furi_record_close("notification"); | ||||
|     infrared->notifications = NULL; | ||||
| 
 | ||||
|     furi_record_close("dialogs"); | ||||
|     infrared->dialogs = NULL; | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     infrared->gui = NULL; | ||||
| 
 | ||||
|     string_clear(infrared->file_path); | ||||
| 
 | ||||
|     free(infrared); | ||||
| } | ||||
| 
 | ||||
| bool infrared_add_remote_with_button( | ||||
|     Infrared* infrared, | ||||
|     const char* button_name, | ||||
|     InfraredSignal* signal) { | ||||
|     InfraredRemote* remote = infrared->remote; | ||||
| 
 | ||||
|     string_t new_name, new_path; | ||||
|     string_init_set_str(new_name, INFRARED_DEFAULT_REMOTE_NAME); | ||||
|     string_init_set_str(new_path, INFRARED_APP_FOLDER); | ||||
| 
 | ||||
|     infrared_find_vacant_remote_name(new_name, string_get_cstr(new_path)); | ||||
|     string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION); | ||||
| 
 | ||||
|     infrared_remote_reset(remote); | ||||
|     infrared_remote_set_name(remote, string_get_cstr(new_name)); | ||||
|     infrared_remote_set_path(remote, string_get_cstr(new_path)); | ||||
| 
 | ||||
|     string_clear(new_name); | ||||
|     string_clear(new_path); | ||||
|     return infrared_remote_add_button(remote, button_name, signal); | ||||
| } | ||||
| 
 | ||||
| bool infrared_rename_current_remote(Infrared* infrared, const char* name) { | ||||
|     InfraredRemote* remote = infrared->remote; | ||||
|     const char* remote_path = infrared_remote_get_path(remote); | ||||
| 
 | ||||
|     if(!strcmp(infrared_remote_get_name(remote), name)) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     string_t new_name; | ||||
|     string_init_set_str(new_name, name); | ||||
| 
 | ||||
|     infrared_find_vacant_remote_name(new_name, remote_path); | ||||
| 
 | ||||
|     string_t new_path; | ||||
|     string_init_set(new_path, infrared_remote_get_path(remote)); | ||||
|     if(string_end_with_str_p(new_path, INFRARED_APP_EXTENSION)) { | ||||
|         size_t filename_start = string_search_rchar(new_path, '/'); | ||||
|         string_left(new_path, filename_start); | ||||
|     } | ||||
|     string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION); | ||||
| 
 | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
| 
 | ||||
|     FS_Error status = storage_common_rename( | ||||
|         storage, infrared_remote_get_path(remote), string_get_cstr(new_path)); | ||||
|     infrared_remote_set_name(remote, string_get_cstr(new_name)); | ||||
| 
 | ||||
|     string_clear(new_name); | ||||
|     string_clear(new_path); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
|     return (status == FSE_OK || status == FSE_EXIST); | ||||
| } | ||||
| 
 | ||||
| void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { | ||||
|     if(infrared_signal_is_raw(signal)) { | ||||
|         InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); | ||||
|         infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size); | ||||
|     } else { | ||||
|         InfraredMessage* message = infrared_signal_get_message(signal); | ||||
|         infrared_worker_set_decoded_signal(infrared->worker, message); | ||||
|     } | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedIrSend); | ||||
|     infrared_worker_tx_start(infrared->worker); | ||||
| } | ||||
| 
 | ||||
| void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) { | ||||
|     furi_assert(button_index < infrared_remote_get_button_count(infrared->remote)); | ||||
| 
 | ||||
|     InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index); | ||||
|     InfraredSignal* signal = infrared_remote_button_get_signal(button); | ||||
| 
 | ||||
|     infrared_tx_start_signal(infrared, signal); | ||||
| } | ||||
| 
 | ||||
| void infrared_tx_start_received(Infrared* infrared) { | ||||
|     infrared_tx_start_signal(infrared, infrared->received_signal); | ||||
| } | ||||
| 
 | ||||
| void infrared_tx_stop(Infrared* infrared) { | ||||
|     infrared_worker_tx_stop(infrared->worker); | ||||
| } | ||||
| 
 | ||||
| void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) { | ||||
|     va_list args; | ||||
|     va_start(args, text); | ||||
| 
 | ||||
|     vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args); | ||||
| 
 | ||||
|     va_end(args); | ||||
| } | ||||
| 
 | ||||
| void infrared_text_store_clear(Infrared* infrared, uint32_t bank) { | ||||
|     memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE); | ||||
| } | ||||
| 
 | ||||
| void infrared_play_notification_message(Infrared* infrared, uint32_t message) { | ||||
|     furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*)); | ||||
|     notification_message(infrared->notifications, infrared_notification_sequences[message]); | ||||
| } | ||||
| 
 | ||||
| void infrared_show_loading_popup(Infrared* infrared, bool show) { | ||||
|     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); | ||||
|     ViewStack* view_stack = infrared->view_stack; | ||||
|     Loading* loading = infrared->loading; | ||||
| 
 | ||||
|     if(show) { | ||||
|         // Raise timer priority so that animations can play
 | ||||
|         vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); | ||||
|         view_stack_add_view(view_stack, loading_get_view(loading)); | ||||
|     } else { | ||||
|         view_stack_remove_view(view_stack, loading_get_view(loading)); | ||||
|         // Restore default timer priority
 | ||||
|         vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_sent_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend); | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
| 
 | ||||
|     if(infrared_worker_signal_is_decoded(received_signal)) { | ||||
|         infrared_signal_set_message( | ||||
|             infrared->received_signal, infrared_worker_get_decoded_signal(received_signal)); | ||||
|     } else { | ||||
|         const uint32_t* timings; | ||||
|         size_t timings_size; | ||||
|         infrared_worker_get_raw_signal(received_signal, &timings, &timings_size); | ||||
|         infrared_signal_set_raw_signal( | ||||
|             infrared->received_signal, | ||||
|             timings, | ||||
|             timings_size, | ||||
|             INFRARED_COMMON_CARRIER_FREQUENCY, | ||||
|             INFRARED_COMMON_DUTY_CYCLE); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event( | ||||
|         infrared->view_dispatcher, InfraredCustomEventTypeSignalReceived); | ||||
| } | ||||
| 
 | ||||
| void infrared_text_input_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     view_dispatcher_send_custom_event( | ||||
|         infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone); | ||||
| } | ||||
| 
 | ||||
| void infrared_popup_closed_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
|     view_dispatcher_send_custom_event( | ||||
|         infrared->view_dispatcher, InfraredCustomEventTypePopupClosed); | ||||
| } | ||||
| 
 | ||||
| int32_t infrared_app(void* p) { | ||||
|     Infrared* infrared = infrared_alloc(); | ||||
| 
 | ||||
|     infrared_make_app_folder(infrared); | ||||
| 
 | ||||
|     bool is_remote_loaded = false; | ||||
| 
 | ||||
|     if(p) { | ||||
|         string_set_str(infrared->file_path, (const char*)p); | ||||
|         is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); | ||||
|         if(!is_remote_loaded) { | ||||
|             dialog_message_show_storage_error( | ||||
|                 infrared->dialogs, "Failed to load\nselected remote"); | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(is_remote_loaded) { | ||||
|         scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); | ||||
|     } else { | ||||
|         scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_run(infrared->view_dispatcher); | ||||
| 
 | ||||
|     infrared_free(infrared); | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										3
									
								
								applications/infrared/infrared.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								applications/infrared/infrared.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| typedef struct Infrared Infrared; | ||||
| @ -1,250 +0,0 @@ | ||||
| #include "infrared_app.h" | ||||
| #include "m-string.h" | ||||
| #include <infrared_worker.h> | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| #include <stdio.h> | ||||
| #include <callback-connector.h> | ||||
| 
 | ||||
| int32_t InfraredApp::run(void* args) { | ||||
|     InfraredAppEvent event; | ||||
|     bool consumed; | ||||
|     bool exit = false; | ||||
| 
 | ||||
|     if(args) { | ||||
|         string_t path; | ||||
|         string_init_set_str(path, (char*)args); | ||||
|         if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { | ||||
|             bool result = remote_manager.load(path); | ||||
|             if(result) { | ||||
|                 current_scene = InfraredApp::Scene::Remote; | ||||
|             } else { | ||||
|                 printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); | ||||
|                 return -1; | ||||
|             } | ||||
|         } | ||||
|         string_clear(path); | ||||
|     } | ||||
| 
 | ||||
|     scenes[current_scene]->on_enter(this); | ||||
| 
 | ||||
|     while(!exit) { | ||||
|         view_manager.receive_event(&event); | ||||
| 
 | ||||
|         if(event.type == InfraredAppEvent::Type::Exit) break; | ||||
| 
 | ||||
|         consumed = scenes[current_scene]->on_event(this, &event); | ||||
| 
 | ||||
|         if(!consumed) { | ||||
|             if(event.type == InfraredAppEvent::Type::Back) { | ||||
|                 exit = switch_to_previous_scene(); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     scenes[current_scene]->on_exit(this); | ||||
| 
 | ||||
|     return 0; | ||||
| }; | ||||
| 
 | ||||
| InfraredApp::InfraredApp() { | ||||
|     furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); | ||||
|     string_init_set_str(file_path, InfraredApp::infrared_directory); | ||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); | ||||
|     dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs")); | ||||
|     infrared_worker = infrared_worker_alloc(); | ||||
| } | ||||
| 
 | ||||
| InfraredApp::~InfraredApp() { | ||||
|     infrared_worker_free(infrared_worker); | ||||
|     furi_record_close("notification"); | ||||
|     furi_record_close("dialogs"); | ||||
|     string_clear(file_path); | ||||
|     for(auto& [key, scene] : scenes) delete scene; | ||||
| } | ||||
| 
 | ||||
| InfraredAppViewManager* InfraredApp::get_view_manager() { | ||||
|     return &view_manager; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_learn_new_remote(bool value) { | ||||
|     learn_new_remote = value; | ||||
| } | ||||
| 
 | ||||
| bool InfraredApp::get_learn_new_remote() { | ||||
|     return learn_new_remote; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::switch_to_next_scene(Scene next_scene) { | ||||
|     previous_scenes_list.push_front(current_scene); | ||||
|     switch_to_next_scene_without_saving(next_scene); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::switch_to_next_scene_without_saving(Scene next_scene) { | ||||
|     if(next_scene != Scene::Exit) { | ||||
|         scenes[current_scene]->on_exit(this); | ||||
|         current_scene = next_scene; | ||||
|         scenes[current_scene]->on_enter(this); | ||||
|         view_manager.clear_events(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::search_and_switch_to_previous_scene( | ||||
|     const std::initializer_list<Scene>& scenes_list) { | ||||
|     Scene previous_scene = Scene::Start; | ||||
|     bool scene_found = false; | ||||
| 
 | ||||
|     while(!scene_found) { | ||||
|         previous_scene = get_previous_scene(); | ||||
| 
 | ||||
|         if(previous_scene == Scene::Exit) break; | ||||
| 
 | ||||
|         for(Scene element : scenes_list) { | ||||
|             if(previous_scene == element) { | ||||
|                 scene_found = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(previous_scene == Scene::Exit) { | ||||
|         InfraredAppEvent event; | ||||
|         event.type = InfraredAppEvent::Type::Exit; | ||||
|         view_manager.send_event(&event); | ||||
|     } else { | ||||
|         scenes[current_scene]->on_exit(this); | ||||
|         current_scene = previous_scene; | ||||
|         scenes[current_scene]->on_enter(this); | ||||
|         view_manager.clear_events(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool InfraredApp::switch_to_previous_scene(uint8_t count) { | ||||
|     Scene previous_scene = Scene::Start; | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < count; i++) previous_scene = get_previous_scene(); | ||||
| 
 | ||||
|     if(previous_scene == Scene::Exit) return true; | ||||
| 
 | ||||
|     scenes[current_scene]->on_exit(this); | ||||
|     current_scene = previous_scene; | ||||
|     scenes[current_scene]->on_enter(this); | ||||
|     view_manager.clear_events(); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| InfraredApp::Scene InfraredApp::get_previous_scene() { | ||||
|     Scene scene = Scene::Exit; | ||||
| 
 | ||||
|     if(!previous_scenes_list.empty()) { | ||||
|         scene = previous_scenes_list.front(); | ||||
|         previous_scenes_list.pop_front(); | ||||
|     } | ||||
| 
 | ||||
|     return scene; | ||||
| } | ||||
| 
 | ||||
| InfraredAppRemoteManager* InfraredApp::get_remote_manager() { | ||||
|     return &remote_manager; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_text_store(uint8_t index, const char* text...) { | ||||
|     furi_check(index < text_store_max); | ||||
| 
 | ||||
|     va_list args; | ||||
|     va_start(args, text); | ||||
| 
 | ||||
|     vsnprintf(text_store[index], text_store_size, text, args); | ||||
| 
 | ||||
|     va_end(args); | ||||
| } | ||||
| 
 | ||||
| char* InfraredApp::get_text_store(uint8_t index) { | ||||
|     furi_check(index < text_store_max); | ||||
| 
 | ||||
|     return text_store[index]; | ||||
| } | ||||
| 
 | ||||
| uint8_t InfraredApp::get_text_store_size() { | ||||
|     return text_store_size; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::text_input_callback(void* context) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
|     event.type = InfraredAppEvent::Type::TextEditDone; | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::popup_callback(void* context) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
|     event.type = InfraredAppEvent::Type::PopupTimer; | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_edit_element(InfraredApp::EditElement value) { | ||||
|     element = value; | ||||
| } | ||||
| 
 | ||||
| InfraredApp::EditElement InfraredApp::get_edit_element(void) { | ||||
|     return element; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_edit_action(InfraredApp::EditAction value) { | ||||
|     action = value; | ||||
| } | ||||
| 
 | ||||
| InfraredApp::EditAction InfraredApp::get_edit_action(void) { | ||||
|     return action; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_current_button(int value) { | ||||
|     current_button = value; | ||||
| } | ||||
| 
 | ||||
| int InfraredApp::get_current_button() { | ||||
|     return current_button; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_success() { | ||||
|     notification_message(notification, &sequence_success); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_blink_read() { | ||||
|     notification_message(notification, &sequence_blink_cyan_10); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_blink_send() { | ||||
|     notification_message(notification, &sequence_blink_magenta_10); | ||||
| } | ||||
| 
 | ||||
| DialogsApp* InfraredApp::get_dialogs() { | ||||
|     return dialogs; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_green_on() { | ||||
|     notification_message(notification, &sequence_set_only_green_255); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_green_off() { | ||||
|     notification_message(notification, &sequence_reset_green); | ||||
| } | ||||
| 
 | ||||
| InfraredWorker* InfraredApp::get_infrared_worker() { | ||||
|     return infrared_worker; | ||||
| } | ||||
| 
 | ||||
| const InfraredAppSignal& InfraredApp::get_received_signal() const { | ||||
|     return received_signal; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::set_received_signal(const InfraredAppSignal& signal) { | ||||
|     received_signal = signal; | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::signal_sent_callback(void* context) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     app->notify_blink_send(); | ||||
| } | ||||
| @ -1,326 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app.h | ||||
|   * Infrared: Main infrared application class | ||||
|   */ | ||||
| #pragma once | ||||
| #include <map> | ||||
| #include <infrared.h> | ||||
| #include <furi.h> | ||||
| #include <forward_list> | ||||
| #include <stdint.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| #include <infrared_worker.h> | ||||
| 
 | ||||
| #include "scene/infrared_app_scene.h" | ||||
| #include "scene/infrared_app_scene.h" | ||||
| #include "infrared_app_view_manager.h" | ||||
| #include "infrared_app_remote_manager.h" | ||||
| #include "infrared_app_view_manager.h" | ||||
| 
 | ||||
| /** Main Infrared application class */ | ||||
| class InfraredApp { | ||||
| public: | ||||
|     /** Enum to save scene state: edit element */ | ||||
|     enum class EditElement : uint8_t { | ||||
|         Button, | ||||
|         Remote, | ||||
|     }; | ||||
|     /** Enum to save scene state: edit action */ | ||||
|     enum class EditAction : uint8_t { | ||||
|         Rename, | ||||
|         Delete, | ||||
|     }; | ||||
|     /** List of scenes for Infrared application */ | ||||
|     enum class Scene : uint8_t { | ||||
|         Exit, | ||||
|         Start, | ||||
|         Universal, | ||||
|         UniversalTV, | ||||
|         UniversalAudio, | ||||
|         UniversalAirConditioner, | ||||
|         Learn, | ||||
|         LearnSuccess, | ||||
|         LearnEnterName, | ||||
|         LearnDone, | ||||
|         AskBack, | ||||
|         Remote, | ||||
|         RemoteList, | ||||
|         Edit, | ||||
|         EditKeySelect, | ||||
|         EditRename, | ||||
|         EditDelete, | ||||
|         EditRenameDone, | ||||
|         EditDeleteDone, | ||||
|     }; | ||||
| 
 | ||||
|     /** Start application
 | ||||
|  * | ||||
|  * @param args - application arguments. | ||||
|  *      Allowed argument is path to remote file. | ||||
|  * @retval 0 on success, error code otherwise | ||||
|  */ | ||||
|     int32_t run(void* args); | ||||
| 
 | ||||
|     /** Switch to next scene. Put current scene number on stack.
 | ||||
|  * Doesn't save scene state. | ||||
|  * | ||||
|  * @param index - next scene index | ||||
|  */ | ||||
|     void switch_to_next_scene(Scene index); | ||||
| 
 | ||||
|     /** Switch to next scene, but don't put current scene on
 | ||||
|  * stack. Thus calling switch_to_previous_scene() doesn't return | ||||
|  * to current scene. | ||||
|  * | ||||
|  * @param index - next scene index | ||||
|  */ | ||||
|     void switch_to_next_scene_without_saving(Scene index); | ||||
| 
 | ||||
|     /** Switch to previous scene. Pop scenes from stack and switch to last one.
 | ||||
|  * | ||||
|  * @param count - how many scenes should be popped | ||||
|  * @retval false on failed, true on success | ||||
|  */ | ||||
|     bool switch_to_previous_scene(uint8_t count = 1); | ||||
| 
 | ||||
|     /** Get previous scene in scene stack
 | ||||
|  * | ||||
|  * @retval previous scene | ||||
|  */ | ||||
|     Scene get_previous_scene(); | ||||
| 
 | ||||
|     /** Get view manager instance
 | ||||
|  * | ||||
|  * @retval view manager instance | ||||
|  */ | ||||
|     InfraredAppViewManager* get_view_manager(); | ||||
| 
 | ||||
|     /** Set one of text stores
 | ||||
|  * | ||||
|  * @param index - index of text store | ||||
|  * @param text - text to set | ||||
|  */ | ||||
|     void set_text_store(uint8_t index, const char* text...); | ||||
| 
 | ||||
|     /** Get value in text store
 | ||||
|  * | ||||
|  * @param index - index of text store | ||||
|  * @retval value in text_store | ||||
|  */ | ||||
|     char* get_text_store(uint8_t index); | ||||
| 
 | ||||
|     /** Get text store size
 | ||||
|  * | ||||
|  * @retval size of text store | ||||
|  */ | ||||
|     uint8_t get_text_store_size(); | ||||
| 
 | ||||
|     /** Get remote manager instance
 | ||||
|  * | ||||
|  * @retval remote manager instance | ||||
|  */ | ||||
|     InfraredAppRemoteManager* get_remote_manager(); | ||||
| 
 | ||||
|     /** Get infrared worker instance
 | ||||
|  * | ||||
|  * @retval infrared worker instance | ||||
|  */ | ||||
|     InfraredWorker* get_infrared_worker(); | ||||
| 
 | ||||
|     /** Get signal, previously got on Learn scene
 | ||||
|  * | ||||
|  * @retval received signal | ||||
|  */ | ||||
|     const InfraredAppSignal& get_received_signal() const; | ||||
| 
 | ||||
|     /** Set received signal
 | ||||
|  * | ||||
|  * @param signal - signal | ||||
|  */ | ||||
|     void set_received_signal(const InfraredAppSignal& signal); | ||||
| 
 | ||||
|     /** Switch to previous scene in one of provided in list.
 | ||||
|  * Pop scene stack, and find first scene from list. | ||||
|  * | ||||
|  * @param scenes_list - list of scenes | ||||
|  */ | ||||
|     void search_and_switch_to_previous_scene(const std::initializer_list<Scene>& scenes_list); | ||||
| 
 | ||||
|     /** Set edit element value. It is used on edit scene to determine
 | ||||
|  * what should be deleted - remote or button. | ||||
|  * | ||||
|  * @param value - value to set | ||||
|  */ | ||||
|     void set_edit_element(EditElement value); | ||||
| 
 | ||||
|     /** Get edit element
 | ||||
|  * | ||||
|  * @retval edit element value | ||||
|  */ | ||||
|     EditElement get_edit_element(void); | ||||
| 
 | ||||
|     /** Set edit action value. It is used on edit scene to determine
 | ||||
|  * what action to perform - deletion or renaming. | ||||
|  * | ||||
|  * @param value - value to set | ||||
|  */ | ||||
|     void set_edit_action(EditAction value); | ||||
| 
 | ||||
|     /** Get edit action
 | ||||
|  * | ||||
|  * @retval edit action value | ||||
|  */ | ||||
|     EditAction get_edit_action(void); | ||||
| 
 | ||||
|     /** Get state of learning new signal.
 | ||||
|  * Adding new remote with 1 button from start scene and | ||||
|  * learning 1 additional button to remote have very similar | ||||
|  * flow, so they are joined. Difference in flow is handled | ||||
|  * by this boolean flag. | ||||
|  * | ||||
|  * @retval false if flow is in learning new remote, true if | ||||
|  *      adding signal to created remote | ||||
|  * | ||||
|  */ | ||||
|     bool get_learn_new_remote(); | ||||
| 
 | ||||
|     /** Set state of learning new signal.
 | ||||
|  * Adding new remote with 1 button from start scene and | ||||
|  * learning 1 additional button to remote have very similar | ||||
|  * flow, so they are joined. Difference in flow is handled | ||||
|  * by this boolean flag. | ||||
|  * | ||||
|  * @param value - false if flow is in learning new remote, true if | ||||
|  *      adding signal to created remote | ||||
|  */ | ||||
|     void set_learn_new_remote(bool value); | ||||
| 
 | ||||
|     /** Button is not assigned value
 | ||||
|  */ | ||||
|     enum : int { | ||||
|         ButtonNA = -1, | ||||
|     }; | ||||
| 
 | ||||
|     /** Get current button index
 | ||||
|  * | ||||
|  * @retval current button index | ||||
|  */ | ||||
|     int get_current_button(); | ||||
| 
 | ||||
|     /** Set current button index
 | ||||
|  * | ||||
|  * @param current button index | ||||
|  */ | ||||
|     void set_current_button(int value); | ||||
| 
 | ||||
|     /** Play success notification */ | ||||
|     void notify_success(); | ||||
|     /** Play red blink notification */ | ||||
|     void notify_blink_read(); | ||||
|     /** Light green */ | ||||
|     void notify_green_on(); | ||||
|     /** Disable green light */ | ||||
|     void notify_green_off(); | ||||
|     /** Blink on send */ | ||||
|     void notify_blink_send(); | ||||
| 
 | ||||
|     /** Get Dialogs instance */ | ||||
|     DialogsApp* get_dialogs(); | ||||
| 
 | ||||
|     /** Text input callback
 | ||||
|  * | ||||
|  * @param context - context to pass to callback | ||||
|  */ | ||||
|     static void text_input_callback(void* context); | ||||
| 
 | ||||
|     /** Popup callback
 | ||||
|  * | ||||
|  * @param context - context to pass to callback | ||||
|  */ | ||||
|     static void popup_callback(void* context); | ||||
| 
 | ||||
|     /** Signal sent callback
 | ||||
|  * | ||||
|  * @param context - context to pass to callback | ||||
|  */ | ||||
|     static void signal_sent_callback(void* context); | ||||
| 
 | ||||
|     /** Main class constructor, initializes all critical objects */ | ||||
|     InfraredApp(); | ||||
|     /** Main class destructor, deinitializes all critical objects */ | ||||
|     ~InfraredApp(); | ||||
| 
 | ||||
|     string_t file_path; | ||||
| 
 | ||||
|     /** Path to Infrared directory */ | ||||
|     static constexpr const char* infrared_directory = "/any/infrared"; | ||||
|     /** Infrared files extension (remote files and universal databases) */ | ||||
|     static constexpr const char* infrared_extension = ".ir"; | ||||
|     /** Max Raw timings in signal */ | ||||
|     static constexpr const uint32_t max_raw_timings_in_signal = 512; | ||||
|     /** Max line length in Infrared file */ | ||||
|     static constexpr const uint32_t max_line_length = | ||||
|         (9 + 1) * InfraredApp::max_raw_timings_in_signal + 100; | ||||
| 
 | ||||
| private: | ||||
|     /** Text store size */ | ||||
|     static constexpr const uint8_t text_store_size = 128; | ||||
|     /** Amount of text stores */ | ||||
|     static constexpr const uint8_t text_store_max = 2; | ||||
|     /** Store text here, for some views, because they doesn't
 | ||||
|  * hold ownership of text */ | ||||
|     char text_store[text_store_max][text_store_size + 1]; | ||||
|     /**
 | ||||
|  * Flag to control adding new signal flow. | ||||
|  * Adding new remote with 1 button from start scene and | ||||
|  * learning 1 additional button to remote have very similar | ||||
|  * flow, so they are joined. Difference in flow is handled | ||||
|  * by this boolean flag. | ||||
|  */ | ||||
|     bool learn_new_remote; | ||||
|     /** Value to control edit scene */ | ||||
|     EditElement element; | ||||
|     /** Value to control edit scene */ | ||||
|     EditAction action; | ||||
|     /** Selected button index */ | ||||
|     uint32_t current_button; | ||||
| 
 | ||||
|     /** Notification instance */ | ||||
|     NotificationApp* notification; | ||||
|     /** Dialogs instance */ | ||||
|     DialogsApp* dialogs; | ||||
|     /** View manager instance */ | ||||
|     InfraredAppViewManager view_manager; | ||||
|     /** Remote manager instance */ | ||||
|     InfraredAppRemoteManager remote_manager; | ||||
|     /** Infrared worker instance */ | ||||
|     InfraredWorker* infrared_worker; | ||||
|     /** Signal received on Learn scene */ | ||||
|     InfraredAppSignal received_signal; | ||||
| 
 | ||||
|     /** Stack of previous scenes */ | ||||
|     std::forward_list<Scene> previous_scenes_list; | ||||
|     /** Now acting scene */ | ||||
|     Scene current_scene = Scene::Start; | ||||
| 
 | ||||
|     /** Map of index/scene objects */ | ||||
|     std::map<Scene, InfraredAppScene*> scenes = { | ||||
|         {Scene::Start, new InfraredAppSceneStart()}, | ||||
|         {Scene::Universal, new InfraredAppSceneUniversal()}, | ||||
|         {Scene::UniversalTV, new InfraredAppSceneUniversalTV()}, | ||||
|         {Scene::Learn, new InfraredAppSceneLearn()}, | ||||
|         {Scene::LearnSuccess, new InfraredAppSceneLearnSuccess()}, | ||||
|         {Scene::LearnEnterName, new InfraredAppSceneLearnEnterName()}, | ||||
|         {Scene::LearnDone, new InfraredAppSceneLearnDone()}, | ||||
|         {Scene::AskBack, new InfraredAppSceneAskBack()}, | ||||
|         {Scene::Remote, new InfraredAppSceneRemote()}, | ||||
|         {Scene::RemoteList, new InfraredAppSceneRemoteList()}, | ||||
|         {Scene::Edit, new InfraredAppSceneEdit()}, | ||||
|         {Scene::EditKeySelect, new InfraredAppSceneEditKeySelect()}, | ||||
|         {Scene::EditRename, new InfraredAppSceneEditRename()}, | ||||
|         {Scene::EditDelete, new InfraredAppSceneEditDelete()}, | ||||
|         {Scene::EditRenameDone, new InfraredAppSceneEditRenameDone()}, | ||||
|         {Scene::EditDeleteDone, new InfraredAppSceneEditDeleteDone()}, | ||||
|     }; | ||||
| }; | ||||
| @ -1,93 +0,0 @@ | ||||
| 
 | ||||
| #include "helpers/infrared_parser.h" | ||||
| #include "infrared_app_brute_force.h" | ||||
| #include "infrared_app_signal.h" | ||||
| #include <memory> | ||||
| #include <m-string.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| void InfraredAppBruteForce::add_record(int index, const char* name) { | ||||
|     records[name].index = index; | ||||
|     records[name].amount = 0; | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppBruteForce::calculate_messages() { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
|     result = flipper_format_file_open_existing(ff, universal_db_filename); | ||||
| 
 | ||||
|     if(result) { | ||||
|         InfraredAppSignal signal; | ||||
| 
 | ||||
|         string_t signal_name; | ||||
|         string_init(signal_name); | ||||
|         while(flipper_format_read_string(ff, "name", signal_name)) { | ||||
|             auto element = records.find(string_get_cstr(signal_name)); | ||||
|             if(element != records.cend()) { | ||||
|                 ++element->second.amount; | ||||
|             } | ||||
|         } | ||||
|         string_clear(signal_name); | ||||
|     } | ||||
| 
 | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppBruteForce::stop_bruteforce() { | ||||
|     furi_assert((current_record.size())); | ||||
| 
 | ||||
|     if(current_record.size()) { | ||||
|         furi_assert(ff); | ||||
|         current_record.clear(); | ||||
|         flipper_format_free(ff); | ||||
|         furi_record_close("storage"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppBruteForce::send_next_bruteforce(void) { | ||||
|     furi_assert(current_record.size()); | ||||
|     furi_assert(ff); | ||||
| 
 | ||||
|     InfraredAppSignal signal; | ||||
|     std::string signal_name; | ||||
|     bool result = false; | ||||
|     do { | ||||
|         result = infrared_parser_read_signal(ff, signal, signal_name); | ||||
|     } while(result && current_record.compare(signal_name)); | ||||
| 
 | ||||
|     if(result) { | ||||
|         signal.transmit(); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppBruteForce::start_bruteforce(int index, int& record_amount) { | ||||
|     bool result = false; | ||||
|     record_amount = 0; | ||||
| 
 | ||||
|     for(const auto& it : records) { | ||||
|         if(it.second.index == index) { | ||||
|             record_amount = it.second.amount; | ||||
|             if(record_amount) { | ||||
|                 current_record = it.first; | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(record_amount) { | ||||
|         Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
|         ff = flipper_format_file_alloc(storage); | ||||
|         result = flipper_format_file_open_existing(ff, universal_db_filename); | ||||
|         if(!result) { | ||||
|             flipper_format_free(ff); | ||||
|             furi_record_close("storage"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| @ -1,67 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app_brute_force.h | ||||
|   * Infrared: Brute Force class description | ||||
|   */ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <memory> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| /** Class handles brute force mechanic */ | ||||
| class InfraredAppBruteForce { | ||||
|     /** Universal database filename */ | ||||
|     const char* universal_db_filename; | ||||
| 
 | ||||
|     /** Current record name (POWER, MUTE, VOL+, etc).
 | ||||
|      * This is the name of signal to brute force. */ | ||||
|     std::string current_record; | ||||
| 
 | ||||
|     /** Flipper File Format instance */ | ||||
|     FlipperFormat* ff; | ||||
| 
 | ||||
|     /** Data about every record - index in button panel view
 | ||||
|      * and amount of signals, which is need for correct | ||||
|      * progress bar displaying. */ | ||||
|     typedef struct { | ||||
|         /** Index of record in button panel view model */ | ||||
|         int index; | ||||
|         /** Amount of signals of that type (POWER, MUTE, etc) */ | ||||
|         int amount; | ||||
|     } Record; | ||||
| 
 | ||||
|     /** Container to hold Record info.
 | ||||
|      * 'key' is record name, because we have to search by both, index and name, | ||||
|      * but index search has place once per button press, and should not be | ||||
|      * noticed, but name search should occur during entering universal menu, | ||||
|      * and will go through container for every record in file, that's why | ||||
|      * more critical to have faster search by record name. | ||||
|      */ | ||||
|     std::unordered_map<std::string, Record> records; | ||||
| 
 | ||||
| public: | ||||
|     /** Calculate messages. Walk through the file ('universal_db_name')
 | ||||
|      * and calculate amount of records of certain type. */ | ||||
|     bool calculate_messages(); | ||||
| 
 | ||||
|     /** Start brute force */ | ||||
|     bool start_bruteforce(int index, int& record_amount); | ||||
| 
 | ||||
|     /** Stop brute force */ | ||||
|     void stop_bruteforce(); | ||||
| 
 | ||||
|     /** Send next signal during brute force */ | ||||
|     bool send_next_bruteforce(); | ||||
| 
 | ||||
|     /** Add record to container of records */ | ||||
|     void add_record(int index, const char* name); | ||||
| 
 | ||||
|     /** Initialize class, set db file */ | ||||
|     InfraredAppBruteForce(const char* filename) | ||||
|         : universal_db_filename(filename) { | ||||
|     } | ||||
| 
 | ||||
|     /** Deinitialize class */ | ||||
|     ~InfraredAppBruteForce() { | ||||
|     } | ||||
| }; | ||||
| @ -1,48 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app_event.h | ||||
|   * Infrared: Scene events description | ||||
|   */ | ||||
| #pragma once | ||||
| #include <infrared.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| 
 | ||||
| /** Infrared events class */ | ||||
| class InfraredAppEvent { | ||||
| public: | ||||
|     /** Type of event enum */ | ||||
|     enum class Type : uint8_t { | ||||
|         /** Tick event come after no other events came in 100 ms */ | ||||
|         Tick, | ||||
|         /** Exit application event */ | ||||
|         Exit, | ||||
|         /** Back event */ | ||||
|         Back, | ||||
|         /** Menu selected event type. Provided with payload value. */ | ||||
|         MenuSelected, | ||||
|         /** Button press event. Need for continuous signal sending. */ | ||||
|         MenuSelectedPress, | ||||
|         /** Button release event. Need for continuous signal sending. */ | ||||
|         MenuSelectedRelease, | ||||
|         /** Events from DialogEx view module */ | ||||
|         DialogExSelected, | ||||
|         /** Infrared signal received event */ | ||||
|         InfraredMessageReceived, | ||||
|         /** Text edit done event */ | ||||
|         TextEditDone, | ||||
|         /** Popup timer finished event */ | ||||
|         PopupTimer, | ||||
|         /** Button panel pressed event */ | ||||
|         ButtonPanelPressed, | ||||
|     }; | ||||
| 
 | ||||
|     union { | ||||
|         int32_t dummy; | ||||
|         /** Menu selected event type payload. Selected index. */ | ||||
|         int32_t menu_index; | ||||
|         /** DialogEx view module event type payload */ | ||||
|         DialogExResult dialog_ex_result; | ||||
|     } payload; | ||||
| 
 | ||||
|     /** Type of event */ | ||||
|     Type type; | ||||
| }; | ||||
| @ -1,266 +0,0 @@ | ||||
| #include "m-string.h" | ||||
| #include "storage/filesystem_api_defines.h" | ||||
| #include <flipper_format/flipper_format.h> | ||||
| #include "infrared_app_remote_manager.h" | ||||
| #include "infrared/helpers/infrared_parser.h" | ||||
| #include "infrared/infrared_app_signal.h" | ||||
| 
 | ||||
| #include <utility> | ||||
| 
 | ||||
| #include <infrared.h> | ||||
| #include <cstdio> | ||||
| #include <furi.h> | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <storage/storage.h> | ||||
| #include "infrared_app.h" | ||||
| #include <toolbox/path.h> | ||||
| 
 | ||||
| static const char* default_remote_name = "remote"; | ||||
| 
 | ||||
| void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
| 
 | ||||
|     string_t base_path; | ||||
|     string_init_set(base_path, path); | ||||
| 
 | ||||
|     if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { | ||||
|         size_t filename_start = string_search_rchar(base_path, '/'); | ||||
|         string_left(base_path, filename_start); | ||||
|     } | ||||
| 
 | ||||
|     string_printf( | ||||
|         base_path, | ||||
|         "%s/%s%s", | ||||
|         string_get_cstr(path), | ||||
|         string_get_cstr(name), | ||||
|         InfraredApp::infrared_extension); | ||||
| 
 | ||||
|     FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); | ||||
| 
 | ||||
|     if(error == FSE_OK) { | ||||
|         /* if suggested name is occupied, try another one (name2, name3, etc) */ | ||||
|         size_t dot = string_search_rchar(base_path, '.'); | ||||
|         string_left(base_path, dot); | ||||
| 
 | ||||
|         string_t path_temp; | ||||
|         string_init(path_temp); | ||||
| 
 | ||||
|         uint32_t i = 1; | ||||
|         do { | ||||
|             string_printf( | ||||
|                 path_temp, | ||||
|                 "%s%u%s", | ||||
|                 string_get_cstr(base_path), | ||||
|                 ++i, | ||||
|                 InfraredApp::infrared_extension); | ||||
|             error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); | ||||
|         } while(error == FSE_OK); | ||||
| 
 | ||||
|         string_clear(path_temp); | ||||
| 
 | ||||
|         if(error == FSE_NOT_EXIST) { | ||||
|             string_cat_printf(name, "%u", i); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(base_path); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { | ||||
|     remote->buttons.emplace_back(button_name, signal); | ||||
|     return store(); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::add_remote_with_button( | ||||
|     const char* button_name, | ||||
|     const InfraredAppSignal& signal) { | ||||
|     furi_check(button_name != nullptr); | ||||
| 
 | ||||
|     string_t new_name; | ||||
|     string_init_set_str(new_name, default_remote_name); | ||||
| 
 | ||||
|     string_t new_path; | ||||
|     string_init_set_str(new_path, InfraredApp::infrared_directory); | ||||
| 
 | ||||
|     find_vacant_remote_name(new_name, new_path); | ||||
| 
 | ||||
|     string_cat_printf( | ||||
|         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||
| 
 | ||||
|     remote = std::make_unique<InfraredAppRemote>(new_path); | ||||
|     remote->name = std::string(string_get_cstr(new_name)); | ||||
| 
 | ||||
|     string_clear(new_path); | ||||
|     string_clear(new_name); | ||||
| 
 | ||||
|     return add_button(button_name, signal); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> InfraredAppRemoteManager::get_button_list(void) const { | ||||
|     std::vector<std::string> name_vector; | ||||
|     name_vector.reserve(remote->buttons.size()); | ||||
| 
 | ||||
|     for(const auto& it : remote->buttons) { | ||||
|         name_vector.emplace_back(it.name); | ||||
|     } | ||||
| 
 | ||||
|     // copy elision
 | ||||
|     return name_vector; | ||||
| } | ||||
| 
 | ||||
| const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) const { | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     auto& buttons = remote->buttons; | ||||
|     furi_check(index < buttons.size()); | ||||
| 
 | ||||
|     return buttons.at(index).signal; | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::delete_remote() { | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
| 
 | ||||
|     FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); | ||||
|     reset_remote(); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
|     return (error == FSE_OK || error == FSE_NOT_EXIST); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppRemoteManager::reset_remote() { | ||||
|     remote.reset(); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::delete_button(uint32_t index) { | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     auto& buttons = remote->buttons; | ||||
|     furi_check(index < buttons.size()); | ||||
| 
 | ||||
|     buttons.erase(buttons.begin() + index); | ||||
|     return store(); | ||||
| } | ||||
| 
 | ||||
| std::string InfraredAppRemoteManager::get_button_name(uint32_t index) { | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     auto& buttons = remote->buttons; | ||||
|     furi_check(index < buttons.size()); | ||||
|     return buttons[index].name.c_str(); | ||||
| } | ||||
| 
 | ||||
| std::string InfraredAppRemoteManager::get_remote_name() { | ||||
|     return remote.get() ? remote->name : std::string(); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::rename_remote(const char* str) { | ||||
|     furi_check(str != nullptr); | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     furi_check(!string_empty_p(remote->path)); | ||||
| 
 | ||||
|     if(!remote->name.compare(str)) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     string_t new_name; | ||||
|     string_init_set_str(new_name, str); | ||||
|     find_vacant_remote_name(new_name, remote->path); | ||||
| 
 | ||||
|     string_t new_path; | ||||
|     string_init_set(new_path, remote->path); | ||||
|     if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { | ||||
|         size_t filename_start = string_search_rchar(new_path, '/'); | ||||
|         string_left(new_path, filename_start); | ||||
|     } | ||||
|     string_cat_printf( | ||||
|         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||
| 
 | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
| 
 | ||||
|     FS_Error error = | ||||
|         storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); | ||||
|     remote->name = std::string(string_get_cstr(new_name)); | ||||
| 
 | ||||
|     string_clear(new_name); | ||||
|     string_clear(new_path); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
|     return (error == FSE_OK || error == FSE_EXIST); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::rename_button(uint32_t index, const char* str) { | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     auto& buttons = remote->buttons; | ||||
|     furi_check(index < buttons.size()); | ||||
| 
 | ||||
|     buttons[index].name = str; | ||||
|     return store(); | ||||
| } | ||||
| 
 | ||||
| size_t InfraredAppRemoteManager::get_number_of_buttons() { | ||||
|     furi_check(remote.get() != nullptr); | ||||
|     return remote->buttons.size(); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::store(void) { | ||||
|     bool result = false; | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
| 
 | ||||
|     if(!storage_simply_mkdir(storage, InfraredApp::infrared_directory)) return false; | ||||
| 
 | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
| 
 | ||||
|     FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); | ||||
|     result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); | ||||
|     if(result) { | ||||
|         result = flipper_format_write_header_cstr(ff, "IR signals file", 1); | ||||
|     } | ||||
|     if(result) { | ||||
|         for(const auto& button : remote->buttons) { | ||||
|             result = infrared_parser_save_signal(ff, button.signal, button.name.c_str()); | ||||
|             if(!result) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppRemoteManager::load(string_t path) { | ||||
|     bool result = false; | ||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
| 
 | ||||
|     FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); | ||||
|     result = flipper_format_file_open_existing(ff, string_get_cstr(path)); | ||||
|     if(result) { | ||||
|         string_t header; | ||||
|         string_init(header); | ||||
|         uint32_t version; | ||||
|         result = flipper_format_read_header(ff, header, &version); | ||||
|         if(result) { | ||||
|             result = !string_cmp_str(header, "IR signals file") && (version == 1); | ||||
|         } | ||||
|         string_clear(header); | ||||
|     } | ||||
|     if(result) { | ||||
|         string_t new_name; | ||||
|         string_init(new_name); | ||||
| 
 | ||||
|         remote = std::make_unique<InfraredAppRemote>(path); | ||||
|         path_extract_filename(path, new_name, true); | ||||
|         remote->name = std::string(string_get_cstr(new_name)); | ||||
| 
 | ||||
|         string_clear(new_name); | ||||
|         InfraredAppSignal signal; | ||||
|         std::string signal_name; | ||||
|         while(infrared_parser_read_signal(ff, signal, signal_name)) { | ||||
|             remote->buttons.emplace_back(signal_name.c_str(), std::move(signal)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return result; | ||||
| } | ||||
| @ -1,189 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app_remote_manager.h | ||||
|   * Infrared: Remote manager class. | ||||
|   * It holds remote, can load/save/rename remote, | ||||
|   * add/remove/rename buttons. | ||||
|   */ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "infrared_app_signal.h" | ||||
| 
 | ||||
| #include "m-string.h" | ||||
| #include <infrared_worker.h> | ||||
| #include <infrared.h> | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| /** Class to handle remote button */ | ||||
| class InfraredAppRemoteButton { | ||||
|     /** Allow field access */ | ||||
|     friend class InfraredAppRemoteManager; | ||||
|     /** Name of signal */ | ||||
|     std::string name; | ||||
|     /** Signal data */ | ||||
|     InfraredAppSignal signal; | ||||
| 
 | ||||
| public: | ||||
|     /** Initialize remote button
 | ||||
|      * | ||||
|      * @param name - button name | ||||
|      * @param signal - signal to copy for remote button | ||||
|      */ | ||||
|     InfraredAppRemoteButton(const char* name, const InfraredAppSignal& signal) | ||||
|         : name(name) | ||||
|         , signal(signal) { | ||||
|     } | ||||
| 
 | ||||
|     /** Initialize remote button
 | ||||
|      * | ||||
|      * @param name - button name | ||||
|      * @param signal - signal to move for remote button | ||||
|      */ | ||||
|     InfraredAppRemoteButton(const char* name, InfraredAppSignal&& signal) | ||||
|         : name(name) | ||||
|         , signal(std::move(signal)) { | ||||
|     } | ||||
| 
 | ||||
|     /** Deinitialize remote button */ | ||||
|     ~InfraredAppRemoteButton() { | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /** Class to handle remote */ | ||||
| class InfraredAppRemote { | ||||
|     /** Allow field access */ | ||||
|     friend class InfraredAppRemoteManager; | ||||
|     /** Button container */ | ||||
|     std::vector<InfraredAppRemoteButton> buttons; | ||||
|     /** Name of remote */ | ||||
|     std::string name; | ||||
|     /** Path to remote file */ | ||||
|     string_t path; | ||||
| 
 | ||||
| public: | ||||
|     /** Initialize new remote
 | ||||
|      *  | ||||
|      * @param path - remote file path | ||||
|      */ | ||||
|     InfraredAppRemote(string_t file_path) { | ||||
|         string_init_set(path, file_path); | ||||
|     } | ||||
| 
 | ||||
|     ~InfraredAppRemote() { | ||||
|         string_clear(path); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /** Class to handle remote manager */ | ||||
| class InfraredAppRemoteManager { | ||||
|     /** Remote instance. There can be 1 remote loaded at a time. */ | ||||
|     std::unique_ptr<InfraredAppRemote> remote; | ||||
| 
 | ||||
| public: | ||||
|     /** Restriction to button name length. Buttons larger are ignored. */ | ||||
|     static constexpr const uint32_t max_button_name_length = 22; | ||||
| 
 | ||||
|     /** Restriction to remote name length. Remotes larger are ignored. */ | ||||
|     static constexpr const uint32_t max_remote_name_length = 22; | ||||
| 
 | ||||
|     /** Construct button from signal, and create remote
 | ||||
|      * | ||||
|      * @param button_name - name of button to create | ||||
|      * @param signal - signal to create button from | ||||
|      * @retval true for success, false otherwise | ||||
|      * */ | ||||
|     bool add_remote_with_button(const char* button_name, const InfraredAppSignal& signal); | ||||
| 
 | ||||
|     /** Add button to current remote
 | ||||
|      * | ||||
|      * @param button_name - name of button to create | ||||
|      * @param signal - signal to create button from | ||||
|      * @retval true for success, false otherwise | ||||
|      * */ | ||||
|     bool add_button(const char* button_name, const InfraredAppSignal& signal); | ||||
| 
 | ||||
|     /** Rename button in current remote
 | ||||
|      * | ||||
|      * @param index - index of button to rename | ||||
|      * @param str - new button name | ||||
|      */ | ||||
|     bool rename_button(uint32_t index, const char* str); | ||||
| 
 | ||||
|     /** Rename current remote
 | ||||
|      * | ||||
|      * @param str - new remote name | ||||
|      */ | ||||
|     bool rename_remote(const char* str); | ||||
| 
 | ||||
|     /** Find vacant remote name. If suggested name is occupied,
 | ||||
|      * incremented digit(2,3,4,etc) added to name and check repeated. | ||||
|      * | ||||
|      * @param name - suggested remote name | ||||
|      * @param path - remote file path | ||||
|      */ | ||||
|     void find_vacant_remote_name(string_t name, string_t path); | ||||
| 
 | ||||
|     /** Get button list
 | ||||
|      * | ||||
|      * @retval container of button names | ||||
|      */ | ||||
|     std::vector<std::string> get_button_list() const; | ||||
| 
 | ||||
|     /** Get button name by index
 | ||||
|      * | ||||
|      * @param index - index of button to get name from | ||||
|      * @retval button name | ||||
|      */ | ||||
|     std::string get_button_name(uint32_t index); | ||||
| 
 | ||||
|     /** Get remote name
 | ||||
|      * | ||||
|      * @retval remote name | ||||
|      */ | ||||
|     std::string get_remote_name(); | ||||
| 
 | ||||
|     /** Get number of buttons
 | ||||
|      * | ||||
|      * @retval number of buttons | ||||
|      */ | ||||
|     size_t get_number_of_buttons(); | ||||
| 
 | ||||
|     /** Get button's signal
 | ||||
|      * | ||||
|      * @param index - index of interested button | ||||
|      * @retval signal | ||||
|      */ | ||||
|     const InfraredAppSignal& get_button_data(size_t index) const; | ||||
| 
 | ||||
|     /** Delete button
 | ||||
|      * | ||||
|      * @param index - index of interested button | ||||
|      * @retval true if success, false otherwise | ||||
|      */ | ||||
|     bool delete_button(uint32_t index); | ||||
| 
 | ||||
|     /** Delete remote
 | ||||
|      * | ||||
|      * @retval true if success, false otherwise | ||||
|      */ | ||||
|     bool delete_remote(); | ||||
| 
 | ||||
|     /** Clean all loaded info in current remote */ | ||||
|     void reset_remote(); | ||||
| 
 | ||||
|     /** Store current remote data on disk
 | ||||
|      * | ||||
|      * @retval true if success, false otherwise | ||||
|      */ | ||||
|     bool store(); | ||||
| 
 | ||||
|     /** Load data from disk into current remote
 | ||||
|      * | ||||
|      * @param path - path to remote file | ||||
|      * @retval true if success, false otherwise | ||||
|      */ | ||||
|     bool load(string_t path); | ||||
| }; | ||||
| @ -1,116 +0,0 @@ | ||||
| #include "infrared_app_signal.h" | ||||
| #include <infrared_transmit.h> | ||||
| 
 | ||||
| void InfraredAppSignal::copy_raw_signal( | ||||
|     const uint32_t* timings, | ||||
|     size_t size, | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle) { | ||||
|     furi_assert(size); | ||||
|     furi_assert(timings); | ||||
| 
 | ||||
|     payload.raw.frequency = frequency; | ||||
|     payload.raw.duty_cycle = duty_cycle; | ||||
|     payload.raw.timings_cnt = size; | ||||
|     if(size) { | ||||
|         payload.raw.timings = new uint32_t[size]; | ||||
|         memcpy(payload.raw.timings, timings, size * sizeof(uint32_t)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSignal::clear_timings() { | ||||
|     if(raw_signal) { | ||||
|         delete[] payload.raw.timings; | ||||
|         payload.raw.timings_cnt = 0; | ||||
|         payload.raw.timings = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| InfraredAppSignal::InfraredAppSignal( | ||||
|     const uint32_t* timings, | ||||
|     size_t timings_cnt, | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle) { | ||||
|     raw_signal = true; | ||||
|     copy_raw_signal(timings, timings_cnt, frequency, duty_cycle); | ||||
| } | ||||
| 
 | ||||
| InfraredAppSignal::InfraredAppSignal(const InfraredMessage* infrared_message) { | ||||
|     raw_signal = false; | ||||
|     payload.message = *infrared_message; | ||||
| } | ||||
| 
 | ||||
| InfraredAppSignal& InfraredAppSignal::operator=(const InfraredAppSignal& other) { | ||||
|     clear_timings(); | ||||
|     raw_signal = other.raw_signal; | ||||
|     if(!raw_signal) { | ||||
|         payload.message = other.payload.message; | ||||
|     } else { | ||||
|         copy_raw_signal( | ||||
|             other.payload.raw.timings, | ||||
|             other.payload.raw.timings_cnt, | ||||
|             other.payload.raw.frequency, | ||||
|             other.payload.raw.duty_cycle); | ||||
|     } | ||||
| 
 | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| InfraredAppSignal::InfraredAppSignal(const InfraredAppSignal& other) { | ||||
|     raw_signal = other.raw_signal; | ||||
|     if(!raw_signal) { | ||||
|         payload.message = other.payload.message; | ||||
|     } else { | ||||
|         copy_raw_signal( | ||||
|             other.payload.raw.timings, | ||||
|             other.payload.raw.timings_cnt, | ||||
|             other.payload.raw.frequency, | ||||
|             other.payload.raw.duty_cycle); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| InfraredAppSignal::InfraredAppSignal(InfraredAppSignal&& other) { | ||||
|     raw_signal = other.raw_signal; | ||||
|     if(!raw_signal) { | ||||
|         payload.message = other.payload.message; | ||||
|     } else { | ||||
|         furi_assert(other.payload.raw.timings_cnt > 0); | ||||
| 
 | ||||
|         payload.raw.timings = other.payload.raw.timings; | ||||
|         payload.raw.timings_cnt = other.payload.raw.timings_cnt; | ||||
|         payload.raw.frequency = other.payload.raw.frequency; | ||||
|         payload.raw.duty_cycle = other.payload.raw.duty_cycle; | ||||
|         other.payload.raw.timings = nullptr; | ||||
|         other.payload.raw.timings_cnt = 0; | ||||
|         other.raw_signal = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSignal::set_message(const InfraredMessage* infrared_message) { | ||||
|     clear_timings(); | ||||
|     raw_signal = false; | ||||
|     payload.message = *infrared_message; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSignal::set_raw_signal( | ||||
|     uint32_t* timings, | ||||
|     size_t timings_cnt, | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle) { | ||||
|     clear_timings(); | ||||
|     raw_signal = true; | ||||
|     copy_raw_signal(timings, timings_cnt, frequency, duty_cycle); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSignal::transmit() const { | ||||
|     if(!raw_signal) { | ||||
|         infrared_send(&payload.message, 1); | ||||
|     } else { | ||||
|         infrared_send_raw_ext( | ||||
|             payload.raw.timings, | ||||
|             payload.raw.timings_cnt, | ||||
|             true, | ||||
|             payload.raw.frequency, | ||||
|             payload.raw.duty_cycle); | ||||
|     } | ||||
| } | ||||
| @ -1,134 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app_signal.h | ||||
|   * Infrared: Signal class | ||||
|   */ | ||||
| #pragma once | ||||
| #include <infrared_worker.h> | ||||
| #include <stdint.h> | ||||
| #include <string> | ||||
| #include <infrared.h> | ||||
| 
 | ||||
| /** Infrared application signal class */ | ||||
| class InfraredAppSignal { | ||||
| public: | ||||
|     /** Raw signal structure */ | ||||
|     typedef struct { | ||||
|         /** Timings amount */ | ||||
|         size_t timings_cnt; | ||||
|         /** Samples of raw signal in ms */ | ||||
|         uint32_t* timings; | ||||
|         /** PWM Frequency of raw signal */ | ||||
|         uint32_t frequency; | ||||
|         /** PWM Duty cycle of raw signal */ | ||||
|         float duty_cycle; | ||||
|     } RawSignal; | ||||
| 
 | ||||
| private: | ||||
|     /** if true - signal is raw, if false - signal is parsed */ | ||||
|     bool raw_signal; | ||||
|     /** signal data, either raw or parsed */ | ||||
|     union { | ||||
|         /** signal data for parsed signal */ | ||||
|         InfraredMessage message; | ||||
|         /** raw signal data */ | ||||
|         RawSignal raw; | ||||
|     } payload; | ||||
| 
 | ||||
|     /** Copy raw signal into object
 | ||||
|      * | ||||
|      * @param timings - timings (samples) of raw signal | ||||
|      * @param size - number of timings | ||||
|      * @frequency - PWM frequency of raw signal | ||||
|      * @duty_cycle - PWM duty cycle | ||||
|      */ | ||||
|     void | ||||
|         copy_raw_signal(const uint32_t* timings, size_t size, uint32_t frequency, float duty_cycle); | ||||
|     /** Clear and free timings data */ | ||||
|     void clear_timings(); | ||||
| 
 | ||||
| public: | ||||
|     /** Construct Infrared signal class */ | ||||
|     InfraredAppSignal() { | ||||
|         raw_signal = false; | ||||
|         payload.message.protocol = InfraredProtocolUnknown; | ||||
|     } | ||||
| 
 | ||||
|     /** Destruct signal class and free all allocated data */ | ||||
|     ~InfraredAppSignal() { | ||||
|         clear_timings(); | ||||
|     } | ||||
| 
 | ||||
|     /** Construct object with raw signal
 | ||||
|      * | ||||
|      * @param timings - timings (samples) of raw signal | ||||
|      * @param size - number of timings | ||||
|      * @frequency - PWM frequency of raw signal | ||||
|      * @duty_cycle - PWM duty cycle | ||||
|      */ | ||||
|     InfraredAppSignal( | ||||
|         const uint32_t* timings, | ||||
|         size_t timings_cnt, | ||||
|         uint32_t frequency, | ||||
|         float duty_cycle); | ||||
| 
 | ||||
|     /** Construct object with parsed signal
 | ||||
|      * | ||||
|      * @param infrared_message - parsed_signal to construct from | ||||
|      */ | ||||
|     InfraredAppSignal(const InfraredMessage* infrared_message); | ||||
| 
 | ||||
|     /** Copy constructor */ | ||||
|     InfraredAppSignal(const InfraredAppSignal& other); | ||||
|     /** Move constructor */ | ||||
|     InfraredAppSignal(InfraredAppSignal&& other); | ||||
| 
 | ||||
|     /** Assignment operator */ | ||||
|     InfraredAppSignal& operator=(const InfraredAppSignal& signal); | ||||
| 
 | ||||
|     /** Set object to parsed signal
 | ||||
|      * | ||||
|      * @param infrared_message - parsed_signal to construct from | ||||
|      */ | ||||
|     void set_message(const InfraredMessage* infrared_message); | ||||
| 
 | ||||
|     /** Set object to raw signal
 | ||||
|      * | ||||
|      * @param timings - timings (samples) of raw signal | ||||
|      * @param size - number of timings | ||||
|      * @frequency - PWM frequency of raw signal | ||||
|      * @duty_cycle - PWM duty cycle | ||||
|      */ | ||||
|     void | ||||
|         set_raw_signal(uint32_t* timings, size_t timings_cnt, uint32_t frequency, float duty_cycle); | ||||
| 
 | ||||
|     /** Transmit held signal (???) */ | ||||
|     void transmit() const; | ||||
| 
 | ||||
|     /** Show is held signal raw
 | ||||
|      * | ||||
|      * @retval true if signal is raw, false if signal is parsed | ||||
|      */ | ||||
|     bool is_raw(void) const { | ||||
|         return raw_signal; | ||||
|     } | ||||
| 
 | ||||
|     /** Get parsed signal.
 | ||||
|      * User must check is_raw() signal before calling this function. | ||||
|      * | ||||
|      * @retval parsed signal pointer | ||||
|      */ | ||||
|     const InfraredMessage& get_message(void) const { | ||||
|         furi_assert(!raw_signal); | ||||
|         return payload.message; | ||||
|     } | ||||
| 
 | ||||
|     /** Get raw signal.
 | ||||
|      * User must check is_raw() signal before calling this function. | ||||
|      * | ||||
|      * @retval raw signal | ||||
|      */ | ||||
|     const RawSignal& get_raw_signal(void) const { | ||||
|         furi_assert(raw_signal); | ||||
|         return payload.raw; | ||||
|     } | ||||
| }; | ||||
| @ -1,163 +0,0 @@ | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <gui/view_stack.h> | ||||
| #include <gui/modules/loading.h> | ||||
| #include <gui/modules/button_panel.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <furi.h> | ||||
| #include <callback-connector.h> | ||||
| 
 | ||||
| #include "infrared/infrared_app_view_manager.h" | ||||
| #include "infrared/view/infrared_progress_view.h" | ||||
| #include "infrared_app.h" | ||||
| #include "infrared/infrared_app_event.h" | ||||
| 
 | ||||
| InfraredAppViewManager::InfraredAppViewManager() { | ||||
|     event_queue = osMessageQueueNew(10, sizeof(InfraredAppEvent), NULL); | ||||
| 
 | ||||
|     view_dispatcher = view_dispatcher_alloc(); | ||||
|     auto callback = cbc::obtain_connector(this, &InfraredAppViewManager::previous_view_callback); | ||||
| 
 | ||||
|     gui = static_cast<Gui*>(furi_record_open("gui")); | ||||
|     view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); | ||||
| 
 | ||||
|     button_menu = button_menu_alloc(); | ||||
|     submenu = submenu_alloc(); | ||||
|     popup = popup_alloc(); | ||||
|     dialog_ex = dialog_ex_alloc(); | ||||
|     text_input = text_input_alloc(); | ||||
|     button_panel = button_panel_alloc(); | ||||
|     progress_view = infrared_progress_view_alloc(); | ||||
|     loading_view = loading_alloc(); | ||||
|     universal_view_stack = view_stack_alloc(); | ||||
|     view_stack_add_view(universal_view_stack, button_panel_get_view(button_panel)); | ||||
|     view_set_orientation(view_stack_get_view(universal_view_stack), ViewOrientationVertical); | ||||
| 
 | ||||
|     add_view(ViewId::UniversalRemote, view_stack_get_view(universal_view_stack)); | ||||
|     add_view(ViewId::ButtonMenu, button_menu_get_view(button_menu)); | ||||
|     add_view(ViewId::Submenu, submenu_get_view(submenu)); | ||||
|     add_view(ViewId::Popup, popup_get_view(popup)); | ||||
|     add_view(ViewId::DialogEx, dialog_ex_get_view(dialog_ex)); | ||||
|     add_view(ViewId::TextInput, text_input_get_view(text_input)); | ||||
| 
 | ||||
|     view_set_previous_callback(view_stack_get_view(universal_view_stack), callback); | ||||
|     view_set_previous_callback(button_menu_get_view(button_menu), callback); | ||||
|     view_set_previous_callback(submenu_get_view(submenu), callback); | ||||
|     view_set_previous_callback(popup_get_view(popup), callback); | ||||
|     view_set_previous_callback(dialog_ex_get_view(dialog_ex), callback); | ||||
|     view_set_previous_callback(text_input_get_view(text_input), callback); | ||||
| } | ||||
| 
 | ||||
| InfraredAppViewManager::~InfraredAppViewManager() { | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::UniversalRemote)); | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::ButtonMenu)); | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::TextInput)); | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::DialogEx)); | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Submenu)); | ||||
|     view_dispatcher_remove_view( | ||||
|         view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Popup)); | ||||
| 
 | ||||
|     view_stack_remove_view(universal_view_stack, button_panel_get_view(button_panel)); | ||||
|     view_stack_free(universal_view_stack); | ||||
|     button_panel_free(button_panel); | ||||
|     submenu_free(submenu); | ||||
|     popup_free(popup); | ||||
|     button_menu_free(button_menu); | ||||
|     dialog_ex_free(dialog_ex); | ||||
|     text_input_free(text_input); | ||||
|     infrared_progress_view_free(progress_view); | ||||
|     loading_free(loading_view); | ||||
| 
 | ||||
|     view_dispatcher_free(view_dispatcher); | ||||
|     furi_record_close("gui"); | ||||
|     osMessageQueueDelete(event_queue); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppViewManager::switch_to(ViewId type) { | ||||
|     view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type)); | ||||
| } | ||||
| 
 | ||||
| TextInput* InfraredAppViewManager::get_text_input() { | ||||
|     return text_input; | ||||
| } | ||||
| 
 | ||||
| DialogEx* InfraredAppViewManager::get_dialog_ex() { | ||||
|     return dialog_ex; | ||||
| } | ||||
| 
 | ||||
| Submenu* InfraredAppViewManager::get_submenu() { | ||||
|     return submenu; | ||||
| } | ||||
| 
 | ||||
| Popup* InfraredAppViewManager::get_popup() { | ||||
|     return popup; | ||||
| } | ||||
| 
 | ||||
| ButtonMenu* InfraredAppViewManager::get_button_menu() { | ||||
|     return button_menu; | ||||
| } | ||||
| 
 | ||||
| ButtonPanel* InfraredAppViewManager::get_button_panel() { | ||||
|     return button_panel; | ||||
| } | ||||
| 
 | ||||
| InfraredProgressView* InfraredAppViewManager::get_progress() { | ||||
|     return progress_view; | ||||
| } | ||||
| 
 | ||||
| Loading* InfraredAppViewManager::get_loading() { | ||||
|     return loading_view; | ||||
| } | ||||
| 
 | ||||
| ViewStack* InfraredAppViewManager::get_universal_view_stack() { | ||||
|     return universal_view_stack; | ||||
| } | ||||
| 
 | ||||
| osMessageQueueId_t InfraredAppViewManager::get_event_queue() { | ||||
|     return event_queue; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppViewManager::clear_events() { | ||||
|     InfraredAppEvent event; | ||||
|     while(osMessageQueueGet(event_queue, &event, NULL, 0) == osOK) | ||||
|         ; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppViewManager::receive_event(InfraredAppEvent* event) { | ||||
|     if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { | ||||
|         event->type = InfraredAppEvent::Type::Tick; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void InfraredAppViewManager::send_event(InfraredAppEvent* event) { | ||||
|     uint32_t timeout = 0; | ||||
|     /* Rapid button hammering on signal send scenes causes queue overflow - ignore it,
 | ||||
|      * but try to keep button release event - it switches off INFRARED DMA sending. */ | ||||
|     if(event->type == InfraredAppEvent::Type::MenuSelectedRelease) { | ||||
|         timeout = 200; | ||||
|     } | ||||
|     if((event->type == InfraredAppEvent::Type::DialogExSelected) && | ||||
|        (event->payload.dialog_ex_result == DialogExReleaseCenter)) { | ||||
|         timeout = 200; | ||||
|     } | ||||
| 
 | ||||
|     osMessageQueuePut(event_queue, event, 0, timeout); | ||||
| } | ||||
| 
 | ||||
| uint32_t InfraredAppViewManager::previous_view_callback(void*) { | ||||
|     if(event_queue != NULL) { | ||||
|         InfraredAppEvent event; | ||||
|         event.type = InfraredAppEvent::Type::Back; | ||||
|         send_event(&event); | ||||
|     } | ||||
| 
 | ||||
|     return VIEW_IGNORE; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppViewManager::add_view(ViewId view_type, View* view) { | ||||
|     view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view); | ||||
| } | ||||
| @ -1,164 +0,0 @@ | ||||
| /**
 | ||||
|   * @file infrared_app_view_manager.h | ||||
|   * Infrared: Scene events description | ||||
|   */ | ||||
| #pragma once | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <gui/view_stack.h> | ||||
| #include <gui/modules/button_panel.h> | ||||
| #include <furi.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/loading.h> | ||||
| 
 | ||||
| #include "infrared_app_event.h" | ||||
| #include "view/infrared_progress_view.h" | ||||
| 
 | ||||
| /** Infrared View manager class */ | ||||
| class InfraredAppViewManager { | ||||
| public: | ||||
|     /** Infrared View Id enum, it is used
 | ||||
|      * to identify added views */ | ||||
|     enum class ViewId : uint8_t { | ||||
|         DialogEx, | ||||
|         TextInput, | ||||
|         Submenu, | ||||
|         ButtonMenu, | ||||
|         UniversalRemote, | ||||
|         Popup, | ||||
|     }; | ||||
| 
 | ||||
|     /** Class constructor */ | ||||
|     InfraredAppViewManager(); | ||||
|     /** Class destructor */ | ||||
|     ~InfraredAppViewManager(); | ||||
| 
 | ||||
|     /** Switch to another view
 | ||||
|      * | ||||
|      * @param id - view id to switch to | ||||
|      */ | ||||
|     void switch_to(ViewId id); | ||||
| 
 | ||||
|     /** Receive event from queue
 | ||||
|      * | ||||
|      * @param event - received event | ||||
|      */ | ||||
|     void receive_event(InfraredAppEvent* event); | ||||
| 
 | ||||
|     /** Send event to queue
 | ||||
|      * | ||||
|      * @param event - event to send | ||||
|      */ | ||||
|     void send_event(InfraredAppEvent* event); | ||||
| 
 | ||||
|     /** Clear events that already in queue
 | ||||
|      * | ||||
|      * @param event - event to send | ||||
|      */ | ||||
|     void clear_events(); | ||||
| 
 | ||||
|     /** Get dialog_ex view module
 | ||||
|      * | ||||
|      * @retval dialog_ex view module | ||||
|      */ | ||||
|     DialogEx* get_dialog_ex(); | ||||
| 
 | ||||
|     /** Get submenu view module
 | ||||
|      * | ||||
|      * @retval submenu view module | ||||
|      */ | ||||
|     Submenu* get_submenu(); | ||||
| 
 | ||||
|     /** Get popup view module
 | ||||
|      * | ||||
|      * @retval popup view module | ||||
|      */ | ||||
|     Popup* get_popup(); | ||||
| 
 | ||||
|     /** Get text_input view module
 | ||||
|      * | ||||
|      * @retval text_input view module | ||||
|      */ | ||||
|     TextInput* get_text_input(); | ||||
| 
 | ||||
|     /** Get button_menu view module
 | ||||
|      * | ||||
|      * @retval button_menu view module | ||||
|      */ | ||||
|     ButtonMenu* get_button_menu(); | ||||
| 
 | ||||
|     /** Get button_panel view module
 | ||||
|      * | ||||
|      * @retval button_panel view module | ||||
|      */ | ||||
|     ButtonPanel* get_button_panel(); | ||||
| 
 | ||||
|     /** Get view_stack view module used in universal remote
 | ||||
|      * | ||||
|      * @retval view_stack view module | ||||
|      */ | ||||
|     ViewStack* get_universal_view_stack(); | ||||
| 
 | ||||
|     /** Get progress view module
 | ||||
|      * | ||||
|      * @retval progress view module | ||||
|      */ | ||||
|     InfraredProgressView* get_progress(); | ||||
| 
 | ||||
|     /** Get loading view module
 | ||||
|      * | ||||
|      * @retval loading view module | ||||
|      */ | ||||
|     Loading* get_loading(); | ||||
| 
 | ||||
|     /** Get event queue
 | ||||
|      * | ||||
|      * @retval event queue | ||||
|      */ | ||||
|     osMessageQueueId_t get_event_queue(); | ||||
| 
 | ||||
|     /** Callback to handle back button
 | ||||
|      * | ||||
|      * @param context - context to pass to callback | ||||
|      * @retval always returns VIEW_IGNORE | ||||
|      */ | ||||
|     uint32_t previous_view_callback(void* context); | ||||
| 
 | ||||
| private: | ||||
|     /** View Dispatcher instance.
 | ||||
|      * It handles view switching */ | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     /** Gui instance */ | ||||
|     Gui* gui; | ||||
|     /** Text input view module instance */ | ||||
|     TextInput* text_input; | ||||
|     /** DialogEx view module instance */ | ||||
|     DialogEx* dialog_ex; | ||||
|     /** Submenu view module instance */ | ||||
|     Submenu* submenu; | ||||
|     /** Popup view module instance */ | ||||
|     Popup* popup; | ||||
|     /** ButtonMenu view module instance */ | ||||
|     ButtonMenu* button_menu; | ||||
|     /** ButtonPanel view module instance */ | ||||
|     ButtonPanel* button_panel; | ||||
|     /** ViewStack view module instance */ | ||||
|     ViewStack* universal_view_stack; | ||||
|     /** ProgressView view module instance */ | ||||
|     InfraredProgressView* progress_view; | ||||
|     /** Loading view module instance */ | ||||
|     Loading* loading_view; | ||||
| 
 | ||||
|     /** Queue to handle events, which are processed in scenes */ | ||||
|     osMessageQueueId_t event_queue; | ||||
| 
 | ||||
|     /** Add View to pull of views
 | ||||
|      * | ||||
|      * @param view_id - id to identify view | ||||
|      * @param view - view to add | ||||
|      */ | ||||
|     void add_view(ViewId view_id, View* view); | ||||
| }; | ||||
							
								
								
									
										154
									
								
								applications/infrared/infrared_brute_force.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								applications/infrared/infrared_brute_force.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | ||||
| #include "infrared_brute_force.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <m-dict.h> | ||||
| #include <m-string.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #include "infrared_signal.h" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t index; | ||||
|     uint32_t count; | ||||
| } InfraredBruteForceRecord; | ||||
| 
 | ||||
| DICT_DEF2( | ||||
|     InfraredBruteForceRecordDict, | ||||
|     string_t, | ||||
|     STRING_OPLIST, | ||||
|     InfraredBruteForceRecord, | ||||
|     M_POD_OPLIST); | ||||
| 
 | ||||
| struct InfraredBruteForce { | ||||
|     FlipperFormat* ff; | ||||
|     const char* db_filename; | ||||
|     string_t current_record_name; | ||||
|     InfraredBruteForceRecordDict_t records; | ||||
| }; | ||||
| 
 | ||||
| InfraredBruteForce* infrared_brute_force_alloc() { | ||||
|     InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce)); | ||||
|     brute_force->ff = NULL; | ||||
|     brute_force->db_filename = NULL; | ||||
|     string_init(brute_force->current_record_name); | ||||
|     InfraredBruteForceRecordDict_init(brute_force->records); | ||||
|     return brute_force; | ||||
| } | ||||
| 
 | ||||
| void infrared_brute_force_free(InfraredBruteForce* brute_force) { | ||||
|     furi_assert(!brute_force->ff); | ||||
|     InfraredBruteForceRecordDict_clear(brute_force->records); | ||||
|     string_clear(brute_force->current_record_name); | ||||
|     free(brute_force); | ||||
| } | ||||
| 
 | ||||
| void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) { | ||||
|     brute_force->db_filename = db_filename; | ||||
| } | ||||
| 
 | ||||
| bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { | ||||
|     furi_assert(brute_force->db_filename); | ||||
|     bool success = false; | ||||
| 
 | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
| 
 | ||||
|     success = flipper_format_file_open_existing(ff, brute_force->db_filename); | ||||
|     if(success) { | ||||
|         string_t signal_name; | ||||
|         string_init(signal_name); | ||||
|         while(flipper_format_read_string(ff, "name", signal_name)) { | ||||
|             InfraredBruteForceRecord* record = | ||||
|                 InfraredBruteForceRecordDict_get(brute_force->records, signal_name); | ||||
|             if(record) { | ||||
|                 ++(record->count); | ||||
|             } | ||||
|         } | ||||
|         string_clear(signal_name); | ||||
|     } | ||||
| 
 | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool infrared_brute_force_start( | ||||
|     InfraredBruteForce* brute_force, | ||||
|     uint32_t index, | ||||
|     uint32_t* record_count) { | ||||
|     bool success = false; | ||||
|     *record_count = 0; | ||||
| 
 | ||||
|     InfraredBruteForceRecordDict_it_t it; | ||||
|     for(InfraredBruteForceRecordDict_it(it, brute_force->records); | ||||
|         !InfraredBruteForceRecordDict_end_p(it); | ||||
|         InfraredBruteForceRecordDict_next(it)) { | ||||
|         const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it); | ||||
|         if(record->value.index == index) { | ||||
|             *record_count = record->value.count; | ||||
|             if(*record_count) { | ||||
|                 string_set(brute_force->current_record_name, record->key); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(*record_count) { | ||||
|         Storage* storage = furi_record_open("storage"); | ||||
|         brute_force->ff = flipper_format_file_alloc(storage); | ||||
|         success = flipper_format_file_open_existing(brute_force->ff, brute_force->db_filename); | ||||
|         if(!success) { | ||||
|             flipper_format_free(brute_force->ff); | ||||
|             brute_force->ff = NULL; | ||||
|             furi_record_close("storage"); | ||||
|         } | ||||
|     } | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { | ||||
|     return brute_force->ff; | ||||
| } | ||||
| 
 | ||||
| void infrared_brute_force_stop(InfraredBruteForce* brute_force) { | ||||
|     furi_assert(string_size(brute_force->current_record_name)); | ||||
|     furi_assert(brute_force->ff); | ||||
| 
 | ||||
|     string_reset(brute_force->current_record_name); | ||||
|     flipper_format_free(brute_force->ff); | ||||
|     furi_record_close("storage"); | ||||
|     brute_force->ff = NULL; | ||||
| } | ||||
| 
 | ||||
| bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { | ||||
|     furi_assert(string_size(brute_force->current_record_name)); | ||||
|     furi_assert(brute_force->ff); | ||||
|     bool success = false; | ||||
| 
 | ||||
|     string_t signal_name; | ||||
|     string_init(signal_name); | ||||
|     InfraredSignal* signal = infrared_signal_alloc(); | ||||
| 
 | ||||
|     do { | ||||
|         success = infrared_signal_read(signal, brute_force->ff, signal_name); | ||||
|     } while(success && !string_equal_p(brute_force->current_record_name, signal_name)); | ||||
| 
 | ||||
|     if(success) { | ||||
|         infrared_signal_transmit(signal); | ||||
|     } | ||||
| 
 | ||||
|     infrared_signal_free(signal); | ||||
|     string_clear(signal_name); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| void infrared_brute_force_add_record( | ||||
|     InfraredBruteForce* brute_force, | ||||
|     uint32_t index, | ||||
|     const char* name) { | ||||
|     InfraredBruteForceRecord value = {.index = index, .count = 0}; | ||||
|     string_t key; | ||||
|     string_init_set_str(key, name); | ||||
|     InfraredBruteForceRecordDict_set_at(brute_force->records, key, value); | ||||
|     string_clear(key); | ||||
| } | ||||
							
								
								
									
										22
									
								
								applications/infrared/infrared_brute_force.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								applications/infrared/infrared_brute_force.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| typedef struct InfraredBruteForce InfraredBruteForce; | ||||
| 
 | ||||
| InfraredBruteForce* infrared_brute_force_alloc(); | ||||
| void infrared_brute_force_free(InfraredBruteForce* brute_force); | ||||
| void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename); | ||||
| bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); | ||||
| bool infrared_brute_force_start( | ||||
|     InfraredBruteForce* brute_force, | ||||
|     uint32_t index, | ||||
|     uint32_t* record_count); | ||||
| bool infrared_brute_force_is_started(InfraredBruteForce* brute_force); | ||||
| void infrared_brute_force_stop(InfraredBruteForce* brute_force); | ||||
| bool infrared_brute_force_send_next(InfraredBruteForce* brute_force); | ||||
| void infrared_brute_force_add_record( | ||||
|     InfraredBruteForce* brute_force, | ||||
|     uint32_t index, | ||||
|     const char* name); | ||||
| @ -1,16 +1,12 @@ | ||||
| #include <furi_hal_delay.h> | ||||
| #include <infrared.h> | ||||
| #include <cli/cli.h> | ||||
| #include <cmsis_os2.h> | ||||
| #include <infrared_worker.h> | ||||
| #include <furi.h> | ||||
| #include <furi_hal_infrared.h> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| #include <m-string.h> | ||||
| #include <infrared_transmit.h> | ||||
| #include <sys/types.h> | ||||
| #include "../helpers/infrared_parser.h" | ||||
| #include <cli/cli.h> | ||||
| #include <infrared.h> | ||||
| #include <infrared_worker.h> | ||||
| #include <furi_hal_infrared.h> | ||||
| 
 | ||||
| #include "infrared_signal.h" | ||||
| 
 | ||||
| #define INFRARED_CLI_BUF_SIZE 10 | ||||
| 
 | ||||
| static void infrared_cli_start_ir_rx(Cli* cli, string_t args); | ||||
| static void infrared_cli_start_ir_tx(Cli* cli, string_t args); | ||||
| @ -92,79 +88,83 @@ static void infrared_cli_print_usage(void) { | ||||
|         INFRARED_MAX_FREQUENCY); | ||||
| } | ||||
| 
 | ||||
| static bool parse_message(const char* str, InfraredMessage* message) { | ||||
| static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) { | ||||
|     char protocol_name[32]; | ||||
|     int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message->address, &message->command); | ||||
|     InfraredMessage message; | ||||
|     int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message.address, &message.command); | ||||
| 
 | ||||
|     if(parsed != 3) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     message->protocol = infrared_get_protocol_by_name(protocol_name); | ||||
|     message->repeat = false; | ||||
| 
 | ||||
|     return infrared_parser_is_parsed_signal_valid(message); | ||||
|     message.repeat = false; | ||||
|     infrared_signal_set_message(signal, &message); | ||||
|     return infrared_signal_is_valid(signal); | ||||
| } | ||||
| 
 | ||||
| static bool parse_signal_raw( | ||||
|     const char* str, | ||||
|     uint32_t* timings, | ||||
|     uint32_t* timings_cnt, | ||||
|     float* duty_cycle, | ||||
|     uint32_t* frequency) { | ||||
|     char frequency_str[10]; | ||||
|     char duty_cycle_str[10]; | ||||
| static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { | ||||
|     char frequency_str[INFRARED_CLI_BUF_SIZE]; | ||||
|     char duty_cycle_str[INFRARED_CLI_BUF_SIZE]; | ||||
|     int parsed = sscanf(str, "RAW F:%9s DC:%9s", frequency_str, duty_cycle_str); | ||||
|     if(parsed != 2) return false; | ||||
| 
 | ||||
|     *frequency = atoi(frequency_str); | ||||
|     *duty_cycle = (float)atoi(duty_cycle_str) / 100; | ||||
|     str += strlen(frequency_str) + strlen(duty_cycle_str) + 10; | ||||
| 
 | ||||
|     uint32_t timings_cnt_max = *timings_cnt; | ||||
|     *timings_cnt = 0; | ||||
| 
 | ||||
|     while(1) { | ||||
|         char timing_str[10]; | ||||
|         for(; *str == ' '; ++str) | ||||
|             ; | ||||
|         if(1 != sscanf(str, "%9s", timing_str)) break; | ||||
|         str += strlen(timing_str); | ||||
|         uint32_t timing = atoi(timing_str); | ||||
|         if(timing <= 0) break; | ||||
|         if(*timings_cnt >= timings_cnt_max) break; | ||||
|         timings[*timings_cnt] = timing; | ||||
|         ++*timings_cnt; | ||||
|     if(parsed != 2) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return infrared_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt); | ||||
|     uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); | ||||
|     uint32_t frequency = atoi(frequency_str); | ||||
|     float duty_cycle = (float)atoi(duty_cycle_str) / 100; | ||||
| 
 | ||||
|     str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE; | ||||
| 
 | ||||
|     size_t timings_size = 0; | ||||
|     while(1) { | ||||
|         while(*str == ' ') { | ||||
|             ++str; | ||||
|         } | ||||
| 
 | ||||
|         char timing_str[INFRARED_CLI_BUF_SIZE]; | ||||
|         if(sscanf(str, "%9s", timing_str) != 1) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         str += strlen(timing_str); | ||||
|         uint32_t timing = atoi(timing_str); | ||||
| 
 | ||||
|         if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         timings[timings_size] = timing; | ||||
|         ++timings_size; | ||||
|     } | ||||
| 
 | ||||
|     infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); | ||||
|     free(timings); | ||||
| 
 | ||||
|     return infrared_signal_is_valid(signal); | ||||
| } | ||||
| 
 | ||||
| static void infrared_cli_start_ir_tx(Cli* cli, string_t args) { | ||||
|     UNUSED(cli); | ||||
|     InfraredMessage message; | ||||
|     const char* str = string_get_cstr(args); | ||||
|     uint32_t frequency; | ||||
|     float duty_cycle; | ||||
|     uint32_t timings_cnt = MAX_TIMINGS_AMOUNT; | ||||
|     uint32_t* timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt); | ||||
|     InfraredSignal* signal = infrared_signal_alloc(); | ||||
| 
 | ||||
|     if(parse_message(str, &message)) { | ||||
|         infrared_send(&message, 1); | ||||
|     } else if(parse_signal_raw(str, timings, &timings_cnt, &duty_cycle, &frequency)) { | ||||
|         infrared_send_raw_ext(timings, timings_cnt, true, frequency, duty_cycle); | ||||
|     bool success = infrared_cli_parse_message(str, signal) || infrared_cli_parse_raw(str, signal); | ||||
|     if(success) { | ||||
|         infrared_signal_transmit(signal); | ||||
|     } else { | ||||
|         printf("Wrong arguments.\r\n"); | ||||
|         infrared_cli_print_usage(); | ||||
|     } | ||||
| 
 | ||||
|     free(timings); | ||||
|     infrared_signal_free(signal); | ||||
| } | ||||
| 
 | ||||
| static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { | ||||
|     UNUSED(context); | ||||
|     if(furi_hal_infrared_is_busy()) { | ||||
|         printf("INFRARED is busy. Exit."); | ||||
|         printf("INFRARED is busy. Exiting."); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| @ -189,8 +189,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { | ||||
|         infrared_cli_print_usage(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void infrared_on_system_start() { | ||||
| void infrared_on_system_start() { | ||||
| #ifdef SRV_CLI | ||||
|     Cli* cli = (Cli*)furi_record_open("cli"); | ||||
|     cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL); | ||||
							
								
								
									
										51
									
								
								applications/infrared/infrared_custom_event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								applications/infrared/infrared_custom_event.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| 
 | ||||
| enum InfraredCustomEventType { | ||||
|     // Reserve first 100 events for button types and indexes, starting from 0
 | ||||
|     InfraredCustomEventTypeReserved = 100, | ||||
|     InfraredCustomEventTypeMenuSelected, | ||||
|     InfraredCustomEventTypeTransmitStarted, | ||||
|     InfraredCustomEventTypeTransmitStopped, | ||||
|     InfraredCustomEventTypeSignalReceived, | ||||
|     InfraredCustomEventTypeTextEditDone, | ||||
|     InfraredCustomEventTypePopupClosed, | ||||
|     InfraredCustomEventTypeButtonSelected, | ||||
|     InfraredCustomEventTypeBackPressed, | ||||
| }; | ||||
| 
 | ||||
| #pragma pack(push, 1) | ||||
| typedef union { | ||||
|     uint32_t packed_value; | ||||
|     struct { | ||||
|         uint16_t type; | ||||
|         int16_t value; | ||||
|     } content; | ||||
| } InfraredCustomEvent; | ||||
| #pragma pack(pop) | ||||
| 
 | ||||
| static inline uint32_t infrared_custom_event_pack(uint16_t type, int16_t value) { | ||||
|     InfraredCustomEvent event = {.content = {.type = type, .value = value}}; | ||||
|     return event.packed_value; | ||||
| } | ||||
| 
 | ||||
| static inline void | ||||
|     infrared_custom_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) { | ||||
|     InfraredCustomEvent event = {.packed_value = packed_value}; | ||||
|     if(type) *type = event.content.type; | ||||
|     if(value) *value = event.content.value; | ||||
| } | ||||
| 
 | ||||
| static inline uint16_t infrared_custom_event_get_type(uint32_t packed_value) { | ||||
|     uint16_t type; | ||||
|     infrared_custom_event_unpack(packed_value, &type, NULL); | ||||
|     return type; | ||||
| } | ||||
| 
 | ||||
| static inline int16_t infrared_custom_event_get_value(uint32_t packed_value) { | ||||
|     int16_t value; | ||||
|     infrared_custom_event_unpack(packed_value, NULL, &value); | ||||
|     return value; | ||||
| } | ||||
							
								
								
									
										134
									
								
								applications/infrared/infrared_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								applications/infrared/infrared_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_stack.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/loading.h> | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <gui/modules/button_panel.h> | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include <notification/notification_messages.h> | ||||
| 
 | ||||
| #include <infrared_worker.h> | ||||
| 
 | ||||
| #include "infrared.h" | ||||
| #include "infrared_remote.h" | ||||
| #include "infrared_brute_force.h" | ||||
| #include "infrared_custom_event.h" | ||||
| 
 | ||||
| #include "scenes/infrared_scene.h" | ||||
| #include "views/infrared_progress_view.h" | ||||
| #include "views/infrared_debug_view.h" | ||||
| 
 | ||||
| #define INFRARED_FILE_NAME_SIZE 100 | ||||
| #define INFRARED_TEXT_STORE_NUM 2 | ||||
| #define INFRARED_TEXT_STORE_SIZE 128 | ||||
| 
 | ||||
| #define INFRARED_MAX_BUTTON_NAME_LENGTH 22 | ||||
| #define INFRARED_MAX_REMOTE_NAME_LENGTH 22 | ||||
| 
 | ||||
| #define INFRARED_APP_FOLDER "/any/infrared" | ||||
| #define INFRARED_APP_EXTENSION ".ir" | ||||
| 
 | ||||
| #define INFRARED_DEFAULT_REMOTE_NAME "Remote" | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredButtonIndexNone = -1, | ||||
| } InfraredButtonIndex; | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredEditTargetNone, | ||||
|     InfraredEditTargetRemote, | ||||
|     InfraredEditTargetButton, | ||||
| } InfraredEditTarget; | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredEditModeNone, | ||||
|     InfraredEditModeRename, | ||||
|     InfraredEditModeDelete, | ||||
| } InfraredEditMode; | ||||
| 
 | ||||
| typedef struct { | ||||
|     bool is_learning_new_remote; | ||||
|     bool is_debug_enabled; | ||||
|     InfraredEditTarget edit_target : 8; | ||||
|     InfraredEditMode edit_mode : 8; | ||||
|     int32_t current_button_index; | ||||
| } InfraredAppState; | ||||
| 
 | ||||
| struct Infrared { | ||||
|     SceneManager* scene_manager; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
| 
 | ||||
|     Gui* gui; | ||||
|     Storage* storage; | ||||
|     DialogsApp* dialogs; | ||||
|     NotificationApp* notifications; | ||||
|     InfraredWorker* worker; | ||||
|     InfraredRemote* remote; | ||||
|     InfraredSignal* received_signal; | ||||
|     InfraredBruteForce* brute_force; | ||||
| 
 | ||||
|     Submenu* submenu; | ||||
|     TextInput* text_input; | ||||
|     DialogEx* dialog_ex; | ||||
|     ButtonMenu* button_menu; | ||||
|     Popup* popup; | ||||
| 
 | ||||
|     ViewStack* view_stack; | ||||
|     InfraredDebugView* debug_view; | ||||
| 
 | ||||
|     ButtonPanel* button_panel; | ||||
|     Loading* loading; | ||||
|     InfraredProgressView* progress; | ||||
| 
 | ||||
|     string_t file_path; | ||||
|     char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; | ||||
|     InfraredAppState app_state; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredViewSubmenu, | ||||
|     InfraredViewTextInput, | ||||
|     InfraredViewDialogEx, | ||||
|     InfraredViewButtonMenu, | ||||
|     InfraredViewPopup, | ||||
|     InfraredViewStack, | ||||
|     InfraredViewDebugView, | ||||
| } InfraredView; | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredNotificationMessageSuccess, | ||||
|     InfraredNotificationMessageGreenOn, | ||||
|     InfraredNotificationMessageGreenOff, | ||||
|     InfraredNotificationMessageBlinkRead, | ||||
|     InfraredNotificationMessageBlinkSend, | ||||
|     InfraredNotificationMessageYellowOn, | ||||
|     InfraredNotificationMessageYellowOff, | ||||
| } InfraredNotificationMessage; | ||||
| 
 | ||||
| bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal); | ||||
| bool infrared_rename_current_remote(Infrared* infrared, const char* name); | ||||
| void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal); | ||||
| void infrared_tx_start_button_index(Infrared* infrared, size_t button_index); | ||||
| void infrared_tx_start_received(Infrared* infrared); | ||||
| void infrared_tx_stop(Infrared* infrared); | ||||
| void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...); | ||||
| void infrared_text_store_clear(Infrared* infrared, uint32_t bank); | ||||
| void infrared_play_notification_message(Infrared* infrared, uint32_t message); | ||||
| void infrared_show_loading_popup(Infrared* infrared, bool show); | ||||
| 
 | ||||
| void infrared_signal_sent_callback(void* context); | ||||
| void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal); | ||||
| void infrared_text_input_callback(void* context); | ||||
| void infrared_popup_closed_callback(void* context); | ||||
							
								
								
									
										176
									
								
								applications/infrared/infrared_remote.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								applications/infrared/infrared_remote.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,176 @@ | ||||
| #include "infrared_remote.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <toolbox/path.h> | ||||
| #include <storage/storage.h> | ||||
| #include <furi/common_defines.h> | ||||
| 
 | ||||
| #define TAG "InfraredRemote" | ||||
| 
 | ||||
| ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST); | ||||
| 
 | ||||
| struct InfraredRemote { | ||||
|     InfraredButtonArray_t buttons; | ||||
|     string_t name; | ||||
|     string_t path; | ||||
| }; | ||||
| 
 | ||||
| static void infrared_remote_clear_buttons(InfraredRemote* remote) { | ||||
|     InfraredButtonArray_it_t it; | ||||
|     for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); | ||||
|         InfraredButtonArray_next(it)) { | ||||
|         infrared_remote_button_free(*InfraredButtonArray_cref(it)); | ||||
|     } | ||||
|     InfraredButtonArray_reset(remote->buttons); | ||||
| } | ||||
| 
 | ||||
| InfraredRemote* infrared_remote_alloc() { | ||||
|     InfraredRemote* remote = malloc(sizeof(InfraredRemote)); | ||||
|     InfraredButtonArray_init(remote->buttons); | ||||
|     string_init(remote->name); | ||||
|     string_init(remote->path); | ||||
|     return remote; | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_free(InfraredRemote* remote) { | ||||
|     infrared_remote_clear_buttons(remote); | ||||
|     InfraredButtonArray_clear(remote->buttons); | ||||
|     string_clear(remote->path); | ||||
|     string_clear(remote->name); | ||||
|     free(remote); | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_reset(InfraredRemote* remote) { | ||||
|     infrared_remote_clear_buttons(remote); | ||||
|     string_reset(remote->name); | ||||
|     string_reset(remote->path); | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_set_name(InfraredRemote* remote, const char* name) { | ||||
|     string_set_str(remote->name, name); | ||||
| } | ||||
| 
 | ||||
| const char* infrared_remote_get_name(InfraredRemote* remote) { | ||||
|     return string_get_cstr(remote->name); | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_set_path(InfraredRemote* remote, const char* path) { | ||||
|     string_set_str(remote->path, path); | ||||
| } | ||||
| 
 | ||||
| const char* infrared_remote_get_path(InfraredRemote* remote) { | ||||
|     return string_get_cstr(remote->path); | ||||
| } | ||||
| 
 | ||||
| size_t infrared_remote_get_button_count(InfraredRemote* remote) { | ||||
|     return InfraredButtonArray_size(remote->buttons); | ||||
| } | ||||
| 
 | ||||
| InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) { | ||||
|     furi_assert(index < InfraredButtonArray_size(remote->buttons)); | ||||
|     return *InfraredButtonArray_get(remote->buttons, index); | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) { | ||||
|     InfraredRemoteButton* button = infrared_remote_button_alloc(); | ||||
|     infrared_remote_button_set_name(button, name); | ||||
|     infrared_remote_button_set_signal(button, signal); | ||||
|     InfraredButtonArray_push_back(remote->buttons, button); | ||||
|     return infrared_remote_store(remote); | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) { | ||||
|     furi_assert(index < InfraredButtonArray_size(remote->buttons)); | ||||
|     InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index); | ||||
|     infrared_remote_button_set_name(button, new_name); | ||||
|     return infrared_remote_store(remote); | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { | ||||
|     furi_assert(index < InfraredButtonArray_size(remote->buttons)); | ||||
|     InfraredRemoteButton* button; | ||||
|     InfraredButtonArray_pop_at(&button, remote->buttons, index); | ||||
|     infrared_remote_button_free(button); | ||||
|     return infrared_remote_store(remote); | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_store(InfraredRemote* remote) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
|     const char* path = string_get_cstr(remote->path); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "store file: \'%s\'", path); | ||||
| 
 | ||||
|     bool success = flipper_format_file_open_always(ff, path) && | ||||
|                    flipper_format_write_header_cstr(ff, "IR signals file", 1); | ||||
|     if(success) { | ||||
|         InfraredButtonArray_it_t it; | ||||
|         for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); | ||||
|             InfraredButtonArray_next(it)) { | ||||
|             InfraredRemoteButton* button = *InfraredButtonArray_cref(it); | ||||
|             success = infrared_signal_save( | ||||
|                 infrared_remote_button_get_signal(button), | ||||
|                 ff, | ||||
|                 infrared_remote_button_get_name(button)); | ||||
|             if(!success) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_load(InfraredRemote* remote, string_t path) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||
| 
 | ||||
|     string_t buf; | ||||
|     string_init(buf); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "load file: \'%s\'", string_get_cstr(path)); | ||||
|     bool success = flipper_format_file_open_existing(ff, string_get_cstr(path)); | ||||
| 
 | ||||
|     if(success) { | ||||
|         uint32_t version; | ||||
|         success = flipper_format_read_header(ff, buf, &version) && | ||||
|                   !string_cmp_str(buf, "IR signals file") && (version == 1); | ||||
|     } | ||||
| 
 | ||||
|     if(success) { | ||||
|         path_extract_filename(path, buf, true); | ||||
|         infrared_remote_clear_buttons(remote); | ||||
|         infrared_remote_set_name(remote, string_get_cstr(buf)); | ||||
|         infrared_remote_set_path(remote, string_get_cstr(path)); | ||||
| 
 | ||||
|         for(bool can_read = true; can_read;) { | ||||
|             InfraredRemoteButton* button = infrared_remote_button_alloc(); | ||||
|             can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf); | ||||
|             if(can_read) { | ||||
|                 infrared_remote_button_set_name(button, string_get_cstr(buf)); | ||||
|                 InfraredButtonArray_push_back(remote->buttons, button); | ||||
|             } else { | ||||
|                 infrared_remote_button_free(button); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buf); | ||||
|     flipper_format_free(ff); | ||||
|     furi_record_close("storage"); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool infrared_remote_remove(InfraredRemote* remote) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
| 
 | ||||
|     FS_Error status = storage_common_remove(storage, string_get_cstr(remote->path)); | ||||
|     infrared_remote_reset(remote); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
|     return (status == FSE_OK || status == FSE_NOT_EXIST); | ||||
| } | ||||
							
								
								
									
										28
									
								
								applications/infrared/infrared_remote.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								applications/infrared/infrared_remote.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #include "infrared_remote_button.h" | ||||
| 
 | ||||
| typedef struct InfraredRemote InfraredRemote; | ||||
| 
 | ||||
| InfraredRemote* infrared_remote_alloc(); | ||||
| void infrared_remote_free(InfraredRemote* remote); | ||||
| void infrared_remote_reset(InfraredRemote* remote); | ||||
| 
 | ||||
| void infrared_remote_set_name(InfraredRemote* remote, const char* name); | ||||
| const char* infrared_remote_get_name(InfraredRemote* remote); | ||||
| 
 | ||||
| void infrared_remote_set_path(InfraredRemote* remote, const char* path); | ||||
| const char* infrared_remote_get_path(InfraredRemote* remote); | ||||
| 
 | ||||
| size_t infrared_remote_get_button_count(InfraredRemote* remote); | ||||
| InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index); | ||||
| 
 | ||||
| bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); | ||||
| bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); | ||||
| bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); | ||||
| 
 | ||||
| bool infrared_remote_store(InfraredRemote* remote); | ||||
| bool infrared_remote_load(InfraredRemote* remote, string_t path); | ||||
| bool infrared_remote_remove(InfraredRemote* remote); | ||||
							
								
								
									
										38
									
								
								applications/infrared/infrared_remote_button.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								applications/infrared/infrared_remote_button.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| #include "infrared_remote_button.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| struct InfraredRemoteButton { | ||||
|     string_t name; | ||||
|     InfraredSignal* signal; | ||||
| }; | ||||
| 
 | ||||
| InfraredRemoteButton* infrared_remote_button_alloc() { | ||||
|     InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton)); | ||||
|     string_init(button->name); | ||||
|     button->signal = infrared_signal_alloc(); | ||||
|     return button; | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_button_free(InfraredRemoteButton* button) { | ||||
|     string_clear(button->name); | ||||
|     infrared_signal_free(button->signal); | ||||
|     free(button); | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) { | ||||
|     string_set_str(button->name, name); | ||||
| } | ||||
| 
 | ||||
| const char* infrared_remote_button_get_name(InfraredRemoteButton* button) { | ||||
|     return string_get_cstr(button->name); | ||||
| } | ||||
| 
 | ||||
| void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) { | ||||
|     infrared_signal_set_signal(button->signal, signal); | ||||
| } | ||||
| 
 | ||||
| InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) { | ||||
|     return button->signal; | ||||
| } | ||||
							
								
								
									
										14
									
								
								applications/infrared/infrared_remote_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								applications/infrared/infrared_remote_button.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "infrared_signal.h" | ||||
| 
 | ||||
| typedef struct InfraredRemoteButton InfraredRemoteButton; | ||||
| 
 | ||||
| InfraredRemoteButton* infrared_remote_button_alloc(); | ||||
| void infrared_remote_button_free(InfraredRemoteButton* button); | ||||
| 
 | ||||
| void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name); | ||||
| const char* infrared_remote_button_get_name(InfraredRemoteButton* button); | ||||
| 
 | ||||
| void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal); | ||||
| InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button); | ||||
| @ -1,9 +0,0 @@ | ||||
| #include "infrared_app.h" | ||||
| 
 | ||||
| extern "C" int32_t infrared_app(void* p) { | ||||
|     InfraredApp* app = new InfraredApp(); | ||||
|     int32_t result = app->run(p); | ||||
|     delete app; | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										264
									
								
								applications/infrared/infrared_signal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								applications/infrared/infrared_signal.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,264 @@ | ||||
| #include "infrared_signal.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <furi/check.h> | ||||
| #include <infrared_worker.h> | ||||
| #include <infrared_transmit.h> | ||||
| 
 | ||||
| #define TAG "InfraredSignal" | ||||
| 
 | ||||
| struct InfraredSignal { | ||||
|     bool is_raw; | ||||
|     union { | ||||
|         InfraredMessage message; | ||||
|         InfraredRawSignal raw; | ||||
|     } payload; | ||||
| }; | ||||
| 
 | ||||
| static void infrared_signal_clear_timings(InfraredSignal* signal) { | ||||
|     if(signal->is_raw) { | ||||
|         free(signal->payload.raw.timings); | ||||
|         signal->payload.raw.timings_size = 0; | ||||
|         signal->payload.raw.timings = NULL; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool infrared_signal_is_message_valid(InfraredMessage* message) { | ||||
|     if(!infrared_is_protocol_valid(message->protocol)) { | ||||
|         FURI_LOG_E(TAG, "Unknown protocol"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t address_length = infrared_get_protocol_address_length(message->protocol); | ||||
|     uint32_t address_mask = (1UL << address_length) - 1; | ||||
| 
 | ||||
|     if(message->address != (message->address & address_mask)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, | ||||
|             "Address is out of range (mask 0x%08lX): 0x%lX\r\n", | ||||
|             address_mask, | ||||
|             message->address); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t command_length = infrared_get_protocol_command_length(message->protocol); | ||||
|     uint32_t command_mask = (1UL << command_length) - 1; | ||||
| 
 | ||||
|     if(message->command != (message->command & command_mask)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, | ||||
|             "Command is out of range (mask 0x%08lX): 0x%lX\r\n", | ||||
|             command_mask, | ||||
|             message->command); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { | ||||
|     if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, | ||||
|             "Frequency is out of range (%lX - %lX): %lX", | ||||
|             INFRARED_MIN_FREQUENCY, | ||||
|             INFRARED_MAX_FREQUENCY, | ||||
|             raw->frequency); | ||||
|         return false; | ||||
| 
 | ||||
|     } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) { | ||||
|         FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle); | ||||
|         return false; | ||||
| 
 | ||||
|     } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) { | ||||
|         FURI_LOG_E( | ||||
|             TAG, | ||||
|             "Timings amount is out of range (0 - %lX): %lX", | ||||
|             MAX_TIMINGS_AMOUNT, | ||||
|             raw->timings_size); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) { | ||||
|     const char* protocol_name = infrared_get_protocol_name(message->protocol); | ||||
|     return flipper_format_write_string_cstr(ff, "type", "parsed") && | ||||
|            flipper_format_write_string_cstr(ff, "protocol", protocol_name) && | ||||
|            flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) && | ||||
|            flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); | ||||
| } | ||||
| 
 | ||||
| static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) { | ||||
|     furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); | ||||
|     return flipper_format_write_string_cstr(ff, "type", "raw") && | ||||
|            flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && | ||||
|            flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) && | ||||
|            flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size); | ||||
| } | ||||
| 
 | ||||
| static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { | ||||
|     string_t buf; | ||||
|     string_init(buf); | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_read_string(ff, "protocol", buf)) break; | ||||
| 
 | ||||
|         InfraredMessage message; | ||||
|         message.protocol = infrared_get_protocol_by_name(string_get_cstr(buf)); | ||||
| 
 | ||||
|         success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) && | ||||
|                   flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) && | ||||
|                   infrared_signal_is_message_valid(&message); | ||||
| 
 | ||||
|         if(!success) break; | ||||
| 
 | ||||
|         infrared_signal_set_message(signal, &message); | ||||
|     } while(0); | ||||
| 
 | ||||
|     string_clear(buf); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { | ||||
|     uint32_t timings_size, frequency; | ||||
|     float duty_cycle; | ||||
| 
 | ||||
|     bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) && | ||||
|                    flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) && | ||||
|                    flipper_format_get_value_count(ff, "data", &timings_size); | ||||
| 
 | ||||
|     if(!success || timings_size > MAX_TIMINGS_AMOUNT) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); | ||||
|     success = flipper_format_read_uint32(ff, "data", timings, timings_size); | ||||
| 
 | ||||
|     if(success) { | ||||
|         infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); | ||||
|     } | ||||
| 
 | ||||
|     free(timings); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| InfraredSignal* infrared_signal_alloc() { | ||||
|     InfraredSignal* signal = malloc(sizeof(InfraredSignal)); | ||||
| 
 | ||||
|     signal->is_raw = false; | ||||
|     signal->payload.message.protocol = InfraredProtocolUnknown; | ||||
| 
 | ||||
|     return signal; | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_free(InfraredSignal* signal) { | ||||
|     infrared_signal_clear_timings(signal); | ||||
|     free(signal); | ||||
| } | ||||
| 
 | ||||
| bool infrared_signal_is_raw(InfraredSignal* signal) { | ||||
|     return signal->is_raw; | ||||
| } | ||||
| 
 | ||||
| bool infrared_signal_is_valid(InfraredSignal* signal) { | ||||
|     return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) : | ||||
|                             infrared_signal_is_message_valid(&signal->payload.message); | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) { | ||||
|     if(other->is_raw) { | ||||
|         const InfraredRawSignal* raw = &other->payload.raw; | ||||
|         infrared_signal_set_raw_signal( | ||||
|             signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); | ||||
|     } else { | ||||
|         const InfraredMessage* message = &other->payload.message; | ||||
|         infrared_signal_set_message(signal, message); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_set_raw_signal( | ||||
|     InfraredSignal* signal, | ||||
|     const uint32_t* timings, | ||||
|     size_t timings_size, | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle) { | ||||
|     infrared_signal_clear_timings(signal); | ||||
| 
 | ||||
|     signal->is_raw = true; | ||||
| 
 | ||||
|     signal->payload.raw.timings_size = timings_size; | ||||
|     signal->payload.raw.frequency = frequency; | ||||
|     signal->payload.raw.duty_cycle = duty_cycle; | ||||
| 
 | ||||
|     signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t)); | ||||
|     memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t)); | ||||
| } | ||||
| 
 | ||||
| InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) { | ||||
|     furi_assert(signal->is_raw); | ||||
|     return &signal->payload.raw; | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) { | ||||
|     infrared_signal_clear_timings(signal); | ||||
| 
 | ||||
|     signal->is_raw = false; | ||||
|     signal->payload.message = *message; | ||||
| } | ||||
| 
 | ||||
| InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) { | ||||
|     furi_assert(!signal->is_raw); | ||||
|     return &signal->payload.message; | ||||
| } | ||||
| 
 | ||||
| bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) { | ||||
|     if(!flipper_format_write_comment_cstr(ff, "") || | ||||
|        !flipper_format_write_string_cstr(ff, "name", name)) { | ||||
|         return false; | ||||
|     } else if(signal->is_raw) { | ||||
|         return infrared_signal_save_raw(&signal->payload.raw, ff); | ||||
|     } else { | ||||
|         return infrared_signal_save_message(&signal->payload.message, ff); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) { | ||||
|     string_t buf; | ||||
|     string_init(buf); | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_read_string(ff, "name", buf)) break; | ||||
|         string_set(name, buf); | ||||
|         if(!flipper_format_read_string(ff, "type", buf)) break; | ||||
|         if(!string_cmp_str(buf, "raw")) { | ||||
|             success = infrared_signal_read_raw(signal, ff); | ||||
|         } else if(!string_cmp_str(buf, "parsed")) { | ||||
|             success = infrared_signal_read_message(signal, ff); | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) "); | ||||
|         } | ||||
|     } while(0); | ||||
| 
 | ||||
|     string_clear(buf); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| void infrared_signal_transmit(InfraredSignal* signal) { | ||||
|     if(signal->is_raw) { | ||||
|         InfraredRawSignal* raw_signal = &signal->payload.raw; | ||||
|         infrared_send_raw_ext( | ||||
|             raw_signal->timings, | ||||
|             raw_signal->timings_size, | ||||
|             true, | ||||
|             raw_signal->frequency, | ||||
|             raw_signal->duty_cycle); | ||||
|     } else { | ||||
|         InfraredMessage* message = &signal->payload.message; | ||||
|         infrared_send(message, 1); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								applications/infrared/infrared_signal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								applications/infrared/infrared_signal.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #include <infrared.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| typedef struct InfraredSignal InfraredSignal; | ||||
| 
 | ||||
| typedef struct { | ||||
|     size_t timings_size; | ||||
|     uint32_t* timings; | ||||
|     uint32_t frequency; | ||||
|     float duty_cycle; | ||||
| } InfraredRawSignal; | ||||
| 
 | ||||
| InfraredSignal* infrared_signal_alloc(); | ||||
| void infrared_signal_free(InfraredSignal* signal); | ||||
| 
 | ||||
| bool infrared_signal_is_raw(InfraredSignal* signal); | ||||
| bool infrared_signal_is_valid(InfraredSignal* signal); | ||||
| 
 | ||||
| void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other); | ||||
| 
 | ||||
| void infrared_signal_set_raw_signal( | ||||
|     InfraredSignal* signal, | ||||
|     const uint32_t* timings, | ||||
|     size_t timings_size, | ||||
|     uint32_t frequency, | ||||
|     float duty_cycle); | ||||
| InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal); | ||||
| 
 | ||||
| void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message); | ||||
| InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); | ||||
| 
 | ||||
| bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); | ||||
| bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name); | ||||
| 
 | ||||
| void infrared_signal_transmit(InfraredSignal* signal); | ||||
| @ -1,305 +0,0 @@ | ||||
| /**
 | ||||
|  * @file infrared_app_scene.h | ||||
|  * Infrared: Application scenes | ||||
|  */ | ||||
| #pragma once | ||||
| #include "../infrared_app_event.h" | ||||
| #include <furi_hal_infrared.h> | ||||
| #include "infrared.h" | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include "../infrared_app_brute_force.h" | ||||
| 
 | ||||
| /** Anonymous class */ | ||||
| class InfraredApp; | ||||
| 
 | ||||
| /** Base Scene class */ | ||||
| class InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     virtual void on_enter(InfraredApp* app) = 0; | ||||
|     /** Events handler callback */ | ||||
|     virtual bool on_event(InfraredApp* app, InfraredAppEvent* event) = 0; | ||||
|     /** Called when exit scene */ | ||||
|     virtual void on_exit(InfraredApp* app) = 0; | ||||
|     /** Virtual destructor of base class */ | ||||
|     virtual ~InfraredAppScene(){}; | ||||
| 
 | ||||
| private: | ||||
| }; | ||||
| 
 | ||||
| /** Start scene
 | ||||
|  * Main Infrared application menu | ||||
|  */ | ||||
| class InfraredAppSceneStart : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** Save previously selected submenu index
 | ||||
|      * to highlight it when get back */ | ||||
|     uint32_t submenu_item_selected = 0; | ||||
| }; | ||||
| 
 | ||||
| /** Universal menu scene
 | ||||
|  * Scene to select universal remote | ||||
|  */ | ||||
| class InfraredAppSceneUniversal : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** Save previously selected submenu index
 | ||||
|      * to highlight it when get back */ | ||||
|     uint32_t submenu_item_selected = 0; | ||||
| }; | ||||
| 
 | ||||
| /** Learn new signal scene
 | ||||
|  * On this scene catching new IR signal performed. | ||||
|  */ | ||||
| class InfraredAppSceneLearn : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| /** New signal learn succeeded scene
 | ||||
|  */ | ||||
| class InfraredAppSceneLearnSuccess : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
|     bool button_pressed = false; | ||||
| }; | ||||
| 
 | ||||
| /** Scene to enter name for new button in remote
 | ||||
|  */ | ||||
| class InfraredAppSceneLearnEnterName : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| /** Scene where signal is learnt
 | ||||
|  */ | ||||
| class InfraredAppSceneLearnDone : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| /** Remote interface scene
 | ||||
|  * On this scene you can send IR signals from selected remote | ||||
|  */ | ||||
| class InfraredAppSceneRemote : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** container of button names in current remote. */ | ||||
|     std::vector<std::string> buttons_names; | ||||
|     /** Save previously selected index
 | ||||
|      * to highlight it when get back */ | ||||
|     uint32_t buttonmenu_item_selected = 0; | ||||
|     /** state flag to show button is pressed.
 | ||||
|      * As long as send-signal button pressed no other button | ||||
|      * events are handled. */ | ||||
|     bool button_pressed = false; | ||||
| }; | ||||
| 
 | ||||
| /** List of remotes scene
 | ||||
|  * Every remote is a file, located on internal/external storage. | ||||
|  * Every file has same format, and same extension. | ||||
|  * Files are parsed as you enter 'Remote scene' and showed | ||||
|  * as a buttons. | ||||
|  */ | ||||
| class InfraredAppSceneRemoteList : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** Save previously selected index
 | ||||
|      * to highlight it when get back */ | ||||
|     uint32_t submenu_item_selected = 0; | ||||
|     /** Remote names to show them in submenu */ | ||||
|     std::vector<std::string> remote_names; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneAskBack : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEdit : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** Save previously selected index
 | ||||
|      * to highlight it when get back */ | ||||
|     uint32_t submenu_item_selected = 0; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEditKeySelect : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     /** Button names to show them in submenu */ | ||||
|     std::vector<std::string> buttons_names; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEditRename : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEditDelete : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEditRenameDone : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneEditDeleteDone : public InfraredAppScene { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneUniversalCommon : public InfraredAppScene { | ||||
|     /** Brute force started flag */ | ||||
|     bool brute_force_started = false; | ||||
| 
 | ||||
| protected: | ||||
|     /** Events handler callback */ | ||||
|     bool on_event(InfraredApp* app, InfraredAppEvent* event) final; | ||||
|     /** Called when exit scene */ | ||||
|     void on_exit(InfraredApp* app) final; | ||||
| 
 | ||||
|     /** Show popup window
 | ||||
|      * | ||||
|      * @param app - application instance | ||||
|      */ | ||||
|     void show_popup(InfraredApp* app, int record_amount); | ||||
| 
 | ||||
|     /** Hide popup window
 | ||||
|      * | ||||
|      * @param app - application instance | ||||
|      */ | ||||
|     void hide_popup(InfraredApp* app); | ||||
| 
 | ||||
|     /** Propagate progress in popup window
 | ||||
|      * | ||||
|      * @param app - application instance | ||||
|      */ | ||||
|     bool progress_popup(InfraredApp* app); | ||||
| 
 | ||||
|     /** Item selected callback
 | ||||
|      * | ||||
|      * @param context - context | ||||
|      * @param index - selected item index | ||||
|      */ | ||||
|     static void infrared_app_item_callback(void* context, uint32_t index); | ||||
| 
 | ||||
|     /** Brute Force instance */ | ||||
|     InfraredAppBruteForce brute_force; | ||||
| 
 | ||||
|     /** Constructor */ | ||||
|     InfraredAppSceneUniversalCommon(const char* filename) | ||||
|         : brute_force(filename) { | ||||
|     } | ||||
| 
 | ||||
|     /** Destructor */ | ||||
|     ~InfraredAppSceneUniversalCommon() { | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| class InfraredAppSceneUniversalTV : public InfraredAppSceneUniversalCommon { | ||||
| public: | ||||
|     /** Called when enter scene */ | ||||
|     void on_enter(InfraredApp* app) final; | ||||
| 
 | ||||
|     /** Constructor
 | ||||
|      * Specifies path to brute force db library */ | ||||
|     InfraredAppSceneUniversalTV() | ||||
|         : InfraredAppSceneUniversalCommon("/ext/infrared/assets/tv.ir") { | ||||
|     } | ||||
| 
 | ||||
|     /** Destructor */ | ||||
|     ~InfraredAppSceneUniversalTV() { | ||||
|     } | ||||
| }; | ||||
| @ -1,73 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "gui/modules/dialog_ex.h" | ||||
| #include "infrared.h" | ||||
| #include "infrared/scene/infrared_app_scene.h" | ||||
| #include <string> | ||||
| 
 | ||||
| static void dialog_result_callback(DialogExResult result, void* context) { | ||||
|     auto app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::DialogExSelected; | ||||
|     event.payload.dialog_ex_result = result; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneAskBack::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     DialogEx* dialog_ex = view_manager->get_dialog_ex(); | ||||
| 
 | ||||
|     if(app->get_learn_new_remote()) { | ||||
|         dialog_ex_set_header(dialog_ex, "Exit to Infrared menu?", 64, 0, AlignCenter, AlignTop); | ||||
|     } else { | ||||
|         dialog_ex_set_header(dialog_ex, "Exit to remote menu?", 64, 0, AlignCenter, AlignTop); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_text( | ||||
|         dialog_ex, "All unsaved data\nwill be lost", 64, 31, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Exit"); | ||||
|     dialog_ex_set_center_button_text(dialog_ex, nullptr); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Stay"); | ||||
|     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneAskBack::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::DialogExSelected) { | ||||
|         switch(event->payload.dialog_ex_result) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = true; | ||||
|             if(app->get_learn_new_remote()) { | ||||
|                 app->search_and_switch_to_previous_scene({InfraredApp::Scene::Start}); | ||||
|             } else { | ||||
|                 app->search_and_switch_to_previous_scene( | ||||
|                     {InfraredApp::Scene::Edit, InfraredApp::Scene::Remote}); | ||||
|             } | ||||
|             break; | ||||
|         case DialogExResultCenter: | ||||
|             furi_assert(0); | ||||
|             break; | ||||
|         case DialogExResultRight: | ||||
|             app->switch_to_previous_scene(); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::Back) { | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneAskBack::on_exit(InfraredApp*) { | ||||
| } | ||||
| @ -1,79 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "gui/modules/submenu.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuIndexAddKey, | ||||
|     SubmenuIndexRenameKey, | ||||
|     SubmenuIndexDeleteKey, | ||||
|     SubmenuIndexRenameRemote, | ||||
|     SubmenuIndexDeleteRemote, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::MenuSelected; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEdit::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_add_item(submenu, "Add Button", SubmenuIndexAddKey, submenu_callback, app); | ||||
|     submenu_add_item(submenu, "Rename Button", SubmenuIndexRenameKey, submenu_callback, app); | ||||
|     submenu_add_item(submenu, "Delete Button", SubmenuIndexDeleteKey, submenu_callback, app); | ||||
|     submenu_add_item(submenu, "Rename Remote", SubmenuIndexRenameRemote, submenu_callback, app); | ||||
|     submenu_add_item(submenu, "Delete Remote", SubmenuIndexDeleteRemote, submenu_callback, app); | ||||
|     submenu_set_selected_item(submenu, submenu_item_selected); | ||||
|     submenu_item_selected = 0; | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEdit::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::MenuSelected) { | ||||
|         submenu_item_selected = event->payload.menu_index; | ||||
|         switch(event->payload.menu_index) { | ||||
|         case SubmenuIndexAddKey: | ||||
|             app->set_learn_new_remote(false); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Learn); | ||||
|             break; | ||||
|         case SubmenuIndexRenameKey: | ||||
|             app->set_edit_action(InfraredApp::EditAction::Rename); | ||||
|             app->set_edit_element(InfraredApp::EditElement::Button); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect); | ||||
|             break; | ||||
|         case SubmenuIndexDeleteKey: | ||||
|             app->set_edit_action(InfraredApp::EditAction::Delete); | ||||
|             app->set_edit_element(InfraredApp::EditElement::Button); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect); | ||||
|             break; | ||||
|         case SubmenuIndexRenameRemote: | ||||
|             app->set_edit_action(InfraredApp::EditAction::Rename); | ||||
|             app->set_edit_element(InfraredApp::EditElement::Remote); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditRename); | ||||
|             break; | ||||
|         case SubmenuIndexDeleteRemote: | ||||
|             app->set_edit_action(InfraredApp::EditAction::Delete); | ||||
|             app->set_edit_element(InfraredApp::EditElement::Remote); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditDelete); | ||||
|             break; | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEdit::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_reset(submenu); | ||||
| } | ||||
| @ -1,100 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "infrared.h" | ||||
| #include "infrared/scene/infrared_app_scene.h" | ||||
| #include <string> | ||||
| 
 | ||||
| static void dialog_result_callback(DialogExResult result, void* context) { | ||||
|     auto app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::DialogExSelected; | ||||
|     event.payload.dialog_ex_result = result; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditDelete::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     DialogEx* dialog_ex = view_manager->get_dialog_ex(); | ||||
| 
 | ||||
|     auto remote_manager = app->get_remote_manager(); | ||||
| 
 | ||||
|     if(app->get_edit_element() == InfraredApp::EditElement::Button) { | ||||
|         auto signal = remote_manager->get_button_data(app->get_current_button()); | ||||
|         dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop); | ||||
|         if(!signal.is_raw()) { | ||||
|             auto message = &signal.get_message(); | ||||
|             app->set_text_store( | ||||
|                 0, | ||||
|                 "%s\n%s\nA=0x%0*lX C=0x%0*lX", | ||||
|                 remote_manager->get_button_name(app->get_current_button()).c_str(), | ||||
|                 infrared_get_protocol_name(message->protocol), | ||||
|                 ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), | ||||
|                 message->address, | ||||
|                 ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), | ||||
|                 message->command); | ||||
|         } else { | ||||
|             app->set_text_store( | ||||
|                 0, | ||||
|                 "%s\nRAW\n%ld samples", | ||||
|                 remote_manager->get_button_name(app->get_current_button()).c_str(), | ||||
|                 signal.get_raw_signal().timings_cnt); | ||||
|         } | ||||
|     } else { | ||||
|         dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 0, AlignCenter, AlignTop); | ||||
|         app->set_text_store( | ||||
|             0, | ||||
|             "%s\n with %lu buttons", | ||||
|             remote_manager->get_remote_name().c_str(), | ||||
|             remote_manager->get_number_of_buttons()); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_text(dialog_ex, app->get_text_store(0), 64, 31, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Cancel"); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Delete"); | ||||
|     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEditDelete::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::DialogExSelected) { | ||||
|         switch(event->payload.dialog_ex_result) { | ||||
|         case DialogExResultLeft: | ||||
|             app->switch_to_previous_scene(); | ||||
|             break; | ||||
|         case DialogExResultCenter: | ||||
|             furi_assert(0); | ||||
|             break; | ||||
|         case DialogExResultRight: { | ||||
|             auto remote_manager = app->get_remote_manager(); | ||||
|             bool result = false; | ||||
|             if(app->get_edit_element() == InfraredApp::EditElement::Remote) { | ||||
|                 result = remote_manager->delete_remote(); | ||||
|             } else { | ||||
|                 result = remote_manager->delete_button(app->get_current_button()); | ||||
|                 app->set_current_button(InfraredApp::ButtonNA); | ||||
|             } | ||||
| 
 | ||||
|             if(!result) { | ||||
|                 app->search_and_switch_to_previous_scene( | ||||
|                     {InfraredApp::Scene::RemoteList, InfraredApp::Scene::Start}); | ||||
|             } else { | ||||
|                 app->switch_to_next_scene(InfraredApp::Scene::EditDeleteDone); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditDelete::on_exit(InfraredApp*) { | ||||
| } | ||||
| @ -1,38 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| 
 | ||||
| void InfraredAppSceneEditDeleteDone::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Popup* popup = view_manager->get_popup(); | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
| 
 | ||||
|     popup_set_callback(popup, InfraredApp::popup_callback); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_enable_timeout(popup); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEditDeleteDone::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::PopupTimer) { | ||||
|         if(app->get_edit_element() == InfraredApp::EditElement::Remote) { | ||||
|             app->search_and_switch_to_previous_scene( | ||||
|                 {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); | ||||
|         } else { | ||||
|             app->search_and_switch_to_previous_scene({InfraredApp::Scene::Remote}); | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditDeleteDone::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Popup* popup = view_manager->get_popup(); | ||||
|     popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop); | ||||
| } | ||||
| @ -1,58 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "gui/modules/submenu.h" | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::MenuSelected; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditKeySelect::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
|     int item_number = 0; | ||||
| 
 | ||||
|     const char* header = app->get_edit_action() == InfraredApp::EditAction::Rename ? | ||||
|                              "Rename Button:" : | ||||
|                              "Delete Button:"; | ||||
|     submenu_set_header(submenu, header); | ||||
| 
 | ||||
|     auto remote_manager = app->get_remote_manager(); | ||||
|     buttons_names = remote_manager->get_button_list(); | ||||
|     for(const auto& it : buttons_names) { | ||||
|         submenu_add_item(submenu, it.c_str(), item_number++, submenu_callback, app); | ||||
|     } | ||||
|     if((item_number > 0) && (app->get_current_button() != InfraredApp::ButtonNA)) { | ||||
|         submenu_set_selected_item(submenu, app->get_current_button()); | ||||
|         app->set_current_button(InfraredApp::ButtonNA); | ||||
|     } | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEditKeySelect::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::MenuSelected) { | ||||
|         app->set_current_button(event->payload.menu_index); | ||||
|         consumed = true; | ||||
|         if(app->get_edit_action() == InfraredApp::EditAction::Rename) { | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditRename); | ||||
|         } else { | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::EditDelete); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditKeySelect::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_reset(submenu); | ||||
| } | ||||
| @ -1,83 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "m-string.h" | ||||
| #include "toolbox/path.h" | ||||
| 
 | ||||
| void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     TextInput* text_input = view_manager->get_text_input(); | ||||
|     size_t enter_name_length = 0; | ||||
| 
 | ||||
|     auto remote_manager = app->get_remote_manager(); | ||||
|     if(app->get_edit_element() == InfraredApp::EditElement::Button) { | ||||
|         furi_assert(app->get_current_button() != InfraredApp::ButtonNA); | ||||
|         auto button_name = remote_manager->get_button_name(app->get_current_button()); | ||||
|         char* buffer_str = app->get_text_store(0); | ||||
|         size_t max_len = InfraredAppRemoteManager::max_button_name_length; | ||||
|         strncpy(buffer_str, button_name.c_str(), max_len); | ||||
|         buffer_str[max_len + 1] = 0; | ||||
|         enter_name_length = max_len; | ||||
|         text_input_set_header_text(text_input, "Name the button"); | ||||
|     } else { | ||||
|         auto remote_name = remote_manager->get_remote_name(); | ||||
|         strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size()); | ||||
|         enter_name_length = InfraredAppRemoteManager::max_remote_name_length; | ||||
|         text_input_set_header_text(text_input, "Name the remote"); | ||||
| 
 | ||||
|         string_t folder_path; | ||||
|         string_init(folder_path); | ||||
| 
 | ||||
|         if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { | ||||
|             path_extract_dirname(string_get_cstr(app->file_path), folder_path); | ||||
|         } | ||||
| 
 | ||||
|         ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||
|             string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); | ||||
|         text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||
| 
 | ||||
|         string_clear(folder_path); | ||||
|     } | ||||
| 
 | ||||
|     text_input_set_result_callback( | ||||
|         text_input, | ||||
|         InfraredApp::text_input_callback, | ||||
|         app, | ||||
|         app->get_text_store(0), | ||||
|         enter_name_length, | ||||
|         false); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEditRename::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::TextEditDone) { | ||||
|         auto remote_manager = app->get_remote_manager(); | ||||
|         bool result = false; | ||||
|         if(app->get_edit_element() == InfraredApp::EditElement::Button) { | ||||
|             result = | ||||
|                 remote_manager->rename_button(app->get_current_button(), app->get_text_store(0)); | ||||
|             app->set_current_button(InfraredApp::ButtonNA); | ||||
|         } else { | ||||
|             result = remote_manager->rename_remote(app->get_text_store(0)); | ||||
|         } | ||||
|         if(!result) { | ||||
|             app->search_and_switch_to_previous_scene( | ||||
|                 {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); | ||||
|         } else { | ||||
|             app->switch_to_next_scene_without_saving(InfraredApp::Scene::EditRenameDone); | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditRename::on_exit(InfraredApp* app) { | ||||
|     TextInput* text_input = app->get_view_manager()->get_text_input(); | ||||
| 
 | ||||
|     void* validator_context = text_input_get_validator_callback_context(text_input); | ||||
|     text_input_set_validator(text_input, NULL, NULL); | ||||
| 
 | ||||
|     if(validator_context != NULL) validator_is_file_free((ValidatorIsFile*)validator_context); | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| 
 | ||||
| void InfraredAppSceneEditRenameDone::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Popup* popup = view_manager->get_popup(); | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
| 
 | ||||
|     popup_set_callback(popup, InfraredApp::popup_callback); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_enable_timeout(popup); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneEditRenameDone::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::PopupTimer) { | ||||
|         app->switch_to_next_scene(InfraredApp::Scene::Remote); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneEditRenameDone::on_exit(InfraredApp*) { | ||||
| } | ||||
| @ -1,76 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "../infrared_app_event.h" | ||||
| #include "infrared.h" | ||||
| #include <infrared_worker.h> | ||||
| 
 | ||||
| static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { | ||||
|     furi_assert(context); | ||||
|     furi_assert(received_signal); | ||||
| 
 | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
| 
 | ||||
|     if(infrared_worker_signal_is_decoded(received_signal)) { | ||||
|         InfraredAppSignal signal(infrared_worker_get_decoded_signal(received_signal)); | ||||
|         app->set_received_signal(signal); | ||||
|     } else { | ||||
|         const uint32_t* timings; | ||||
|         size_t timings_cnt; | ||||
|         infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); | ||||
|         InfraredAppSignal signal( | ||||
|             timings, timings_cnt, INFRARED_COMMON_CARRIER_FREQUENCY, INFRARED_COMMON_DUTY_CYCLE); | ||||
|         app->set_received_signal(signal); | ||||
|     } | ||||
| 
 | ||||
|     infrared_worker_rx_set_received_signal_callback(app->get_infrared_worker(), NULL, NULL); | ||||
|     InfraredAppEvent event; | ||||
|     event.type = InfraredAppEvent::Type::InfraredMessageReceived; | ||||
|     auto view_manager = app->get_view_manager(); | ||||
|     view_manager->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearn::on_enter(InfraredApp* app) { | ||||
|     auto view_manager = app->get_view_manager(); | ||||
|     auto popup = view_manager->get_popup(); | ||||
| 
 | ||||
|     auto worker = app->get_infrared_worker(); | ||||
|     infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, app); | ||||
|     infrared_worker_rx_start(worker); | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 32, &I_InfraredLearnShort_128x31); | ||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     popup_set_text( | ||||
|         popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); | ||||
|     popup_set_callback(popup, NULL); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneLearn::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     switch(event->type) { | ||||
|     case InfraredAppEvent::Type::Tick: | ||||
|         consumed = true; | ||||
|         app->notify_blink_read(); | ||||
|         break; | ||||
|     case InfraredAppEvent::Type::InfraredMessageReceived: | ||||
|         app->notify_success(); | ||||
|         app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnSuccess); | ||||
|         break; | ||||
|     case InfraredAppEvent::Type::Back: | ||||
|         consumed = true; | ||||
|         app->switch_to_previous_scene(); | ||||
|         break; | ||||
|     default: | ||||
|         furi_assert(0); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearn::on_exit(InfraredApp* app) { | ||||
|     infrared_worker_rx_stop(app->get_infrared_worker()); | ||||
|     auto view_manager = app->get_view_manager(); | ||||
|     auto popup = view_manager->get_popup(); | ||||
|     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
| } | ||||
| @ -1,41 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void InfraredAppSceneLearnDone::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Popup* popup = view_manager->get_popup(); | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     DOLPHIN_DEED(DolphinDeedIrSave); | ||||
| 
 | ||||
|     if(app->get_learn_new_remote()) { | ||||
|         popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop); | ||||
|     } else { | ||||
|         popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
|     } | ||||
| 
 | ||||
|     popup_set_callback(popup, InfraredApp::popup_callback); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_enable_timeout(popup); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneLearnDone::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::PopupTimer) { | ||||
|         app->switch_to_next_scene(InfraredApp::Scene::Remote); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearnDone::on_exit(InfraredApp* app) { | ||||
|     app->set_learn_new_remote(false); | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Popup* popup = view_manager->get_popup(); | ||||
|     popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop); | ||||
| } | ||||
| @ -1,60 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "gui/modules/text_input.h" | ||||
| 
 | ||||
| void InfraredAppSceneLearnEnterName::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     TextInput* text_input = view_manager->get_text_input(); | ||||
| 
 | ||||
|     auto signal = app->get_received_signal(); | ||||
| 
 | ||||
|     if(!signal.is_raw()) { | ||||
|         auto message = &signal.get_message(); | ||||
|         app->set_text_store( | ||||
|             0, | ||||
|             "%.4s_%0*lX", | ||||
|             infrared_get_protocol_name(message->protocol), | ||||
|             ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command); | ||||
|     } else { | ||||
|         auto raw_signal = signal.get_raw_signal(); | ||||
|         app->set_text_store(0, "RAW_%d", raw_signal.timings_cnt); | ||||
|     } | ||||
| 
 | ||||
|     text_input_set_header_text(text_input, "Name the button"); | ||||
|     text_input_set_result_callback( | ||||
|         text_input, | ||||
|         InfraredApp::text_input_callback, | ||||
|         app, | ||||
|         app->get_text_store(0), | ||||
|         InfraredAppRemoteManager::max_button_name_length, | ||||
|         true); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneLearnEnterName::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::TextEditDone) { | ||||
|         auto remote_manager = app->get_remote_manager(); | ||||
|         bool result = false; | ||||
|         if(app->get_learn_new_remote()) { | ||||
|             result = remote_manager->add_remote_with_button( | ||||
|                 app->get_text_store(0), app->get_received_signal()); | ||||
|         } else { | ||||
|             result = | ||||
|                 remote_manager->add_button(app->get_text_store(0), app->get_received_signal()); | ||||
|         } | ||||
| 
 | ||||
|         if(!result) { | ||||
|             app->search_and_switch_to_previous_scene( | ||||
|                 {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); | ||||
|         } else { | ||||
|             app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnDone); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearnEnterName::on_exit(InfraredApp*) { | ||||
| } | ||||
| @ -1,142 +0,0 @@ | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <memory> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| #include "../infrared_app.h" | ||||
| #include "infrared.h" | ||||
| 
 | ||||
| static void dialog_result_callback(DialogExResult result, void* context) { | ||||
|     auto app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::DialogExSelected; | ||||
|     event.payload.dialog_ex_result = result; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearnSuccess::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     DialogEx* dialog_ex = view_manager->get_dialog_ex(); | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedIrLearnSuccess); | ||||
|     app->notify_green_on(); | ||||
| 
 | ||||
|     infrared_worker_tx_set_get_signal_callback( | ||||
|         app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app); | ||||
|     infrared_worker_tx_set_signal_sent_callback( | ||||
|         app->get_infrared_worker(), InfraredApp::signal_sent_callback, app); | ||||
| 
 | ||||
|     auto signal = app->get_received_signal(); | ||||
| 
 | ||||
|     if(!signal.is_raw()) { | ||||
|         auto message = &signal.get_message(); | ||||
|         uint8_t adr_digits = | ||||
|             ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4); | ||||
|         uint8_t cmd_digits = | ||||
|             ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4); | ||||
|         uint8_t max_digits = MAX(adr_digits, cmd_digits); | ||||
|         max_digits = MIN(max_digits, 7); | ||||
|         size_t label_x_offset = 63 + (7 - max_digits) * 3; | ||||
| 
 | ||||
|         app->set_text_store(0, "%s", infrared_get_protocol_name(message->protocol)); | ||||
|         app->set_text_store( | ||||
|             1, | ||||
|             "A: 0x%0*lX\nC: 0x%0*lX\n", | ||||
|             adr_digits, | ||||
|             message->address, | ||||
|             cmd_digits, | ||||
|             message->command); | ||||
| 
 | ||||
|         dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 7, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, app->get_text_store(1), label_x_offset, 34, AlignLeft, AlignCenter); | ||||
|     } else { | ||||
|         dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); | ||||
|         app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt); | ||||
|         dialog_ex_set_text(dialog_ex, app->get_text_store(0), 75, 23, AlignLeft, AlignTop); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Retry"); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Save"); | ||||
|     dialog_ex_set_center_button_text(dialog_ex, "Send"); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); | ||||
|     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_enable_extended_events(dialog_ex); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneLearnSuccess::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
|     if(event->type == InfraredAppEvent::Type::Tick) { | ||||
|         /* Send event every tick to suppress any switching off green light */ | ||||
|         if(!button_pressed) { | ||||
|             app->notify_green_on(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::DialogExSelected) { | ||||
|         switch(event->payload.dialog_ex_result) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = true; | ||||
|             if(!button_pressed) { | ||||
|                 app->switch_to_next_scene_without_saving(InfraredApp::Scene::Learn); | ||||
|             } | ||||
|             break; | ||||
|         case DialogExResultRight: { | ||||
|             consumed = true; | ||||
|             if(!button_pressed) { | ||||
|                 app->switch_to_next_scene(InfraredApp::Scene::LearnEnterName); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         case DialogExPressCenter: | ||||
|             if(!button_pressed) { | ||||
|                 button_pressed = true; | ||||
| 
 | ||||
|                 auto signal = app->get_received_signal(); | ||||
|                 if(signal.is_raw()) { | ||||
|                     infrared_worker_set_raw_signal( | ||||
|                         app->get_infrared_worker(), | ||||
|                         signal.get_raw_signal().timings, | ||||
|                         signal.get_raw_signal().timings_cnt); | ||||
|                 } else { | ||||
|                     infrared_worker_set_decoded_signal( | ||||
|                         app->get_infrared_worker(), &signal.get_message()); | ||||
|                 } | ||||
| 
 | ||||
|                 infrared_worker_tx_start(app->get_infrared_worker()); | ||||
|             } | ||||
|             break; | ||||
|         case DialogExReleaseCenter: | ||||
|             if(button_pressed) { | ||||
|                 button_pressed = false; | ||||
|                 infrared_worker_tx_stop(app->get_infrared_worker()); | ||||
|                 app->notify_green_off(); | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::Back) { | ||||
|         if(!button_pressed) { | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::AskBack); | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneLearnSuccess::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     DialogEx* dialog_ex = view_manager->get_dialog_ex(); | ||||
|     dialog_ex_reset(dialog_ex); | ||||
|     app->notify_green_off(); | ||||
|     infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr); | ||||
|     infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr); | ||||
| } | ||||
| @ -1,131 +0,0 @@ | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <input/input.h> | ||||
| #include <infrared_worker.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| #include "../infrared_app.h" | ||||
| #include "../infrared_app_view_manager.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     ButtonIndexPlus = -2, | ||||
|     ButtonIndexEdit = -1, | ||||
|     ButtonIndexNA = 0, | ||||
| } ButtonIndex; | ||||
| 
 | ||||
| static void button_menu_callback(void* context, int32_t index, InputType type) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     if(type == InputTypePress) { | ||||
|         event.type = InfraredAppEvent::Type::MenuSelectedPress; | ||||
|     } else if(type == InputTypeRelease) { | ||||
|         event.type = InfraredAppEvent::Type::MenuSelectedRelease; | ||||
|     } else if(type == InputTypeShort) { | ||||
|         event.type = InfraredAppEvent::Type::MenuSelected; | ||||
|     } else { | ||||
|         furi_assert(0); | ||||
|     } | ||||
| 
 | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneRemote::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     ButtonMenu* button_menu = view_manager->get_button_menu(); | ||||
|     auto remote_manager = app->get_remote_manager(); | ||||
|     int i = 0; | ||||
|     button_pressed = false; | ||||
| 
 | ||||
|     infrared_worker_tx_set_get_signal_callback( | ||||
|         app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app); | ||||
|     infrared_worker_tx_set_signal_sent_callback( | ||||
|         app->get_infrared_worker(), InfraredApp::signal_sent_callback, app); | ||||
|     buttons_names = remote_manager->get_button_list(); | ||||
| 
 | ||||
|     i = 0; | ||||
|     for(auto& name : buttons_names) { | ||||
|         button_menu_add_item( | ||||
|             button_menu, name.c_str(), i++, button_menu_callback, ButtonMenuItemTypeCommon, app); | ||||
|     } | ||||
| 
 | ||||
|     button_menu_add_item( | ||||
|         button_menu, "+", ButtonIndexPlus, button_menu_callback, ButtonMenuItemTypeControl, app); | ||||
|     button_menu_add_item( | ||||
|         button_menu, "Edit", ButtonIndexEdit, button_menu_callback, ButtonMenuItemTypeControl, app); | ||||
| 
 | ||||
|     app->set_text_store(0, "%s", remote_manager->get_remote_name().c_str()); | ||||
|     button_menu_set_header(button_menu, app->get_text_store(0)); | ||||
|     if(buttonmenu_item_selected != ButtonIndexNA) { | ||||
|         button_menu_set_selected_item(button_menu, buttonmenu_item_selected); | ||||
|         buttonmenu_item_selected = ButtonIndexNA; | ||||
|     } | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = true; | ||||
| 
 | ||||
|     if((event->type == InfraredAppEvent::Type::MenuSelected) || | ||||
|        (event->type == InfraredAppEvent::Type::MenuSelectedPress) || | ||||
|        (event->type == InfraredAppEvent::Type::MenuSelectedRelease)) { | ||||
|         switch(event->payload.menu_index) { | ||||
|         case ButtonIndexPlus: | ||||
|             furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); | ||||
|             buttonmenu_item_selected = event->payload.menu_index; | ||||
|             app->set_learn_new_remote(false); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Learn); | ||||
|             break; | ||||
|         case ButtonIndexEdit: | ||||
|             furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); | ||||
|             buttonmenu_item_selected = event->payload.menu_index; | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Edit); | ||||
|             break; | ||||
|         default: | ||||
|             furi_assert(event->type != InfraredAppEvent::Type::MenuSelected); | ||||
|             bool pressed = (event->type == InfraredAppEvent::Type::MenuSelectedPress); | ||||
| 
 | ||||
|             if(pressed && !button_pressed) { | ||||
|                 button_pressed = true; | ||||
| 
 | ||||
|                 auto button_signal = | ||||
|                     app->get_remote_manager()->get_button_data(event->payload.menu_index); | ||||
|                 if(button_signal.is_raw()) { | ||||
|                     infrared_worker_set_raw_signal( | ||||
|                         app->get_infrared_worker(), | ||||
|                         button_signal.get_raw_signal().timings, | ||||
|                         button_signal.get_raw_signal().timings_cnt); | ||||
|                 } else { | ||||
|                     infrared_worker_set_decoded_signal( | ||||
|                         app->get_infrared_worker(), &button_signal.get_message()); | ||||
|                 } | ||||
| 
 | ||||
|                 DOLPHIN_DEED(DolphinDeedIrSend); | ||||
|                 infrared_worker_tx_start(app->get_infrared_worker()); | ||||
|             } else if(!pressed && button_pressed) { | ||||
|                 button_pressed = false; | ||||
|                 infrared_worker_tx_stop(app->get_infrared_worker()); | ||||
|                 app->notify_green_off(); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } else if(event->type == InfraredAppEvent::Type::Back) { | ||||
|         if(!button_pressed) { | ||||
|             app->search_and_switch_to_previous_scene( | ||||
|                 {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); | ||||
|         } | ||||
|     } else { | ||||
|         consumed = false; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneRemote::on_exit(InfraredApp* app) { | ||||
|     infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr); | ||||
|     infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr); | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     ButtonMenu* button_menu = view_manager->get_button_menu(); | ||||
| 
 | ||||
|     button_menu_reset(button_menu); | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| #include "assets_icons.h" | ||||
| #include "infrared/infrared_app_event.h" | ||||
| #include <text_store.h> | ||||
| 
 | ||||
| void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     bool file_select_result; | ||||
|     auto remote_manager = app->get_remote_manager(); | ||||
|     DialogsApp* dialogs = app->get_dialogs(); | ||||
| 
 | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     ButtonMenu* button_menu = view_manager->get_button_menu(); | ||||
|     button_menu_reset(button_menu); | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); | ||||
| 
 | ||||
|     file_select_result = dialog_file_browser_show( | ||||
|         dialogs, | ||||
|         app->file_path, | ||||
|         app->file_path, | ||||
|         InfraredApp::infrared_extension, | ||||
|         true, | ||||
|         &I_ir_10px, | ||||
|         true); | ||||
| 
 | ||||
|     if(file_select_result) { | ||||
|         if(remote_manager->load(app->file_path)) { | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Remote); | ||||
|             result = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!result) { | ||||
|         app->switch_to_previous_scene(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneRemoteList::on_event(InfraredApp*, InfraredAppEvent*) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneRemoteList::on_exit(InfraredApp*) { | ||||
| } | ||||
| @ -1,68 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuIndexUniversalLibrary, | ||||
|     SubmenuIndexLearnNewRemote, | ||||
|     SubmenuIndexSavedRemotes, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::MenuSelected; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneStart::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_add_item( | ||||
|         submenu, "Universal Library", SubmenuIndexUniversalLibrary, submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); | ||||
|     submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); | ||||
|     submenu_set_selected_item(submenu, submenu_item_selected); | ||||
| 
 | ||||
|     string_set_str(app->file_path, InfraredApp::infrared_directory); | ||||
|     submenu_item_selected = 0; | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneStart::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::MenuSelected) { | ||||
|         submenu_item_selected = event->payload.menu_index; | ||||
|         switch(event->payload.menu_index) { | ||||
|         case SubmenuIndexUniversalLibrary: | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Universal); | ||||
|             break; | ||||
|         case SubmenuIndexLearnNewRemote: | ||||
|             app->set_learn_new_remote(true); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Learn); | ||||
|             break; | ||||
|         case SubmenuIndexSavedRemotes: | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::RemoteList); | ||||
|             break; | ||||
|         default: | ||||
|             furi_assert(0); | ||||
|             break; | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneStart::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     app->get_remote_manager()->reset_remote(); | ||||
|     submenu_reset(submenu); | ||||
| } | ||||
| @ -1,57 +0,0 @@ | ||||
| #include "../infrared_app.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuIndexUniversalTV, | ||||
|     SubmenuIndexUniversalAudio, | ||||
|     SubmenuIndexUniversalAirConditioner, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::MenuSelected; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneUniversal::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_add_item(submenu, "TVs", SubmenuIndexUniversalTV, submenu_callback, app); | ||||
|     submenu_set_selected_item(submenu, submenu_item_selected); | ||||
|     submenu_item_selected = 0; | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneUniversal::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InfraredAppEvent::Type::MenuSelected) { | ||||
|         submenu_item_selected = event->payload.menu_index; | ||||
|         switch(event->payload.menu_index) { | ||||
|         case SubmenuIndexUniversalTV: | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::UniversalTV); | ||||
|             break; | ||||
|         case SubmenuIndexUniversalAudio: | ||||
|             //            app->switch_to_next_scene(InfraredApp::Scene::UniversalAudio);
 | ||||
|             break; | ||||
|         case SubmenuIndexUniversalAirConditioner: | ||||
|             //            app->switch_to_next_scene(InfraredApp::Scene::UniversalAirConditioner);
 | ||||
|             break; | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneUniversal::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     Submenu* submenu = view_manager->get_submenu(); | ||||
| 
 | ||||
|     submenu_reset(submenu); | ||||
| } | ||||
| @ -1,107 +0,0 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| #include <gui/modules/button_menu.h> | ||||
| #include <gui/modules/button_panel.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_stack.h> | ||||
| 
 | ||||
| #include "../infrared_app.h" | ||||
| #include "infrared/infrared_app_event.h" | ||||
| #include "infrared/infrared_app_view_manager.h" | ||||
| #include "infrared/scene/infrared_app_scene.h" | ||||
| #include "../view/infrared_progress_view.h" | ||||
| 
 | ||||
| void InfraredAppSceneUniversalCommon::infrared_app_item_callback(void* context, uint32_t index) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     InfraredAppEvent event; | ||||
| 
 | ||||
|     event.type = InfraredAppEvent::Type::ButtonPanelPressed; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->get_view_manager()->send_event(&event); | ||||
| } | ||||
| 
 | ||||
| static void infrared_progress_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     auto app = static_cast<InfraredApp*>(context); | ||||
| 
 | ||||
|     InfraredAppEvent infrared_event = { | ||||
|         .payload = {.dummy = 0}, | ||||
|         .type = InfraredAppEvent::Type::Back, | ||||
|     }; | ||||
|     app->get_view_manager()->clear_events(); | ||||
|     app->get_view_manager()->send_event(&infrared_event); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneUniversalCommon::hide_popup(InfraredApp* app) { | ||||
|     auto stack_view = app->get_view_manager()->get_universal_view_stack(); | ||||
|     auto progress_view = app->get_view_manager()->get_progress(); | ||||
|     view_stack_remove_view(stack_view, infrared_progress_view_get_view(progress_view)); | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneUniversalCommon::show_popup(InfraredApp* app, int record_amount) { | ||||
|     auto stack_view = app->get_view_manager()->get_universal_view_stack(); | ||||
|     auto progress_view = app->get_view_manager()->get_progress(); | ||||
|     infrared_progress_view_set_progress_total(progress_view, record_amount); | ||||
|     infrared_progress_view_set_back_callback(progress_view, infrared_progress_back_callback, app); | ||||
|     view_stack_add_view(stack_view, infrared_progress_view_get_view(progress_view)); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneUniversalCommon::progress_popup(InfraredApp* app) { | ||||
|     auto progress_view = app->get_view_manager()->get_progress(); | ||||
|     return infrared_progress_view_increase_progress(progress_view); | ||||
| } | ||||
| 
 | ||||
| bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(brute_force_started) { | ||||
|         if(event->type == InfraredAppEvent::Type::Tick) { | ||||
|             auto view_manager = app->get_view_manager(); | ||||
|             app->notify_blink_send(); | ||||
|             InfraredAppEvent tick_event = { | ||||
|                 .payload = {.dummy = 0}, | ||||
|                 .type = InfraredAppEvent::Type::Tick, | ||||
|             }; | ||||
|             view_manager->send_event(&tick_event); | ||||
|             bool result = brute_force.send_next_bruteforce(); | ||||
|             if(result) { | ||||
|                 result = progress_popup(app); | ||||
|             } | ||||
|             if(!result) { | ||||
|                 brute_force.stop_bruteforce(); | ||||
|                 brute_force_started = false; | ||||
|                 hide_popup(app); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event->type == InfraredAppEvent::Type::Back) { | ||||
|             brute_force_started = false; | ||||
|             brute_force.stop_bruteforce(); | ||||
|             hide_popup(app); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } else { | ||||
|         if(event->type == InfraredAppEvent::Type::ButtonPanelPressed) { | ||||
|             int record_amount = 0; | ||||
|             if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) { | ||||
|                 DOLPHIN_DEED(DolphinDeedIrBruteForce); | ||||
|                 brute_force_started = true; | ||||
|                 show_popup(app, record_amount); | ||||
|                 app->notify_blink_send(); | ||||
|             } else { | ||||
|                 app->switch_to_previous_scene(); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event->type == InfraredAppEvent::Type::Back) { | ||||
|             app->switch_to_previous_scene(); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void InfraredAppSceneUniversalCommon::on_exit(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     ButtonPanel* button_panel = view_manager->get_button_panel(); | ||||
|     button_panel_reset(button_panel); | ||||
| } | ||||
| @ -1,123 +0,0 @@ | ||||
| #include <stdint.h> | ||||
| #include <gui/modules/loading.h> | ||||
| #include <gui/view_stack.h> | ||||
| #include "infrared/scene/infrared_app_scene.h" | ||||
| #include "infrared/infrared_app.h" | ||||
| 
 | ||||
| void InfraredAppSceneUniversalTV::on_enter(InfraredApp* app) { | ||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||
|     ButtonPanel* button_panel = view_manager->get_button_panel(); | ||||
|     button_panel_reserve(button_panel, 2, 3); | ||||
| 
 | ||||
|     int i = 0; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         0, | ||||
|         0, | ||||
|         3, | ||||
|         19, | ||||
|         &I_Power_25x27, | ||||
|         &I_Power_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "POWER"); | ||||
|     ++i; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         1, | ||||
|         0, | ||||
|         36, | ||||
|         19, | ||||
|         &I_Mute_25x27, | ||||
|         &I_Mute_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "MUTE"); | ||||
|     ++i; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         0, | ||||
|         1, | ||||
|         3, | ||||
|         66, | ||||
|         &I_Vol_up_25x27, | ||||
|         &I_Vol_up_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "VOL+"); | ||||
|     ++i; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         1, | ||||
|         1, | ||||
|         36, | ||||
|         66, | ||||
|         &I_Up_25x27, | ||||
|         &I_Up_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "CH+"); | ||||
|     ++i; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         0, | ||||
|         2, | ||||
|         3, | ||||
|         98, | ||||
|         &I_Vol_down_25x27, | ||||
|         &I_Vol_down_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "VOL-"); | ||||
|     ++i; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         1, | ||||
|         2, | ||||
|         36, | ||||
|         98, | ||||
|         &I_Down_25x27, | ||||
|         &I_Down_hvr_25x27, | ||||
|         infrared_app_item_callback, | ||||
|         app); | ||||
|     brute_force.add_record(i, "CH-"); | ||||
| 
 | ||||
|     button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote"); | ||||
|     button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); | ||||
|     button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch"); | ||||
| 
 | ||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::UniversalRemote); | ||||
| 
 | ||||
|     auto stack_view = app->get_view_manager()->get_universal_view_stack(); | ||||
|     auto loading_view = app->get_view_manager()->get_loading(); | ||||
|     view_stack_add_view(stack_view, loading_get_view(loading_view)); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Problem: Update events are not handled in Loading View, because: | ||||
|      * 1) Timer task has least prio | ||||
|      * 2) Storage service uses drivers that capture whole CPU time | ||||
|      *      to handle SD communication | ||||
|      * | ||||
|      * Ugly workaround, but it works for current situation: | ||||
|      * raise timer task prio for DB scanning period. | ||||
|      */ | ||||
|     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); | ||||
|     TaskHandle_t storage_task = xTaskGetHandle("StorageSrv"); | ||||
|     uint32_t timer_prio = uxTaskPriorityGet(timer_task); | ||||
|     uint32_t storage_prio = uxTaskPriorityGet(storage_task); | ||||
|     vTaskPrioritySet(timer_task, storage_prio + 1); | ||||
|     bool result = brute_force.calculate_messages(); | ||||
|     vTaskPrioritySet(timer_task, timer_prio); | ||||
| 
 | ||||
|     view_stack_remove_view(stack_view, loading_get_view(loading_view)); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         app->switch_to_previous_scene(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,92 @@ | ||||
| #include "../../infrared_i.h" | ||||
| 
 | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { | ||||
|     Infrared* infrared = context; | ||||
|     uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); | ||||
|     view_dispatcher_send_custom_event(infrared->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| static void infrared_scene_universal_common_progress_back_callback(void* context) { | ||||
|     Infrared* infrared = context; | ||||
|     uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1); | ||||
|     view_dispatcher_send_custom_event(infrared->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) { | ||||
|     ViewStack* view_stack = infrared->view_stack; | ||||
|     InfraredProgressView* progress = infrared->progress; | ||||
|     infrared_progress_view_set_progress_total(progress, record_count); | ||||
|     infrared_progress_view_set_back_callback( | ||||
|         progress, infrared_scene_universal_common_progress_back_callback, infrared); | ||||
|     view_stack_add_view(view_stack, infrared_progress_view_get_view(progress)); | ||||
| } | ||||
| 
 | ||||
| static void infrared_scene_universal_common_hide_popup(Infrared* infrared) { | ||||
|     ViewStack* view_stack = infrared->view_stack; | ||||
|     InfraredProgressView* progress = infrared->progress; | ||||
|     view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress)); | ||||
| } | ||||
| 
 | ||||
| void infrared_scene_universal_common_on_enter(void* context) { | ||||
|     Infrared* infrared = context; | ||||
|     view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); | ||||
| } | ||||
| 
 | ||||
| bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { | ||||
|     Infrared* infrared = context; | ||||
|     SceneManager* scene_manager = infrared->scene_manager; | ||||
|     InfraredBruteForce* brute_force = infrared->brute_force; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(infrared_brute_force_is_started(brute_force)) { | ||||
|         if(event.type == SceneManagerEventTypeTick) { | ||||
|             infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend); | ||||
|             bool success = infrared_brute_force_send_next(brute_force); | ||||
|             if(success) { | ||||
|                 success = infrared_progress_view_increase_progress(infrared->progress); | ||||
|             } | ||||
|             if(!success) { | ||||
|                 infrared_brute_force_stop(brute_force); | ||||
|                 infrared_scene_universal_common_hide_popup(infrared); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event.type == SceneManagerEventTypeCustom) { | ||||
|             if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { | ||||
|                 infrared_brute_force_stop(brute_force); | ||||
|                 infrared_scene_universal_common_hide_popup(infrared); | ||||
|                 consumed = true; | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         if(event.type == SceneManagerEventTypeBack) { | ||||
|             scene_manager_previous_scene(scene_manager); | ||||
|             consumed = true; | ||||
|         } else if(event.type == SceneManagerEventTypeCustom) { | ||||
|             if(infrared_custom_event_get_type(event.event) == | ||||
|                InfraredCustomEventTypeButtonSelected) { | ||||
|                 uint32_t record_count; | ||||
|                 if(infrared_brute_force_start( | ||||
|                        brute_force, infrared_custom_event_get_value(event.event), &record_count)) { | ||||
|                     DOLPHIN_DEED(DolphinDeedIrBruteForce); | ||||
|                     infrared_scene_universal_common_show_popup(infrared, record_count); | ||||
|                     infrared_play_notification_message( | ||||
|                         infrared, InfraredNotificationMessageBlinkSend); | ||||
|                 } else { | ||||
|                     scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void infrared_scene_universal_common_on_exit(void* context) { | ||||
|     Infrared* infrared = context; | ||||
|     ButtonPanel* button_panel = infrared->button_panel; | ||||
|     view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel)); | ||||
|     button_panel_reset(button_panel); | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| void infrared_scene_universal_common_on_enter(void* context); | ||||
| bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event); | ||||
| void infrared_scene_universal_common_on_exit(void* context); | ||||
| void infrared_scene_universal_common_item_callback(void* context, uint32_t index); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov