Add software
This commit is contained in:
		
							
								
								
									
										82
									
								
								Software/portapack-mayhem/firmware/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								Software/portapack-mayhem/firmware/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
# Copyright 2016 Jared Boone <jared@sharebrained.com>
 | 
			
		||||
#
 | 
			
		||||
# This file is part of PortaPack.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
# any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
# the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
# Boston, MA 02110-1301, USA.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
project(firmware)
 | 
			
		||||
 | 
			
		||||
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
 | 
			
		||||
set(COMMON ${PROJECT_SOURCE_DIR}/common)
 | 
			
		||||
set(CHIBIOS ${PROJECT_SOURCE_DIR}/chibios)
 | 
			
		||||
set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
 | 
			
		||||
 | 
			
		||||
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)
 | 
			
		||||
set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
 | 
			
		||||
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
 | 
			
		||||
 | 
			
		||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
 | 
			
		||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(application)
 | 
			
		||||
add_subdirectory(baseband)
 | 
			
		||||
 | 
			
		||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${FIRMWARE_FILENAME}
 | 
			
		||||
	COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME}
 | 
			
		||||
	DEPENDS baseband application ${MAKE_SPI_IMAGE}
 | 
			
		||||
		 ${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
 | 
			
		||||
	VERBATIM
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(
 | 
			
		||||
	firmware ALL
 | 
			
		||||
	DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(
 | 
			
		||||
	program
 | 
			
		||||
	COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE}
 | 
			
		||||
	COMMAND sleep 3s
 | 
			
		||||
	COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
 | 
			
		||||
	DEPENDS ${FIRMWARE_FILENAME}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# TODO: Bad hack to fix location of LICENSE file for tar.
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
 | 
			
		||||
	COMMAND cp ${LICENSE_PATH} LICENSE
 | 
			
		||||
	COMMAND cp ${HACKRF_FIRMWARE_DFU_IMAGE} ${HACKRF_FIRMWARE_DFU_FILENAME}
 | 
			
		||||
	COMMAND tar -c -j -f ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} LICENSE
 | 
			
		||||
	COMMAND zip -9 -q ${FIRMWARE_NAME}-${VERSION}.zip ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} LICENSE
 | 
			
		||||
	COMMAND rm -f LICENSE ${HACKRF_FIRMWARE_DFU_FILENAME}
 | 
			
		||||
	DEPENDS ${FIRMWARE_FILENAME} ${LICENSE_PATH} ${HACKRF_FIRMWARE_DFU_FILENAME}
 | 
			
		||||
	VERBATIM
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT MD5SUMS SHA256SUMS
 | 
			
		||||
	COMMAND md5sum --binary ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip >MD5SUMS
 | 
			
		||||
	COMMAND sha256sum --binary ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip >SHA256SUMS
 | 
			
		||||
	DEPENDS ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(
 | 
			
		||||
	release
 | 
			
		||||
	DEPENDS MD5SUMS SHA256SUMS
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										444
									
								
								Software/portapack-mayhem/firmware/application/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								Software/portapack-mayhem/firmware/application/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,444 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
# Copyright (C) 2016 Furrtek
 | 
			
		||||
#
 | 
			
		||||
# This file is part of PortaPack.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
# any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
# the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
# Boston, MA 02110-1301, USA.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Build global options
 | 
			
		||||
# NOTE: Can be overridden externally.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
enable_language(C CXX ASM)
 | 
			
		||||
 | 
			
		||||
project(application)
 | 
			
		||||
 | 
			
		||||
# Compiler options here.
 | 
			
		||||
set(USE_OPT "-Os -g --specs=nano.specs")
 | 
			
		||||
 | 
			
		||||
# C specific options here (added to USE_OPT).
 | 
			
		||||
set(USE_COPT "-std=gnu99")
 | 
			
		||||
 | 
			
		||||
# C++ specific options here (added to USE_OPT).
 | 
			
		||||
set(USE_CPPOPT "-std=c++17 -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized")
 | 
			
		||||
 | 
			
		||||
# Enable this if you want the linker to remove unused code and data
 | 
			
		||||
set(USE_LINK_GC yes)
 | 
			
		||||
 | 
			
		||||
# Linker extra options here.
 | 
			
		||||
set(USE_LDOPT)
 | 
			
		||||
 | 
			
		||||
# Enable this if you want link time optimizations (LTO)
 | 
			
		||||
set(USE_LTO no)
 | 
			
		||||
 | 
			
		||||
# If enabled, this option allows to compile the application in THUMB mode.
 | 
			
		||||
set(USE_THUMB yes)
 | 
			
		||||
 | 
			
		||||
# Enable this if you want to see the full log while compiling.
 | 
			
		||||
set(USE_VERBOSE_COMPILE no)
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Build global options
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Architecture or project specific options
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Enables the use of FPU on Cortex-M4 (no, softfp, hard).
 | 
			
		||||
set(USE_FPU no)
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Architecture or project specific options
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Project, sources and paths
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
set(CPLD_20150901_SVF_PATH ${HARDWARE_PATH}/portapack_h1/cpld/20150901/output_files/portapack_h1_cpld.svf)
 | 
			
		||||
set(CPLD_20150901_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20150901_data.cpp)
 | 
			
		||||
 | 
			
		||||
set(CPLD_20170522_SVF_PATH ${HARDWARE_PATH}/portapack_h1/cpld/20170522/output_files/portapack_h1_cpld.svf)
 | 
			
		||||
set(CPLD_20170522_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20170522_data.cpp)
 | 
			
		||||
 | 
			
		||||
set(HACKRF_CPLD_DATA_HPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.hpp)
 | 
			
		||||
set(HACKRF_CPLD_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.cpp)
 | 
			
		||||
 | 
			
		||||
# Imported source files and paths
 | 
			
		||||
include(${CHIBIOS_PORTAPACK}/boards/PORTAPACK_APPLICATION/board.cmake)
 | 
			
		||||
include(${CHIBIOS_PORTAPACK}/os/hal/platforms/LPC43xx_M0/platform.cmake)
 | 
			
		||||
include(${CHIBIOS}/os/hal/hal.cmake)
 | 
			
		||||
include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
 | 
			
		||||
include(${CHIBIOS}/os/kernel/kernel.cmake)
 | 
			
		||||
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
 | 
			
		||||
include(${CHIBIOS}/test/test.cmake)
 | 
			
		||||
 | 
			
		||||
# Define linker script file here
 | 
			
		||||
set(LDSCRIPT ${PORTLD}/LPC43xx_M0.ld)
 | 
			
		||||
 | 
			
		||||
# C sources that can be compiled in ARM or THUMB mode depending on the global
 | 
			
		||||
# setting.
 | 
			
		||||
set(CSRC
 | 
			
		||||
	${PORTSRC}
 | 
			
		||||
	${KERNSRC}
 | 
			
		||||
	${TESTSRC}
 | 
			
		||||
	${HALSRC}
 | 
			
		||||
	${PLATFORMSRC}
 | 
			
		||||
	${BOARDSRC}
 | 
			
		||||
	${FATFSSRC}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
 | 
			
		||||
# setting.
 | 
			
		||||
set(CPPSRC
 | 
			
		||||
	main.cpp
 | 
			
		||||
	${COMMON}/acars_packet.cpp
 | 
			
		||||
	${COMMON}/adsb.cpp
 | 
			
		||||
	${COMMON}/adsb_frame.cpp
 | 
			
		||||
	${COMMON}/ais_baseband.cpp
 | 
			
		||||
	${COMMON}/ais_packet.cpp
 | 
			
		||||
	${COMMON}/ak4951.cpp
 | 
			
		||||
	${COMMON}/backlight.cpp
 | 
			
		||||
	${COMMON}/baseband_cpld.cpp
 | 
			
		||||
	${COMMON}/bch_code.cpp
 | 
			
		||||
	${COMMON}/buffer.cpp
 | 
			
		||||
	${COMMON}/buffer_exchange.cpp
 | 
			
		||||
	${COMMON}/chibios_cpp.cpp
 | 
			
		||||
	${COMMON}/cpld_max5.cpp
 | 
			
		||||
	${COMMON}/cpld_update.cpp
 | 
			
		||||
	${COMMON}/cpld_xilinx.cpp
 | 
			
		||||
	${COMMON}/debug.cpp
 | 
			
		||||
	${COMMON}/ert_packet.cpp
 | 
			
		||||
	${COMMON}/event.cpp
 | 
			
		||||
	${COMMON}/gcc.cpp
 | 
			
		||||
	${COMMON}/hackrf_hal.cpp
 | 
			
		||||
	${COMMON}/i2c_pp.cpp
 | 
			
		||||
	${COMMON}/jtag.cpp
 | 
			
		||||
	${COMMON}/jtag_tap.cpp
 | 
			
		||||
	${COMMON}/lcd_ili9341.cpp
 | 
			
		||||
	${COMMON}/lfsr_random.cpp
 | 
			
		||||
	${COMMON}/manchester.cpp
 | 
			
		||||
	${COMMON}/message_queue.cpp
 | 
			
		||||
	${COMMON}/morse.cpp
 | 
			
		||||
	${COMMON}/png_writer.cpp
 | 
			
		||||
	${COMMON}/pocsag.cpp
 | 
			
		||||
	${COMMON}/pocsag_packet.cpp
 | 
			
		||||
	${COMMON}/aprs_packet.cpp
 | 
			
		||||
	${COMMON}/portapack_io.cpp
 | 
			
		||||
	${COMMON}/portapack_persistent_memory.cpp
 | 
			
		||||
	${COMMON}/portapack_shared_memory.cpp
 | 
			
		||||
	${COMMON}/sonde_packet.cpp
 | 
			
		||||
	# ${COMMON}/test_packet.cpp
 | 
			
		||||
	${COMMON}/tpms_packet.cpp
 | 
			
		||||
	${COMMON}/ui.cpp
 | 
			
		||||
	${COMMON}/ui_focus.cpp
 | 
			
		||||
	${COMMON}/ui_painter.cpp
 | 
			
		||||
	${COMMON}/ui_text.cpp
 | 
			
		||||
	${COMMON}/ui_widget.cpp
 | 
			
		||||
	${COMMON}/utility.cpp
 | 
			
		||||
	${COMMON}/wm8731.cpp
 | 
			
		||||
	app_settings.cpp
 | 
			
		||||
	audio.cpp
 | 
			
		||||
	baseband_api.cpp
 | 
			
		||||
	capture_thread.cpp
 | 
			
		||||
	clock_manager.cpp
 | 
			
		||||
	core_control.cpp
 | 
			
		||||
	database.cpp
 | 
			
		||||
	de_bruijn.cpp
 | 
			
		||||
	#emu_cc1101.cpp
 | 
			
		||||
	rfm69.cpp
 | 
			
		||||
	event_m0.cpp
 | 
			
		||||
	file.cpp
 | 
			
		||||
	freqman.cpp
 | 
			
		||||
	io_file.cpp
 | 
			
		||||
	io_wave.cpp
 | 
			
		||||
	irq_controls.cpp
 | 
			
		||||
	irq_lcd_frame.cpp
 | 
			
		||||
	irq_rtc.cpp
 | 
			
		||||
	log_file.cpp
 | 
			
		||||
	portapack.cpp
 | 
			
		||||
	qrcodegen.cpp
 | 
			
		||||
	radio.cpp
 | 
			
		||||
	receiver_model.cpp
 | 
			
		||||
	recent_entries.cpp
 | 
			
		||||
	replay_thread.cpp
 | 
			
		||||
	rf_path.cpp
 | 
			
		||||
	rtc_time.cpp
 | 
			
		||||
	sd_card.cpp
 | 
			
		||||
	serializer.cpp
 | 
			
		||||
	spectrum_color_lut.cpp
 | 
			
		||||
	string_format.cpp
 | 
			
		||||
	temperature_logger.cpp
 | 
			
		||||
	touch.cpp
 | 
			
		||||
	tone_key.cpp
 | 
			
		||||
	transmitter_model.cpp
 | 
			
		||||
	tuning.cpp
 | 
			
		||||
	hw/debounce.cpp
 | 
			
		||||
	hw/encoder.cpp
 | 
			
		||||
	hw/max2837.cpp
 | 
			
		||||
	hw/max5864.cpp
 | 
			
		||||
	hw/rffc507x.cpp
 | 
			
		||||
	hw/rffc507x_spi.cpp
 | 
			
		||||
	hw/si5351.cpp
 | 
			
		||||
	hw/spi_pp.cpp
 | 
			
		||||
	hw/touch_adc.cpp
 | 
			
		||||
	ui_baseband_stats_view.cpp
 | 
			
		||||
	ui_navigation.cpp
 | 
			
		||||
	ui_playdead.cpp
 | 
			
		||||
	ui_record_view.cpp
 | 
			
		||||
	ui_sd_card_status_view.cpp
 | 
			
		||||
	ui/ui_alphanum.cpp
 | 
			
		||||
	ui/ui_audio.cpp
 | 
			
		||||
	ui/ui_channel.cpp
 | 
			
		||||
	ui/ui_font_fixed_8x16.cpp
 | 
			
		||||
	ui/ui_geomap.cpp
 | 
			
		||||
        ui/ui_qrcode.cpp
 | 
			
		||||
	ui/ui_menu.cpp
 | 
			
		||||
	ui/ui_btngrid.cpp
 | 
			
		||||
	ui/ui_receiver.cpp
 | 
			
		||||
	ui/ui_rssi.cpp
 | 
			
		||||
	ui/ui_tv.cpp
 | 
			
		||||
	ui/ui_spectrum.cpp
 | 
			
		||||
	ui/ui_tabview.cpp
 | 
			
		||||
	ui/ui_textentry.cpp
 | 
			
		||||
	ui/ui_transmitter.cpp
 | 
			
		||||
	apps/ui_about_simple.cpp
 | 
			
		||||
	apps/ui_adsb_rx.cpp
 | 
			
		||||
	apps/ui_adsb_tx.cpp
 | 
			
		||||
	apps/ui_afsk_rx.cpp
 | 
			
		||||
	apps/ui_aprs_rx.cpp
 | 
			
		||||
	apps/ui_btle_rx.cpp
 | 
			
		||||
	apps/ui_nrf_rx.cpp
 | 
			
		||||
	apps/ui_aprs_tx.cpp
 | 
			
		||||
	apps/ui_bht_tx.cpp
 | 
			
		||||
	apps/ui_coasterp.cpp
 | 
			
		||||
	apps/ui_debug.cpp
 | 
			
		||||
	apps/ui_encoders.cpp
 | 
			
		||||
	apps/ui_fileman.cpp
 | 
			
		||||
	apps/ui_freqman.cpp
 | 
			
		||||
	apps/ui_jammer.cpp
 | 
			
		||||
	apps/ui_keyfob.cpp
 | 
			
		||||
	apps/ui_lcr.cpp
 | 
			
		||||
	apps/lge_app.cpp
 | 
			
		||||
	apps/ui_looking_glass_app.cpp
 | 
			
		||||
	apps/ui_mictx.cpp
 | 
			
		||||
	apps/ui_modemsetup.cpp
 | 
			
		||||
	apps/ui_morse.cpp
 | 
			
		||||
	# apps/ui_nuoptix.cpp
 | 
			
		||||
	apps/ui_pocsag_tx.cpp
 | 
			
		||||
	apps/ui_rds.cpp
 | 
			
		||||
	apps/ui_remote.cpp
 | 
			
		||||
	apps/ui_scanner.cpp
 | 
			
		||||
	apps/ui_search.cpp
 | 
			
		||||
	apps/ui_sd_wipe.cpp
 | 
			
		||||
	apps/ui_settings.cpp
 | 
			
		||||
	apps/ui_siggen.cpp
 | 
			
		||||
	apps/ui_sonde.cpp
 | 
			
		||||
	apps/ui_sstvtx.cpp
 | 
			
		||||
	# apps/ui_test.cpp
 | 
			
		||||
	apps/ui_tone_search.cpp
 | 
			
		||||
	apps/ui_touch_calibration.cpp
 | 
			
		||||
	apps/ui_touchtunes.cpp
 | 
			
		||||
	apps/ui_view_wav.cpp
 | 
			
		||||
	apps/ui_whipcalc.cpp
 | 
			
		||||
	apps/acars_app.cpp
 | 
			
		||||
	apps/ais_app.cpp
 | 
			
		||||
	apps/analog_audio_app.cpp
 | 
			
		||||
	apps/analog_tv_app.cpp
 | 
			
		||||
	apps/capture_app.cpp
 | 
			
		||||
	apps/ert_app.cpp
 | 
			
		||||
	apps/lge_app.cpp
 | 
			
		||||
	apps/pocsag_app.cpp
 | 
			
		||||
	apps/replay_app.cpp
 | 
			
		||||
	apps/ui_playlist.cpp
 | 
			
		||||
	apps/gps_sim_app.cpp
 | 
			
		||||
	apps/soundboard_app.cpp
 | 
			
		||||
	apps/tpms_app.cpp
 | 
			
		||||
	protocols/aprs.cpp
 | 
			
		||||
	protocols/ax25.cpp
 | 
			
		||||
	protocols/bht.cpp
 | 
			
		||||
	protocols/dcs.cpp
 | 
			
		||||
	protocols/encoders.cpp
 | 
			
		||||
	protocols/lcr.cpp
 | 
			
		||||
	protocols/modems.cpp
 | 
			
		||||
	protocols/rds.cpp
 | 
			
		||||
	# ui_handwrite.cpp
 | 
			
		||||
	# ui_loadmodule.cpp
 | 
			
		||||
	# ui_numbers.cpp
 | 
			
		||||
	# ui_replay_view.cpp
 | 
			
		||||
	# ui_script.cpp
 | 
			
		||||
	ui_sd_card_debug.cpp
 | 
			
		||||
	${CPLD_20150901_DATA_CPP}
 | 
			
		||||
	${CPLD_20170522_DATA_CPP}
 | 
			
		||||
	${HACKRF_CPLD_DATA_CPP}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# C sources to be compiled in ARM mode regardless of the global setting.
 | 
			
		||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
 | 
			
		||||
#       option that results in lower performance and larger code size.
 | 
			
		||||
set(ACSRC)
 | 
			
		||||
 | 
			
		||||
# C++ sources to be compiled in ARM mode regardless of the global setting.
 | 
			
		||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
 | 
			
		||||
#       option that results in lower performance and larger code size.
 | 
			
		||||
set(ACPPSRC)
 | 
			
		||||
 | 
			
		||||
# C sources to be compiled in THUMB mode regardless of the global setting.
 | 
			
		||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
 | 
			
		||||
#       option that results in lower performance and larger code size.
 | 
			
		||||
set(TCSRC)
 | 
			
		||||
 | 
			
		||||
# C sources to be compiled in THUMB mode regardless of the global setting.
 | 
			
		||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
 | 
			
		||||
#       option that results in lower performance and larger code size.
 | 
			
		||||
set(TCPPSRC)
 | 
			
		||||
 | 
			
		||||
# List ASM source files here
 | 
			
		||||
set(ASMSRC ${PORTASM})
 | 
			
		||||
 | 
			
		||||
set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC}
 | 
			
		||||
	${HALINC} ${PLATFORMINC} ${BOARDINC}
 | 
			
		||||
	${FATFSINC}
 | 
			
		||||
	${CHIBIOS}/os/various
 | 
			
		||||
	ui
 | 
			
		||||
	hw
 | 
			
		||||
	apps
 | 
			
		||||
	protocols
 | 
			
		||||
	bitmaps
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Project, sources and paths
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Compiler settings
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# TODO: Entertain using MCU=cortex-m0.small-multiply for LPC43xx M0 core.
 | 
			
		||||
# However, on GCC-ARM-Embedded 4.9 2015q2, it seems to produce non-functional
 | 
			
		||||
# binaries.
 | 
			
		||||
set(MCU cortex-m0)
 | 
			
		||||
 | 
			
		||||
# ARM-specific options here
 | 
			
		||||
set(AOPT)
 | 
			
		||||
 | 
			
		||||
# THUMB-specific options here
 | 
			
		||||
set(TOPT "-mthumb -DTHUMB")
 | 
			
		||||
 | 
			
		||||
# Define C warning options here
 | 
			
		||||
set(CWARN "-Wall -Wextra -Wstrict-prototypes")
 | 
			
		||||
 | 
			
		||||
# Define C++ warning options here
 | 
			
		||||
set(CPPWARN "-Wall -Wextra -Wno-psabi")
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Compiler settings
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Start of default section
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# List all default C defines here, like -D_DEBUG=1
 | 
			
		||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
 | 
			
		||||
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
 | 
			
		||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"'")
 | 
			
		||||
 | 
			
		||||
# List all default ASM defines here, like -D_DEBUG=1
 | 
			
		||||
set(DADEFS)
 | 
			
		||||
 | 
			
		||||
# List all default directories to look for include files here
 | 
			
		||||
set(DINCDIR)
 | 
			
		||||
 | 
			
		||||
# List the default directory to look for the libraries here
 | 
			
		||||
set(DLIBDIR)
 | 
			
		||||
 | 
			
		||||
# List all default libraries here
 | 
			
		||||
set(DLIBS)
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# End of default section
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Start of user section
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# List all user C define here, like -D_DEBUG=1
 | 
			
		||||
set(UDEFS)
 | 
			
		||||
 | 
			
		||||
# Define ASM defines here
 | 
			
		||||
set(UADEFS)
 | 
			
		||||
 | 
			
		||||
# List all user directories here
 | 
			
		||||
set(UINCDIR)
 | 
			
		||||
 | 
			
		||||
# List the user directory to look for the libraries here
 | 
			
		||||
set(ULIBDIR)
 | 
			
		||||
 | 
			
		||||
# List all user libraries here
 | 
			
		||||
set(ULIBS)
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# End of user defines
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
set(RULESPATH ${CHIBIOS}/os/ports/GCC/ARMCMx)
 | 
			
		||||
include(${RULESPATH}/rules.cmake)
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${CPLD_20150901_DATA_CPP}
 | 
			
		||||
	COMMAND ${EXTRACT_CPLD_DATA} ${CPLD_20150901_SVF_PATH} rev_20150901 >${CPLD_20150901_DATA_CPP}
 | 
			
		||||
	DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_20150901_SVF_PATH}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${CPLD_20170522_DATA_CPP}
 | 
			
		||||
	COMMAND ${EXTRACT_CPLD_DATA} ${CPLD_20170522_SVF_PATH} rev_20170522 >${CPLD_20170522_DATA_CPP}
 | 
			
		||||
	DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_20170522_SVF_PATH}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${HACKRF_CPLD_DATA_CPP}
 | 
			
		||||
	COMMAND ${HACKRF_CPLD_TOOL} --xsvf ${HACKRF_CPLD_XSVF_PATH} --portapack-data ${HACKRF_CPLD_DATA_CPP}
 | 
			
		||||
	DEPENDS ${HACKRF_CPLD_TOOL} ${HACKRF_CPLD_XSVF_PATH}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
 | 
			
		||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
 | 
			
		||||
add_definitions(${DEFS})
 | 
			
		||||
include_directories(. ${INCDIR})
 | 
			
		||||
link_directories(${LLIBDIR})
 | 
			
		||||
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
 | 
			
		||||
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
	OUTPUT ${PROJECT_NAME}.bin
 | 
			
		||||
	COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
 | 
			
		||||
	DEPENDS ${PROJECT_NAME}.elf
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(
 | 
			
		||||
	${PROJECT_NAME}
 | 
			
		||||
	DEPENDS ${PROJECT_NAME}.bin
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										102
									
								
								Software/portapack-mayhem/firmware/application/app_settings.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Software/portapack-mayhem/firmware/application/app_settings.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyright (C) 2022 Arjan Onwezen
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace std {
 | 
			
		||||
 | 
			
		||||
int app_settings::load(std::string application, AppSettings* settings) {
 | 
			
		||||
 | 
			
		||||
	if (portapack::persistent_memory::load_app_settings()) {
 | 
			
		||||
		file_path = folder+"/"+application+".ini";
 | 
			
		||||
 | 
			
		||||
		auto error = settings_file.open(file_path);
 | 
			
		||||
		if (!error.is_valid()) {
 | 
			
		||||
			auto error = settings_file.read(file_content, std::min((int)settings_file.size(), MAX_FILE_CONTENT_SIZE));
 | 
			
		||||
 | 
			
		||||
			settings->baseband_bandwidth=std::app_settings::read_long_long(file_content, "baseband_bandwidth=");
 | 
			
		||||
			settings->channel_bandwidth=std::app_settings::read_long_long(file_content, "channel_bandwidth=");
 | 
			
		||||
			settings->lna=std::app_settings::read_long_long(file_content, "lna=");
 | 
			
		||||
			settings->modulation=std::app_settings::read_long_long(file_content, "modulation=");
 | 
			
		||||
			settings->rx_amp=std::app_settings::read_long_long(file_content, "rx_amp=");
 | 
			
		||||
			settings->rx_frequency=std::app_settings::read_long_long(file_content, "rx_frequency=");
 | 
			
		||||
			settings->sampling_rate=std::app_settings::read_long_long(file_content, "sampling_rate=");
 | 
			
		||||
			settings->vga=std::app_settings::read_long_long(file_content, "vga=");
 | 
			
		||||
			settings->tx_amp=std::app_settings::read_long_long(file_content, "tx_amp=");
 | 
			
		||||
			settings->tx_frequency=std::app_settings::read_long_long(file_content, "tx_frequency=");
 | 
			
		||||
			settings->tx_gain=std::app_settings::read_long_long(file_content, "tx_gain=");
 | 
			
		||||
			rc = SETTINGS_OK;
 | 
			
		||||
		}
 | 
			
		||||
		else rc = SETTINGS_UNABLE_TO_LOAD;
 | 
			
		||||
	}
 | 
			
		||||
	else rc = SETTINGS_DISABLED;
 | 
			
		||||
	return(rc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int app_settings::save(std::string application, AppSettings* settings) {
 | 
			
		||||
 | 
			
		||||
	if (portapack::persistent_memory::save_app_settings()) {        
 | 
			
		||||
		file_path = folder+"/"+application+".ini";
 | 
			
		||||
		make_new_directory(folder);
 | 
			
		||||
 | 
			
		||||
		auto error = settings_file.create(file_path);
 | 
			
		||||
		if (!error.is_valid()) {
 | 
			
		||||
			// Save common setting
 | 
			
		||||
			settings_file.write_line("baseband_bandwidth="+to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()));
 | 
			
		||||
			settings_file.write_line("channel_bandwidth="+to_string_dec_uint(portapack::transmitter_model.channel_bandwidth()));
 | 
			
		||||
			settings_file.write_line("lna="+to_string_dec_uint(portapack::receiver_model.lna()));
 | 
			
		||||
			settings_file.write_line("rx_amp="+to_string_dec_uint(portapack::receiver_model.rf_amp()));
 | 
			
		||||
			settings_file.write_line("sampling_rate="+to_string_dec_uint(portapack::receiver_model.sampling_rate()));
 | 
			
		||||
			settings_file.write_line("tx_amp="+to_string_dec_uint(portapack::transmitter_model.rf_amp()));
 | 
			
		||||
			settings_file.write_line("tx_gain="+to_string_dec_uint(portapack::transmitter_model.tx_gain()));
 | 
			
		||||
			settings_file.write_line("vga="+to_string_dec_uint(portapack::receiver_model.vga()));
 | 
			
		||||
			// Save other settings from struct
 | 
			
		||||
			settings_file.write_line("rx_frequency="+to_string_dec_uint(settings->rx_frequency));
 | 
			
		||||
			settings_file.write_line("tx_frequency="+to_string_dec_uint(settings->tx_frequency));
 | 
			
		||||
 | 
			
		||||
			rc = SETTINGS_OK;
 | 
			
		||||
		}
 | 
			
		||||
		else rc = SETTINGS_UNABLE_TO_SAVE;
 | 
			
		||||
	}
 | 
			
		||||
	else rc = SETTINGS_DISABLED;
 | 
			
		||||
	return(rc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
long long int app_settings::read_long_long(char* file_content, const char* setting_text) {
 | 
			
		||||
	auto position = strstr(file_content, (char *)setting_text);
 | 
			
		||||
	if (position) {
 | 
			
		||||
		position += strlen((char *)setting_text);
 | 
			
		||||
		setting_value = strtoll(position, nullptr, 10);
 | 
			
		||||
	}
 | 
			
		||||
	return(setting_value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} /* namespace std */
 | 
			
		||||
@@ -0,0 +1,87 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyright (C) 2022 Arjan Onwezen
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __APP_SETTINGS_H__
 | 
			
		||||
#define __APP_SETTINGS_H__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace std {
 | 
			
		||||
class app_settings {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
#define SETTINGS_OK	 	 	0		// settings found
 | 
			
		||||
#define SETTINGS_UNABLE_TO_LOAD		-1		// settings (file) not found
 | 
			
		||||
#define SETTINGS_UNABLE_TO_SAVE		-2		// unable to save settings
 | 
			
		||||
#define SETTINGS_DISABLED		-3		// load/save settings disabled in settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	struct AppSettings {
 | 
			
		||||
		uint32_t 	baseband_bandwidth;
 | 
			
		||||
		uint32_t 	channel_bandwidth;
 | 
			
		||||
		uint8_t 	lna;
 | 
			
		||||
		uint8_t		modulation;
 | 
			
		||||
		uint8_t 	rx_amp;
 | 
			
		||||
		uint32_t 	rx_frequency;
 | 
			
		||||
		uint32_t 	sampling_rate;
 | 
			
		||||
		uint8_t 	tx_amp;
 | 
			
		||||
		uint32_t 	tx_frequency;
 | 
			
		||||
		uint8_t 	tx_gain;
 | 
			
		||||
		uint8_t 	vga;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	int load(std::string application, AppSettings* settings);
 | 
			
		||||
	int save(std::string application, AppSettings* settings);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
#define MAX_FILE_CONTENT_SIZE 1000
 | 
			
		||||
 | 
			
		||||
	char 		file_content[MAX_FILE_CONTENT_SIZE] = {};	
 | 
			
		||||
	std::string 	file_path = "";
 | 
			
		||||
	std::string 	folder = "SETTINGS";
 | 
			
		||||
	int		rc = SETTINGS_OK;
 | 
			
		||||
	File 		settings_file { };
 | 
			
		||||
	long long int	setting_value {} ;	
 | 
			
		||||
 | 
			
		||||
	long long int read_long_long(char* file_content, const char* setting_text);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}; // class app_settings
 | 
			
		||||
} // namespace std
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif/*__APP_SETTINGS_H__*/
 | 
			
		||||
@@ -0,0 +1,149 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "acars_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace acars;
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequency) {
 | 
			
		||||
	(void)frequency;
 | 
			
		||||
	std::string entry { };	//= "Raw: F:" + to_string_dec_uint(frequency) + "Hz ";
 | 
			
		||||
	entry.reserve(256);
 | 
			
		||||
	
 | 
			
		||||
	// Raw hex dump of all the bytes
 | 
			
		||||
	//for (size_t c = 0; c < packet.length(); c += 32)
 | 
			
		||||
	//	entry += to_string_hex(packet.read(c, 32), 8) + " ";
 | 
			
		||||
	
 | 
			
		||||
	for (size_t c = 0; c < 256; c += 32)
 | 
			
		||||
		entry += to_string_bin(packet.read(c, 32), 32);
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(packet.received_at(), entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*void ACARSLogger::log_decoded(
 | 
			
		||||
	const acars::Packet& packet,
 | 
			
		||||
	const std::string text) {
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(packet.timestamp(), text);
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void ACARSAppView::update_freq(rf::Frequency f) {
 | 
			
		||||
	set_target_frequency(f);
 | 
			
		||||
	portapack::persistent_memory::set_tuned_frequency(f);	// Maybe not ?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACARSAppView::ACARSAppView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_acars);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&check_log,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_sampling_rate(2457600);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	update_freq(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	check_log.set_value(logging);
 | 
			
		||||
	check_log.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		logging = v;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	logger = std::make_unique<ACARSLogger>();
 | 
			
		||||
	if (logger)
 | 
			
		||||
		logger->append("acars.txt");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACARSAppView::~ACARSAppView() {
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ACARSAppView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ACARSAppView::on_packet(const acars::Packet& packet) {
 | 
			
		||||
	std::string console_info;
 | 
			
		||||
	
 | 
			
		||||
	/*if (!packet.is_valid()) {
 | 
			
		||||
		console_info = to_string_datetime(packet.received_at(), HMS);
 | 
			
		||||
		console_info += " INVALID";
 | 
			
		||||
		
 | 
			
		||||
		console.writeln(console_info);
 | 
			
		||||
	} else {
 | 
			
		||||
		console_info = to_string_datetime(packet.received_at(), HMS);
 | 
			
		||||
		console_info += ":" + to_string_bin(packet.read(0, 32), 32);
 | 
			
		||||
		//console_info += " REG:" + packet.registration_number();
 | 
			
		||||
		
 | 
			
		||||
		console.writeln(console_info);
 | 
			
		||||
	}*/
 | 
			
		||||
	
 | 
			
		||||
	packet_counter++;
 | 
			
		||||
	if (packet_counter % 10 == 0)
 | 
			
		||||
		console.writeln("Block #" + to_string_dec_uint(packet_counter));
 | 
			
		||||
	
 | 
			
		||||
	// Log raw data whatever it contains
 | 
			
		||||
	if (logger && logging)
 | 
			
		||||
		logger->log_raw_data(packet, target_frequency());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ACARSAppView::set_target_frequency(const uint32_t new_value) {
 | 
			
		||||
	target_frequency_ = new_value;
 | 
			
		||||
	receiver_model.set_tuning_frequency(new_value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t ACARSAppView::target_frequency() const {
 | 
			
		||||
	return target_frequency_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,116 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __ACARS_APP_H__
 | 
			
		||||
#define __ACARS_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_rssi.hpp"
 | 
			
		||||
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "acars_packet.hpp"
 | 
			
		||||
 | 
			
		||||
class ACARSLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::string& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void log_raw_data(const acars::Packet& packet, const uint32_t frequency);
 | 
			
		||||
	//void log_decoded(const acars::Packet& packet, const std::string text);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class ACARSAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ACARSAppView(NavigationView& nav);
 | 
			
		||||
	~ACARSAppView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "ACARS (WIP)"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	bool logging { false };
 | 
			
		||||
	uint32_t packet_counter { 0 };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 8 },
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_log {
 | 
			
		||||
		{ 22 * 8, 21 },
 | 
			
		||||
		3,
 | 
			
		||||
		"LOG",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 3 * 16, 240, 256 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<ACARSLogger> logger { };
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency_ { };
 | 
			
		||||
	
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
 | 
			
		||||
	void on_packet(const acars::Packet& packet);
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency() const;
 | 
			
		||||
	void set_target_frequency(const uint32_t new_value);
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::ACARSPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const ACARSPacketMessage*>(p);
 | 
			
		||||
			const acars::Packet packet { message->packet };
 | 
			
		||||
			this->on_packet(packet);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__ACARS_APP_H__*/
 | 
			
		||||
							
								
								
									
										474
									
								
								Software/portapack-mayhem/firmware/application/apps/ais_app.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								Software/portapack-mayhem/firmware/application/apps/ais_app.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,474 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ais_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "database.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace ais {
 | 
			
		||||
namespace format {
 | 
			
		||||
 | 
			
		||||
static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) {
 | 
			
		||||
	const auto suffix = suffixes[(normalized < 0) ? 0 : 1];
 | 
			
		||||
	const uint32_t normalized_abs = std::abs(normalized);
 | 
			
		||||
	const uint32_t t = (normalized_abs * 5) / 3;
 | 
			
		||||
	const uint32_t degrees = t / (100 * 10000);
 | 
			
		||||
	const uint32_t fraction = t % (100 * 10000);
 | 
			
		||||
	return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string latlon(const Latitude latitude, const Longitude longitude) {
 | 
			
		||||
	if( latitude.is_valid() && longitude.is_valid() ) {
 | 
			
		||||
		return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE");
 | 
			
		||||
	} else if( latitude.is_not_available() && longitude.is_not_available() ) {
 | 
			
		||||
		return "not available";
 | 
			
		||||
	} else {
 | 
			
		||||
		return "invalid";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static float latlon_float(const int32_t normalized) {
 | 
			
		||||
	return ((((float) normalized) * 5) / 3) / (100 * 10000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string mmsi(
 | 
			
		||||
	const ais::MMSI& mmsi
 | 
			
		||||
) {
 | 
			
		||||
	return to_string_dec_uint(mmsi, 9, '0'); // MMSI is always is always 9 characters pre-padded with zeros
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static std::string mid(
 | 
			
		||||
	const ais::MMSI& mmsi
 | 
			
		||||
) {
 | 
			
		||||
	std::database 			db;
 | 
			
		||||
	std::string 			mid_code = "";
 | 
			
		||||
	std::database::MidDBRecord 	mid_record = {};
 | 
			
		||||
	int 				return_code = 0;
 | 
			
		||||
 | 
			
		||||
	// Try getting the country name from mids.db using MID code for given MMSI
 | 
			
		||||
	mid_code = to_string_dec_uint(mmsi, 9, ' ').substr(0, 3);
 | 
			
		||||
	return_code = db.retrieve_mid_record(&mid_record, mid_code);
 | 
			
		||||
	switch(return_code) {
 | 
			
		||||
		case DATABASE_RECORD_FOUND: 	return mid_record.country;
 | 
			
		||||
		case DATABASE_NOT_FOUND: 	return "No mids.db file";
 | 
			
		||||
		default:			return "";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string text(const std::string &text) {
 | 
			
		||||
	size_t end = text.find_last_not_of("@");
 | 
			
		||||
    return (end == std::string::npos) ? "" : text.substr(0, end + 1);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string navigational_status(const unsigned int value) {
 | 
			
		||||
	switch(value) {
 | 
			
		||||
		case 0: return "under way w/engine";
 | 
			
		||||
		case 1: return "at anchor";
 | 
			
		||||
		case 2: return "not under command";
 | 
			
		||||
		case 3: return "restricted maneuv";
 | 
			
		||||
		case 4: return "constrained draught";
 | 
			
		||||
		case 5: return "moored";
 | 
			
		||||
		case 6: return "aground";
 | 
			
		||||
		case 7: return "fishing";
 | 
			
		||||
		case 8: return "sailing";
 | 
			
		||||
		case 9: case 10: case 13: return "reserved";
 | 
			
		||||
		case 11: return "towing astern";
 | 
			
		||||
		case 12: return "towing ahead/along";
 | 
			
		||||
		case 14: return "SART/MOB/EPIRB";
 | 
			
		||||
		case 15: return "undefined";
 | 
			
		||||
		default: return "unknown";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string rate_of_turn(const RateOfTurn value) {
 | 
			
		||||
	switch(value) {
 | 
			
		||||
	case -128: return "not available";
 | 
			
		||||
	case -127: return "left >5 deg/30sec";
 | 
			
		||||
	case    0: return "0 deg/min";
 | 
			
		||||
	case  127: return "right >5 deg/30sec";
 | 
			
		||||
	default:
 | 
			
		||||
		{
 | 
			
		||||
			std::string result = (value < 0) ? "left " : "right ";
 | 
			
		||||
			const float value_deg_sqrt = value / 4.733f;
 | 
			
		||||
			const int32_t value_deg = value_deg_sqrt * value_deg_sqrt;
 | 
			
		||||
			result += to_string_dec_uint(value_deg);
 | 
			
		||||
			result += " deg/min";
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string speed_over_ground(const SpeedOverGround value) {
 | 
			
		||||
	if( value == 1023 ) {
 | 
			
		||||
		return "not available";
 | 
			
		||||
	} else if( value == 1022 ) {
 | 
			
		||||
		return ">= 102.2 knots";
 | 
			
		||||
	} else {
 | 
			
		||||
		return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " knots";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string course_over_ground(const CourseOverGround value) {
 | 
			
		||||
	if( value > 3600 ) {
 | 
			
		||||
		return "invalid";
 | 
			
		||||
	} else if( value == 3600 ) {
 | 
			
		||||
		return "not available";
 | 
			
		||||
	} else {
 | 
			
		||||
		return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " deg";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string true_heading(const TrueHeading value) {
 | 
			
		||||
	if( value == 511 ) {
 | 
			
		||||
		return "not available";
 | 
			
		||||
	} else if( value > 359 ) {
 | 
			
		||||
		return "invalid";
 | 
			
		||||
	} else {
 | 
			
		||||
		return to_string_dec_uint(value) + " deg";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace format */
 | 
			
		||||
} /* namespace ais */
 | 
			
		||||
 | 
			
		||||
void AISLogger::on_packet(const ais::Packet& packet) {
 | 
			
		||||
	// TODO: Unstuff here, not in baseband!
 | 
			
		||||
	std::string entry;
 | 
			
		||||
	entry.reserve((packet.length() + 3) / 4);
 | 
			
		||||
 | 
			
		||||
	for(size_t i=0; i<packet.length(); i+=4) {
 | 
			
		||||
		const auto nibble = packet.read(i, 4);
 | 
			
		||||
		entry += (nibble >= 10) ? ('W' + nibble) : ('0' + nibble);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log_file.write_entry(packet.received_at(), entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISRecentEntry::update(const ais::Packet& packet) {
 | 
			
		||||
	received_count++;
 | 
			
		||||
 | 
			
		||||
	switch(packet.message_id()) {
 | 
			
		||||
	case 1:
 | 
			
		||||
	case 2:
 | 
			
		||||
	case 3:
 | 
			
		||||
		navigational_status = packet.read(38, 4);
 | 
			
		||||
		last_position.rate_of_turn = packet.read(42, 8);
 | 
			
		||||
		last_position.speed_over_ground = packet.read(50, 10);
 | 
			
		||||
		last_position.timestamp = packet.received_at();
 | 
			
		||||
		last_position.latitude = packet.latitude(89);
 | 
			
		||||
		last_position.longitude = packet.longitude(61);
 | 
			
		||||
		last_position.course_over_ground = packet.read(116, 12);
 | 
			
		||||
		last_position.true_heading = packet.read(128, 9);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 4:
 | 
			
		||||
		last_position.timestamp = packet.received_at();
 | 
			
		||||
		last_position.latitude = packet.latitude(107);
 | 
			
		||||
		last_position.longitude = packet.longitude(79);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 5:
 | 
			
		||||
		call_sign = packet.text(70, 7);
 | 
			
		||||
		name = packet.text(112, 20);
 | 
			
		||||
		destination = packet.text(302, 20);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 18:
 | 
			
		||||
	    last_position.timestamp = packet.received_at();
 | 
			
		||||
		last_position.speed_over_ground = packet.read(46, 10);
 | 
			
		||||
		last_position.latitude = packet.latitude(85);
 | 
			
		||||
		last_position.longitude = packet.longitude(57);
 | 
			
		||||
		last_position.course_over_ground = packet.read(112, 12);
 | 
			
		||||
		last_position.true_heading = packet.read(124, 9);
 | 
			
		||||
	    break;
 | 
			
		||||
 | 
			
		||||
	case 21:
 | 
			
		||||
		name = packet.text(43, 20);
 | 
			
		||||
		last_position.timestamp = packet.received_at();
 | 
			
		||||
		last_position.latitude = packet.latitude(192);
 | 
			
		||||
		last_position.longitude = packet.longitude(164);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 24:
 | 
			
		||||
		switch (packet.read(38, 2))
 | 
			
		||||
		{
 | 
			
		||||
		case 0:
 | 
			
		||||
			name = packet.text(40, 20);
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case 1:
 | 
			
		||||
			call_sign = packet.text(90, 7);
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<AISRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	std::string line = ais::format::mmsi(entry.mmsi) + " ";
 | 
			
		||||
	if( !entry.name.empty() ) {
 | 
			
		||||
		line += ais::format::text(entry.name);
 | 
			
		||||
	} else {
 | 
			
		||||
		line += ais::format::text(entry.call_sign);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	line.resize(target_rect.width() / 8, ' ');
 | 
			
		||||
	painter.draw_string(target_rect.location(), style, line);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&button_done,
 | 
			
		||||
		&button_see_map,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_done.on_select = [this](const ui::Button&) {
 | 
			
		||||
		if( this->on_close ) {
 | 
			
		||||
			this->on_close();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_see_map.on_select = [this, &nav](Button&) {
 | 
			
		||||
		geomap_view = nav.push<GeoMapView>(
 | 
			
		||||
			ais::format::text(entry_.name),
 | 
			
		||||
			0,
 | 
			
		||||
			GeoPos::alt_unit::METERS,
 | 
			
		||||
			ais::format::latlon_float(entry_.last_position.latitude.normalized()),
 | 
			
		||||
			ais::format::latlon_float(entry_.last_position.longitude.normalized()),
 | 
			
		||||
			entry_.last_position.true_heading,
 | 
			
		||||
			[this]() {
 | 
			
		||||
				send_updates = false;
 | 
			
		||||
			});
 | 
			
		||||
			send_updates = true;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AISRecentEntryDetailView::AISRecentEntryDetailView(const AISRecentEntryDetailView&Entry) : View()
 | 
			
		||||
{
 | 
			
		||||
    (void)Entry;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AISRecentEntryDetailView & AISRecentEntryDetailView::operator=(const AISRecentEntryDetailView&Entry)
 | 
			
		||||
{
 | 
			
		||||
    (void)Entry;
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISRecentEntryDetailView::update_position() {
 | 
			
		||||
	if (send_updates)
 | 
			
		||||
		geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISRecentEntryDetailView::focus() {
 | 
			
		||||
	button_done.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Rect AISRecentEntryDetailView::draw_field(
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Rect& draw_rect,
 | 
			
		||||
	const Style& style,
 | 
			
		||||
	const std::string& label,
 | 
			
		||||
	const std::string& value
 | 
			
		||||
) {
 | 
			
		||||
	const int label_length_max = 4;
 | 
			
		||||
 | 
			
		||||
	painter.draw_string(Point { draw_rect.left(), draw_rect.top() }, style, label);
 | 
			
		||||
	painter.draw_string(Point { draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top() }, style, value);
 | 
			
		||||
 | 
			
		||||
	return { draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISRecentEntryDetailView::paint(Painter& painter) {
 | 
			
		||||
	View::paint(painter);
 | 
			
		||||
 | 
			
		||||
	const auto s = style();
 | 
			
		||||
	const auto rect = screen_rect();
 | 
			
		||||
 | 
			
		||||
	auto field_rect = Rect { rect.left(), rect.top() + 16, rect.width(), 16 };
 | 
			
		||||
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "MMSI", ais::format::mmsi(entry_.mmsi));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Ctry", ais::format::mid(entry_.mmsi));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Name", ais::format::text(entry_.name));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Call", ais::format::text(entry_.call_sign));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Dest", ais::format::text(entry_.destination));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Pos ", ais::format::latlon(entry_.last_position.latitude, entry_.last_position.longitude));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Stat", ais::format::navigational_status(entry_.navigational_status));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "RoT ", ais::format::rate_of_turn(entry_.last_position.rate_of_turn));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "SoG ", ais::format::speed_over_ground(entry_.last_position.speed_over_ground));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "CoG ", ais::format::course_over_ground(entry_.last_position.course_over_ground));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Head", ais::format::true_heading(entry_.last_position.true_heading));
 | 
			
		||||
	field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISRecentEntryDetailView::set_entry(const AISRecentEntry& entry) {
 | 
			
		||||
	entry_ = entry;
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AISAppView::AISAppView(NavigationView& nav) : nav_ { nav } {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_ais);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_channel,
 | 
			
		||||
		&options_channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&recent_entries_view,
 | 
			
		||||
		&recent_entry_detail_view,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_ais", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
		target_frequency_ = app_settings.rx_frequency;
 | 
			
		||||
	}
 | 
			
		||||
	else target_frequency_ = initial_target_frequency;
 | 
			
		||||
 | 
			
		||||
	recent_entry_detail_view.hidden(true);
 | 
			
		||||
 | 
			
		||||
    	receiver_model.set_tuning_frequency(tuning_frequency());
 | 
			
		||||
    	receiver_model.set_sampling_rate(sampling_rate);
 | 
			
		||||
    	receiver_model.set_baseband_bandwidth(baseband_bandwidth);
 | 
			
		||||
    	receiver_model.enable();  // Before using radio::enable(), but not updating Ant.DC-Bias.
 | 
			
		||||
 | 
			
		||||
	options_channel.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		this->on_frequency_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
	options_channel.set_by_value(target_frequency());
 | 
			
		||||
 | 
			
		||||
	recent_entries_view.on_select = [this](const AISRecentEntry& entry) {
 | 
			
		||||
		this->on_show_detail(entry);
 | 
			
		||||
	};
 | 
			
		||||
	recent_entry_detail_view.on_close = [this]() {
 | 
			
		||||
		this->on_show_list();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	logger = std::make_unique<AISLogger>();
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->append(u"ais.txt");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AISAppView::~AISAppView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = target_frequency_;
 | 
			
		||||
	settings.save("rx_ais", &app_settings);
 | 
			
		||||
 | 
			
		||||
	receiver_model.disable();   // to switch off all, including DC bias.
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::focus() {
 | 
			
		||||
	options_channel.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
	const Rect content_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	recent_entries_view.set_parent_rect(content_rect);
 | 
			
		||||
	recent_entry_detail_view.set_parent_rect(content_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::on_packet(const ais::Packet& packet) {
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->on_packet(packet);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto& entry = ::on_packet(recent, packet.source_id());
 | 
			
		||||
	entry.update(packet);
 | 
			
		||||
	recent_entries_view.set_dirty();
 | 
			
		||||
 | 
			
		||||
	// TODO: Crude hack, should be a more formal listener arrangement...
 | 
			
		||||
	if( entry.key() == recent_entry_detail_view.entry().key() ) {
 | 
			
		||||
		recent_entry_detail_view.set_entry(entry);
 | 
			
		||||
		recent_entry_detail_view.update_position();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::on_show_list() {
 | 
			
		||||
	recent_entries_view.hidden(false);
 | 
			
		||||
	recent_entry_detail_view.hidden(true);
 | 
			
		||||
	recent_entries_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::on_show_detail(const AISRecentEntry& entry) {
 | 
			
		||||
	recent_entries_view.hidden(true);
 | 
			
		||||
	recent_entry_detail_view.hidden(false);
 | 
			
		||||
	recent_entry_detail_view.set_entry(entry);
 | 
			
		||||
	recent_entry_detail_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::on_frequency_changed(const uint32_t new_target_frequency) {
 | 
			
		||||
	set_target_frequency(new_target_frequency);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AISAppView::set_target_frequency(const uint32_t new_value) {
 | 
			
		||||
	target_frequency_ = new_value;
 | 
			
		||||
	radio::set_tuning_frequency(tuning_frequency());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t AISAppView::target_frequency() const {
 | 
			
		||||
	return target_frequency_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t AISAppView::tuning_frequency() const {
 | 
			
		||||
	return target_frequency() - (sampling_rate / 4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										256
									
								
								Software/portapack-mayhem/firmware/application/apps/ais_app.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								Software/portapack-mayhem/firmware/application/apps/ais_app.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __AIS_APP_H__
 | 
			
		||||
#define __AIS_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_rssi.hpp"
 | 
			
		||||
#include "ui_channel.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_geomap.hpp"
 | 
			
		||||
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "ais_packet.hpp"
 | 
			
		||||
 | 
			
		||||
#include "lpc43xx_cpp.hpp"
 | 
			
		||||
using namespace lpc43xx;
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <iterator>
 | 
			
		||||
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
 | 
			
		||||
struct AISPosition {
 | 
			
		||||
	rtc::RTC timestamp { };
 | 
			
		||||
	ais::Latitude latitude { };
 | 
			
		||||
	ais::Longitude longitude { };
 | 
			
		||||
	ais::RateOfTurn rate_of_turn { -128 };
 | 
			
		||||
	ais::SpeedOverGround speed_over_ground { 1023 };
 | 
			
		||||
	ais::CourseOverGround course_over_ground { 3600 };
 | 
			
		||||
	ais::TrueHeading true_heading { 511 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AISRecentEntry {
 | 
			
		||||
	using Key = ais::MMSI;
 | 
			
		||||
 | 
			
		||||
	static constexpr Key invalid_key = 0xffffffff;
 | 
			
		||||
 | 
			
		||||
	ais::MMSI mmsi;
 | 
			
		||||
	std::string name;
 | 
			
		||||
	std::string call_sign;
 | 
			
		||||
	std::string destination;
 | 
			
		||||
	AISPosition last_position;
 | 
			
		||||
	size_t received_count;
 | 
			
		||||
	int8_t navigational_status;
 | 
			
		||||
 | 
			
		||||
	AISRecentEntry(
 | 
			
		||||
	) : AISRecentEntry { 0 }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	AISRecentEntry(
 | 
			
		||||
		const ais::MMSI& mmsi
 | 
			
		||||
	) : mmsi { mmsi },
 | 
			
		||||
		name { },
 | 
			
		||||
		call_sign { },
 | 
			
		||||
		destination { },
 | 
			
		||||
		last_position { },
 | 
			
		||||
		received_count { 0 },
 | 
			
		||||
		navigational_status { -1 }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return mmsi;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void update(const ais::Packet& packet);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using AISRecentEntries = RecentEntries<AISRecentEntry>;
 | 
			
		||||
 | 
			
		||||
class AISLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::filesystem::path& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void on_packet(const ais::Packet& packet);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
 | 
			
		||||
 | 
			
		||||
class AISRecentEntryDetailView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	std::function<void(void)> on_close { };
 | 
			
		||||
 | 
			
		||||
	AISRecentEntryDetailView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void set_entry(const AISRecentEntry& new_entry);
 | 
			
		||||
	const AISRecentEntry& entry() const { return entry_; };
 | 
			
		||||
 | 
			
		||||
	void update_position();
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void paint(Painter&) override;
 | 
			
		||||
 | 
			
		||||
    AISRecentEntryDetailView(const AISRecentEntryDetailView&Entry);
 | 
			
		||||
    AISRecentEntryDetailView &operator=(const AISRecentEntryDetailView&Entry);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	AISRecentEntry entry_ { };
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 125, 224, 96, 24 },
 | 
			
		||||
		"Done"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_see_map {
 | 
			
		||||
		{ 19, 224, 96, 24 },
 | 
			
		||||
		"See on map"
 | 
			
		||||
	};
 | 
			
		||||
	GeoMapView* geomap_view { nullptr };
 | 
			
		||||
	bool send_updates { false };
 | 
			
		||||
 | 
			
		||||
	Rect draw_field(
 | 
			
		||||
		Painter& painter,
 | 
			
		||||
		const Rect& draw_rect,
 | 
			
		||||
		const Style& style,
 | 
			
		||||
		const std::string& label,
 | 
			
		||||
		const std::string& value
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AISAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AISAppView(NavigationView& nav);
 | 
			
		||||
	~AISAppView();
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	// Prevent painting of region covered entirely by a child.
 | 
			
		||||
	// TODO: Add flag to View that specifies view does not need to be cleared before painting.
 | 
			
		||||
	void paint(Painter&) override { };
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "AIS Boats RX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr uint32_t initial_target_frequency = 162025000;
 | 
			
		||||
	static constexpr uint32_t sampling_rate = 2457600;
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 1750000;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	AISRecentEntries recent { };
 | 
			
		||||
	std::unique_ptr<AISLogger> logger { };
 | 
			
		||||
 | 
			
		||||
	const RecentEntriesColumns columns { {
 | 
			
		||||
		{ "MMSI", 9 },
 | 
			
		||||
		{ "Name/Call", 20 },
 | 
			
		||||
	} };
 | 
			
		||||
	AISRecentEntriesView recent_entries_view { columns, recent };
 | 
			
		||||
	AISRecentEntryDetailView recent_entry_detail_view { nav_ };
 | 
			
		||||
 | 
			
		||||
	static constexpr auto header_height = 1 * 16;
 | 
			
		||||
 | 
			
		||||
	Text label_channel {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		"Ch"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_channel {
 | 
			
		||||
		{ 3 * 8, 0 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{ "87B", 161975000 },
 | 
			
		||||
			{ "88B", 162025000 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::AISPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const AISPacketMessage*>(p);
 | 
			
		||||
			const ais::Packet packet { message->packet };
 | 
			
		||||
			if( packet.is_valid() ) {
 | 
			
		||||
				this->on_packet(packet);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency_ = initial_target_frequency;
 | 
			
		||||
 | 
			
		||||
	void on_packet(const ais::Packet& packet);
 | 
			
		||||
	void on_show_list();
 | 
			
		||||
	void on_show_detail(const AISRecentEntry& entry);
 | 
			
		||||
 | 
			
		||||
	void on_frequency_changed(const uint32_t new_target_frequency);
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency() const;
 | 
			
		||||
	void set_target_frequency(const uint32_t new_value);
 | 
			
		||||
 | 
			
		||||
	uint32_t tuning_frequency() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__AIS_APP_H__*/
 | 
			
		||||
@@ -0,0 +1,437 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "analog_audio_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace tonekey;
 | 
			
		||||
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
/* AMOptionsView *********************************************************/
 | 
			
		||||
 | 
			
		||||
AMOptionsView::AMOptionsView(
 | 
			
		||||
	const Rect parent_rect, const Style* const style
 | 
			
		||||
) : View { parent_rect }
 | 
			
		||||
{
 | 
			
		||||
	set_style(style);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_config,
 | 
			
		||||
		&options_config,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	options_config.set_selected_index(receiver_model.am_configuration());
 | 
			
		||||
	options_config.on_change = [this](size_t n, OptionsField::value_t) {
 | 
			
		||||
		receiver_model.set_am_configuration(n);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* NBFMOptionsView *******************************************************/
 | 
			
		||||
 | 
			
		||||
NBFMOptionsView::NBFMOptionsView(
 | 
			
		||||
	const Rect parent_rect, const Style* const style
 | 
			
		||||
) : View { parent_rect }
 | 
			
		||||
{
 | 
			
		||||
	set_style(style);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_config,
 | 
			
		||||
		&options_config,
 | 
			
		||||
		&text_squelch,
 | 
			
		||||
		&field_squelch
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	options_config.set_selected_index(receiver_model.nbfm_configuration());
 | 
			
		||||
	options_config.on_change = [this](size_t n, OptionsField::value_t) {
 | 
			
		||||
		receiver_model.set_nbfm_configuration(n);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_squelch.set_value(receiver_model.squelch_level());
 | 
			
		||||
	field_squelch.on_change = [this](int32_t v) {
 | 
			
		||||
		receiver_model.set_squelch_level(v);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* SPECOptionsView *******************************************************/
 | 
			
		||||
 | 
			
		||||
SPECOptionsView::SPECOptionsView(
 | 
			
		||||
	AnalogAudioView* view, const Rect parent_rect, const Style* const style
 | 
			
		||||
) : View { parent_rect }
 | 
			
		||||
{
 | 
			
		||||
	set_style(style);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_config,
 | 
			
		||||
		&options_config,
 | 
			
		||||
		&text_speed,
 | 
			
		||||
		&field_speed
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	options_config.set_selected_index(view->get_spec_bw_index());
 | 
			
		||||
	options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
 | 
			
		||||
		view->set_spec_bw(n, bw);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_speed.set_value(view->get_spec_trigger());
 | 
			
		||||
	field_speed.on_change = [this, view](int32_t v) {
 | 
			
		||||
		view->set_spec_trigger(v);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* AnalogAudioView *******************************************************/
 | 
			
		||||
 | 
			
		||||
AnalogAudioView::AnalogAudioView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&audio,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&options_modulation,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&text_ctcss,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&waterfall
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_audio", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		receiver_model.set_rf_amp(app_settings.rx_amp);
 | 
			
		||||
		field_frequency.set_value(app_settings.rx_frequency);
 | 
			
		||||
	}
 | 
			
		||||
	else field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	
 | 
			
		||||
	//Filename Datetime and Frequency
 | 
			
		||||
	record_view.set_filename_date_frequency(true);
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_tuning_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_tuning_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_frequency();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_lna.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_rf_gain();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_vga.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_rf_gain();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const auto modulation = receiver_model.modulation();
 | 
			
		||||
	options_modulation.set_by_value(toUType(modulation));
 | 
			
		||||
 | 
			
		||||
	options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
 | 
			
		||||
	};
 | 
			
		||||
	options_modulation.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_modulation();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
 | 
			
		||||
	field_volume.on_change = [this](int32_t v) {
 | 
			
		||||
		this->on_headphone_volume_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	waterfall.on_select = [this](int32_t offset) {
 | 
			
		||||
		field_frequency.set_value(receiver_model.tuning_frequency() + offset);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
 | 
			
		||||
	update_modulation(static_cast<ReceiverModel::Mode>(modulation));
 | 
			
		||||
    	on_modulation_changed(static_cast<ReceiverModel::Mode>(modulation));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AnalogAudioView::get_spec_bw_index() {
 | 
			
		||||
    return spec_bw_index;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
 | 
			
		||||
    spec_bw_index = index;
 | 
			
		||||
    spec_bw = bw;
 | 
			
		||||
 | 
			
		||||
    baseband::set_spectrum(bw, spec_trigger);
 | 
			
		||||
    receiver_model.set_sampling_rate(bw);
 | 
			
		||||
    receiver_model.set_baseband_bandwidth(bw/2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t AnalogAudioView::get_spec_trigger() {
 | 
			
		||||
    return spec_trigger;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
 | 
			
		||||
    spec_trigger = trigger;
 | 
			
		||||
 | 
			
		||||
    baseband::set_spectrum(spec_bw, spec_trigger);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnalogAudioView::~AnalogAudioView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_audio", &app_settings);
 | 
			
		||||
 | 
			
		||||
	// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
 | 
			
		||||
	// both?
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000); 	// Just a hack to avoid hanging other apps if the last modulation was SPEC
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_hide() {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	waterfall.set_parent_rect(waterfall_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_tuning_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(bandwidth_hz);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	update_modulation(modulation);
 | 
			
		||||
	on_show_options_modulation();
 | 
			
		||||
	waterfall.on_show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::remove_options_widget() {
 | 
			
		||||
	if( options_widget ) {
 | 
			
		||||
		remove_child(options_widget.get());
 | 
			
		||||
		options_widget.reset();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	field_lna.set_style(nullptr);
 | 
			
		||||
	options_modulation.set_style(nullptr);
 | 
			
		||||
	field_frequency.set_style(nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::set_options_widget(std::unique_ptr<Widget> new_widget) {
 | 
			
		||||
	remove_options_widget();
 | 
			
		||||
 | 
			
		||||
	if( new_widget ) {
 | 
			
		||||
		options_widget = std::move(new_widget);
 | 
			
		||||
	} else {
 | 
			
		||||
		// TODO: Lame hack to hide options view due to my bad paint/damage algorithm.
 | 
			
		||||
		options_widget = std::make_unique<Rectangle>(options_view_rect, style_options_group.background);
 | 
			
		||||
	}
 | 
			
		||||
	add_child(options_widget.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_show_options_frequency() {
 | 
			
		||||
	auto widget = std::make_unique<FrequencyOptionsView>(options_view_rect, &style_options_group);
 | 
			
		||||
 | 
			
		||||
	widget->set_step(receiver_model.frequency_step());
 | 
			
		||||
	widget->on_change_step = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_frequency_step_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	widget->set_reference_ppm_correction(persistent_memory::correction_ppb() / 1000);
 | 
			
		||||
	widget->on_change_reference_ppm_correction = [this](int32_t v) {
 | 
			
		||||
		this->on_reference_ppm_correction_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	field_frequency.set_style(&style_options_group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_show_options_rf_gain() {
 | 
			
		||||
	auto widget = std::make_unique<RadioGainOptionsView>(options_view_rect, &style_options_group);
 | 
			
		||||
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	field_lna.set_style(&style_options_group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_show_options_modulation() {
 | 
			
		||||
	std::unique_ptr<Widget> widget;
 | 
			
		||||
 | 
			
		||||
	const auto modulation = static_cast<ReceiverModel::Mode>(receiver_model.modulation());
 | 
			
		||||
	switch(modulation) {
 | 
			
		||||
	case ReceiverModel::Mode::AMAudio:
 | 
			
		||||
		widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
 | 
			
		||||
		waterfall.show_audio_spectrum_view(false);
 | 
			
		||||
		text_ctcss.hidden(true);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case ReceiverModel::Mode::NarrowbandFMAudio:
 | 
			
		||||
		widget = std::make_unique<NBFMOptionsView>(nbfm_view_rect, &style_options_group);
 | 
			
		||||
		waterfall.show_audio_spectrum_view(false);
 | 
			
		||||
		text_ctcss.hidden(false);
 | 
			
		||||
		break;
 | 
			
		||||
	
 | 
			
		||||
	case ReceiverModel::Mode::WidebandFMAudio:
 | 
			
		||||
		waterfall.show_audio_spectrum_view(true);
 | 
			
		||||
		text_ctcss.hidden(true);
 | 
			
		||||
		break;
 | 
			
		||||
	
 | 
			
		||||
	case ReceiverModel::Mode::SpectrumAnalysis:
 | 
			
		||||
		widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, &style_options_group);
 | 
			
		||||
		waterfall.show_audio_spectrum_view(false);
 | 
			
		||||
		text_ctcss.hidden(true);
 | 
			
		||||
		break;
 | 
			
		||||
		
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	options_modulation.set_style(&style_options_group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_frequency_step_changed(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_frequency_step(f);
 | 
			
		||||
	field_frequency.set_step(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
 | 
			
		||||
	persistent_memory::set_correction_ppb(v * 1000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::on_headphone_volume_changed(int32_t v) {
 | 
			
		||||
	const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
 | 
			
		||||
	receiver_model.set_headphone_volume(new_volume);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
 | 
			
		||||
	audio::output::mute();
 | 
			
		||||
	record_view.stop();
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
 | 
			
		||||
	portapack::spi_flash::image_tag_t image_tag;
 | 
			
		||||
	switch(modulation) {
 | 
			
		||||
	case ReceiverModel::Mode::AMAudio:				image_tag = portapack::spi_flash::image_tag_am_audio;			break;
 | 
			
		||||
	case ReceiverModel::Mode::NarrowbandFMAudio:	image_tag = portapack::spi_flash::image_tag_nfm_audio;			break;
 | 
			
		||||
	case ReceiverModel::Mode::WidebandFMAudio:		image_tag = portapack::spi_flash::image_tag_wfm_audio;			break;
 | 
			
		||||
	case ReceiverModel::Mode::SpectrumAnalysis:		image_tag = portapack::spi_flash::image_tag_wideband_spectrum;	break;
 | 
			
		||||
	default:
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	baseband::run_image(image_tag);
 | 
			
		||||
 | 
			
		||||
	if (modulation == ReceiverModel::Mode::SpectrumAnalysis) {
 | 
			
		||||
		baseband::set_spectrum(spec_bw, spec_trigger);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
 | 
			
		||||
	receiver_model.set_modulation(modulation);
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw/2 : 1750000);
 | 
			
		||||
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
 | 
			
		||||
	// TODO: This doesn't belong here! There's a better way.
 | 
			
		||||
	size_t sampling_rate = 0;
 | 
			
		||||
	switch(modulation) {
 | 
			
		||||
	case ReceiverModel::Mode::AMAudio:				sampling_rate = 12000; break;
 | 
			
		||||
	case ReceiverModel::Mode::NarrowbandFMAudio:	sampling_rate = 24000; break;
 | 
			
		||||
	case ReceiverModel::Mode::WidebandFMAudio:		sampling_rate = 48000; break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	record_view.set_sampling_rate(sampling_rate);
 | 
			
		||||
 | 
			
		||||
	if( !is_wideband_spectrum_mode ) {
 | 
			
		||||
		audio::output::unmute();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
 | 
			
		||||
	float diff, min_diff = value;
 | 
			
		||||
	size_t min_idx { 0 };
 | 
			
		||||
	size_t c;
 | 
			
		||||
	
 | 
			
		||||
	// Find nearest match
 | 
			
		||||
	for (c = 0; c < tone_keys.size(); c++) {
 | 
			
		||||
		diff = abs(((float)value / 100.0) - tone_keys[c].second);
 | 
			
		||||
		if (diff < min_diff) {
 | 
			
		||||
			min_idx = c;
 | 
			
		||||
			min_diff = diff;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Arbitrary confidence threshold
 | 
			
		||||
	if (min_diff < 40)
 | 
			
		||||
		text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
 | 
			
		||||
	else
 | 
			
		||||
		text_ctcss.set("???");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,269 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __ANALOG_AUDIO_APP_H__
 | 
			
		||||
#define __ANALOG_AUDIO_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "tone_key.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
constexpr Style style_options_group {
 | 
			
		||||
	.font = font::fixed_8x16,
 | 
			
		||||
	.background = Color::blue(),
 | 
			
		||||
	.foreground = Color::white(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AMOptionsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AMOptionsView(const Rect parent_rect, const Style* const style);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text label_config {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		"BW",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_config {
 | 
			
		||||
		{ 3 * 8, 0 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ "DSB ", 0 },
 | 
			
		||||
			{ "USB ", 0 },
 | 
			
		||||
			{ "LSB ", 0 },
 | 
			
		||||
			{ "CW  ", 0 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NBFMOptionsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	NBFMOptionsView(const Rect parent_rect, const Style* const style);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text label_config {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		"BW",
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_config {
 | 
			
		||||
		{ 3 * 8, 0 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ " 8k5", 0 },
 | 
			
		||||
			{ "11k ", 0 },
 | 
			
		||||
			{ "16k ", 0 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_squelch {
 | 
			
		||||
		{ 9 * 8, 0 * 16, 8 * 8, 1 * 16 },
 | 
			
		||||
		"SQ   /99"
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_squelch {
 | 
			
		||||
		{ 12 * 8, 0 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AnalogAudioView;
 | 
			
		||||
 | 
			
		||||
class SPECOptionsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SPECOptionsView(AnalogAudioView* view, const Rect parent_rect, const Style* const style);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text label_config {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		"BW",
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_config {
 | 
			
		||||
		{ 3 * 8, 0 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ "20m ", 20000000 },
 | 
			
		||||
			{ "10m ", 10000000 },
 | 
			
		||||
			{ " 5m ", 5000000 },
 | 
			
		||||
			{ " 2m ", 2000000 },
 | 
			
		||||
			{ " 1m ", 1000000 },
 | 
			
		||||
			{ "500k", 500000 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_speed {
 | 
			
		||||
		{ 9 * 8, 0 * 16, 8 * 8, 1 * 16 },
 | 
			
		||||
		"SP   /63"
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_speed {
 | 
			
		||||
		{ 12 * 8, 0 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 63 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AnalogAudioView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AnalogAudioView(NavigationView& nav);
 | 
			
		||||
	~AnalogAudioView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Audio RX"; };
 | 
			
		||||
 | 
			
		||||
	size_t get_spec_bw_index();
 | 
			
		||||
	void set_spec_bw(size_t index, uint32_t bw);
 | 
			
		||||
 | 
			
		||||
	uint16_t get_spec_trigger();
 | 
			
		||||
	void set_spec_trigger(uint16_t trigger);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 };
 | 
			
		||||
	const Rect nbfm_view_rect { 0 * 8, 1 * 16, 18 * 8, 1 * 16 };
 | 
			
		||||
 | 
			
		||||
	size_t spec_bw_index = 0;
 | 
			
		||||
	uint32_t spec_bw = 20000000;
 | 
			
		||||
	uint16_t spec_trigger = 63;
 | 
			
		||||
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	//bool exit_on_squelch { false };
 | 
			
		||||
	
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Audio audio {
 | 
			
		||||
		{ 21 * 8, 10, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 5 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_modulation {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ " AM ", toUType(ReceiverModel::Mode::AMAudio) },
 | 
			
		||||
			{ "NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio) },
 | 
			
		||||
			{ "WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio) },
 | 
			
		||||
			{ "SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis) },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_volume {
 | 
			
		||||
		{ 28 * 8, 0 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_ctcss {
 | 
			
		||||
		{ 19 * 8, 1 * 16, 11 * 8, 1 * 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Widget> options_widget { };
 | 
			
		||||
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"AUD",
 | 
			
		||||
		RecordView::FileType::WAV, 
 | 
			
		||||
		4096, 
 | 
			
		||||
		4
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	spectrum::WaterfallWidget waterfall { true };
 | 
			
		||||
 | 
			
		||||
	void on_tuning_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
 | 
			
		||||
	void on_modulation_changed(const ReceiverModel::Mode modulation);
 | 
			
		||||
	void on_show_options_frequency();
 | 
			
		||||
	void on_show_options_rf_gain();
 | 
			
		||||
	void on_show_options_modulation();
 | 
			
		||||
	void on_frequency_step_changed(rf::Frequency f);
 | 
			
		||||
	void on_reference_ppm_correction_changed(int32_t v);
 | 
			
		||||
	void on_headphone_volume_changed(int32_t v);
 | 
			
		||||
	void on_edit_frequency();
 | 
			
		||||
 | 
			
		||||
	void remove_options_widget();
 | 
			
		||||
	void set_options_widget(std::unique_ptr<Widget> new_widget);
 | 
			
		||||
 | 
			
		||||
	void update_modulation(const ReceiverModel::Mode modulation);
 | 
			
		||||
	
 | 
			
		||||
	//void squelched();
 | 
			
		||||
	void handle_coded_squelch(const uint32_t value);
 | 
			
		||||
	
 | 
			
		||||
	/*MessageHandlerRegistration message_handler_squelch_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			(void)p;
 | 
			
		||||
			this->squelched();
 | 
			
		||||
		}
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_coded_squelch {
 | 
			
		||||
		Message::ID::CodedSquelch,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
 | 
			
		||||
			this->handle_coded_squelch(message.value);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__ANALOG_AUDIO_APP_H__*/
 | 
			
		||||
@@ -0,0 +1,253 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "analog_tv_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace tonekey;
 | 
			
		||||
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
/* AnalogTvView *******************************************************/
 | 
			
		||||
 | 
			
		||||
AnalogTvView::AnalogTvView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&audio,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&options_modulation,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&tv
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_tv", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		receiver_model.set_rf_amp(app_settings.rx_amp);
 | 
			
		||||
		field_frequency.set_value(app_settings.rx_frequency);
 | 
			
		||||
	}
 | 
			
		||||
	else field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_tuning_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_tuning_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_frequency();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_lna.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_rf_gain();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_vga.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_rf_gain();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const auto modulation = receiver_model.modulation();
 | 
			
		||||
	options_modulation.set_by_value(toUType(ReceiverModel::Mode::WidebandFMAudio));
 | 
			
		||||
	options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
 | 
			
		||||
	};
 | 
			
		||||
	options_modulation.on_show_options = [this]() {
 | 
			
		||||
		this->on_show_options_modulation();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_volume.set_value(0);
 | 
			
		||||
	field_volume.on_change = [this](int32_t v) {
 | 
			
		||||
		this->on_headphone_volume_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tv.on_select = [this](int32_t offset) {
 | 
			
		||||
		field_frequency.set_value(receiver_model.tuning_frequency() + offset);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	update_modulation(static_cast<ReceiverModel::Mode>(modulation));
 | 
			
		||||
        on_modulation_changed(ReceiverModel::Mode::WidebandFMAudio);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnalogTvView::~AnalogTvView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_tv", &app_settings);
 | 
			
		||||
 | 
			
		||||
	// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
 | 
			
		||||
	// both?
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_hide() {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	tv.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	const ui::Rect tv_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	tv.set_parent_rect(tv_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_tuning_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(bandwidth_hz);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_modulation_changed(const ReceiverModel::Mode modulation) {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	tv.on_hide();
 | 
			
		||||
	update_modulation(modulation);
 | 
			
		||||
	on_show_options_modulation();
 | 
			
		||||
	tv.on_show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::remove_options_widget() {
 | 
			
		||||
	if( options_widget ) {
 | 
			
		||||
		remove_child(options_widget.get());
 | 
			
		||||
		options_widget.reset();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	field_lna.set_style(nullptr);
 | 
			
		||||
	options_modulation.set_style(nullptr);
 | 
			
		||||
	field_frequency.set_style(nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::set_options_widget(std::unique_ptr<Widget> new_widget) {
 | 
			
		||||
	remove_options_widget();
 | 
			
		||||
 | 
			
		||||
	if( new_widget ) {
 | 
			
		||||
		options_widget = std::move(new_widget);
 | 
			
		||||
	} else {
 | 
			
		||||
		// TODO: Lame hack to hide options view due to my bad paint/damage algorithm.
 | 
			
		||||
		options_widget = std::make_unique<Rectangle>(options_view_rect, style_options_group_new.background);
 | 
			
		||||
	}
 | 
			
		||||
	add_child(options_widget.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_show_options_frequency() {
 | 
			
		||||
	auto widget = std::make_unique<FrequencyOptionsView>(options_view_rect, &style_options_group_new);
 | 
			
		||||
 | 
			
		||||
	widget->set_step(receiver_model.frequency_step());
 | 
			
		||||
	widget->on_change_step = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_frequency_step_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	widget->set_reference_ppm_correction(persistent_memory::correction_ppb() / 1000);
 | 
			
		||||
	widget->on_change_reference_ppm_correction = [this](int32_t v) {
 | 
			
		||||
		this->on_reference_ppm_correction_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	field_frequency.set_style(&style_options_group_new);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_show_options_rf_gain() {
 | 
			
		||||
	auto widget = std::make_unique<RadioGainOptionsView>(options_view_rect, &style_options_group_new);
 | 
			
		||||
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	field_lna.set_style(&style_options_group_new);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_show_options_modulation() {
 | 
			
		||||
	std::unique_ptr<Widget> widget;
 | 
			
		||||
 | 
			
		||||
	static_cast<ReceiverModel::Mode>(receiver_model.modulation());
 | 
			
		||||
	tv.show_audio_spectrum_view(true);
 | 
			
		||||
	
 | 
			
		||||
	set_options_widget(std::move(widget));
 | 
			
		||||
	options_modulation.set_style(&style_options_group_new);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_frequency_step_changed(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_frequency_step(f);
 | 
			
		||||
	field_frequency.set_step(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_reference_ppm_correction_changed(int32_t v) {
 | 
			
		||||
	persistent_memory::set_correction_ppb(v * 1000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::on_headphone_volume_changed(int32_t v) {
 | 
			
		||||
	(void)v; //avoid warning
 | 
			
		||||
	//tv::TVView::set_headphone_volume(this,v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnalogTvView::update_modulation(const ReceiverModel::Mode modulation) {
 | 
			
		||||
	audio::output::mute();
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
 | 
			
		||||
	portapack::spi_flash::image_tag_t image_tag;
 | 
			
		||||
	image_tag = portapack::spi_flash::image_tag_am_tv;	
 | 
			
		||||
 | 
			
		||||
	baseband::run_image(image_tag);
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_modulation(modulation);
 | 
			
		||||
        receiver_model.set_sampling_rate(2000000);
 | 
			
		||||
        receiver_model.set_baseband_bandwidth(2000000);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,137 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __ANALOG_TV_APP_H__
 | 
			
		||||
#define __ANALOG_TV_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_tv.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include "tone_key.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
constexpr Style style_options_group_new {
 | 
			
		||||
	.font = font::fixed_8x16,
 | 
			
		||||
	.background = Color::blue(),
 | 
			
		||||
	.foreground = Color::white(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AnalogTvView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AnalogTvView(NavigationView& nav);
 | 
			
		||||
	~AnalogTvView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Analog TV RX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 };
 | 
			
		||||
	const Rect nbfm_view_rect { 0 * 8, 1 * 16, 18 * 8, 1 * 16 };
 | 
			
		||||
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Audio audio {
 | 
			
		||||
		{ 21 * 8, 10, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 5 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_modulation {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ "TV ", toUType(ReceiverModel::Mode::WidebandFMAudio) },
 | 
			
		||||
			{ "TV ", toUType(ReceiverModel::Mode::WidebandFMAudio) },
 | 
			
		||||
			{ "TV ", toUType(ReceiverModel::Mode::WidebandFMAudio) },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_volume {
 | 
			
		||||
		{ 27 * 8, 0 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 0, 255 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Widget> options_widget { };
 | 
			
		||||
 | 
			
		||||
	tv::TVWidget tv { };
 | 
			
		||||
 | 
			
		||||
	void on_tuning_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
 | 
			
		||||
	void on_modulation_changed(const ReceiverModel::Mode modulation);
 | 
			
		||||
	void on_show_options_frequency();
 | 
			
		||||
	void on_show_options_rf_gain();
 | 
			
		||||
	void on_show_options_modulation();
 | 
			
		||||
	void on_frequency_step_changed(rf::Frequency f);
 | 
			
		||||
	void on_reference_ppm_correction_changed(int32_t v);
 | 
			
		||||
	void on_headphone_volume_changed(int32_t v);
 | 
			
		||||
	void on_edit_frequency();
 | 
			
		||||
 | 
			
		||||
	void remove_options_widget();
 | 
			
		||||
	void set_options_widget(std::unique_ptr<Widget> new_widget);
 | 
			
		||||
 | 
			
		||||
	void update_modulation(const ReceiverModel::Mode modulation);
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__ANALOG_TV_APP_H__*/
 | 
			
		||||
@@ -0,0 +1,171 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "capture_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
CaptureAppView::CaptureAppView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_capture);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_frequency_step,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&option_bandwidth,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&waterfall,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Hack for initialization
 | 
			
		||||
	// TODO: This should be included in a more global section so apps dont need to do it 
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	//-------------------
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_tuning_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_tuning_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_frequency_step.set_by_value(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		receiver_model.set_frequency_step(v);
 | 
			
		||||
		this->field_frequency.set_step(v);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
 | 
			
		||||
		sampling_rate = 8 * base_rate;	// Decimation by 8 done on baseband side
 | 
			
		||||
  	    /* base_rate  is used for FFT calculation and display LCD, and also in  recording writing SD Card  rate. */
 | 
			
		||||
		/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz , when selected 1 Mhz BW ...*/ 
 | 
			
		||||
	    /* ex. recording 500kHz BW  to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card  */
 | 
			
		||||
 | 
			
		||||
		waterfall.on_hide();
 | 
			
		||||
		record_view.set_sampling_rate(sampling_rate);
 | 
			
		||||
		receiver_model.set_sampling_rate(sampling_rate);
 | 
			
		||||
       /* Set up  proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */ 
 | 
			
		||||
		
 | 
			
		||||
		switch(sampling_rate) {   // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem. 
 | 
			
		||||
	  
 | 
			
		||||
	  			case 0 ... 2000000:  //  BW Captured range  (0 <= 250kHz max )  fs = 8 x 250 kHz 
 | 
			
		||||
					anti_alias_baseband_bandwidth_filter = 1750000;  // Minimum BPF MAX2837 for all those lower BW options.
 | 
			
		||||
        	 		break;
 | 
			
		||||
 | 
			
		||||
				case 4000000 ... 6000000:    // BW capture  range (500k ... 750kHz max )  fs_max = 8 x 750kHz = 6Mhz 
 | 
			
		||||
     				//  BW 500k ... 750kHz   ,  ex. 500kHz   (fs = 8*BW =  4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW  750 kHz (fs = 6Mhz)  
 | 
			
		||||
					anti_alias_baseband_bandwidth_filter = 2500000;  // in some IC MAX2837 appear 2250000 , but both works similar. 
 | 
			
		||||
					break;	
 | 
			
		||||
 | 
			
		||||
				case 8800000:    // BW capture 1,1Mhz  fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
 | 
			
		||||
					anti_alias_baseband_bandwidth_filter = 3500000;   
 | 
			
		||||
        	 		break;
 | 
			
		||||
 | 
			
		||||
			    case 14000000:   // BW capture 1,75Mhz  , fs = 8 x 1,75Mhz = 14Mhz 
 | 
			
		||||
				    // good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture   
 | 
			
		||||
         			anti_alias_baseband_bandwidth_filter = 5000000; 
 | 
			
		||||
        			break;
 | 
			
		||||
 | 
			
		||||
    			case 16000000:   // BW capture 2Mhz  , fs = 8 x 2Mhz = 16Mhz
 | 
			
		||||
                     //  good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture   
 | 
			
		||||
         			anti_alias_baseband_bandwidth_filter = 6000000; 
 | 
			
		||||
        			break;
 | 
			
		||||
   
 | 
			
		||||
            	case 20000000:   // BW capture 2,5Mhz  , fs= 8 x 2,5 Mhz = 20Mhz
 | 
			
		||||
         			//  good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture 
 | 
			
		||||
					 anti_alias_baseband_bandwidth_filter = 7000000; 
 | 
			
		||||
	   	         	break;
 | 
			
		||||
		   	
 | 
			
		||||
      			default:         // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling)  and others.  
 | 
			
		||||
        			 //  We tested also 9Mhz FPB stightly too much noise floor , better 8Mhz 
 | 
			
		||||
					 anti_alias_baseband_bandwidth_filter = 8000000;
 | 
			
		||||
		}   
 | 
			
		||||
		receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
 | 
			
		||||
							
 | 
			
		||||
		
 | 
			
		||||
		waterfall.on_show();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	option_bandwidth.set_selected_index(7);		// 500k,  Preselected starting default option 500kHz 
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::Capture);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CaptureAppView::~CaptureAppView() {
 | 
			
		||||
 | 
			
		||||
	// Hack for preventing halting other apps
 | 
			
		||||
	// TODO: This should be also part of something global
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
 | 
			
		||||
	// ----------------------------
 | 
			
		||||
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CaptureAppView::on_hide() {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
 | 
			
		||||
	const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	waterfall.set_parent_rect(waterfall_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CaptureAppView::focus() {
 | 
			
		||||
	record_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CaptureAppView::on_tuning_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,119 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __CAPTURE_APP_HPP__
 | 
			
		||||
#define __CAPTURE_APP_HPP__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class CaptureAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	CaptureAppView(NavigationView& nav);
 | 
			
		||||
	~CaptureAppView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Capture"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
 | 
			
		||||
	uint32_t sampling_rate = 0;
 | 
			
		||||
	uint32_t anti_alias_baseband_bandwidth_filter = 2500000; // we rename the previous var , and change type static constexpr to normal var.
 | 
			
		||||
 | 
			
		||||
	void on_tuning_frequency_changed(rf::Frequency f);
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0 * 8, 1 * 16 }, "Rate:", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 24 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 24 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyStepView field_frequency_step {
 | 
			
		||||
		{ 10 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 16 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 21 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField option_bandwidth {
 | 
			
		||||
		{ 5 * 8, 1 * 16 },
 | 
			
		||||
		5,
 | 
			
		||||
		{
 | 
			
		||||
			{ "  8k5", 8500 },
 | 
			
		||||
			{ " 11k ", 11000 },
 | 
			
		||||
			{ " 16k ", 16000 },
 | 
			
		||||
			{ " 25k ", 25000 },
 | 
			
		||||
			{ " 50k ", 50000 },
 | 
			
		||||
			{ "100k ", 100000 },
 | 
			
		||||
			{ "250k ", 250000 },
 | 
			
		||||
			{ "500k ", 500000 },   // Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.
 | 
			
		||||
			{ "600k ", 600000 },   // That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec)  
 | 
			
		||||
 			{ "750k ", 750000 },   // From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) 
 | 
			
		||||
			{ "1100k", 1100000 },
 | 
			
		||||
	       	{ "1750k", 1750000 },
 | 
			
		||||
			{ "2000k", 2000000 },
 | 
			
		||||
			{ "2500k", 2500000 },
 | 
			
		||||
			{ "2750k", 2750000 }    // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling  ADC)
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"BBD_????", RecordView::FileType::RawS16, 16384, 3
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	spectrum::WaterfallWidget waterfall { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__CAPTURE_APP_HPP__*/
 | 
			
		||||
							
								
								
									
										444
									
								
								Software/portapack-mayhem/firmware/application/apps/dump1090.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								Software/portapack-mayhem/firmware/application/apps/dump1090.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,444 @@
 | 
			
		||||
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
 | 
			
		||||
//
 | 
			
		||||
// dump1090.h: main program header
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2014-2016 Oliver Jowett <oliver@mutability.co.uk>
 | 
			
		||||
//
 | 
			
		||||
// This file is free software: you may copy, redistribute and/or modify it
 | 
			
		||||
// under the terms of the GNU General Public License as published by the
 | 
			
		||||
// Free Software Foundation, either version 2 of the License, or (at your
 | 
			
		||||
// option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This file is distributed in the hope that it will be useful, but
 | 
			
		||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
// General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
// This file incorporates work covered by the following copyright and
 | 
			
		||||
// permission notice:
 | 
			
		||||
//
 | 
			
		||||
//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez@gmail.com>
 | 
			
		||||
//
 | 
			
		||||
//   All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
//   Redistribution and use in source and binary forms, with or without
 | 
			
		||||
//   modification, are permitted provided that the following conditions are
 | 
			
		||||
//   met:
 | 
			
		||||
//
 | 
			
		||||
//    *  Redistributions of source code must retain the above copyright
 | 
			
		||||
//       notice, this list of conditions and the following disclaimer.
 | 
			
		||||
//
 | 
			
		||||
//    *  Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
//       notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
//       documentation and/or other materials provided with the distribution.
 | 
			
		||||
//
 | 
			
		||||
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
//   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
//   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
//   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
			
		||||
//   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
//   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
//   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
			
		||||
//   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
			
		||||
//   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
//   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
//   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#ifndef __DUMP1090_H
 | 
			
		||||
#define __DUMP1090_H
 | 
			
		||||
 | 
			
		||||
// Default version number, if not overriden by the Makefile
 | 
			
		||||
#ifndef MODES_DUMP1090_VERSION
 | 
			
		||||
# define MODES_DUMP1090_VERSION     "unknown"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef MODES_DUMP1090_VARIANT
 | 
			
		||||
# define MODES_DUMP1090_VARIANT     "dump1090-unknown"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define MODES_DEFAULT_FREQ         1090000000
 | 
			
		||||
#define MODES_DEFAULT_WIDTH        1000
 | 
			
		||||
#define MODES_DEFAULT_HEIGHT       700
 | 
			
		||||
#define MODES_RTL_BUFFERS          15                         // Number of RTL buffers
 | 
			
		||||
#define MODES_RTL_BUF_SIZE         (16*16384)                 // 256k
 | 
			
		||||
#define MODES_MAG_BUF_SAMPLES      (MODES_RTL_BUF_SIZE / 2)   // Each sample is 2 bytes
 | 
			
		||||
#define MODES_MAG_BUFFERS          12                         // Number of magnitude buffers (should be smaller than RTL_BUFFERS for flowcontrol to work)
 | 
			
		||||
#define MODES_AUTO_GAIN            -100                       // Use automatic gain
 | 
			
		||||
#define MODES_MAX_GAIN             999999                     // Use max available gain
 | 
			
		||||
#define MODES_MSG_SQUELCH_DB       4.0                        // Minimum SNR, in dB
 | 
			
		||||
#define MODES_MSG_ENCODER_ERRS     3                          // Maximum number of encoding errors
 | 
			
		||||
 | 
			
		||||
#define MODEAC_MSG_SAMPLES       (25 * 2)                     // include up to the SPI bit
 | 
			
		||||
#define MODEAC_MSG_BYTES          2
 | 
			
		||||
#define MODEAC_MSG_SQUELCH_LEVEL  0x07FF                      // Average signal strength limit
 | 
			
		||||
 | 
			
		||||
#define MODES_PREAMBLE_US        8              // microseconds = bits
 | 
			
		||||
#define MODES_PREAMBLE_SAMPLES  (MODES_PREAMBLE_US       * 2)
 | 
			
		||||
#define MODES_PREAMBLE_SIZE     (MODES_PREAMBLE_SAMPLES  * sizeof(uint16_t))
 | 
			
		||||
#define MODES_LONG_MSG_BYTES     14
 | 
			
		||||
#define MODES_SHORT_MSG_BYTES    7
 | 
			
		||||
#define MODES_LONG_MSG_BITS     (MODES_LONG_MSG_BYTES    * 8)
 | 
			
		||||
#define MODES_SHORT_MSG_BITS    (MODES_SHORT_MSG_BYTES   * 8)
 | 
			
		||||
#define MODES_LONG_MSG_SAMPLES  (MODES_LONG_MSG_BITS     * 2)
 | 
			
		||||
#define MODES_SHORT_MSG_SAMPLES (MODES_SHORT_MSG_BITS    * 2)
 | 
			
		||||
#define MODES_LONG_MSG_SIZE     (MODES_LONG_MSG_SAMPLES  * sizeof(uint16_t))
 | 
			
		||||
#define MODES_SHORT_MSG_SIZE    (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t))
 | 
			
		||||
 | 
			
		||||
#define MODES_OS_PREAMBLE_SAMPLES  (20)
 | 
			
		||||
#define MODES_OS_PREAMBLE_SIZE     (MODES_OS_PREAMBLE_SAMPLES  * sizeof(uint16_t))
 | 
			
		||||
#define MODES_OS_LONG_MSG_SAMPLES  (268)
 | 
			
		||||
#define MODES_OS_SHORT_MSG_SAMPLES (135)
 | 
			
		||||
#define MODES_OS_LONG_MSG_SIZE     (MODES_LONG_MSG_SAMPLES  * sizeof(uint16_t))
 | 
			
		||||
#define MODES_OS_SHORT_MSG_SIZE    (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t))
 | 
			
		||||
 | 
			
		||||
#define MODES_OUT_BUF_SIZE         (1500)
 | 
			
		||||
#define MODES_OUT_FLUSH_SIZE       (MODES_OUT_BUF_SIZE - 256)
 | 
			
		||||
#define MODES_OUT_FLUSH_INTERVAL   (60000)
 | 
			
		||||
 | 
			
		||||
#define MODES_USER_LATLON_VALID (1<<0)
 | 
			
		||||
 | 
			
		||||
#define INVALID_ALTITUDE (-9999)
 | 
			
		||||
 | 
			
		||||
/* Where did a bit of data arrive from? In order of increasing priority */
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SOURCE_INVALID,        /* data is not valid */
 | 
			
		||||
    SOURCE_MODE_AC,        /* A/C message */
 | 
			
		||||
    SOURCE_MLAT,           /* derived from mlat */
 | 
			
		||||
    SOURCE_MODE_S,         /* data from a Mode S message, no full CRC */
 | 
			
		||||
    SOURCE_MODE_S_CHECKED, /* data from a Mode S message with full CRC */
 | 
			
		||||
    SOURCE_TISB,           /* data from a TIS-B extended squitter message */
 | 
			
		||||
    SOURCE_ADSR,           /* data from a ADS-R extended squitter message */
 | 
			
		||||
    SOURCE_ADSB,           /* data from a ADS-B extended squitter message */
 | 
			
		||||
} datasource_t;
 | 
			
		||||
 | 
			
		||||
/* What sort of address is this and who sent it?
 | 
			
		||||
 * (Earlier values are higher priority)
 | 
			
		||||
 */
 | 
			
		||||
typedef enum {
 | 
			
		||||
    ADDR_ADSB_ICAO,       /* Mode S or ADS-B, ICAO address, transponder sourced */
 | 
			
		||||
    ADDR_ADSB_ICAO_NT,    /* ADS-B, ICAO address, non-transponder */
 | 
			
		||||
    ADDR_ADSR_ICAO,       /* ADS-R, ICAO address */
 | 
			
		||||
    ADDR_TISB_ICAO,       /* TIS-B, ICAO address */
 | 
			
		||||
 | 
			
		||||
    ADDR_ADSB_OTHER,      /* ADS-B, other address format */
 | 
			
		||||
    ADDR_ADSR_OTHER,      /* ADS-R, other address format */
 | 
			
		||||
    ADDR_TISB_TRACKFILE,  /* TIS-B, Mode A code + track file number */
 | 
			
		||||
    ADDR_TISB_OTHER,      /* TIS-B, other address format */
 | 
			
		||||
 | 
			
		||||
    ADDR_MODE_A,          /* Mode A */
 | 
			
		||||
 | 
			
		||||
    ADDR_UNKNOWN          /* unknown address format */
 | 
			
		||||
} addrtype_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    UNIT_FEET,
 | 
			
		||||
    UNIT_METERS
 | 
			
		||||
} altitude_unit_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    UNIT_NAUTICAL_MILES,
 | 
			
		||||
    UNIT_STATUTE_MILES,
 | 
			
		||||
    UNIT_KILOMETERS,
 | 
			
		||||
} interactive_distance_unit_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    ALTITUDE_BARO,
 | 
			
		||||
    ALTITUDE_GEOM
 | 
			
		||||
} altitude_source_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    AG_INVALID,
 | 
			
		||||
    AG_GROUND,
 | 
			
		||||
    AG_AIRBORNE,
 | 
			
		||||
    AG_UNCERTAIN
 | 
			
		||||
} airground_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR
 | 
			
		||||
} sil_type_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
 | 
			
		||||
} cpr_type_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    HEADING_INVALID,          // Not set
 | 
			
		||||
    HEADING_GROUND_TRACK,     // Direction of track over ground, degrees clockwise from true north
 | 
			
		||||
    HEADING_TRUE,             // Heading, degrees clockwise from true north
 | 
			
		||||
    HEADING_MAGNETIC,         // Heading, degrees clockwise from magnetic north
 | 
			
		||||
    HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus
 | 
			
		||||
    HEADING_TRACK_OR_HEADING  // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus
 | 
			
		||||
} heading_type_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    COMMB_UNKNOWN,
 | 
			
		||||
    COMMB_AMBIGUOUS,
 | 
			
		||||
    COMMB_EMPTY_RESPONSE,
 | 
			
		||||
    COMMB_DATALINK_CAPS,
 | 
			
		||||
    COMMB_GICB_CAPS,
 | 
			
		||||
    COMMB_AIRCRAFT_IDENT,
 | 
			
		||||
    COMMB_ACAS_RA,
 | 
			
		||||
    COMMB_VERTICAL_INTENT,
 | 
			
		||||
    COMMB_TRACK_TURN,
 | 
			
		||||
    COMMB_HEADING_SPEED
 | 
			
		||||
} commb_format_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    NAV_MODE_AUTOPILOT = 1,
 | 
			
		||||
    NAV_MODE_VNAV = 2,
 | 
			
		||||
    NAV_MODE_ALT_HOLD = 4,
 | 
			
		||||
    NAV_MODE_APPROACH = 8,
 | 
			
		||||
    NAV_MODE_LNAV = 16,
 | 
			
		||||
    NAV_MODE_TCAS = 32
 | 
			
		||||
} nav_modes_t;
 | 
			
		||||
 | 
			
		||||
// Matches encoding of the ES type 28/1 emergency/priority status subfield
 | 
			
		||||
typedef enum {
 | 
			
		||||
    EMERGENCY_NONE = 0,
 | 
			
		||||
    EMERGENCY_GENERAL = 1,
 | 
			
		||||
    EMERGENCY_LIFEGUARD = 2,
 | 
			
		||||
    EMERGENCY_MINFUEL = 3,
 | 
			
		||||
    EMERGENCY_NORDO = 4,
 | 
			
		||||
    EMERGENCY_UNLAWFUL = 5,
 | 
			
		||||
    EMERGENCY_DOWNED = 6,
 | 
			
		||||
    EMERGENCY_RESERVED = 7
 | 
			
		||||
} emergency_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS
 | 
			
		||||
} nav_altitude_source_t;
 | 
			
		||||
 | 
			
		||||
#define MODES_NON_ICAO_ADDRESS       (1<<24) // Set on addresses to indicate they are not ICAO addresses
 | 
			
		||||
 | 
			
		||||
#define MODES_INTERACTIVE_REFRESH_TIME 250      // Milliseconds
 | 
			
		||||
#define MODES_INTERACTIVE_DISPLAY_TTL 60000     // Delete from display after 60 seconds
 | 
			
		||||
 | 
			
		||||
#define MODES_NET_HEARTBEAT_INTERVAL 60000      // milliseconds
 | 
			
		||||
 | 
			
		||||
#define MODES_CLIENT_BUF_SIZE  1024
 | 
			
		||||
#define MODES_NET_SNDBUF_SIZE (1024*64)
 | 
			
		||||
#define MODES_NET_SNDBUF_MAX  (7)
 | 
			
		||||
 | 
			
		||||
#define HISTORY_SIZE 120
 | 
			
		||||
#define HISTORY_INTERVAL 30000
 | 
			
		||||
 | 
			
		||||
#define MODES_NOTUSED(V) ((void) V)
 | 
			
		||||
 | 
			
		||||
#define MAX_AMPLITUDE 65535.0
 | 
			
		||||
#define MAX_POWER (MAX_AMPLITUDE * MAX_AMPLITUDE)
 | 
			
		||||
 | 
			
		||||
#define FAUP_DEFAULT_RATE_MULTIPLIER    1.0                  // FA Upload rate multiplier
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//======================== structure declarations =========================
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SDR_NONE, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF, SDR_HACKRF, SDR_LIMESDR
 | 
			
		||||
} sdr_type_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// The struct we use to store information about a decoded message.
 | 
			
		||||
struct modesMessage {
 | 
			
		||||
    // Generic fields
 | 
			
		||||
    unsigned char msg[MODES_LONG_MSG_BYTES];      // Binary message.
 | 
			
		||||
    unsigned char verbatim[MODES_LONG_MSG_BYTES]; // Binary message, as originally received before correction
 | 
			
		||||
    int           msgbits;                        // Number of bits in message
 | 
			
		||||
    int           msgtype;                        // Downlink format #
 | 
			
		||||
    uint32_t      crc;                            // Message CRC
 | 
			
		||||
    int           correctedbits;                  // No. of bits corrected
 | 
			
		||||
    uint32_t      addr;                           // Address Announced
 | 
			
		||||
    addrtype_t    addrtype;                       // address format / source
 | 
			
		||||
    uint64_t      timestampMsg;                   // Timestamp of the message (12MHz clock)
 | 
			
		||||
    uint64_t      sysTimestampMsg;                // Timestamp of the message (system time)
 | 
			
		||||
    int           remote;                         // If set this message is from a remote station
 | 
			
		||||
    double        signalLevel;                    // RSSI, in the range [0..1], as a fraction of full-scale power
 | 
			
		||||
    int           score;                          // Scoring from scoreModesMessage, if used
 | 
			
		||||
    int           reliable;                       // is this a "reliable" message (uncorrected DF11/DF17/DF18)?
 | 
			
		||||
 | 
			
		||||
    datasource_t  source;                         // Characterizes the overall message source
 | 
			
		||||
 | 
			
		||||
    // Raw data, just extracted directly from the message
 | 
			
		||||
    // The names reflect the field names in Annex 4
 | 
			
		||||
    unsigned IID; // extracted from CRC of DF11s
 | 
			
		||||
    unsigned AA;
 | 
			
		||||
    unsigned AC;
 | 
			
		||||
    unsigned CA;
 | 
			
		||||
    unsigned CC;
 | 
			
		||||
    unsigned CF;
 | 
			
		||||
    unsigned DR;
 | 
			
		||||
    unsigned FS;
 | 
			
		||||
    unsigned ID;
 | 
			
		||||
    unsigned KE;
 | 
			
		||||
    unsigned ND;
 | 
			
		||||
    unsigned RI;
 | 
			
		||||
    unsigned SL;
 | 
			
		||||
    unsigned UM;
 | 
			
		||||
    unsigned VS;
 | 
			
		||||
    unsigned char MB[7];
 | 
			
		||||
    unsigned char MD[10];
 | 
			
		||||
    unsigned char ME[7];
 | 
			
		||||
    unsigned char MV[7];
 | 
			
		||||
 | 
			
		||||
    // Decoded data
 | 
			
		||||
    unsigned altitude_baro_valid : 1;
 | 
			
		||||
    unsigned altitude_geom_valid : 1;
 | 
			
		||||
    unsigned track_valid : 1;
 | 
			
		||||
    unsigned track_rate_valid : 1;
 | 
			
		||||
    unsigned heading_valid : 1;
 | 
			
		||||
    unsigned roll_valid : 1;
 | 
			
		||||
    unsigned gs_valid : 1;
 | 
			
		||||
    unsigned ias_valid : 1;
 | 
			
		||||
    unsigned tas_valid : 1;
 | 
			
		||||
    unsigned mach_valid : 1;
 | 
			
		||||
    unsigned baro_rate_valid : 1;
 | 
			
		||||
    unsigned geom_rate_valid : 1;
 | 
			
		||||
    unsigned squawk_valid : 1;
 | 
			
		||||
    unsigned callsign_valid : 1;
 | 
			
		||||
    unsigned cpr_valid : 1;
 | 
			
		||||
    unsigned cpr_odd : 1;
 | 
			
		||||
    unsigned cpr_decoded : 1;
 | 
			
		||||
    unsigned cpr_relative : 1;
 | 
			
		||||
    unsigned category_valid : 1;
 | 
			
		||||
    unsigned geom_delta_valid : 1;
 | 
			
		||||
    unsigned from_mlat : 1;
 | 
			
		||||
    unsigned from_tisb : 1;
 | 
			
		||||
    unsigned spi_valid : 1;
 | 
			
		||||
    unsigned spi : 1;
 | 
			
		||||
    unsigned alert_valid : 1;
 | 
			
		||||
    unsigned alert : 1;
 | 
			
		||||
    unsigned emergency_valid : 1;
 | 
			
		||||
 | 
			
		||||
    unsigned metype; // DF17/18 ME type
 | 
			
		||||
    unsigned mesub;  // DF17/18 ME subtype
 | 
			
		||||
 | 
			
		||||
    commb_format_t commb_format; // Inferred format of a comm-b message
 | 
			
		||||
 | 
			
		||||
    // valid if altitude_baro_valid:
 | 
			
		||||
    int               altitude_baro;       // Altitude in either feet or meters
 | 
			
		||||
    altitude_unit_t   altitude_baro_unit;  // the unit used for altitude
 | 
			
		||||
 | 
			
		||||
    // valid if altitude_geom_valid:
 | 
			
		||||
    int               altitude_geom;       // Altitude in either feet or meters
 | 
			
		||||
    altitude_unit_t   altitude_geom_unit;  // the unit used for altitude
 | 
			
		||||
 | 
			
		||||
    // following fields are valid if the corresponding _valid field is set:
 | 
			
		||||
    int      geom_delta;        // Difference between geometric and baro alt
 | 
			
		||||
    float    heading;           // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity
 | 
			
		||||
    heading_type_t heading_type;// how to interpret 'track_or_heading'
 | 
			
		||||
    float    track_rate;        // Rate of change of track, degrees/second
 | 
			
		||||
    float    roll;              // Roll, degrees, negative is left roll
 | 
			
		||||
    struct {
 | 
			
		||||
        // Groundspeed, kts, reported directly or computed from from EW and NS velocity
 | 
			
		||||
        // For surface movement, this has different interpretations for v0 and v2; both
 | 
			
		||||
        // fields are populated. The tracking layer will update "gs.selected".
 | 
			
		||||
        float v0;
 | 
			
		||||
        float v2;
 | 
			
		||||
        float selected;
 | 
			
		||||
    } gs;
 | 
			
		||||
    unsigned ias;               // Indicated airspeed, kts
 | 
			
		||||
    unsigned tas;               // True airspeed, kts
 | 
			
		||||
    double   mach;              // Mach number
 | 
			
		||||
    int      baro_rate;         // Rate of change of barometric altitude, feet/minute
 | 
			
		||||
    int      geom_rate;         // Rate of change of geometric (GNSS / INS) altitude, feet/minute
 | 
			
		||||
    unsigned squawk;            // 13 bits identity (Squawk), encoded as 4 hex digits
 | 
			
		||||
    char     callsign[9];       // 8 chars flight number, NUL-terminated
 | 
			
		||||
    unsigned category;          // A0 - D7 encoded as a single hex byte
 | 
			
		||||
    emergency_t emergency;      // emergency/priority status
 | 
			
		||||
 | 
			
		||||
    // valid if cpr_valid
 | 
			
		||||
    cpr_type_t cpr_type;       // The encoding type used (surface, airborne, coarse TIS-B)
 | 
			
		||||
    unsigned   cpr_lat;        // Non decoded latitude.
 | 
			
		||||
    unsigned   cpr_lon;        // Non decoded longitude.
 | 
			
		||||
    unsigned   cpr_nucp;       // NUCp/NIC value implied by message type
 | 
			
		||||
 | 
			
		||||
    airground_t airground;     // air/ground state
 | 
			
		||||
 | 
			
		||||
    // valid if cpr_decoded:
 | 
			
		||||
    double decoded_lat;
 | 
			
		||||
    double decoded_lon;
 | 
			
		||||
    unsigned decoded_nic;
 | 
			
		||||
    unsigned decoded_rc;
 | 
			
		||||
 | 
			
		||||
    // various integrity/accuracy things
 | 
			
		||||
    struct {
 | 
			
		||||
        unsigned nic_a_valid : 1;
 | 
			
		||||
        unsigned nic_b_valid : 1;
 | 
			
		||||
        unsigned nic_c_valid : 1;
 | 
			
		||||
        unsigned nic_baro_valid : 1;
 | 
			
		||||
        unsigned nac_p_valid : 1;
 | 
			
		||||
        unsigned nac_v_valid : 1;
 | 
			
		||||
        unsigned gva_valid : 1;
 | 
			
		||||
        unsigned sda_valid : 1;
 | 
			
		||||
 | 
			
		||||
        unsigned nic_a : 1;        // if nic_a_valid
 | 
			
		||||
        unsigned nic_b : 1;        // if nic_b_valid
 | 
			
		||||
        unsigned nic_c : 1;        // if nic_c_valid
 | 
			
		||||
        unsigned nic_baro : 1;     // if nic_baro_valid
 | 
			
		||||
 | 
			
		||||
        unsigned nac_p : 4;        // if nac_p_valid
 | 
			
		||||
        unsigned nac_v : 3;        // if nac_v_valid
 | 
			
		||||
 | 
			
		||||
        unsigned sil : 2;          // if sil_type != SIL_INVALID
 | 
			
		||||
        sil_type_t sil_type;
 | 
			
		||||
 | 
			
		||||
        unsigned gva : 2;          // if gva_valid
 | 
			
		||||
 | 
			
		||||
        unsigned sda : 2;          // if sda_valid
 | 
			
		||||
    } accuracy;
 | 
			
		||||
 | 
			
		||||
    // Operational Status
 | 
			
		||||
    struct {
 | 
			
		||||
        unsigned valid : 1;
 | 
			
		||||
        unsigned version : 3;
 | 
			
		||||
 | 
			
		||||
        unsigned om_acas_ra : 1;
 | 
			
		||||
        unsigned om_ident : 1;
 | 
			
		||||
        unsigned om_atc : 1;
 | 
			
		||||
        unsigned om_saf : 1;
 | 
			
		||||
 | 
			
		||||
        unsigned cc_acas : 1;
 | 
			
		||||
        unsigned cc_cdti : 1;
 | 
			
		||||
        unsigned cc_1090_in : 1;
 | 
			
		||||
        unsigned cc_arv : 1;
 | 
			
		||||
        unsigned cc_ts : 1;
 | 
			
		||||
        unsigned cc_tc : 2;
 | 
			
		||||
        unsigned cc_uat_in : 1;
 | 
			
		||||
        unsigned cc_poa : 1;
 | 
			
		||||
        unsigned cc_b2_low : 1;
 | 
			
		||||
        unsigned cc_lw_valid : 1;
 | 
			
		||||
 | 
			
		||||
        heading_type_t tah;
 | 
			
		||||
        heading_type_t hrd;
 | 
			
		||||
 | 
			
		||||
        unsigned cc_lw;
 | 
			
		||||
        unsigned cc_antenna_offset;
 | 
			
		||||
    } opstatus;
 | 
			
		||||
 | 
			
		||||
    // combined:
 | 
			
		||||
    //   Target State & Status (ADS-B V2 only)
 | 
			
		||||
    //   Comm-B BDS4,0 Vertical Intent
 | 
			
		||||
    struct {
 | 
			
		||||
        unsigned heading_valid : 1;
 | 
			
		||||
        unsigned fms_altitude_valid : 1;
 | 
			
		||||
        unsigned mcp_altitude_valid : 1;
 | 
			
		||||
        unsigned qnh_valid : 1;
 | 
			
		||||
        unsigned modes_valid : 1;
 | 
			
		||||
 | 
			
		||||
        float    heading;       // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended)
 | 
			
		||||
        heading_type_t heading_type;
 | 
			
		||||
        unsigned fms_altitude;  // FMS selected altitude
 | 
			
		||||
        unsigned mcp_altitude;  // MCP/FCU selected altitude
 | 
			
		||||
        float    qnh;           // altimeter setting (QFE or QNH/QNE), millibars
 | 
			
		||||
 | 
			
		||||
        nav_altitude_source_t altitude_source;
 | 
			
		||||
 | 
			
		||||
        nav_modes_t modes;
 | 
			
		||||
    } nav;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif // __DUMP1090_H
 | 
			
		||||
							
								
								
									
										173
									
								
								Software/portapack-mayhem/firmware/application/apps/ert_app.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Software/portapack-mayhem/firmware/application/apps/ert_app.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ert_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
#include "manchester.hpp"
 | 
			
		||||
 | 
			
		||||
#include "crc.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ert {
 | 
			
		||||
 | 
			
		||||
namespace format {
 | 
			
		||||
 | 
			
		||||
std::string type(Packet::Type value) {
 | 
			
		||||
	switch(value) {
 | 
			
		||||
	default:
 | 
			
		||||
	case Packet::Type::Unknown:	return "???";
 | 
			
		||||
	case Packet::Type::IDM:		return "IDM";
 | 
			
		||||
	case Packet::Type::SCM:		return "SCM";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string id(ID value) {
 | 
			
		||||
	return to_string_dec_uint(value, 10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string consumption(Consumption value) {
 | 
			
		||||
	return to_string_dec_uint(value, 10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string commodity_type(CommodityType value) {
 | 
			
		||||
	return to_string_dec_uint(value, 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace format */
 | 
			
		||||
 | 
			
		||||
} /* namespace ert */
 | 
			
		||||
 | 
			
		||||
void ERTLogger::on_packet(const ert::Packet& packet) {
 | 
			
		||||
	const auto formatted = packet.symbols_formatted();
 | 
			
		||||
	log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ERTRecentEntry::Key ERTRecentEntry::invalid_key { };
 | 
			
		||||
 | 
			
		||||
void ERTRecentEntry::update(const ert::Packet& packet) {
 | 
			
		||||
	received_count++;
 | 
			
		||||
 | 
			
		||||
	last_consumption = packet.consumption();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<ERTRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption);
 | 
			
		||||
 | 
			
		||||
	if( entry.received_count > 999 ) {
 | 
			
		||||
		line += " +++";
 | 
			
		||||
	} else {
 | 
			
		||||
		line += " " + to_string_dec_uint(entry.received_count, 3);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	line.resize(target_rect.width() / 8, ' ');
 | 
			
		||||
	painter.draw_string(target_rect.location(), style, line);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ERTAppView::ERTAppView(NavigationView&) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_ert);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&recent_entries_view,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_ert", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_tuning_frequency(initial_target_frequency);
 | 
			
		||||
	receiver_model.set_sampling_rate(sampling_rate);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(baseband_bandwidth);
 | 
			
		||||
	receiver_model.enable();  // Before using radio::enable(), but not updating Ant.DC-Bias.
 | 
			
		||||
 | 
			
		||||
/*	radio::enable({
 | 
			
		||||
		initial_target_frequency,
 | 
			
		||||
		sampling_rate,
 | 
			
		||||
		baseband_bandwidth,
 | 
			
		||||
		rf::Direction::Receive,
 | 
			
		||||
		receiver_model.rf_amp(),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.lna()),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.vga()),
 | 
			
		||||
	}); */
 | 
			
		||||
 | 
			
		||||
	logger = std::make_unique<ERTLogger>();
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->append(u"ert.txt");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ERTAppView::~ERTAppView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	settings.save("rx_ert", &app_settings);
 | 
			
		||||
 | 
			
		||||
	receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ERTAppView::focus() {
 | 
			
		||||
	field_vga.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ERTAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
	recent_entries_view.set_parent_rect({ 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ERTAppView::on_packet(const ert::Packet& packet) {
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->on_packet(packet);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( packet.crc_ok() ) {
 | 
			
		||||
		auto& entry = ::on_packet(recent, ERTRecentEntry::Key { packet.id(), packet.commodity_type() });
 | 
			
		||||
		entry.update(packet);
 | 
			
		||||
		recent_entries_view.set_dirty();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ERTAppView::on_show_list() {
 | 
			
		||||
	recent_entries_view.hidden(false);
 | 
			
		||||
	recent_entries_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										180
									
								
								Software/portapack-mayhem/firmware/application/apps/ert_app.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								Software/portapack-mayhem/firmware/application/apps/ert_app.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __ERT_APP_H__
 | 
			
		||||
#define __ERT_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_rssi.hpp"
 | 
			
		||||
#include "ui_channel.hpp"
 | 
			
		||||
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ert_packet.hpp"
 | 
			
		||||
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
struct ERTKey {
 | 
			
		||||
	ert::ID id;
 | 
			
		||||
	ert::CommodityType commodity_type;
 | 
			
		||||
 | 
			
		||||
	constexpr ERTKey(
 | 
			
		||||
		ert::ID id = ert::invalid_id,
 | 
			
		||||
		ert::CommodityType commodity_type = ert::invalid_commodity_type
 | 
			
		||||
	) : id { id  },
 | 
			
		||||
		commodity_type { commodity_type }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ERTKey( const ERTKey& other ) = default;
 | 
			
		||||
 | 
			
		||||
	ERTKey& operator=(const ERTKey& other) {
 | 
			
		||||
		id = other.id;
 | 
			
		||||
		commodity_type = other.commodity_type;
 | 
			
		||||
		return *this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool operator==(const ERTKey& other) const {
 | 
			
		||||
		return (id == other.id) && (commodity_type == other.commodity_type);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ERTRecentEntry {
 | 
			
		||||
	using Key = ERTKey;
 | 
			
		||||
 | 
			
		||||
	// TODO: Is this the right choice of invalid key value?
 | 
			
		||||
	static const Key invalid_key;
 | 
			
		||||
 | 
			
		||||
	ert::ID id { ert::invalid_id };
 | 
			
		||||
	ert::CommodityType commodity_type { ert::invalid_commodity_type };
 | 
			
		||||
 | 
			
		||||
	size_t received_count { 0 };
 | 
			
		||||
 | 
			
		||||
	ert::Consumption last_consumption { };
 | 
			
		||||
 | 
			
		||||
	ERTRecentEntry(
 | 
			
		||||
		const Key& key
 | 
			
		||||
	) : id { key.id },
 | 
			
		||||
		commodity_type { key.commodity_type }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return { id, commodity_type };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void update(const ert::Packet& packet);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ERTLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::filesystem::path& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void on_packet(const ert::Packet& packet);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using ERTRecentEntries = RecentEntries<ERTRecentEntry>;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
using ERTRecentEntriesView = RecentEntriesView<ERTRecentEntries>;
 | 
			
		||||
 | 
			
		||||
class ERTAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	static constexpr uint32_t initial_target_frequency = 911600000;
 | 
			
		||||
	static constexpr uint32_t sampling_rate = 4194304;
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 2500000;
 | 
			
		||||
 | 
			
		||||
	ERTAppView(NavigationView& nav);
 | 
			
		||||
	~ERTAppView();
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	// Prevent painting of region covered entirely by a child.
 | 
			
		||||
	// TODO: Add flag to View that specifies view does not need to be cleared before painting.
 | 
			
		||||
	void paint(Painter&) override { };
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "ERT Meter RX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	ERTRecentEntries recent { };
 | 
			
		||||
	std::unique_ptr<ERTLogger> logger { };
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	const RecentEntriesColumns columns { {
 | 
			
		||||
		{ "ID", 10 },
 | 
			
		||||
		{ "Tp", 2 },
 | 
			
		||||
		{ "Consumpt", 10 },
 | 
			
		||||
		{ "Cnt", 3 },
 | 
			
		||||
	} };
 | 
			
		||||
	ERTRecentEntriesView recent_entries_view { columns, recent };
 | 
			
		||||
 | 
			
		||||
	static constexpr auto header_height = 1 * 16;
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::ERTPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const ERTPacketMessage*>(p);
 | 
			
		||||
			const ert::Packet packet { message->type, message->packet };
 | 
			
		||||
			this->on_packet(packet);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void on_packet(const ert::Packet& packet);
 | 
			
		||||
	void on_show_list();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__ERT_APP_H__*/
 | 
			
		||||
@@ -0,0 +1,276 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "gps_sim_app.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_fileman.hpp"
 | 
			
		||||
#include "io_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::set_ready() {
 | 
			
		||||
	ready_signal = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) {
 | 
			
		||||
	File data_file, info_file;
 | 
			
		||||
	char file_data[257];
 | 
			
		||||
	
 | 
			
		||||
	// Get file size
 | 
			
		||||
	auto data_open_error = data_file.open("/" + new_file_path.string());
 | 
			
		||||
	if (data_open_error.is_valid()) {
 | 
			
		||||
		file_error();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	file_path = new_file_path;
 | 
			
		||||
	
 | 
			
		||||
	// Get original record frequency if available
 | 
			
		||||
	std::filesystem::path info_file_path = file_path;
 | 
			
		||||
	info_file_path.replace_extension(u".TXT");
 | 
			
		||||
	
 | 
			
		||||
	sample_rate = 500000;
 | 
			
		||||
	
 | 
			
		||||
	auto info_open_error = info_file.open("/" + info_file_path.string());
 | 
			
		||||
	if (!info_open_error.is_valid()) {
 | 
			
		||||
		memset(file_data, 0, 257);
 | 
			
		||||
		auto read_size = info_file.read(file_data, 256);
 | 
			
		||||
		if (!read_size.is_error()) {
 | 
			
		||||
			auto pos1 = strstr(file_data, "center_frequency=");
 | 
			
		||||
			if (pos1) {
 | 
			
		||||
				pos1 += 17;
 | 
			
		||||
				field_frequency.set_value(strtoll(pos1, nullptr, 10));
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			auto pos2 = strstr(file_data, "sample_rate=");
 | 
			
		||||
			if (pos2) {
 | 
			
		||||
				pos2 += 12;
 | 
			
		||||
				sample_rate = strtoll(pos2, nullptr, 10);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
 | 
			
		||||
	
 | 
			
		||||
	auto file_size = data_file.size();
 | 
			
		||||
	auto duration = (file_size * 1000) / (1 * 2 * sample_rate);
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_max(file_size / 1024);
 | 
			
		||||
	text_filename.set(file_path.filename().string().substr(0, 12));
 | 
			
		||||
	text_duration.set(to_string_time_ms(duration));
 | 
			
		||||
	
 | 
			
		||||
	button_play.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::on_tx_progress(const uint32_t progress) {
 | 
			
		||||
	progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::focus() {
 | 
			
		||||
	button_open.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::file_error() {
 | 
			
		||||
	nav_.display_modal("Error", "File read error.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GpsSimAppView::is_active() const {
 | 
			
		||||
	return (bool)replay_thread;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::toggle() {
 | 
			
		||||
	if( is_active() ) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
	} else {
 | 
			
		||||
		start();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::start() {
 | 
			
		||||
	stop(false);
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<stream::Reader> reader;
 | 
			
		||||
	
 | 
			
		||||
	auto p = std::make_unique<FileReader>();
 | 
			
		||||
	auto open_error = p->open(file_path);
 | 
			
		||||
	if( open_error.is_valid() ) {
 | 
			
		||||
		file_error();
 | 
			
		||||
	} else {
 | 
			
		||||
		reader = std::move(p);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( reader ) {
 | 
			
		||||
		button_play.set_bitmap(&bitmap_stop);
 | 
			
		||||
		baseband::set_sample_rate(sample_rate );
 | 
			
		||||
		
 | 
			
		||||
		replay_thread = std::make_unique<ReplayThread>(
 | 
			
		||||
			std::move(reader),
 | 
			
		||||
			read_size, buffer_count,
 | 
			
		||||
			&ready_signal,
 | 
			
		||||
			[](uint32_t return_code) {
 | 
			
		||||
				ReplayThreadDoneMessage message { return_code };
 | 
			
		||||
				EventDispatcher::send_message(message);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
	field_rfgain.on_change = [this](int32_t v) {
 | 
			
		||||
		tx_gain = v;
 | 
			
		||||
	};  
 | 
			
		||||
	field_rfgain.set_value(tx_gain);
 | 
			
		||||
	receiver_model.set_tx_gain(tx_gain); 
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 	field_rfamp.on_change = [this](int32_t v) {
 | 
			
		||||
		rf_amp = (bool)v;
 | 
			
		||||
	};
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);
 | 
			
		||||
	
 | 
			
		||||
	radio::enable({
 | 
			
		||||
		receiver_model.tuning_frequency(),
 | 
			
		||||
		sample_rate ,
 | 
			
		||||
		baseband_bandwidth,
 | 
			
		||||
		rf::Direction::Transmit,
 | 
			
		||||
		rf_amp,         //  previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps  
 | 
			
		||||
		static_cast<int8_t>(receiver_model.lna()),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.vga())
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::stop(const bool do_loop) {
 | 
			
		||||
	if( is_active() )
 | 
			
		||||
		replay_thread.reset();
 | 
			
		||||
	
 | 
			
		||||
	if (do_loop && check_loop.value()) {
 | 
			
		||||
		start();
 | 
			
		||||
	} else {
 | 
			
		||||
		radio::disable();
 | 
			
		||||
		button_play.set_bitmap(&bitmap_play);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	ready_signal = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::handle_replay_thread_done(const uint32_t return_code) {
 | 
			
		||||
	if (return_code == ReplayThread::END_OF_FILE) {
 | 
			
		||||
		stop(true);
 | 
			
		||||
	} else if (return_code == ReplayThread::READ_ERROR) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
		file_error();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_value(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GpsSimAppView::GpsSimAppView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	tx_gain = 35;field_rfgain.set_value(tx_gain);  // Initial default  value (-12 dB's max 47dBs ).
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);  // Initial default value True. (TX RF amp on , +14dB's)
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_gps);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button_open,
 | 
			
		||||
		&text_filename,
 | 
			
		||||
		&text_sample_rate,
 | 
			
		||||
		&text_duration,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_rfgain,
 | 
			
		||||
		&field_rfamp,       // let's not use common persistent rf_amp , local rfamp is enough
 | 
			
		||||
		&check_loop,
 | 
			
		||||
		&button_play,
 | 
			
		||||
		&waterfall,
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(target_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_target_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_target_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_step(5000);
 | 
			
		||||
	
 | 
			
		||||
	button_play.on_select = [this](ImageButton&) {
 | 
			
		||||
		this->toggle();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_open.on_select = [this, &nav](Button&) {
 | 
			
		||||
		auto open_view = nav.push<FileLoadView>(".C8");
 | 
			
		||||
		open_view->on_changed = [this](std::filesystem::path new_file_path) {
 | 
			
		||||
			on_file_changed(new_file_path);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GpsSimAppView::~GpsSimAppView() {
 | 
			
		||||
	radio::disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::on_hide() {
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	if( is_active() )
 | 
			
		||||
		stop(false);
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
 | 
			
		||||
	const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	waterfall.set_parent_rect(waterfall_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::on_target_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	set_target_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GpsSimAppView::set_target_frequency(const rf::Frequency new_value) {
 | 
			
		||||
	persistent_memory::set_tuned_frequency(new_value);;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rf::Frequency GpsSimAppView::target_frequency() const {
 | 
			
		||||
	return persistent_memory::tuned_frequency();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,167 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __GPS_SIM_APP_HPP__
 | 
			
		||||
#define __GPS_SIM_APP_HPP__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "replay_thread.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class GpsSimAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	GpsSimAppView(NavigationView& nav);
 | 
			
		||||
	~GpsSimAppView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "GPS Sim TX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
	
 | 
			
		||||
	uint32_t sample_rate = 0;
 | 
			
		||||
	int32_t tx_gain { 47 };
 | 
			
		||||
	bool rf_amp { true }; // aux private var to store temporal, same as Replay App rf_amp user selection.
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 3000000; //filter bandwidth
 | 
			
		||||
	const size_t read_size { 16384 };
 | 
			
		||||
	const size_t buffer_count { 3 };
 | 
			
		||||
 | 
			
		||||
	void on_file_changed(std::filesystem::path new_file_path);
 | 
			
		||||
	void on_target_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress);
 | 
			
		||||
	
 | 
			
		||||
	void set_target_frequency(const rf::Frequency new_value);
 | 
			
		||||
	rf::Frequency target_frequency() const;
 | 
			
		||||
 | 
			
		||||
	void toggle();
 | 
			
		||||
	void start();
 | 
			
		||||
	void stop(const bool do_loop);
 | 
			
		||||
	bool is_active() const;
 | 
			
		||||
	void set_ready();
 | 
			
		||||
	void handle_replay_thread_done(const uint32_t return_code);
 | 
			
		||||
	void file_error();
 | 
			
		||||
 | 
			
		||||
	std::filesystem::path file_path { };
 | 
			
		||||
	std::unique_ptr<ReplayThread> replay_thread { };
 | 
			
		||||
	bool ready_signal { false };
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 10 * 8, 2 * 16 }, "GAIN   A:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_open {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 10 * 8, 2 * 16 },
 | 
			
		||||
		"Open file"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_filename {
 | 
			
		||||
		{ 11 * 8, 0 * 16, 12 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_sample_rate {
 | 
			
		||||
		{ 24 * 8, 0 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_duration {
 | 
			
		||||
		{ 11 * 8, 1 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 18 * 8, 1 * 16, 12 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 2 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_rfgain {
 | 
			
		||||
		{ 14 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 47 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '	
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_rfamp {     // previously we were using "RFAmpField field_rf_amp" but that is general Receiver amp setting.
 | 
			
		||||
		{ 19 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 14 },                // this time we will display GUI , 0 or 14 dBs same as Mic and Replay App
 | 
			
		||||
		14,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_loop {
 | 
			
		||||
		{ 21 * 8, 2 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Loop",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	ImageButton button_play {
 | 
			
		||||
		{ 28 * 8, 2 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		&bitmap_play,
 | 
			
		||||
		Color::green(),
 | 
			
		||||
		Color::black()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	spectrum::WaterfallWidget waterfall { };
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_replay_thread_error {
 | 
			
		||||
		Message::ID::ReplayThreadDone,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
 | 
			
		||||
			this->handle_replay_thread_done(message.return_code);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RequestSignalMessage*>(p);
 | 
			
		||||
			if (message->signal == RequestSignalMessage::Signal::FillRequest) {
 | 
			
		||||
				this->set_ready();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__GPS_SIM_APP_HPP__*/
 | 
			
		||||
							
								
								
									
										371
									
								
								Software/portapack-mayhem/firmware/application/apps/lge_app.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								Software/portapack-mayhem/firmware/application/apps/lge_app.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,371 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2019 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// The UI for this app is in French because it concerns leisure centers
 | 
			
		||||
// only established in France. "LGE" stands for a trademark I'd rather
 | 
			
		||||
// not spell out completely here.
 | 
			
		||||
 | 
			
		||||
#include "lge_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void LGEView::focus() {
 | 
			
		||||
	options_frame.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LGEView::~LGEView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_lge", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_lge_frame(const uint8_t command, const uint16_t address_a, const uint16_t address_b, std::vector<uint8_t>& data) {
 | 
			
		||||
	std::array<uint8_t, 5> header = {
 | 
			
		||||
		command,
 | 
			
		||||
		(uint8_t)(address_a & 255),
 | 
			
		||||
		(uint8_t)(address_a >> 8),
 | 
			
		||||
		(uint8_t)(address_b & 255),
 | 
			
		||||
		(uint8_t)(address_b >> 8),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	data.insert(data.begin(), header.begin(), header.end());
 | 
			
		||||
	
 | 
			
		||||
	frame_size = rfm69.gen_frame(data);
 | 
			
		||||
	
 | 
			
		||||
	for (auto b : data)
 | 
			
		||||
		console.write(to_string_hex(b, 2) + " ");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_touche() {
 | 
			
		||||
	// 0001.89s
 | 
			
		||||
	// 0D 96 02 12 0E 00 46 28 01 45 27 01 44 23 66 30
 | 
			
		||||
	std::vector<uint8_t> data { 0x46, 0x28, 0x01, 0x45, 0x27, 0x01, 0x44, 0x23 };
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x07Touche:\x1B\x10");
 | 
			
		||||
	generate_lge_frame(0x96, (field_player.value() << 8) | field_room.value(), 0x0001, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_nickname() {
 | 
			
		||||
	// 0040.48s:
 | 
			
		||||
	// 30 02 1A 00 19 00 FF 00 02 19 42 52 45 42 49 53 20 00 00 00 00 00 00 00 00 00
 | 
			
		||||
	// 04 01 B0 04 7F 1F 11 33 40 1F 22 01 07 00 00 01 07 00 00 63 05 00 00 99 A2 
 | 
			
		||||
	
 | 
			
		||||
	std::vector<uint8_t> data { };
 | 
			
		||||
	std::array<uint8_t, 3> data_header = { 0xFF, 0x00, 0x02 };
 | 
			
		||||
	std::array<uint8_t, 22> data_footer = {
 | 
			
		||||
		0x01, 0xB0, 0x04, 0x7F,
 | 
			
		||||
		0x1F, 0x11, 0x33, 0x40,
 | 
			
		||||
		0x1F, 0x22, 0x01, 0x07,
 | 
			
		||||
		0x00, 0x00, 0x01, 0x07,
 | 
			
		||||
		0x00, 0x00, 0x63, 0x05,
 | 
			
		||||
		0x00, 0x00
 | 
			
		||||
	};
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
	
 | 
			
		||||
	//data_header[2] = field_room.value();	// ?
 | 
			
		||||
	//data_footer[0] = field_room.value();	// ?
 | 
			
		||||
	
 | 
			
		||||
	data.insert(data.begin(), data_header.begin(), data_header.end());
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_player.value());
 | 
			
		||||
	
 | 
			
		||||
	c = 0;
 | 
			
		||||
	for (auto &ch : nickname) {
 | 
			
		||||
		data.push_back(ch);
 | 
			
		||||
		c++;
 | 
			
		||||
	}
 | 
			
		||||
	// Space at the end, is this required ?
 | 
			
		||||
	data.push_back(0x20);
 | 
			
		||||
	// Pad with zeroes
 | 
			
		||||
	while (++c < 16)
 | 
			
		||||
		data.push_back(0x00);
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_team.value());
 | 
			
		||||
	
 | 
			
		||||
	data.insert(data.end(), data_footer.begin(), data_footer.end());
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x0ESet nickname:\x1B\x10");
 | 
			
		||||
	
 | 
			
		||||
	generate_lge_frame(0x02, 0x001A, field_player.value(), data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_team() {
 | 
			
		||||
	// 0041.83s:
 | 
			
		||||
	// 3D 03 FF FF FF FF 02 03 01 52 4F 55 47 45 00 00 00 00 00 00 00 00 00 00 00 00
 | 
			
		||||
	// 02 56 45 52 54 45 00 00 00 00 00 00 00 00 00 00 00 01 03 42 4C 45 55 45 00 00
 | 
			
		||||
	// 00 00 00 00 00 00 00 00 00 02 43 29
 | 
			
		||||
 | 
			
		||||
	std::vector<uint8_t> data { };
 | 
			
		||||
	std::array<uint8_t, 2> data_header = { 0x02, 0x01 };
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
 | 
			
		||||
	data.insert(data.begin(), data_header.begin(), data_header.end());
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_team.value());
 | 
			
		||||
	
 | 
			
		||||
	c = 0;
 | 
			
		||||
	for (auto &ch : nickname) {
 | 
			
		||||
		data.push_back(ch);
 | 
			
		||||
		c++;
 | 
			
		||||
	}
 | 
			
		||||
	// Pad with zeroes
 | 
			
		||||
	while (c++ < 16)
 | 
			
		||||
		data.push_back(0x00);
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_team.value() - 1);	// Color ?
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x0ASet team:\x1B\x10");
 | 
			
		||||
	
 | 
			
		||||
	generate_lge_frame(0x03, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_broadcast_nickname() {
 | 
			
		||||
	// 0043.86s:
 | 
			
		||||
	// 3D 04 FF FF FF FF 02 03 19 42 52 45 42 49 53 20 00 00 00 00 00 00 00 00 00 04
 | 
			
		||||
	// 07 50 4F 4E 45 59 20 00 00 00 00 00 00 00 00 00 00 05 1B 41 42 42 59 20 00 00
 | 
			
		||||
	// 00 00 00 00 00 00 00 00 00 04 0A 02
 | 
			
		||||
 | 
			
		||||
	std::vector<uint8_t> data { };
 | 
			
		||||
	std::array<uint8_t, 2> data_header = { 0x02, 0x01 };
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
 | 
			
		||||
	data.insert(data.begin(), data_header.begin(), data_header.end());
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_player.value());
 | 
			
		||||
	
 | 
			
		||||
	c = 0;
 | 
			
		||||
	for (auto &ch : nickname) {
 | 
			
		||||
		data.push_back(ch);
 | 
			
		||||
		c++;
 | 
			
		||||
	}
 | 
			
		||||
	// Space at the end, is this required ?
 | 
			
		||||
	data.push_back(0x20);
 | 
			
		||||
	// Pad with zeroes
 | 
			
		||||
	while (++c < 16)
 | 
			
		||||
		data.push_back(0x00);
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(field_team.value());
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x09" "Broadcast nickname:\x1B\x10");
 | 
			
		||||
	
 | 
			
		||||
	generate_lge_frame(0x04, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_start() {
 | 
			
		||||
	// 0166.13s:
 | 
			
		||||
	// 0A 05 FF FF FF FF 02 EC FF FF FF A3 35
 | 
			
		||||
	std::vector<uint8_t> data { 0x02, 0xEC, 0xFF, 0xFF, 0xFF };
 | 
			
		||||
	
 | 
			
		||||
	//data[0] = field_room.value();	// ?
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x0DStart:\x1B\x10");
 | 
			
		||||
	generate_lge_frame(0x05, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_gameover() {
 | 
			
		||||
	std::vector<uint8_t> data { (uint8_t)field_room.value() };
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x0CGameover:\x1B\x10");
 | 
			
		||||
	generate_lge_frame(0x0D, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::generate_frame_collier() {
 | 
			
		||||
	uint8_t flags = 0;
 | 
			
		||||
	
 | 
			
		||||
	// Custom
 | 
			
		||||
	// 0C 00 13 37 13 37 id flags channel playerid zapduty zaptime checksum CRC CRC
 | 
			
		||||
	// channel: field_channel
 | 
			
		||||
	// playerid: field_player
 | 
			
		||||
	// zapduty: field_power
 | 
			
		||||
	// zaptime: field_duration
 | 
			
		||||
	
 | 
			
		||||
	if (checkbox_heartbeat.value())
 | 
			
		||||
		flags |= 1;
 | 
			
		||||
	if (checkbox_rxtick.value())
 | 
			
		||||
		flags |= 2;
 | 
			
		||||
	
 | 
			
		||||
	uint8_t checksum = 0;
 | 
			
		||||
	uint8_t id = (uint8_t)field_id.value();
 | 
			
		||||
	
 | 
			
		||||
	std::vector<uint8_t> data {
 | 
			
		||||
		id,
 | 
			
		||||
		flags,
 | 
			
		||||
		(uint8_t)field_room.value(),
 | 
			
		||||
		(uint8_t)field_player.value(),
 | 
			
		||||
		(uint8_t)field_power.value(),
 | 
			
		||||
		(uint8_t)(field_duration.value() * 10)
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	for (auto &v : data)
 | 
			
		||||
		checksum += v;
 | 
			
		||||
	
 | 
			
		||||
	data.push_back(checksum - id);
 | 
			
		||||
	
 | 
			
		||||
	console.write("\n\x1B\x06" "Config:\x1B\x10");
 | 
			
		||||
	generate_lge_frame(0x00, 0x3713, 0x3713, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::start_tx() {
 | 
			
		||||
	if (tx_mode == ALL) {
 | 
			
		||||
		transmitter_model.set_tuning_frequency(channels[channel_index]);
 | 
			
		||||
		tx_view.on_show();	// Refresh tuning frequency display
 | 
			
		||||
		tx_view.set_dirty();
 | 
			
		||||
	}
 | 
			
		||||
	transmitter_model.set_sampling_rate(2280000);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
 | 
			
		||||
	chThdSleep(100);
 | 
			
		||||
		
 | 
			
		||||
	baseband::set_fsk_data(frame_size * 8, 2280000 / 9600, 4000, 256);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::stop_tx() {
 | 
			
		||||
	tx_mode = IDLE;
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	tx_view.set_transmitting(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LGEView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	(void)progress;
 | 
			
		||||
	
 | 
			
		||||
	if (!done) return;
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	
 | 
			
		||||
	/*if (repeats < 2) {
 | 
			
		||||
		chThdSleep(100);
 | 
			
		||||
		repeats++;
 | 
			
		||||
		start_tx();
 | 
			
		||||
	} else {*/
 | 
			
		||||
		if (tx_mode == ALL) {
 | 
			
		||||
			if (channel_index < 2) {
 | 
			
		||||
				channel_index++;
 | 
			
		||||
				repeats = 0;
 | 
			
		||||
				start_tx();
 | 
			
		||||
			} else {
 | 
			
		||||
				stop_tx();
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			stop_tx();
 | 
			
		||||
		}
 | 
			
		||||
	//}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LGEView::LGEView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_fsktx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_frame,
 | 
			
		||||
		&field_room,
 | 
			
		||||
		&button_text,
 | 
			
		||||
		&field_team,
 | 
			
		||||
		&field_player,
 | 
			
		||||
		&field_id,
 | 
			
		||||
		&field_power,
 | 
			
		||||
		&field_duration,
 | 
			
		||||
		&checkbox_heartbeat,
 | 
			
		||||
		&checkbox_rxtick,
 | 
			
		||||
		&checkbox_channels,
 | 
			
		||||
		&console,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_lge", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	field_room.set_value(1);
 | 
			
		||||
	field_team.set_value(1);
 | 
			
		||||
	field_player.set_value(1);
 | 
			
		||||
	field_id.set_value(1);
 | 
			
		||||
	field_power.set_value(1);
 | 
			
		||||
	field_duration.set_value(2);
 | 
			
		||||
	
 | 
			
		||||
	button_text.on_select = [this, &nav](Button&) {
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			nickname,
 | 
			
		||||
			15,
 | 
			
		||||
			[this](std::string& buffer) {
 | 
			
		||||
				button_text.set_text(buffer);
 | 
			
		||||
			});
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (tx_mode == IDLE) {
 | 
			
		||||
			auto i = options_frame.selected_index_value();
 | 
			
		||||
			if (i == 0)
 | 
			
		||||
				generate_frame_touche();
 | 
			
		||||
			else if (i == 1)
 | 
			
		||||
				generate_frame_nickname();
 | 
			
		||||
			else if (i == 2)
 | 
			
		||||
				generate_frame_team();
 | 
			
		||||
			else if (i == 3)
 | 
			
		||||
				generate_frame_broadcast_nickname();
 | 
			
		||||
			else if (i == 4)
 | 
			
		||||
				generate_frame_start();
 | 
			
		||||
			else if (i == 5)
 | 
			
		||||
				generate_frame_gameover();
 | 
			
		||||
			else if (i == 6)
 | 
			
		||||
				generate_frame_collier();
 | 
			
		||||
			
 | 
			
		||||
			repeats = 0;
 | 
			
		||||
			channel_index = 0;
 | 
			
		||||
			tx_mode = checkbox_channels.value() ? ALL : SINGLE;
 | 
			
		||||
			tx_view.set_transmitting(true);
 | 
			
		||||
			start_tx();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		stop_tx();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										201
									
								
								Software/portapack-mayhem/firmware/application/apps/lge_app.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								Software/portapack-mayhem/firmware/application/apps/lge_app.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2019 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "rfm69.hpp"
 | 
			
		||||
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class LGEView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	LGEView(NavigationView& nav);
 | 
			
		||||
	~LGEView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "LGE tool TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		ALL
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
	
 | 
			
		||||
    	RFM69 rfm69 { 5, 0x2DD4, true, true };
 | 
			
		||||
    
 | 
			
		||||
	uint32_t frame_size { 0 };
 | 
			
		||||
	uint32_t repeats { 0 };
 | 
			
		||||
	uint32_t channel_index { 0 };
 | 
			
		||||
	std::string nickname { "ABCDEF" };
 | 
			
		||||
	
 | 
			
		||||
	rf::Frequency channels[3] = { 868067000, 868183000, 868295000 };
 | 
			
		||||
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void stop_tx();
 | 
			
		||||
	
 | 
			
		||||
	void generate_lge_frame(const uint8_t command, std::vector<uint8_t>& data) {
 | 
			
		||||
		generate_lge_frame(command, 0xFFFF, 0xFFFF, data);
 | 
			
		||||
	}
 | 
			
		||||
	void generate_lge_frame(const uint8_t command, const uint16_t address_a, const uint16_t address_b, std::vector<uint8_t>& data);
 | 
			
		||||
	void generate_frame_touche();
 | 
			
		||||
	void generate_frame_nickname();
 | 
			
		||||
	void generate_frame_team();
 | 
			
		||||
	void generate_frame_broadcast_nickname();
 | 
			
		||||
	void generate_frame_start();
 | 
			
		||||
	void generate_frame_gameover();
 | 
			
		||||
	void generate_frame_collier();
 | 
			
		||||
	
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		//{ { 7 * 8, 1 * 8 }, "NO FUN ALLOWED !", Color::red() },
 | 
			
		||||
		{ { 1 * 8, 1 * 8 }, "Frame:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 3 * 8 }, "Room:", Color::light_grey() },
 | 
			
		||||
		{ { 14 * 8, 3 * 8 }, "Text:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 5 * 8 }, "Team:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 7 * 8 }, "Player:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 10 * 8 }, "Vest:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 12 * 8 }, "ID:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 14 * 8 }, "Pow:  /10", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 16 * 8 }, "Time:  x100ms", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_frame {
 | 
			
		||||
		{ 7 * 8, 1 * 8 },
 | 
			
		||||
		13,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Key", 0 },
 | 
			
		||||
			{ "Set nickname", 1 },
 | 
			
		||||
			{ "Set team", 2 },
 | 
			
		||||
			{ "Brdcst nick", 3 },
 | 
			
		||||
			{ "Start", 4 },
 | 
			
		||||
			{ "Game over", 5 },
 | 
			
		||||
			{ "Set vest", 6 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_channels {
 | 
			
		||||
		{ 20 * 8, 1 * 8 },
 | 
			
		||||
		7,
 | 
			
		||||
		"All ch.",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_room {
 | 
			
		||||
		{ 7 * 8, 3 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 1, 2 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_text {
 | 
			
		||||
		{ 14 * 8, 5 * 8, 16 * 8, 3 * 8 },
 | 
			
		||||
		"ABCDEF"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_team {
 | 
			
		||||
		{ 7 * 8, 5 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 1, 6 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_player {
 | 
			
		||||
		{ 7 * 8, 7 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 50 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_heartbeat {
 | 
			
		||||
		{ 17 * 8, 12 * 8 },
 | 
			
		||||
		9,
 | 
			
		||||
		"Heartbeat",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox checkbox_rxtick {
 | 
			
		||||
		{ 17 * 8, 15 * 8 },
 | 
			
		||||
		7,
 | 
			
		||||
		"RX tick",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_id {
 | 
			
		||||
		{ 7 * 8, 12 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 1, 2 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_power {
 | 
			
		||||
		{ 7 * 8, 14 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 10 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_duration {
 | 
			
		||||
		{ 7 * 8, 16 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 25 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 18 * 8, 30 * 8, 7 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,246 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "pocsag_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace pocsag;
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
 | 
			
		||||
void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) {
 | 
			
		||||
	std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " +
 | 
			
		||||
						to_string_dec_uint(packet.bitrate()) + " Codewords:";
 | 
			
		||||
	
 | 
			
		||||
	// Raw hex dump of all the codewords
 | 
			
		||||
	for (size_t c = 0; c < 16; c++)
 | 
			
		||||
		entry += to_string_hex(packet[c], 8) + " ";
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(packet.timestamp(), entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGLogger::log_decoded(
 | 
			
		||||
	const pocsag::POCSAGPacket& packet,
 | 
			
		||||
	const std::string text) {
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(packet.timestamp(), text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void POCSAGAppView::update_freq(rf::Frequency f) {
 | 
			
		||||
	set_target_frequency(f);
 | 
			
		||||
	portapack::persistent_memory::set_tuned_frequency(f);	// Maybe not ?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POCSAGAppView::POCSAGAppView(NavigationView& nav) {
 | 
			
		||||
	uint32_t ignore_address;
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_pocsag);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&audio,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&check_log,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&check_ignore,
 | 
			
		||||
		&sym_ignore,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_pocsag", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
		field_frequency.set_value(app_settings.rx_frequency);
 | 
			
		||||
	}
 | 
			
		||||
	else field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	check_log.set_value(logging);
 | 
			
		||||
	check_log.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		logging = v;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
 | 
			
		||||
	field_volume.on_change = [this](int32_t v) {
 | 
			
		||||
		this->on_headphone_volume_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	check_ignore.set_value(ignore);
 | 
			
		||||
	check_ignore.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		ignore = v;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ignore_address = persistent_memory::pocsag_ignore_address();
 | 
			
		||||
	for (size_t c = 0; c < 7; c++) {
 | 
			
		||||
		sym_ignore.set_sym(6 - c, ignore_address % 10);
 | 
			
		||||
		ignore_address /= 10;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger = std::make_unique<POCSAGLogger>();
 | 
			
		||||
	if (logger)
 | 
			
		||||
		logger->append("pocsag.txt");
 | 
			
		||||
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
	audio::output::unmute();
 | 
			
		||||
 | 
			
		||||
	baseband::set_pocsag();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POCSAGAppView::~POCSAGAppView() {	
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_pocsag", &app_settings);
 | 
			
		||||
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
 | 
			
		||||
	// Save ignored address
 | 
			
		||||
	persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGAppView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGAppView::on_headphone_volume_changed(int32_t v) {
 | 
			
		||||
	const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
 | 
			
		||||
	receiver_model.set_headphone_volume(new_volume);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Useless ?
 | 
			
		||||
void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGAppView::on_packet(const POCSAGPacketMessage * message) {
 | 
			
		||||
	std::string alphanum_text = "";
 | 
			
		||||
	
 | 
			
		||||
	if (message->packet.flag() != NORMAL)
 | 
			
		||||
		console.writeln("\n\x1B\x0CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
 | 
			
		||||
	else {
 | 
			
		||||
		pocsag_decode_batch(message->packet, &pocsag_state);
 | 
			
		||||
 | 
			
		||||
		if ((ignore) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
 | 
			
		||||
			// Ignore (inform, but no log)
 | 
			
		||||
			//console.write("\n\x1B\x03" + to_string_time(message->packet.timestamp()) +
 | 
			
		||||
			//			" Ignored address " + to_string_dec_uint(pocsag_state.address));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		// Too many errors for reliable decode
 | 
			
		||||
		if ((ignore) && (pocsag_state.errors >= 3)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		std::string console_info;
 | 
			
		||||
		const uint32_t roundVal = 50;
 | 
			
		||||
		const uint32_t bitrate = roundVal * ((message->packet.bitrate() + (roundVal/2))/roundVal);
 | 
			
		||||
		console_info = "\n" + to_string_datetime(message->packet.timestamp(), HM);
 | 
			
		||||
		console_info += " " + to_string_dec_uint(bitrate);
 | 
			
		||||
		console_info += " ADDR:" + to_string_dec_uint(pocsag_state.address);
 | 
			
		||||
		console_info += " F" + to_string_dec_uint(pocsag_state.function);
 | 
			
		||||
 | 
			
		||||
		// Store last received address for POCSAG TX
 | 
			
		||||
		persistent_memory::set_pocsag_last_address(pocsag_state.address);
 | 
			
		||||
		
 | 
			
		||||
		if (pocsag_state.out_type == ADDRESS) {
 | 
			
		||||
			// Address only
 | 
			
		||||
			
 | 
			
		||||
			console.write(console_info);
 | 
			
		||||
			
 | 
			
		||||
			if (logger && logging) {
 | 
			
		||||
				logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
 | 
			
		||||
													" F" + to_string_dec_uint(pocsag_state.function) +
 | 
			
		||||
													" Address only");
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			last_address = pocsag_state.address;
 | 
			
		||||
		} else if (pocsag_state.out_type == MESSAGE) {
 | 
			
		||||
			if (pocsag_state.address != last_address) {
 | 
			
		||||
				// New message
 | 
			
		||||
				console.writeln(console_info);
 | 
			
		||||
				console.write(pocsag_state.output);
 | 
			
		||||
				
 | 
			
		||||
				last_address = pocsag_state.address;
 | 
			
		||||
			} else {
 | 
			
		||||
				// Message continues...
 | 
			
		||||
				console.write(pocsag_state.output);
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (logger && logging)
 | 
			
		||||
				logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
 | 
			
		||||
													" F" + to_string_dec_uint(pocsag_state.function) +
 | 
			
		||||
													" Alpha: " + pocsag_state.output);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Log raw data whatever it contains
 | 
			
		||||
	if (logger && logging)
 | 
			
		||||
		logger->log_raw_data(message->packet, target_frequency());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGAppView::set_target_frequency(const uint32_t new_value) {
 | 
			
		||||
	target_frequency_ = new_value;
 | 
			
		||||
	receiver_model.set_tuning_frequency(new_value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t POCSAGAppView::target_frequency() const {
 | 
			
		||||
	return target_frequency_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,148 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __POCSAG_APP_H__
 | 
			
		||||
#define __POCSAG_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_rssi.hpp"
 | 
			
		||||
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "pocsag.hpp"
 | 
			
		||||
#include "pocsag_packet.hpp"
 | 
			
		||||
 | 
			
		||||
class POCSAGLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::string& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency);
 | 
			
		||||
	void log_decoded(const pocsag::POCSAGPacket& packet, const std::string text);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class POCSAGAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	POCSAGAppView(NavigationView& nav);
 | 
			
		||||
	~POCSAGAppView();
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "POCSAG RX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr uint32_t initial_target_frequency = 466175000;
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	bool logging { true };
 | 
			
		||||
	bool ignore { true };
 | 
			
		||||
	uint32_t last_address = 0xFFFFFFFF;
 | 
			
		||||
	pocsag::POCSAGState pocsag_state { };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Audio audio{
 | 
			
		||||
		{ 21 * 8, 10, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 8 },
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_log {
 | 
			
		||||
		{ 24 * 8, 21 },
 | 
			
		||||
		3,
 | 
			
		||||
		"LOG",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_volume{
 | 
			
		||||
		{ 28 * 8, 0 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_ignore {
 | 
			
		||||
		{ 1 * 8, 21 },
 | 
			
		||||
		12,
 | 
			
		||||
		"Ignore addr:",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	SymField sym_ignore {
 | 
			
		||||
		{ 16 * 8, 21 },
 | 
			
		||||
		7,
 | 
			
		||||
		SymField::SYMFIELD_DEC
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 3 * 16, 240, 256 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<POCSAGLogger> logger { };
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency_ = initial_target_frequency;
 | 
			
		||||
	
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
 | 
			
		||||
	void on_packet(const POCSAGPacketMessage * message);
 | 
			
		||||
 | 
			
		||||
	void on_headphone_volume_changed(int32_t v);
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency() const;
 | 
			
		||||
	void set_target_frequency(const uint32_t new_value);
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::POCSAGPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const POCSAGPacketMessage*>(p);
 | 
			
		||||
			this->on_packet(message);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__POCSAG_APP_H__*/
 | 
			
		||||
@@ -0,0 +1,291 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyleft  (ↄ) 2022 NotPike
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "replay_app.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_fileman.hpp"
 | 
			
		||||
#include "io_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::set_ready() {
 | 
			
		||||
	ready_signal = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) {
 | 
			
		||||
	File data_file, info_file;
 | 
			
		||||
	char file_data[257];
 | 
			
		||||
	
 | 
			
		||||
	// Get file size
 | 
			
		||||
	auto data_open_error = data_file.open("/" + new_file_path.string());
 | 
			
		||||
	if (data_open_error.is_valid()) {
 | 
			
		||||
		file_error();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	file_path = new_file_path;
 | 
			
		||||
	
 | 
			
		||||
	// Get original record frequency if available
 | 
			
		||||
	std::filesystem::path info_file_path = file_path;
 | 
			
		||||
	info_file_path.replace_extension(u".TXT");
 | 
			
		||||
	
 | 
			
		||||
	sample_rate = 500000;
 | 
			
		||||
	
 | 
			
		||||
	auto info_open_error = info_file.open("/" + info_file_path.string());
 | 
			
		||||
	if (!info_open_error.is_valid()) {
 | 
			
		||||
		memset(file_data, 0, 257);
 | 
			
		||||
		auto read_size = info_file.read(file_data, 256);
 | 
			
		||||
		if (!read_size.is_error()) {
 | 
			
		||||
			auto pos1 = strstr(file_data, "center_frequency=");
 | 
			
		||||
			if (pos1) {
 | 
			
		||||
				pos1 += 17;
 | 
			
		||||
				field_frequency.set_value(strtoll(pos1, nullptr, 10));
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			auto pos2 = strstr(file_data, "sample_rate=");
 | 
			
		||||
			if (pos2) {
 | 
			
		||||
				pos2 += 12;
 | 
			
		||||
				sample_rate = strtoll(pos2, nullptr, 10);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
 | 
			
		||||
	
 | 
			
		||||
	auto file_size = data_file.size();
 | 
			
		||||
	auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_max(file_size);
 | 
			
		||||
	text_filename.set(file_path.filename().string().substr(0, 12));
 | 
			
		||||
	text_duration.set(to_string_time_ms(duration));
 | 
			
		||||
	
 | 
			
		||||
	button_play.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::on_tx_progress(const uint32_t progress) {
 | 
			
		||||
	progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::focus() {
 | 
			
		||||
	button_open.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::file_error() {
 | 
			
		||||
	nav_.display_modal("Error", "File read error.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ReplayAppView::is_active() const {
 | 
			
		||||
	return (bool)replay_thread;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::toggle() {
 | 
			
		||||
	if( is_active() ) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
	} else {
 | 
			
		||||
		start();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::start() {
 | 
			
		||||
	stop(false);
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<stream::Reader> reader;
 | 
			
		||||
	
 | 
			
		||||
	auto p = std::make_unique<FileReader>();
 | 
			
		||||
	auto open_error = p->open(file_path);
 | 
			
		||||
	if( open_error.is_valid() ) {
 | 
			
		||||
		file_error();
 | 
			
		||||
		return;                               // Fixes TX bug if there's a file error
 | 
			
		||||
	} else {
 | 
			
		||||
		reader = std::move(p);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( reader ) {
 | 
			
		||||
		button_play.set_bitmap(&bitmap_stop);
 | 
			
		||||
		baseband::set_sample_rate(sample_rate * 8);
 | 
			
		||||
		
 | 
			
		||||
		replay_thread = std::make_unique<ReplayThread>(
 | 
			
		||||
			std::move(reader),
 | 
			
		||||
			read_size, buffer_count,
 | 
			
		||||
			&ready_signal,
 | 
			
		||||
			[](uint32_t return_code) {
 | 
			
		||||
				ReplayThreadDoneMessage message { return_code };
 | 
			
		||||
				EventDispatcher::send_message(message);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
    field_rfgain.on_change = [this](int32_t v) {
 | 
			
		||||
		tx_gain = v;
 | 
			
		||||
	};  
 | 
			
		||||
	field_rfgain.set_value(tx_gain);
 | 
			
		||||
	receiver_model.set_tx_gain(tx_gain); 
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 	field_rfamp.on_change = [this](int32_t v) {
 | 
			
		||||
		rf_amp = (bool)v;
 | 
			
		||||
	};
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);
 | 
			
		||||
 | 
			
		||||
	//Enable Bias Tee if selected
 | 
			
		||||
	radio::set_antenna_bias(portapack::get_antenna_bias());
 | 
			
		||||
		
 | 
			
		||||
	radio::enable({
 | 
			
		||||
		receiver_model.tuning_frequency(),
 | 
			
		||||
		sample_rate * 8,
 | 
			
		||||
		baseband_bandwidth,
 | 
			
		||||
		rf::Direction::Transmit,
 | 
			
		||||
        rf_amp,         //  previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps  
 | 
			
		||||
		static_cast<int8_t>(receiver_model.lna()),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.vga())
 | 
			
		||||
	});  
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::stop(const bool do_loop) {
 | 
			
		||||
	if( is_active() )
 | 
			
		||||
		replay_thread.reset();
 | 
			
		||||
	
 | 
			
		||||
	if (do_loop && check_loop.value()) {
 | 
			
		||||
		start();
 | 
			
		||||
	} else {
 | 
			
		||||
		radio::set_antenna_bias(false);    //Turn off Bias Tee
 | 
			
		||||
		radio::disable();
 | 
			
		||||
		button_play.set_bitmap(&bitmap_play);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	ready_signal = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::handle_replay_thread_done(const uint32_t return_code) {
 | 
			
		||||
	if (return_code == ReplayThread::END_OF_FILE) {
 | 
			
		||||
		stop(true);
 | 
			
		||||
	} else if (return_code == ReplayThread::READ_ERROR) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
		file_error();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_value(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ReplayAppView::ReplayAppView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	tx_gain = 35;field_rfgain.set_value(tx_gain);  // Initial default  value (-12 dB's max ).
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);  // Initial default value True. (TX RF amp on , +14dB's)
 | 
			
		||||
 | 
			
		||||
	field_rfamp.on_change = [this](int32_t v) {	// allow initial value change just after opened file.	
 | 
			
		||||
		rf_amp = (bool)v;
 | 
			
		||||
	};
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);
 | 
			
		||||
 | 
			
		||||
    field_rfgain.on_change = [this](int32_t v) { // allow initial value change just after opened file.
 | 
			
		||||
		tx_gain = v;
 | 
			
		||||
	};  
 | 
			
		||||
	field_rfgain.set_value(tx_gain);
 | 
			
		||||
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_replay);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button_open,
 | 
			
		||||
		&text_filename,
 | 
			
		||||
		&text_sample_rate,
 | 
			
		||||
		&text_duration,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_rfgain, 
 | 
			
		||||
		&field_rfamp,       // let's not use common rf_amp
 | 
			
		||||
		&check_loop,
 | 
			
		||||
		&button_play,
 | 
			
		||||
		&waterfall,
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(target_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_target_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_target_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_step(5000);
 | 
			
		||||
	
 | 
			
		||||
	button_play.on_select = [this](ImageButton&) {
 | 
			
		||||
		this->toggle();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_open.on_select = [this, &nav](Button&) {
 | 
			
		||||
		auto open_view = nav.push<FileLoadView>(".C16");
 | 
			
		||||
		open_view->on_changed = [this](std::filesystem::path new_file_path) {
 | 
			
		||||
			on_file_changed(new_file_path);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ReplayAppView::~ReplayAppView() {
 | 
			
		||||
	radio::disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::on_hide() {
 | 
			
		||||
	stop(false);
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
 | 
			
		||||
	const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	waterfall.set_parent_rect(waterfall_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::on_target_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	set_target_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReplayAppView::set_target_frequency(const rf::Frequency new_value) {
 | 
			
		||||
	persistent_memory::set_tuned_frequency(new_value);;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rf::Frequency ReplayAppView::target_frequency() const {
 | 
			
		||||
	return persistent_memory::tuned_frequency();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,167 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __REPLAY_APP_HPP__
 | 
			
		||||
#define __REPLAY_APP_HPP__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "replay_thread.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class ReplayAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ReplayAppView(NavigationView& nav);
 | 
			
		||||
	~ReplayAppView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Replay"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
	
 | 
			
		||||
	uint32_t sample_rate = 0;
 | 
			
		||||
	int32_t tx_gain { 47 };
 | 
			
		||||
	bool rf_amp { true }; // aux private var to store temporal, Replay App rf_amp user selection.
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 2500000;
 | 
			
		||||
	const size_t read_size { 16384 };
 | 
			
		||||
	const size_t buffer_count { 3 };
 | 
			
		||||
 | 
			
		||||
	void on_file_changed(std::filesystem::path new_file_path);
 | 
			
		||||
	void on_target_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress);
 | 
			
		||||
	
 | 
			
		||||
	void set_target_frequency(const rf::Frequency new_value);
 | 
			
		||||
	rf::Frequency target_frequency() const;
 | 
			
		||||
 | 
			
		||||
	void toggle();
 | 
			
		||||
	void start();
 | 
			
		||||
	void stop(const bool do_loop);
 | 
			
		||||
	bool is_active() const;
 | 
			
		||||
	void set_ready();
 | 
			
		||||
	void handle_replay_thread_done(const uint32_t return_code);
 | 
			
		||||
	void file_error();
 | 
			
		||||
 | 
			
		||||
	std::filesystem::path file_path { };
 | 
			
		||||
	std::unique_ptr<ReplayThread> replay_thread { };
 | 
			
		||||
	bool ready_signal { false };
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 10 * 8, 2 * 16 }, "GAIN   A:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_open {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 10 * 8, 2 * 16 },
 | 
			
		||||
		"Open file"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_filename {
 | 
			
		||||
		{ 11 * 8, 0 * 16, 12 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_sample_rate {
 | 
			
		||||
		{ 24 * 8, 0 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_duration {
 | 
			
		||||
		{ 11 * 8, 1 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 18 * 8, 1 * 16, 12 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 2 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_rfgain {
 | 
			
		||||
		{ 14 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 47 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '	
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_rfamp {     // previously  I was using "RFAmpField field_rf_amp" but that is general Receiver amp setting.
 | 
			
		||||
		{ 19 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 14 },                // this time we will display GUI , 0 or 14 dBs same as Mic App
 | 
			
		||||
		14,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_loop {
 | 
			
		||||
		{ 21 * 8, 2 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Loop",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	ImageButton button_play {
 | 
			
		||||
		{ 28 * 8, 2 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		&bitmap_play,
 | 
			
		||||
		Color::green(),
 | 
			
		||||
		Color::black()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	spectrum::WaterfallWidget waterfall { };
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_replay_thread_error {
 | 
			
		||||
		Message::ID::ReplayThreadDone,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
 | 
			
		||||
			this->handle_replay_thread_done(message.return_code);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RequestSignalMessage*>(p);
 | 
			
		||||
			if (message->signal == RequestSignalMessage::Signal::FillRequest) {
 | 
			
		||||
				this->set_ready();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__REPLAY_APP_HPP__*/
 | 
			
		||||
@@ -0,0 +1,301 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// To prepare samples: for f in ./*.wav; do sox "$f" -r 48000 -c 1 -b8 --norm "conv/$f"; done
 | 
			
		||||
 | 
			
		||||
#include "soundboard_app.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "tonesets.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace tonekey;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
bool SoundBoardView::is_active() const {
 | 
			
		||||
	return (bool)replay_thread;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::stop() {
 | 
			
		||||
	if (is_active())
 | 
			
		||||
		replay_thread.reset();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	tx_view.set_transmitting(false);
 | 
			
		||||
	
 | 
			
		||||
	//button_play.set_bitmap(&bitmap_play);
 | 
			
		||||
	ready_signal = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
 | 
			
		||||
	stop();
 | 
			
		||||
	//progressbar.set_value(0);
 | 
			
		||||
	
 | 
			
		||||
	if (return_code == ReplayThread::END_OF_FILE) {
 | 
			
		||||
		if (check_random.value()) {
 | 
			
		||||
			lfsr_v = lfsr_iterate(lfsr_v);
 | 
			
		||||
			playing_id = lfsr_v % file_list.size();
 | 
			
		||||
			menu_view.set_highlighted(playing_id);
 | 
			
		||||
			start_tx(playing_id);
 | 
			
		||||
		} else if (check_loop.value()) {
 | 
			
		||||
			start_tx(playing_id);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (return_code == ReplayThread::READ_ERROR) {
 | 
			
		||||
		file_error();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::set_ready() {
 | 
			
		||||
	ready_signal = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::focus() {
 | 
			
		||||
	menu_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::file_error() {
 | 
			
		||||
	nav_.display_modal("Error", "File read error.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::start_tx(const uint32_t id) {
 | 
			
		||||
	auto reader = std::make_unique<WAVFileReader>();
 | 
			
		||||
	uint32_t tone_key_index = options_tone_key.selected_index();
 | 
			
		||||
	uint32_t sample_rate;
 | 
			
		||||
	
 | 
			
		||||
	stop();
 | 
			
		||||
 | 
			
		||||
	if (!reader->open(u"/WAV/" + file_list[id].native())) {
 | 
			
		||||
		file_error();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	playing_id = id;
 | 
			
		||||
	
 | 
			
		||||
	//progressbar.set_max(reader->sample_count());
 | 
			
		||||
	
 | 
			
		||||
	//button_play.set_bitmap(&bitmap_stop);
 | 
			
		||||
	
 | 
			
		||||
	sample_rate = reader->sample_rate();
 | 
			
		||||
	
 | 
			
		||||
	replay_thread = std::make_unique<ReplayThread>(
 | 
			
		||||
		std::move(reader),
 | 
			
		||||
		read_size, buffer_count,
 | 
			
		||||
		&ready_signal,
 | 
			
		||||
		[](uint32_t return_code) {
 | 
			
		||||
			ReplayThreadDoneMessage message { return_code };
 | 
			
		||||
			EventDispatcher::send_message(message);
 | 
			
		||||
		}
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_audiotx_config(
 | 
			
		||||
		1536000 / 20,		// Update vu-meter at 20Hz
 | 
			
		||||
		transmitter_model.channel_bandwidth(),
 | 
			
		||||
		0,	// Gain is unused
 | 
			
		||||
		TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
 | 
			
		||||
		0, //AM
 | 
			
		||||
		0, //DSB
 | 
			
		||||
		0, //USB
 | 
			
		||||
		0 //LSB
 | 
			
		||||
	);
 | 
			
		||||
	baseband::set_sample_rate(sample_rate);
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(1536000);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	tx_view.set_transmitting(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*void SoundBoardView::show_infos() {
 | 
			
		||||
	if (!reader->open(file_list[menu_view.highlighted_index()]))
 | 
			
		||||
		return;
 | 
			
		||||
	
 | 
			
		||||
	text_duration.set(to_string_time_ms(reader->ms_duration()));
 | 
			
		||||
	text_title.set(reader->title().substr(0, 15));
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::on_tx_progress(const uint32_t progress) {
 | 
			
		||||
	(void)progress ; // avoid warning
 | 
			
		||||
	//progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::on_select_entry() {
 | 
			
		||||
	tx_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SoundBoardView::refresh_list() {
 | 
			
		||||
	auto reader = std::make_unique<WAVFileReader>();
 | 
			
		||||
	
 | 
			
		||||
	file_list.clear();
 | 
			
		||||
	c_page = page;
 | 
			
		||||
	
 | 
			
		||||
	// List directories and files, put directories up top
 | 
			
		||||
	uint32_t count = 0;
 | 
			
		||||
	for (const auto& entry : std::filesystem::directory_iterator(u"WAV", u"*")) {
 | 
			
		||||
		if (std::filesystem::is_regular_file(entry.status())) {
 | 
			
		||||
			if (entry.path().string().length()) {
 | 
			
		||||
			
 | 
			
		||||
				auto entry_extension = entry.path().extension().string();
 | 
			
		||||
			
 | 
			
		||||
				for (auto &c: entry_extension)
 | 
			
		||||
					c = toupper(c);
 | 
			
		||||
				
 | 
			
		||||
				if (entry_extension == ".WAV") {
 | 
			
		||||
					
 | 
			
		||||
					if (reader->open(u"/WAV/" + entry.path().native())) {
 | 
			
		||||
						if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
 | 
			
		||||
							//sounds[c].ms_duration = reader->ms_duration();
 | 
			
		||||
							//sounds[c].path = u"WAV/" + entry.path().native();
 | 
			
		||||
							if (count >= (page - 1) * 100 && count < page * 100){
 | 
			
		||||
								file_list.push_back(entry.path());
 | 
			
		||||
								if (file_list.size() == 100){
 | 
			
		||||
									page++;
 | 
			
		||||
									break;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							count++;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!file_list.size()) {
 | 
			
		||||
		// Hide widgets, show warning
 | 
			
		||||
		if (page == 1){
 | 
			
		||||
			menu_view.hidden(true);
 | 
			
		||||
			text_empty.hidden(false);
 | 
			
		||||
			set_dirty();
 | 
			
		||||
		}else{
 | 
			
		||||
			page = 1;
 | 
			
		||||
			refresh_list();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Hide warning, show widgets
 | 
			
		||||
		menu_view.hidden(false);
 | 
			
		||||
		text_empty.hidden(true);
 | 
			
		||||
		set_dirty();
 | 
			
		||||
	
 | 
			
		||||
		menu_view.clear();
 | 
			
		||||
		
 | 
			
		||||
		for (size_t n = 0; n < file_list.size(); n++) {
 | 
			
		||||
			menu_view.add_item({
 | 
			
		||||
				file_list[n].string().substr(0, 30),
 | 
			
		||||
				ui::Color::white(),
 | 
			
		||||
				nullptr,
 | 
			
		||||
				[this](){
 | 
			
		||||
					on_select_entry();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		page_info.set("Page: " + to_string_dec_uint(c_page) + "    Sounds: " + to_string_dec_uint(file_list.size()));
 | 
			
		||||
		menu_view.set_highlighted(0);	// Refresh
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (file_list.size() < 100){
 | 
			
		||||
		page = 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SoundBoardView::SoundBoardView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&menu_view,
 | 
			
		||||
		&text_empty,
 | 
			
		||||
		&options_tone_key,
 | 
			
		||||
		//&text_title,
 | 
			
		||||
		//&text_duration,
 | 
			
		||||
		//&progressbar,
 | 
			
		||||
		&page_info,
 | 
			
		||||
		&check_loop,
 | 
			
		||||
		&check_random,
 | 
			
		||||
		&button_prev_page,
 | 
			
		||||
		&button_next_page,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_soundboard", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	refresh_list();
 | 
			
		||||
 | 
			
		||||
	button_next_page.on_select = [this](Button&) {
 | 
			
		||||
		this->refresh_list();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_prev_page.on_select = [this](Button&) {
 | 
			
		||||
		if (c_page == 1) return;
 | 
			
		||||
		if (c_page == 2) page = 1;
 | 
			
		||||
		page = c_page - 1;
 | 
			
		||||
		refresh_list();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	//text_title.set(to_string_dec_uint(file_list.size()));
 | 
			
		||||
	
 | 
			
		||||
	tone_keys_populate(options_tone_key);
 | 
			
		||||
	options_tone_key.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	check_loop.set_value(false);
 | 
			
		||||
	check_random.set_value(false);
 | 
			
		||||
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		start_tx(menu_view.highlighted_index());
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		stop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SoundBoardView::~SoundBoardView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_soundboard", &app_settings);
 | 
			
		||||
 | 
			
		||||
	stop();
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,182 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_SOUNDBOARD_H__
 | 
			
		||||
#define __UI_SOUNDBOARD_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "replay_thread.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "lfsr_random.hpp"
 | 
			
		||||
#include "io_wave.hpp"
 | 
			
		||||
#include "tone_key.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class SoundBoardView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SoundBoardView(NavigationView& nav);
 | 
			
		||||
	~SoundBoardView();
 | 
			
		||||
 | 
			
		||||
	SoundBoardView(const SoundBoardView&) = delete;
 | 
			
		||||
	SoundBoardView(SoundBoardView&&) = delete;
 | 
			
		||||
	SoundBoardView& operator=(const SoundBoardView&) = delete;
 | 
			
		||||
	SoundBoardView& operator=(SoundBoardView&&) = delete;
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Soundboard TX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
	
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		NORMAL = 0,
 | 
			
		||||
		RANDOM
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode = NORMAL;
 | 
			
		||||
	
 | 
			
		||||
	uint32_t playing_id { };
 | 
			
		||||
	uint32_t page = 1;
 | 
			
		||||
	uint32_t c_page = 1;
 | 
			
		||||
	
 | 
			
		||||
	std::vector<std::filesystem::path> file_list { };
 | 
			
		||||
 | 
			
		||||
	const size_t read_size { 2048 };	// Less ?
 | 
			
		||||
	const size_t buffer_count { 3 };
 | 
			
		||||
	std::unique_ptr<ReplayThread> replay_thread { };
 | 
			
		||||
	bool ready_signal { false };
 | 
			
		||||
	lfsr_word_t lfsr_v = 1;
 | 
			
		||||
	
 | 
			
		||||
	//void show_infos();
 | 
			
		||||
	void start_tx(const uint32_t id);
 | 
			
		||||
	//void on_ctcss_changed(uint32_t v);
 | 
			
		||||
	void stop();
 | 
			
		||||
	bool is_active() const;
 | 
			
		||||
	void set_ready();
 | 
			
		||||
	void handle_replay_thread_done(const uint32_t return_code);
 | 
			
		||||
	void file_error();
 | 
			
		||||
	void on_tx_progress(const uint32_t progress);
 | 
			
		||||
	void refresh_list();
 | 
			
		||||
	void on_select_entry();
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
 | 
			
		||||
		{ { 0, 180 }, "Key:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_next_page {
 | 
			
		||||
		{ 30 * 7, 25 * 8, 10 * 3, 2 * 14 },
 | 
			
		||||
		"=>"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_prev_page {
 | 
			
		||||
		{ 17 * 10, 25 * 8, 10 * 3, 2 * 14 },
 | 
			
		||||
		"<="
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text page_info {
 | 
			
		||||
		{ 0, 30 * 8 - 4, 30 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	MenuView menu_view {
 | 
			
		||||
		{ 0, 0, 240, 175 },
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	Text text_empty {
 | 
			
		||||
		{ 7 * 8, 12 * 8, 16 * 8, 16 },
 | 
			
		||||
		"Empty directory !",
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*Text text_title {
 | 
			
		||||
		{ 6 * 8, 20 * 8 + 4, 15 * 8, 16 }
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	/*Text text_duration {
 | 
			
		||||
		{ 22 * 8, 20 * 8 + 4, 6 * 8, 16 }
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_tone_key {
 | 
			
		||||
		{ 32 , 180 },
 | 
			
		||||
		18,
 | 
			
		||||
		{ }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_loop {
 | 
			
		||||
		{ 0, 25 * 8 + 4 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Loop"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_random {
 | 
			
		||||
		{ 10 * 7, 25 * 8 + 4 },
 | 
			
		||||
		6,
 | 
			
		||||
		"Random"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	//ProgressBar progressbar {
 | 
			
		||||
	//	{ 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
 | 
			
		||||
	//};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		5000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_replay_thread_error {
 | 
			
		||||
		Message::ID::ReplayThreadDone,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
 | 
			
		||||
			this->handle_replay_thread_done(message.return_code);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RequestSignalMessage*>(p);
 | 
			
		||||
			if (message->signal == RequestSignalMessage::Signal::FillRequest) {
 | 
			
		||||
				this->set_ready();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_SOUNDBOARD_H__*/
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "spectrum_analysis_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
SpectrumAnalysisModel::SpectrumAnalysisModel() {
 | 
			
		||||
	receiver_model.set_baseband_configuration({
 | 
			
		||||
		.mode = 4,
 | 
			
		||||
		.sampling_rate = 20000000,
 | 
			
		||||
		.decimation_factor = 1,
 | 
			
		||||
	});
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(12000000);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __SPECTRUM_ANALYSIS_APP_H__
 | 
			
		||||
#define __SPECTRUM_ANALYSIS_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
class SpectrumAnalysisModel {
 | 
			
		||||
public:
 | 
			
		||||
	SpectrumAnalysisModel();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	SpectrumAnalysisModel model;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__SPECTRUM_ANALYSIS_APP_H__*/
 | 
			
		||||
							
								
								
									
										283
									
								
								Software/portapack-mayhem/firmware/application/apps/tpms_app.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								Software/portapack-mayhem/firmware/application/apps/tpms_app.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "tpms_app.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
namespace tpms {
 | 
			
		||||
 | 
			
		||||
namespace format {
 | 
			
		||||
 | 
			
		||||
static bool use_kpa = true;
 | 
			
		||||
 | 
			
		||||
std::string type(Reading::Type type) {
 | 
			
		||||
	return to_string_dec_uint(toUType(type), 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string id(TransponderID id) {
 | 
			
		||||
	return to_string_hex(id.value(), 8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string pressure(Pressure pressure) {
 | 
			
		||||
	if(use_kpa){
 | 
			
		||||
		return to_string_dec_int(pressure.kilopascal(), 3);
 | 
			
		||||
	}
 | 
			
		||||
	return to_string_dec_int(pressure.psi(), 3);
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string temperature(Temperature temperature) {
 | 
			
		||||
	return to_string_dec_int(temperature.celsius(), 3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string flags(Flags flags) {
 | 
			
		||||
	return to_string_hex(flags, 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string signal_type(SignalType signal_type) {
 | 
			
		||||
	switch(signal_type) {
 | 
			
		||||
	case SignalType::FSK_19k2_Schrader:		return "FSK 38400 19200 Schrader";
 | 
			
		||||
	case SignalType::OOK_8k192_Schrader:	return "OOK - 8192 Schrader";
 | 
			
		||||
	case SignalType::OOK_8k4_Schrader:		return "OOK - 8400 Schrader";
 | 
			
		||||
	default:								return "- - - -";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace format */
 | 
			
		||||
 | 
			
		||||
} /* namespace tpms */
 | 
			
		||||
 | 
			
		||||
void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_frequency) {
 | 
			
		||||
	const auto hex_formatted = packet.symbols_formatted();
 | 
			
		||||
 | 
			
		||||
	// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
 | 
			
		||||
	const auto tuning_frequency_str = to_string_dec_uint(target_frequency, 10);
 | 
			
		||||
 | 
			
		||||
	std::string entry = tuning_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
 | 
			
		||||
	log_file.write_entry(packet.received_at(), entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TPMSRecentEntry::Key TPMSRecentEntry::invalid_key = { tpms::Reading::Type::None, 0 };
 | 
			
		||||
 | 
			
		||||
void TPMSRecentEntry::update(const tpms::Reading& reading) {
 | 
			
		||||
	received_count++;
 | 
			
		||||
 | 
			
		||||
	if( reading.pressure().is_valid() ) {
 | 
			
		||||
		last_pressure = reading.pressure();
 | 
			
		||||
	}
 | 
			
		||||
	if( reading.temperature().is_valid() ) {
 | 
			
		||||
		last_temperature = reading.temperature();
 | 
			
		||||
	}
 | 
			
		||||
	if( reading.flags().is_valid() ) {
 | 
			
		||||
		last_flags = reading.flags();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<TPMSRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	std::string line = tpms::format::type(entry.type) + " " + tpms::format::id(entry.id);
 | 
			
		||||
 | 
			
		||||
	if( entry.last_pressure.is_valid() ) {
 | 
			
		||||
		line += " " + tpms::format::pressure(entry.last_pressure.value());
 | 
			
		||||
	} else {
 | 
			
		||||
		line += " " "   ";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( entry.last_temperature.is_valid() ) {
 | 
			
		||||
		line += " " + tpms::format::temperature(entry.last_temperature.value());
 | 
			
		||||
	} else {
 | 
			
		||||
		line += " " "   ";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( entry.received_count > 999 ) {
 | 
			
		||||
		line += " +++";
 | 
			
		||||
	} else {
 | 
			
		||||
		line += " " + to_string_dec_uint(entry.received_count, 3);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( entry.last_flags.is_valid() ) {
 | 
			
		||||
		line += " " + tpms::format::flags(entry.last_flags.value());
 | 
			
		||||
	} else {
 | 
			
		||||
		line += " " "  ";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	line.resize(target_rect.width() / 8, ' ');
 | 
			
		||||
	painter.draw_string(target_rect.location(), style, line);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TPMSAppView::TPMSAppView(NavigationView&) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_tpms);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&options_band,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&options_type,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_tpms", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
		options_band.set_by_value(app_settings.rx_frequency);
 | 
			
		||||
	}
 | 
			
		||||
	else options_band.set_by_value(receiver_model.tuning_frequency());
 | 
			
		||||
 | 
			
		||||
    receiver_model.set_tuning_frequency(tuning_frequency());
 | 
			
		||||
    receiver_model.set_sampling_rate(sampling_rate);
 | 
			
		||||
    receiver_model.set_baseband_bandwidth(baseband_bandwidth);
 | 
			
		||||
    receiver_model.enable();  // Before using radio::enable(), but not updating Ant.DC-Bias.
 | 
			
		||||
	
 | 
			
		||||
/*	radio::enable({
 | 
			
		||||
		tuning_frequency(),
 | 
			
		||||
		sampling_rate,
 | 
			
		||||
		baseband_bandwidth,
 | 
			
		||||
		rf::Direction::Receive,
 | 
			
		||||
		receiver_model.rf_amp(),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.lna()),
 | 
			
		||||
		static_cast<int8_t>(receiver_model.vga()),
 | 
			
		||||
	}); */
 | 
			
		||||
 | 
			
		||||
	options_band.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		this->on_band_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
	options_band.set_by_value(target_frequency());
 | 
			
		||||
 | 
			
		||||
	options_type.on_change = [this](size_t, int32_t i) {		
 | 
			
		||||
		if (i == 0){
 | 
			
		||||
			tpms::format::use_kpa = true;
 | 
			
		||||
		} else if (i == 1){
 | 
			
		||||
			tpms::format::use_kpa = false;
 | 
			
		||||
		}	
 | 
			
		||||
		update_type();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	options_type.set_selected_index(0, true);
 | 
			
		||||
 | 
			
		||||
	logger = std::make_unique<TPMSLogger>();
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->append(u"tpms.txt");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TPMSAppView::~TPMSAppView() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = target_frequency_;
 | 
			
		||||
	settings.save("rx_tpms", &app_settings);
 | 
			
		||||
 | 
			
		||||
	receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
 | 
			
		||||
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::focus() {
 | 
			
		||||
	options_band.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::update_type() {
 | 
			
		||||
	if (tpms::format::use_kpa){
 | 
			
		||||
		remove_child(&recent_entries_view_psi);
 | 
			
		||||
		add_child(&recent_entries_view_kpa);
 | 
			
		||||
		recent_entries_view_kpa.set_parent_rect(view_normal_rect);
 | 
			
		||||
	} else {
 | 
			
		||||
		remove_child(&recent_entries_view_kpa);
 | 
			
		||||
		add_child(&recent_entries_view_psi);
 | 
			
		||||
		recent_entries_view_psi.set_parent_rect(view_normal_rect);
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
 | 
			
		||||
	view_normal_rect  = { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
 | 
			
		||||
	update_type();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::on_packet(const tpms::Packet& packet) {
 | 
			
		||||
	if( logger ) {
 | 
			
		||||
		logger->on_packet(packet, target_frequency());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto reading_opt = packet.reading();
 | 
			
		||||
	if( reading_opt.is_valid() ) {
 | 
			
		||||
		const auto reading = reading_opt.value();
 | 
			
		||||
		auto& entry = ::on_packet(recent, TPMSRecentEntry::Key { reading.type(), reading.id() });
 | 
			
		||||
		entry.update(reading);
 | 
			
		||||
		
 | 
			
		||||
		if(tpms::format::use_kpa){
 | 
			
		||||
			recent_entries_view_kpa.set_dirty();
 | 
			
		||||
		} else {
 | 
			
		||||
			recent_entries_view_psi.set_dirty();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::on_show_list() {
 | 
			
		||||
	if(tpms::format::use_kpa){
 | 
			
		||||
		recent_entries_view_kpa.hidden(false);
 | 
			
		||||
		recent_entries_view_kpa.focus();
 | 
			
		||||
	} else {
 | 
			
		||||
		recent_entries_view_psi.hidden(false);
 | 
			
		||||
		recent_entries_view_psi.focus();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
 | 
			
		||||
	set_target_frequency(new_band_frequency);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TPMSAppView::set_target_frequency(const uint32_t new_value) {
 | 
			
		||||
	target_frequency_ = new_value;
 | 
			
		||||
	radio::set_tuning_frequency(tuning_frequency());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t TPMSAppView::target_frequency() const {
 | 
			
		||||
	return target_frequency_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t TPMSAppView::tuning_frequency() const {
 | 
			
		||||
	return target_frequency() - (sampling_rate / 4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										207
									
								
								Software/portapack-mayhem/firmware/application/apps/tpms_app.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								Software/portapack-mayhem/firmware/application/apps/tpms_app.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,207 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __TPMS_APP_H__
 | 
			
		||||
#define __TPMS_APP_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_rssi.hpp"
 | 
			
		||||
#include "ui_channel.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
 | 
			
		||||
#include "tpms_packet.hpp"
 | 
			
		||||
 | 
			
		||||
namespace std {
 | 
			
		||||
 | 
			
		||||
constexpr bool operator==(const tpms::TransponderID& lhs, const tpms::TransponderID& rhs) {
 | 
			
		||||
	return (lhs.value() == rhs.value());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace std */
 | 
			
		||||
 | 
			
		||||
struct TPMSRecentEntry {
 | 
			
		||||
	using Key = std::pair<tpms::Reading::Type, tpms::TransponderID>;
 | 
			
		||||
 | 
			
		||||
	static const Key invalid_key;
 | 
			
		||||
 | 
			
		||||
	tpms::Reading::Type type { invalid_key.first };
 | 
			
		||||
	tpms::TransponderID id { invalid_key.second };
 | 
			
		||||
 | 
			
		||||
	size_t received_count { 0 };
 | 
			
		||||
 | 
			
		||||
	Optional<Pressure> last_pressure { };
 | 
			
		||||
	Optional<Temperature> last_temperature { };
 | 
			
		||||
	Optional<tpms::Flags> last_flags { };
 | 
			
		||||
 | 
			
		||||
	TPMSRecentEntry(
 | 
			
		||||
		const Key& key
 | 
			
		||||
	) : type { key.first },
 | 
			
		||||
		id { key.second }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return { type, id };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void update(const tpms::Reading& reading);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using TPMSRecentEntries = RecentEntries<TPMSRecentEntry>;
 | 
			
		||||
 | 
			
		||||
class TPMSLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::filesystem::path& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void on_packet(const tpms::Packet& packet, const uint32_t target_frequency);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
using TPMSRecentEntriesView = RecentEntriesView<TPMSRecentEntries>;
 | 
			
		||||
 | 
			
		||||
class TPMSAppView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	TPMSAppView(NavigationView& nav);
 | 
			
		||||
	~TPMSAppView();
 | 
			
		||||
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
	// Prevent painting of region covered entirely by a child.
 | 
			
		||||
	// TODO: Add flag to View that specifies view does not need to be cleared before painting.
 | 
			
		||||
	void paint(Painter&) override { };
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "TPMS Cars RX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr uint32_t initial_target_frequency = 315000000;
 | 
			
		||||
	static constexpr uint32_t sampling_rate = 2457600;
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 1750000;
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::TPMSPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const TPMSPacketMessage*>(p);
 | 
			
		||||
			const tpms::Packet packet { message->packet, message->signal_type };
 | 
			
		||||
			this->on_packet(packet);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	static constexpr ui::Dim header_height = 1 * 16;
 | 
			
		||||
 | 
			
		||||
	ui::Rect view_normal_rect { };
 | 
			
		||||
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_band {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{ "315", 315000000 },
 | 
			
		||||
			{ "433", 433920000 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_type {
 | 
			
		||||
		{ 5 * 8, 0 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{ "kPa", 0 },
 | 
			
		||||
			{ "PSI", 1 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	TPMSRecentEntries recent { };
 | 
			
		||||
	std::unique_ptr<TPMSLogger> logger { };
 | 
			
		||||
 | 
			
		||||
	const RecentEntriesColumns columns_kpa { {
 | 
			
		||||
		{ "Tp", 2 },
 | 
			
		||||
		{ "ID", 8 },
 | 
			
		||||
		{ "kPa", 3 },
 | 
			
		||||
		{ "C", 3 },
 | 
			
		||||
		{ "Cnt", 3 },
 | 
			
		||||
		{ "Fl", 2 },
 | 
			
		||||
	} };
 | 
			
		||||
	TPMSRecentEntriesView recent_entries_view_kpa { columns_kpa, recent };
 | 
			
		||||
 | 
			
		||||
	const RecentEntriesColumns columns_psi { {
 | 
			
		||||
		{ "Tp", 2 },
 | 
			
		||||
		{ "ID", 8 },
 | 
			
		||||
		{ "PSI", 3 },
 | 
			
		||||
		{ "C", 3 },
 | 
			
		||||
		{ "Cnt", 3 },
 | 
			
		||||
		{ "Fl", 2 },
 | 
			
		||||
	} };
 | 
			
		||||
	TPMSRecentEntriesView recent_entries_view_psi { columns_psi, recent };
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency_ = initial_target_frequency;
 | 
			
		||||
 | 
			
		||||
	void on_packet(const tpms::Packet& packet);
 | 
			
		||||
	void on_show_list();
 | 
			
		||||
	void update_type();
 | 
			
		||||
 | 
			
		||||
	void on_band_changed(const uint32_t new_band_frequency);
 | 
			
		||||
 | 
			
		||||
	uint32_t target_frequency() const;
 | 
			
		||||
	void set_target_frequency(const uint32_t new_value);
 | 
			
		||||
 | 
			
		||||
	uint32_t tuning_frequency() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__TPMS_APP_H__*/
 | 
			
		||||
							
								
								
									
										145
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_about.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_about.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "cpld_update.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_about.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include "lpc43xx_cpp.hpp"
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace lpc43xx;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
// This is pretty much WaterfallView but in the opposite direction
 | 
			
		||||
CreditsWidget::CreditsWidget(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : Widget { parent_rect }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreditsWidget::paint(Painter&) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreditsWidget::on_show() {
 | 
			
		||||
	clear();
 | 
			
		||||
 | 
			
		||||
	const auto screen_r = screen_rect();
 | 
			
		||||
	display.scroll_set_area(screen_r.top(), screen_r.bottom());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreditsWidget::on_hide() {
 | 
			
		||||
	display.scroll_disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreditsWidget::new_row(
 | 
			
		||||
	const std::array<Color, 240>& pixel_row
 | 
			
		||||
) {
 | 
			
		||||
	// Glitch be here (see comment in main.cpp)
 | 
			
		||||
	const auto draw_y = display.scroll(-1);
 | 
			
		||||
	
 | 
			
		||||
	display.draw_pixels(
 | 
			
		||||
		{ { 0, draw_y - 1 }, { 240, 1 } },
 | 
			
		||||
		pixel_row
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreditsWidget::clear() {
 | 
			
		||||
	display.fill_rectangle(
 | 
			
		||||
		screen_rect(),
 | 
			
		||||
		Color::black()
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::update() {
 | 
			
		||||
	size_t i = 0;
 | 
			
		||||
	std::array<Color, 240> pixel_row;
 | 
			
		||||
	
 | 
			
		||||
	slow_down++;
 | 
			
		||||
	if (slow_down % 3 < 2) return;
 | 
			
		||||
	
 | 
			
		||||
	if (!timer) {
 | 
			
		||||
		if (loop) {
 | 
			
		||||
			credits_index = 0;
 | 
			
		||||
			loop = false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		text = credits[credits_index].text;
 | 
			
		||||
		timer = credits[credits_index].delay;
 | 
			
		||||
		start_pos = credits[credits_index].start_pos;
 | 
			
		||||
		
 | 
			
		||||
		if (timer < 0) {
 | 
			
		||||
			timer = 240;
 | 
			
		||||
			loop = true;
 | 
			
		||||
		} else
 | 
			
		||||
			timer += 16;
 | 
			
		||||
		
 | 
			
		||||
		render_line = 0;
 | 
			
		||||
		credits_index++;
 | 
			
		||||
	} else
 | 
			
		||||
		timer--;
 | 
			
		||||
	
 | 
			
		||||
	if (render_line < 16) {
 | 
			
		||||
		for (const auto c : text) {
 | 
			
		||||
			const auto glyph = style().font.glyph(c);
 | 
			
		||||
			
 | 
			
		||||
			const size_t start = (glyph.size().width() / 8) * render_line; 
 | 
			
		||||
			for (Dim c = 0; c < glyph.size().width(); c++) {
 | 
			
		||||
				const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
 | 
			
		||||
				pixel_row[start_pos + i + c] = pixel ? Color::white() : Color::black();
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			const auto advance = glyph.advance();
 | 
			
		||||
			i += advance.x();
 | 
			
		||||
		}
 | 
			
		||||
		render_line++;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	credits_display.new_row(pixel_row);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AboutView::AboutView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&credits_display,
 | 
			
		||||
		&button_ok
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_ok.on_select = [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::focus() {
 | 
			
		||||
	button_ok.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										125
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_about.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_about.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_ABOUT_H__
 | 
			
		||||
#define __UI_ABOUT_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class CreditsWidget : public Widget {
 | 
			
		||||
public:
 | 
			
		||||
	CreditsWidget(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
 | 
			
		||||
	void paint(Painter&) override;
 | 
			
		||||
	
 | 
			
		||||
	void new_row(const std::array<Color, 240>& pixel_row);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void clear();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AboutView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AboutView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "About"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void update();
 | 
			
		||||
 | 
			
		||||
	uint8_t credits_index { 0 };
 | 
			
		||||
	uint8_t render_line { 0 };
 | 
			
		||||
	Coord start_pos { 0 };
 | 
			
		||||
	uint8_t slow_down { 0 };
 | 
			
		||||
	int32_t timer { 0 };
 | 
			
		||||
	bool loop { false };
 | 
			
		||||
	
 | 
			
		||||
	std::string text { };
 | 
			
		||||
		
 | 
			
		||||
	typedef struct credits_t {
 | 
			
		||||
		size_t start_pos;
 | 
			
		||||
		std::string text;
 | 
			
		||||
		int32_t delay;
 | 
			
		||||
	} credits_t;
 | 
			
		||||
	
 | 
			
		||||
	// TODO: Make this dinamically centered and parse \n as the delay value so it is easy to maintain
 | 
			
		||||
	const credits_t credits[26] = {
 | 
			
		||||
		//           012345678901234567890123456789
 | 
			
		||||
	    { 60,		"PortaPack Mayhem",					0 },
 | 
			
		||||
		{ 60,		"PortaPack|HAVOC",					0 },
 | 
			
		||||
		{ 11 * 8,	           "Gurus  J. Boone",		0 },
 | 
			
		||||
		{ 18 * 8,	                  "M. Ossmann",		16 },
 | 
			
		||||
		{ 11 * 8,	           "HAVOC  Furrtek",		16 },
 | 
			
		||||
		{ 7 * 8,	       "POCSAG rx  T. Sailer",		0 },
 | 
			
		||||
		{ 18 * 8,	                  "E. Oenal",		16 },
 | 
			
		||||
		{ 0 * 8,	"Radiosonde infos  F4GMU",			0 },
 | 
			
		||||
		{ 18 * 8,	                  "RS1729",			16 },
 | 
			
		||||
		{ 4 * 8,	    "RDS waveform  C. Jacquet", 	16 },
 | 
			
		||||
		{ 7 * 8,	       "Xy. infos  cLx", 			16 },
 | 
			
		||||
		{ 2 * 8,	  "OOK scan trick  Samy Kamkar",	16 },
 | 
			
		||||
		{ 7 * 8,	       "World map  NASA", 			16 },
 | 
			
		||||
		{ 0 * 8,	"TouchTunes infos  Notpike",		16 },
 | 
			
		||||
		{ 4 * 8,	    "Subaru infos  Tom",			0 },
 | 
			
		||||
		{ 18 * 8,	                  "Wimmenhove",		16 },
 | 
			
		||||
		{ 1 * 8,	 "GPS,TV,BTLE,NRF  Shao",			24 },
 | 
			
		||||
		{ 6 * 8,	      "Thanks & donators",			16 },
 | 
			
		||||
		{ 1 * 8,	 "Rainer Matla     Keld Norman",	0 },
 | 
			
		||||
		{ 1 * 8,	 " Giorgio C.         DC1RDB",		0 },
 | 
			
		||||
		{ 1 * 8,	 " Sigmounte           Waax",		0 },
 | 
			
		||||
		{ 1 * 8, 	" Windyoona         Channels",		0 },
 | 
			
		||||
		{ 1 * 8, 	"   F4GEV             Pyr3x",		0 },
 | 
			
		||||
		{ 1 * 8,    "  HB3YOE",							24 },
 | 
			
		||||
		{ 11 * 8,	            "MMXVIII",				-1 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	CreditsWidget credits_display {
 | 
			
		||||
		{ 0, 16, 240, 240 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_ok {
 | 
			
		||||
		{ 72, 272, 96, 24 },
 | 
			
		||||
		"OK"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_update {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			this->update();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_ABOUT_H__*/
 | 
			
		||||
@@ -0,0 +1,418 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ch.h>
 | 
			
		||||
 | 
			
		||||
#include "demofont.hpp"
 | 
			
		||||
#include "ymdata.hpp"
 | 
			
		||||
 | 
			
		||||
#include "cpld_update.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_about.hpp"
 | 
			
		||||
#include "touch.hpp"
 | 
			
		||||
#include "sine_table.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include "lpc43xx_cpp.hpp"
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace lpc43xx;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
	
 | 
			
		||||
void AboutView::on_show() {
 | 
			
		||||
	transmitter_model.set_tuning_frequency(1337000000);		// TODO: Change
 | 
			
		||||
	transmitter_model.set_baseband_configuration({
 | 
			
		||||
		.mode = 0,
 | 
			
		||||
		.sampling_rate = 1536000,
 | 
			
		||||
		.decimation_factor = 1,
 | 
			
		||||
	});
 | 
			
		||||
	transmitter_model.set_rf_amp(true);
 | 
			
		||||
	transmitter_model.set_lna(40);
 | 
			
		||||
	transmitter_model.set_vga(40);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_audiotx_data(32, 50, false, 0);
 | 
			
		||||
	
 | 
			
		||||
	//audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::render_video() {
 | 
			
		||||
	uint8_t p, r, luma, chroma, cy;
 | 
			
		||||
	ui::Color cc;
 | 
			
		||||
	char ch;
 | 
			
		||||
	float s;
 | 
			
		||||
	
 | 
			
		||||
	// Send framebuffer to LCD. Gotta go fast !
 | 
			
		||||
	display.render_box({30, 112}, {180, 72}, framebuffer);
 | 
			
		||||
 | 
			
		||||
	// Clear framebuffer to black
 | 
			
		||||
	memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
 | 
			
		||||
 | 
			
		||||
	// Drum hit palette animation
 | 
			
		||||
	if (drum > 1) drum--;
 | 
			
		||||
 | 
			
		||||
	// Render copper bars from Y buffer
 | 
			
		||||
	for (p = 0; p < 72; p++) {
 | 
			
		||||
		luma = copperbuffer[p] & 0x0F;		// 0 is transparent
 | 
			
		||||
		if (luma) {
 | 
			
		||||
			chroma = copperbuffer[p]>>4;
 | 
			
		||||
			cc = ui::Color(std::min((coppercolor[chroma][0]/luma)*drum,255), std::min((coppercolor[chroma][1]/luma)*drum,255), std::min((coppercolor[chroma][2]/luma)*drum,255));
 | 
			
		||||
			for (r = 0; r < 180; r++)
 | 
			
		||||
				framebuffer[(p*180)+r] = cc;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Scroll in/out state machine
 | 
			
		||||
	if (anim_state == 0) {
 | 
			
		||||
		// Scroll in
 | 
			
		||||
		if (ofy < 8) {
 | 
			
		||||
			ofy++;
 | 
			
		||||
			anim_state = 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			anim_state = 1;
 | 
			
		||||
		}
 | 
			
		||||
		if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
 | 
			
		||||
			ofx += 8;
 | 
			
		||||
			anim_state = 0;
 | 
			
		||||
		}
 | 
			
		||||
	} else if (anim_state == 1) {
 | 
			
		||||
		// Just wait
 | 
			
		||||
		if (credits_timer == (30*3)) {
 | 
			
		||||
			credits_timer = 0;
 | 
			
		||||
			anim_state = 2;
 | 
			
		||||
		} else {
 | 
			
		||||
			credits_timer++;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Scroll out
 | 
			
		||||
		if (credits[credits_index].change == true) {
 | 
			
		||||
			if (ofy > -24) {
 | 
			
		||||
				ofy--;
 | 
			
		||||
				anim_state = 2;
 | 
			
		||||
			} else {
 | 
			
		||||
				anim_state = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			anim_state = 0;
 | 
			
		||||
		}
 | 
			
		||||
		if (ofx < 180) {
 | 
			
		||||
			ofx += 8;
 | 
			
		||||
			anim_state = 2;
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		// Switch to next text
 | 
			
		||||
		if (anim_state == 0) {
 | 
			
		||||
			if (credits_index == 9)
 | 
			
		||||
				credits_index = 0;
 | 
			
		||||
			else
 | 
			
		||||
				credits_index++;
 | 
			
		||||
			ofx = -(strlen(credits[credits_index].name) * 16) - 16;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Sine text ("role")
 | 
			
		||||
	p = 0;
 | 
			
		||||
	while ((ch = credits[credits_index].role[p])) {
 | 
			
		||||
		draw_demoglyph({(ui::Coord)(8+(p*16)), (ui::Coord)(ofy+(sine_table_f32[((p*16)+(phase>>5))&0xFF] * 8))}, ch, paletteA);
 | 
			
		||||
		p++;
 | 
			
		||||
	}
 | 
			
		||||
		
 | 
			
		||||
	// Scroll text (name)
 | 
			
		||||
	p = 0;
 | 
			
		||||
	while ((ch = credits[credits_index].name[p])) {
 | 
			
		||||
		draw_demoglyph({(ui::Coord)(ofx+(p*16)), 56}, ch, paletteB);
 | 
			
		||||
		p++;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Clear bars Y buffer
 | 
			
		||||
	memset(copperbuffer, 0, 72);
 | 
			
		||||
		
 | 
			
		||||
	// Render bars to Y buffer
 | 
			
		||||
	for (p = 0; p < 5; p++) {
 | 
			
		||||
		cy = copperbars[p];
 | 
			
		||||
		for (r = 0; r < 16; r++)
 | 
			
		||||
			copperbuffer[cy+r] = copperluma[r] + (p<<4);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Animate bars positions
 | 
			
		||||
	for (p = 0; p < 5; p++) {
 | 
			
		||||
	  	s = sine_table_f32[((p*32)+(phase/24))&0xFF];
 | 
			
		||||
		s += sine_table_f32[((p*16)+(phase/35))&0xFF];
 | 
			
		||||
		copperbars[p] = 28+(uint8_t)(s * 14);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	phase += 128;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color * pal) {
 | 
			
		||||
	uint8_t x, y, c, cl, cr;
 | 
			
		||||
	uint16_t che;
 | 
			
		||||
	int16_t lbx, il;
 | 
			
		||||
 | 
			
		||||
	// Map ASCII to font bitmap
 | 
			
		||||
	if ((ch >= 32) || (ch < 96))
 | 
			
		||||
		che = char_map[ch-32];
 | 
			
		||||
	else
 | 
			
		||||
		che = 0xFF;
 | 
			
		||||
		
 | 
			
		||||
	if (che < 0xFF) {
 | 
			
		||||
		che = (che * 128) + 48;		// Start in bitmap
 | 
			
		||||
 | 
			
		||||
		il = (180 * p.y) + p.x;		// Start il framebuffer
 | 
			
		||||
		
 | 
			
		||||
		for (y = 0; y < 16; y++) {
 | 
			
		||||
			if (p.y + y >= 72) break;					// Over bottom of framebuffer, abort
 | 
			
		||||
			if (p.y + y >= 0) {
 | 
			
		||||
				for (x = 0; x < 8; x++) {
 | 
			
		||||
					c = demofont_bin[x+(y*8)+che];		// Split byte in 2 4BPP pixels
 | 
			
		||||
					cl = c >> 4;
 | 
			
		||||
					cr = c & 0x0F;
 | 
			
		||||
					lbx = p.x + (x*2);
 | 
			
		||||
					if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
 | 
			
		||||
					lbx++;
 | 
			
		||||
					il++;
 | 
			
		||||
					if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
 | 
			
		||||
					il++;
 | 
			
		||||
				}
 | 
			
		||||
				il += 180-16;
 | 
			
		||||
			} else {
 | 
			
		||||
				il += 180;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::render_audio() {
 | 
			
		||||
	uint8_t i, ymdata;
 | 
			
		||||
	uint16_t ym_render_cnt;
 | 
			
		||||
	
 | 
			
		||||
	// This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet
 | 
			
		||||
	
 | 
			
		||||
	// Render 1024 music samples
 | 
			
		||||
	for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
 | 
			
		||||
 | 
			
		||||
		// Update registers at 48000/960 = 50Hz
 | 
			
		||||
		if (ym_sample_cnt == 0) {
 | 
			
		||||
			// "Decompress" on the fly and update YM registers
 | 
			
		||||
			for (i = 0; i < 14; i++) {
 | 
			
		||||
				if (!ym_regs[i].cnt) {
 | 
			
		||||
					// New run
 | 
			
		||||
					ymdata = ymdata_bin[ym_regs[i].ptr++];
 | 
			
		||||
					ym_regs[i].cnt = ymdata & 0x7F;
 | 
			
		||||
					if (ymdata & 0x80) {
 | 
			
		||||
						ym_regs[i].same = true;
 | 
			
		||||
						ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
 | 
			
		||||
					} else {
 | 
			
		||||
						ym_regs[i].same = false;
 | 
			
		||||
					}
 | 
			
		||||
					// Detect drum on channel B
 | 
			
		||||
					if (i == 3)
 | 
			
		||||
						if (ym_regs[3].value > 2) drum = 4;
 | 
			
		||||
				}
 | 
			
		||||
				if (ym_regs[i].same == false) {
 | 
			
		||||
					ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
 | 
			
		||||
					if (i == 13) {
 | 
			
		||||
						// Update envelope attributes
 | 
			
		||||
						ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
 | 
			
		||||
						if (!(ym_regs[13].value & 8)) {
 | 
			
		||||
							ym_env_hold = 1;
 | 
			
		||||
							ym_env_alt = ym_env_att;
 | 
			
		||||
						} else {
 | 
			
		||||
							ym_env_hold = ym_regs[13].value & 1;
 | 
			
		||||
							ym_env_alt = ym_regs[13].value & 2;
 | 
			
		||||
						}
 | 
			
		||||
						// Reset envelope counter
 | 
			
		||||
						ym_env_step = 0x1F;
 | 
			
		||||
						ym_env_holding = 0;
 | 
			
		||||
						ym_env_vol = (ym_env_step ^ ym_env_att);		
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				ym_regs[i].cnt--;
 | 
			
		||||
			}
 | 
			
		||||
			ym_frame++;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Square wave oscillators
 | 
			
		||||
		// 2457600/16/48000 = 3.2, but 4 sounds better than 3...
 | 
			
		||||
		for (i = 0; i < 3; i++) {
 | 
			
		||||
			ym_osc_cnt[i] += 4;
 | 
			
		||||
			if (ym_osc_cnt[i] >= (ym_regs[i*2].value | ((ym_regs[(i*2)+1].value & 0x0f) << 8))) {
 | 
			
		||||
				ym_osc_cnt[i] = 0;
 | 
			
		||||
				ym_osc_out[i] ^= 1;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Noise generator
 | 
			
		||||
		ym_noise_cnt += 4;
 | 
			
		||||
		if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
 | 
			
		||||
			ym_noise_cnt = 0;
 | 
			
		||||
			ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
 | 
			
		||||
			ym_rng >>= 1;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Mix tones and noise
 | 
			
		||||
		for (i = 0; i < 3; i++)
 | 
			
		||||
			ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));
 | 
			
		||||
			
 | 
			
		||||
		// Envelope generator
 | 
			
		||||
		if (!ym_env_holding) {
 | 
			
		||||
			ym_env_cnt += 8;
 | 
			
		||||
			if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value<<8))) {
 | 
			
		||||
				ym_env_cnt = 0;
 | 
			
		||||
				ym_env_step--;
 | 
			
		||||
				if (ym_env_step < 0) {
 | 
			
		||||
					if (ym_env_hold) {
 | 
			
		||||
						if (ym_env_alt)
 | 
			
		||||
							ym_env_att ^= 0x1F;
 | 
			
		||||
						ym_env_holding = 1;
 | 
			
		||||
						ym_env_step = 0;
 | 
			
		||||
					} else {
 | 
			
		||||
						if (ym_env_alt && (ym_env_step & 0x20))
 | 
			
		||||
							ym_env_att ^= 0x1F;
 | 
			
		||||
						ym_env_step &= 0x1F;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ym_env_vol = (ym_env_step ^ ym_env_att);
 | 
			
		||||
 | 
			
		||||
		ym_out = 0;
 | 
			
		||||
		for (i = 0; i < 3; i++) {
 | 
			
		||||
			if (ym_regs[i + 8].value & 0x10) {
 | 
			
		||||
				// Envelope mode
 | 
			
		||||
				ym_out += (ym_ch[i] ? ym_env_vol : 0);
 | 
			
		||||
			} else {
 | 
			
		||||
				// Fixed mode
 | 
			
		||||
				ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;
 | 
			
		||||
 | 
			
		||||
		if (ym_sample_cnt < 960) {
 | 
			
		||||
			ym_sample_cnt++;
 | 
			
		||||
		} else {
 | 
			
		||||
			ym_sample_cnt = 0;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Loop
 | 
			
		||||
		if (ym_frame == ym_frames) ym_init();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::update() {
 | 
			
		||||
	if (framebuffer) {
 | 
			
		||||
		// Update 1 out of 2 frames, 60Hz is very laggy
 | 
			
		||||
		if (refresh_cnt & 1) render_video();
 | 
			
		||||
		refresh_cnt++;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Slowly increase volume to avoid jumpscare
 | 
			
		||||
	if (headphone_vol < (70 << 2)) {
 | 
			
		||||
		audio::headphone::set_volume(volume_t::decibel((headphone_vol/4) - 99) + audio::headphone::volume_range().max);
 | 
			
		||||
		headphone_vol++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::ym_init() {
 | 
			
		||||
	uint8_t reg;
 | 
			
		||||
	
 | 
			
		||||
	for (reg = 0; reg < 14; reg++) {
 | 
			
		||||
		ym_regs[reg].cnt = 0;
 | 
			
		||||
		// Pick up start pointers for each YM registers RLE blocks
 | 
			
		||||
		ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg*2)+3])<<8) + ymdata_bin[(reg*2)+2];
 | 
			
		||||
		ym_regs[reg].same = false;	// Useless ?
 | 
			
		||||
		ym_regs[reg].value = 0;		// Useless ?
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	ym_frame = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AboutView::AboutView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t p, c;
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({ {
 | 
			
		||||
		&text_title,
 | 
			
		||||
		&text_firmware,
 | 
			
		||||
		&text_cpld_hackrf,
 | 
			
		||||
		&text_cpld_hackrf_status,
 | 
			
		||||
		&button_ok,
 | 
			
		||||
	} });
 | 
			
		||||
	
 | 
			
		||||
	if( cpld_hackrf_verify_eeprom() ) {
 | 
			
		||||
		text_cpld_hackrf_status.set(" OK");
 | 
			
		||||
	} else {
 | 
			
		||||
		text_cpld_hackrf_status.set("BAD");
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Politely ask for about 26kB
 | 
			
		||||
	framebuffer = (ui::Color *)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));
 | 
			
		||||
	
 | 
			
		||||
	if (framebuffer) {
 | 
			
		||||
		memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
 | 
			
		||||
		
 | 
			
		||||
		// Copy original font palette
 | 
			
		||||
		c = 0;
 | 
			
		||||
		for (p = 0; p < 48; p+=3)
 | 
			
		||||
			paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p+1], demofont_bin[p+2]);
 | 
			
		||||
		
 | 
			
		||||
		// Increase red in another one
 | 
			
		||||
		c = 0;
 | 
			
		||||
		for (p = 0; p < 48; p+=3)
 | 
			
		||||
			paletteB[c++] = ui::Color(std::min(demofont_bin[p]+64, 255), demofont_bin[p+1], demofont_bin[p+2]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Init YM synth
 | 
			
		||||
	ym_frames = ((uint16_t)(ymdata_bin[1])<<8) + ymdata_bin[0];
 | 
			
		||||
	ym_init();
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	button_ok.on_select = [this,&nav](Button&){
 | 
			
		||||
		if (framebuffer) chHeapFree(framebuffer);	// Do NOT forget this
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AboutView::~AboutView() {
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AboutView::focus() {
 | 
			
		||||
	button_ok.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,174 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_ABOUT_H__
 | 
			
		||||
#define __UI_ABOUT_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class AboutView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AboutView(NavigationView& nav);
 | 
			
		||||
	~AboutView();
 | 
			
		||||
	
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void ym_init();
 | 
			
		||||
	void update();
 | 
			
		||||
	void render_video();
 | 
			
		||||
	void render_audio();
 | 
			
		||||
	void draw_demoglyph(ui::Point p, char ch, ui::Color * pal);
 | 
			
		||||
	uint16_t debug_cnt = 0;
 | 
			
		||||
	
 | 
			
		||||
	typedef struct ymreg_t {
 | 
			
		||||
		uint8_t value;
 | 
			
		||||
		uint8_t cnt;
 | 
			
		||||
		uint16_t ptr;
 | 
			
		||||
		bool same;
 | 
			
		||||
	} ymreg_t;
 | 
			
		||||
	
 | 
			
		||||
	uint16_t headphone_vol = 5 << 2;
 | 
			
		||||
	
 | 
			
		||||
	ymreg_t ym_regs[14];
 | 
			
		||||
	uint16_t ym_frames;
 | 
			
		||||
	uint16_t ym_frame;
 | 
			
		||||
	uint8_t drum = 0;
 | 
			
		||||
	uint16_t ym_osc_cnt[3];
 | 
			
		||||
	uint32_t ym_rng = 1;
 | 
			
		||||
	uint16_t ym_noise_cnt;
 | 
			
		||||
	uint8_t ym_env_att, ym_env_hold, ym_env_alt, ym_env_holding, ym_env_vol;
 | 
			
		||||
	int8_t ym_env_step;
 | 
			
		||||
	uint16_t ym_env_cnt;
 | 
			
		||||
	uint8_t ym_osc_out[3];
 | 
			
		||||
	uint8_t ym_ch[3];
 | 
			
		||||
	uint8_t ym_out;
 | 
			
		||||
	uint16_t ym_sample_cnt = 0;
 | 
			
		||||
	
 | 
			
		||||
	int8_t ym_buffer[1024];
 | 
			
		||||
 | 
			
		||||
	uint8_t refresh_cnt;
 | 
			
		||||
	ui::Color paletteA[16];
 | 
			
		||||
	ui::Color paletteB[16];
 | 
			
		||||
	ui::Color * framebuffer;
 | 
			
		||||
	uint32_t phase = 0;
 | 
			
		||||
	uint8_t copperbars[5] = { 0 };
 | 
			
		||||
	uint8_t copperbuffer[72] = { 0 };
 | 
			
		||||
	
 | 
			
		||||
	uint8_t anim_state = 0;
 | 
			
		||||
	uint8_t credits_index = 0;
 | 
			
		||||
	uint16_t credits_timer = 0;
 | 
			
		||||
	
 | 
			
		||||
	int16_t ofx = -180, ofy = -24;
 | 
			
		||||
 | 
			
		||||
	const uint8_t char_map[64] = { 0xFF, 27, 46, 0xFF, 0xFF, 0xFF, 28, 45,
 | 
			
		||||
									58, 59, 0xFF, 43, 40, 57, 26, 42,
 | 
			
		||||
									29, 30, 31, 32, 33, 34, 35, 36,
 | 
			
		||||
									37, 38, 41, 0xFF, 0xFF, 0xFF, 0xFF, 44,
 | 
			
		||||
									0xFF, 0, 1, 2, 3, 4, 5, 6,
 | 
			
		||||
									7, 8, 9, 10, 11, 12, 13, 14,
 | 
			
		||||
									15, 16, 17, 18, 19, 20, 21, 22,
 | 
			
		||||
									23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
 | 
			
		||||
	
 | 
			
		||||
	const uint8_t copperluma[16] = { 8,7,6,5,4,3,2,1,1,2,3,4,5,6,7,8 };
 | 
			
		||||
	const uint8_t coppercolor[5][3] = {	{ 255,0,0 },
 | 
			
		||||
										{ 0,255,0 },
 | 
			
		||||
										{ 0,0,255 },
 | 
			
		||||
										{ 255,0,255 },
 | 
			
		||||
										{ 255,255,0 } };
 | 
			
		||||
	
 | 
			
		||||
	typedef struct credits_t {
 | 
			
		||||
		char role[12];
 | 
			
		||||
		char name[12];
 | 
			
		||||
		bool change;
 | 
			
		||||
	} credits_t;
 | 
			
		||||
	
 | 
			
		||||
	//								  0123456789A		 0123456789A
 | 
			
		||||
	const credits_t credits[10] = { {"GURUS",			"J. BOONE", 		false},
 | 
			
		||||
									{"GURUS",			"M. OSSMANN", 		true},
 | 
			
		||||
									{"BUGS",			"FURRTEK", 			true},
 | 
			
		||||
									{"RDS WAVE",		"C. JACQUET", 		true},
 | 
			
		||||
									{"POCSAG RX",		"T. SAILER",		false},
 | 
			
		||||
									{"POCSAG RX",		"E. OENAL",			true},
 | 
			
		||||
									{"XYLOS DATA",		"CLX", 				true},
 | 
			
		||||
									{"GREETS TO",		"SIGMOUNTE", 		false},
 | 
			
		||||
									{"GREETS TO",		"WINDYOONA", 		true},
 | 
			
		||||
									{"THIS MUSIC",		"BIG ALEC", 		true}
 | 
			
		||||
									};
 | 
			
		||||
 | 
			
		||||
	Text text_title {
 | 
			
		||||
		{ 100, 32, 40, 16 },
 | 
			
		||||
		"About",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_firmware {
 | 
			
		||||
		{ 0, 236, 240, 16 },
 | 
			
		||||
		"Version   " VERSION_STRING,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_cpld_hackrf {
 | 
			
		||||
		{ 0, 252, 11*8, 16 },
 | 
			
		||||
		"HackRF CPLD",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_cpld_hackrf_status {
 | 
			
		||||
		{ 240 - 3*8, 252, 3*8, 16 },
 | 
			
		||||
		"???"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_ok {
 | 
			
		||||
		{ 72, 272, 96, 24 },
 | 
			
		||||
		"OK"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_update {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			this->update();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::FIFOSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const FIFOSignalMessage*>(p);
 | 
			
		||||
			if (message->signaltype == 1) {
 | 
			
		||||
				this->render_audio();
 | 
			
		||||
				baseband::set_fifo_data(ym_buffer);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_ABOUT_H__*/
 | 
			
		||||
@@ -0,0 +1,85 @@
 | 
			
		||||
#include "ui_about_simple.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui
 | 
			
		||||
{
 | 
			
		||||
    AboutView::AboutView(NavigationView &nav)
 | 
			
		||||
    {
 | 
			
		||||
        add_children({&console, &button_ok});
 | 
			
		||||
 | 
			
		||||
        button_ok.on_select = [&nav](Button &)
 | 
			
		||||
        {
 | 
			
		||||
            nav.pop();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        console.writeln("\x1B\x07List of contributors:\x1B\x10");
 | 
			
		||||
        console.writeln("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AboutView::update()
 | 
			
		||||
    {
 | 
			
		||||
        if (++timer > 200)
 | 
			
		||||
        {
 | 
			
		||||
            timer = 0;
 | 
			
		||||
 | 
			
		||||
            switch (++frame)
 | 
			
		||||
            {
 | 
			
		||||
            case 1:
 | 
			
		||||
                // TODO: Generate this automatically from github
 | 
			
		||||
                // https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
 | 
			
		||||
                console.writeln("\x1B\x06Mayhem:\x1B\x10");
 | 
			
		||||
                console.writeln("eried,euquiq,gregoryfenton");
 | 
			
		||||
                console.writeln("johnelder,jwetzell,nnemanjan00");
 | 
			
		||||
                console.writeln("N0vaPixel,klockee,GullCode");
 | 
			
		||||
                console.writeln("jamesshao8,ITAxReal,rascafr");
 | 
			
		||||
                console.writeln("mcules,dqs105,strijar");
 | 
			
		||||
                console.writeln("zhang00963,RedFox-Fr,aldude999");
 | 
			
		||||
                console.writeln("East2West,fossum,ArjanOnwezen");
 | 
			
		||||
                console.writeln("vXxOinvizioNxX,teixeluis");
 | 
			
		||||
                console.writeln("Brumi-2021,texasyojimbo");
 | 
			
		||||
                console.writeln("heurist1,intoxsick,ckuethe");
 | 
			
		||||
                console.writeln("notpike,jLynx,zigad");
 | 
			
		||||
                console.writeln("MichalLeonBorsuk");
 | 
			
		||||
                console.writeln("");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 2:
 | 
			
		||||
                // https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
 | 
			
		||||
                console.writeln("\x1B\x06Havoc:\x1B\x10");
 | 
			
		||||
                console.writeln("furrtek,mrmookie,NotPike");
 | 
			
		||||
                console.writeln("mjwaxios,ImDroided,Giorgiofox");
 | 
			
		||||
                console.writeln("F4GEV,z4ziggy,xmycroftx");
 | 
			
		||||
                console.writeln("troussos,silascutler");
 | 
			
		||||
                console.writeln("nickbouwhuis,msoose,leres");
 | 
			
		||||
                console.writeln("joakar,dhoetger,clem-42");
 | 
			
		||||
                console.writeln("brianlechthaler,ZeroChaos-...");
 | 
			
		||||
                console.writeln("");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 3:
 | 
			
		||||
                // https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
 | 
			
		||||
                console.writeln("\x1B\x06PortaPack:\x1B\x10");
 | 
			
		||||
                console.writeln("jboone,argilo");
 | 
			
		||||
                console.writeln("");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 4:
 | 
			
		||||
                // https://github.com/mossmann/hackrf/graphs/contributors
 | 
			
		||||
                console.writeln("\x1B\x06HackRF:\x1B\x10");
 | 
			
		||||
                console.writeln("mossmann,dominicgs,bvernoux");
 | 
			
		||||
                console.writeln("bgamari,schneider42,miek");
 | 
			
		||||
                console.writeln("willcode,hessu,Sec42");
 | 
			
		||||
                console.writeln("yhetti,ckuethe,smunaut");
 | 
			
		||||
                console.writeln("wishi,mrbubble62,scateu...");
 | 
			
		||||
                console.writeln("");
 | 
			
		||||
                frame = 0; // Loop
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AboutView::focus()
 | 
			
		||||
    {
 | 
			
		||||
        button_ok.focus();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
#ifndef __UI_ABOUT_SIMPLE_H__
 | 
			
		||||
#define __UI_ABOUT_SIMPLE_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace ui
 | 
			
		||||
{
 | 
			
		||||
    class AboutView : public View
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        AboutView(NavigationView &nav);
 | 
			
		||||
        void focus() override;
 | 
			
		||||
        std::string title() const override { return "About"; };
 | 
			
		||||
        int32_t timer{180};
 | 
			
		||||
        short frame{0};
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        void update();
 | 
			
		||||
 | 
			
		||||
        Console console{
 | 
			
		||||
            {0, 10, 240, 240}};
 | 
			
		||||
 | 
			
		||||
        Button button_ok{
 | 
			
		||||
            {240/3, 270, 240/3, 24},
 | 
			
		||||
            "OK",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        MessageHandlerRegistration message_handler_update{
 | 
			
		||||
            Message::ID::DisplayFrameSync,
 | 
			
		||||
            [this](const Message *const) {
 | 
			
		||||
                this->update();
 | 
			
		||||
            }};
 | 
			
		||||
    };
 | 
			
		||||
} // namespace ui
 | 
			
		||||
 | 
			
		||||
#endif /*__UI_ABOUT_SIMPLE_H__*/
 | 
			
		||||
@@ -0,0 +1,506 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <strings.h>
 | 
			
		||||
 | 
			
		||||
#include "ui_adsb_rx.hpp"
 | 
			
		||||
#include "ui_alphanum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<AircraftRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	char aged_color;
 | 
			
		||||
	Color target_color;
 | 
			
		||||
	auto entry_age = entry.age;
 | 
			
		||||
	
 | 
			
		||||
	// Color decay for flights not being updated anymore
 | 
			
		||||
	if (entry_age < ADSB_DECAY_A) {
 | 
			
		||||
		aged_color = 0x10;
 | 
			
		||||
		target_color = Color::green();
 | 
			
		||||
	} else if (entry_age < ADSB_DECAY_B) {
 | 
			
		||||
		aged_color = 0x07;
 | 
			
		||||
		target_color = Color::light_grey();
 | 
			
		||||
	} else {
 | 
			
		||||
		aged_color = 0x08;
 | 
			
		||||
		target_color = Color::dark_grey();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	std::string entry_string = "\x1B";
 | 
			
		||||
	entry_string += aged_color;
 | 
			
		||||
#if false
 | 
			
		||||
	entry_string += to_string_hex(entry.ICAO_address, 6) + " " +
 | 
			
		||||
		entry.callsign + "  " +
 | 
			
		||||
		(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+") + " " + 
 | 
			
		||||
		entry.time_string;
 | 
			
		||||
#else
 | 
			
		||||
	// SBT
 | 
			
		||||
	entry_string += 
 | 
			
		||||
		(entry.callsign[0]!=' ' ? entry.callsign + " " : to_string_hex(entry.ICAO_address, 6) + "   ") +
 | 
			
		||||
		to_string_dec_uint((unsigned int)((entry.pos.altitude+50)/100),4) +
 | 
			
		||||
		to_string_dec_uint((unsigned int)entry.velo.speed,4) +
 | 
			
		||||
		to_string_dec_uint((unsigned int)(entry.amp>>9),4) + " " +
 | 
			
		||||
		(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
 | 
			
		||||
		to_string_dec_uint(entry.age, 4);
 | 
			
		||||
#endif
 | 
			
		||||
	
 | 
			
		||||
	painter.draw_string(
 | 
			
		||||
		target_rect.location(),
 | 
			
		||||
		style,
 | 
			
		||||
		entry_string
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	if (entry.pos.valid)
 | 
			
		||||
		painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0), bitmap_target, target_color, style.background);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBLogger::log_str(std::string& logline) {
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	log_file.write_entry(datetime,logline);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Aircraft Details
 | 
			
		||||
void ADSBRxAircraftDetailsView::focus() {
 | 
			
		||||
	button_close.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxAircraftDetailsView::~ADSBRxAircraftDetailsView() {
 | 
			
		||||
	on_close_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	const AircraftRecentEntry& entry,
 | 
			
		||||
	const std::function<void(void)> on_close
 | 
			
		||||
) : entry_copy(entry),
 | 
			
		||||
	on_close_(on_close)
 | 
			
		||||
{	
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&text_icao_address,
 | 
			
		||||
		&text_registration,
 | 
			
		||||
		&text_manufacturer,
 | 
			
		||||
		&text_model,
 | 
			
		||||
		&text_type,
 | 
			
		||||
		&text_number_of_engines,
 | 
			
		||||
		&text_engine_type,
 | 
			
		||||
		&text_owner,
 | 
			
		||||
		&text_operator,
 | 
			
		||||
		&button_close
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<ADSBLogger> logger { };
 | 
			
		||||
	//update(entry_copy);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	icao_code = to_string_hex(entry_copy.ICAO_address, 6);
 | 
			
		||||
	text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
 | 
			
		||||
 | 
			
		||||
	// Try getting the aircraft information from icao24.db
 | 
			
		||||
	return_code = db.retrieve_aircraft_record(&aircraft_record, icao_code);
 | 
			
		||||
	switch(return_code) {
 | 
			
		||||
		case DATABASE_RECORD_FOUND: 
 | 
			
		||||
			text_registration.set(aircraft_record.aircraft_registration);
 | 
			
		||||
			text_manufacturer.set(aircraft_record.aircraft_manufacturer);
 | 
			
		||||
			text_model.set(aircraft_record.aircraft_model);	
 | 
			
		||||
			text_owner.set(aircraft_record.aircraft_owner);
 | 
			
		||||
			text_operator.set(aircraft_record.aircraft_operator);
 | 
			
		||||
			// Check for ICAO type, e.g. L2J
 | 
			
		||||
			if(strlen(aircraft_record.icao_type) == 3) {
 | 
			
		||||
				switch(aircraft_record.icao_type[0]) {
 | 
			
		||||
					case 'L': 
 | 
			
		||||
						text_type.set("Landplane"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'S': 
 | 
			
		||||
						text_type.set("Seaplane"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'A': 
 | 
			
		||||
						text_type.set("Amphibian"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'H': 
 | 
			
		||||
						text_type.set("Helicopter"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'G': 
 | 
			
		||||
						text_type.set("Gyrocopter"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'T': 
 | 
			
		||||
						text_type.set("Tilt-wing aircraft"); 
 | 
			
		||||
						break;					
 | 
			
		||||
				}
 | 
			
		||||
				text_number_of_engines.set(std::string(1, aircraft_record.icao_type[1]));
 | 
			
		||||
				switch(aircraft_record.icao_type[2]) {
 | 
			
		||||
					case 'P': 
 | 
			
		||||
						text_engine_type.set("Piston engine"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'T': 
 | 
			
		||||
						text_engine_type.set("Turboprop/Turboshaft engine"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'J': 
 | 
			
		||||
						text_engine_type.set("Jet engine"); 
 | 
			
		||||
						break;
 | 
			
		||||
					case 'E': 
 | 
			
		||||
						text_engine_type.set("Electric engine"); 
 | 
			
		||||
						break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			// Check for ICAO type designator
 | 
			
		||||
			else if(strlen(aircraft_record.icao_type) == 4) {
 | 
			
		||||
				if(strcmp(aircraft_record.icao_type,"SHIP") == 0) text_type.set("Airship");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"BALL") == 0) text_type.set("Balloon");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"GLID") == 0) text_type.set("Glider / sailplane");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"ULAC") == 0) text_type.set("Micro/ultralight aircraft");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"GYRO") == 0) text_type.set("Micro/ultralight autogyro");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"UHEL") == 0) text_type.set("Micro/ultralight helicopter");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"SHIP") == 0) text_type.set("Airship");
 | 
			
		||||
				else if(strcmp(aircraft_record.icao_type,"PARA") == 0) text_type.set("Powered parachute/paraplane");
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case DATABASE_NOT_FOUND: 
 | 
			
		||||
			text_manufacturer.set("No icao24.db file");
 | 
			
		||||
			break;	
 | 
			
		||||
	}
 | 
			
		||||
	button_close.on_select =  [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// End of Aicraft details
 | 
			
		||||
 | 
			
		||||
void ADSBRxDetailsView::focus() {
 | 
			
		||||
	button_see_map.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
 | 
			
		||||
	entry_copy = entry;
 | 
			
		||||
	uint32_t age = entry_copy.age;
 | 
			
		||||
	
 | 
			
		||||
	if (age < 60)
 | 
			
		||||
		text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
 | 
			
		||||
	else
 | 
			
		||||
		text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago");
 | 
			
		||||
	
 | 
			
		||||
	text_infos.set(entry_copy.info_string);
 | 
			
		||||
	if(entry_copy.velo.heading < 360 && entry_copy.velo.speed >=0){ //I don't like this but...
 | 
			
		||||
		text_info2.set("Hdg:" + to_string_dec_uint(entry_copy.velo.heading) + " Spd:" + to_string_dec_int(entry_copy.velo.speed));
 | 
			
		||||
	}else{
 | 
			
		||||
		text_info2.set("");
 | 
			
		||||
	}
 | 
			
		||||
	text_frame_pos_even.set(to_string_hex_array(entry_copy.frame_pos_even.get_raw_data(), 14));
 | 
			
		||||
	text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
 | 
			
		||||
	
 | 
			
		||||
	if (send_updates)
 | 
			
		||||
		geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading, entry_copy.pos.altitude);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxDetailsView::~ADSBRxDetailsView() {
 | 
			
		||||
	on_close_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxDetailsView::ADSBRxDetailsView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	const AircraftRecentEntry& entry,
 | 
			
		||||
	const std::function<void(void)> on_close
 | 
			
		||||
) : entry_copy(entry),
 | 
			
		||||
	on_close_(on_close)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&text_icao_address,
 | 
			
		||||
		&text_callsign,
 | 
			
		||||
		&text_last_seen,
 | 
			
		||||
		&text_airline,
 | 
			
		||||
		&text_country,
 | 
			
		||||
		&text_infos,
 | 
			
		||||
		&text_info2,
 | 
			
		||||
		&text_frame_pos_even,
 | 
			
		||||
		&text_frame_pos_odd,
 | 
			
		||||
		&button_aircraft_details,
 | 
			
		||||
		&button_see_map
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<ADSBLogger> logger { };
 | 
			
		||||
	update(entry_copy);
 | 
			
		||||
 | 
			
		||||
	// The following won't (shouldn't !) change for a given airborne aircraft
 | 
			
		||||
	// Try getting the airline's name from airlines.db
 | 
			
		||||
	airline_code = entry_copy.callsign.substr(0, 3);
 | 
			
		||||
	return_code = db.retrieve_airline_record(&airline_record, airline_code);
 | 
			
		||||
	switch(return_code) {
 | 
			
		||||
		case DATABASE_RECORD_FOUND: 
 | 
			
		||||
			text_airline.set(airline_record.airline);
 | 
			
		||||
			text_country.set(airline_record.country);	
 | 
			
		||||
			break;
 | 
			
		||||
		case DATABASE_NOT_FOUND: 
 | 
			
		||||
			text_airline.set("No airlines.db file");
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	text_callsign.set(entry_copy.callsign);
 | 
			
		||||
	text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
 | 
			
		||||
 | 
			
		||||
        button_aircraft_details.on_select = [this, &nav](Button&) {
 | 
			
		||||
		//detailed_entry_key = entry.key();
 | 
			
		||||
		aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(
 | 
			
		||||
			entry_copy,
 | 
			
		||||
			[this]() {
 | 
			
		||||
				send_updates = false;
 | 
			
		||||
			});
 | 
			
		||||
		send_updates = false;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_see_map.on_select = [this, &nav](Button&) {
 | 
			
		||||
		if (!send_updates) { // Prevent recursively launching the map
 | 
			
		||||
			geomap_view = nav.push<GeoMapView>(
 | 
			
		||||
				entry_copy.callsign,
 | 
			
		||||
				entry_copy.pos.altitude,
 | 
			
		||||
				GeoPos::alt_unit::FEET,
 | 
			
		||||
				entry_copy.pos.latitude,
 | 
			
		||||
				entry_copy.pos.longitude,
 | 
			
		||||
				entry_copy.velo.heading,
 | 
			
		||||
				[this]() {
 | 
			
		||||
				send_updates = false;
 | 
			
		||||
			});
 | 
			
		||||
			send_updates = true;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void ADSBRxView::focus() {
 | 
			
		||||
	field_vga.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxView::~ADSBRxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	settings.save("rx_adsb", &app_settings);
 | 
			
		||||
 | 
			
		||||
	//TODO: once all apps keep there own settin previous frequency logic can be removed
 | 
			
		||||
	receiver_model.set_tuning_frequency(prevFreq);
 | 
			
		||||
	rtc_time::signal_tick_second -= signal_token_tick_second;
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AircraftRecentEntry ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
 | 
			
		||||
	auto it = find(recent, ICAO_address);
 | 
			
		||||
 | 
			
		||||
	// If not found
 | 
			
		||||
	if (it == std::end(recent)){
 | 
			
		||||
		recent.emplace_front(ICAO_address); // Add it
 | 
			
		||||
		truncate_entries(recent); // Truncate the list
 | 
			
		||||
		sort_entries_by_state();
 | 
			
		||||
		it = find(recent, ICAO_address); // Find it again
 | 
			
		||||
	}
 | 
			
		||||
	return *it;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBRxView::replace_entry(AircraftRecentEntry & entry)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t ICAO_address = entry.ICAO_address;
 | 
			
		||||
 | 
			
		||||
	std::replace_if( recent.begin(), recent.end(), 
 | 
			
		||||
		[ICAO_address](const AircraftRecentEntry & compEntry) {return ICAO_address == compEntry.ICAO_address;},
 | 
			
		||||
		entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBRxView::sort_entries_by_state()
 | 
			
		||||
{
 | 
			
		||||
	// Sorting List pn age_state using lambda function as comparator
 | 
			
		||||
	recent.sort([](const AircraftRecentEntry & left, const AircraftRecentEntry & right){return (left.age_state < right.age_state); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	std::string str_timestamp;
 | 
			
		||||
	std::string callsign;
 | 
			
		||||
	std::string str_info;
 | 
			
		||||
	std::string logentry;
 | 
			
		||||
 | 
			
		||||
	auto frame = message->frame;
 | 
			
		||||
	uint32_t ICAO_address = frame.get_ICAO_address();
 | 
			
		||||
 | 
			
		||||
	if (frame.check_CRC() && ICAO_address) {
 | 
			
		||||
		rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
		auto entry = find_or_create_entry(ICAO_address);
 | 
			
		||||
		frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
 | 
			
		||||
		entry.reset_age();
 | 
			
		||||
		if (entry.hits==0)
 | 
			
		||||
		{ 
 | 
			
		||||
			entry.amp = message->amp;
 | 
			
		||||
		} else {
 | 
			
		||||
			entry.amp = ((entry.amp*15)+message->amp)>>4;
 | 
			
		||||
		}
 | 
			
		||||
		str_timestamp = to_string_datetime(datetime, HMS);
 | 
			
		||||
		entry.set_time_string(str_timestamp);
 | 
			
		||||
 | 
			
		||||
		entry.inc_hit();
 | 
			
		||||
		logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
 | 
			
		||||
		logentry += "ICAO:" + to_string_hex(ICAO_address, 6) + " ";
 | 
			
		||||
		
 | 
			
		||||
		if (frame.get_DF() == DF_ADSB) {
 | 
			
		||||
			uint8_t msg_type = frame.get_msg_type();
 | 
			
		||||
			uint8_t msg_sub = frame.get_msg_sub();
 | 
			
		||||
			uint8_t * raw_data = frame.get_raw_data();
 | 
			
		||||
			
 | 
			
		||||
			// 4: // surveillance, altitude reply
 | 
			
		||||
			if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
 | 
			
		||||
				callsign = decode_frame_id(frame);
 | 
			
		||||
				entry.set_callsign(callsign);
 | 
			
		||||
				logentry+=callsign+" ";
 | 
			
		||||
			} 
 | 
			
		||||
			// 9:
 | 
			
		||||
			// 18: { // Extended squitter/non-transponder
 | 
			
		||||
			// 21: // Comm-B, identity reply
 | 
			
		||||
			// 20: // Comm-B, altitude reply
 | 
			
		||||
			else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) || 
 | 
			
		||||
				((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
 | 
			
		||||
				entry.set_frame_pos(frame, raw_data[6] & 4);
 | 
			
		||||
				
 | 
			
		||||
				if (entry.pos.valid) {
 | 
			
		||||
					str_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
 | 
			
		||||
						" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
 | 
			
		||||
						" Lon:" + to_string_decimal(entry.pos.longitude, 2);
 | 
			
		||||
 | 
			
		||||
					// printing the coordinates in the log file with more
 | 
			
		||||
					// resolution, as we are not constrained by screen 
 | 
			
		||||
					// real estate there:
 | 
			
		||||
 | 
			
		||||
					std::string log_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
 | 
			
		||||
						" Lat:" + to_string_decimal(entry.pos.latitude, 7) +
 | 
			
		||||
						" Lon:" + to_string_decimal(entry.pos.longitude, 7);
 | 
			
		||||
 | 
			
		||||
					entry.set_info_string(str_info);
 | 
			
		||||
					logentry+=log_info + " ";
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
			} else if(msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC){
 | 
			
		||||
				entry.set_frame_velo(frame);
 | 
			
		||||
				logentry += "Type:" + to_string_dec_uint(msg_sub) +
 | 
			
		||||
							" Hdg:" + to_string_dec_uint(entry.velo.heading) +
 | 
			
		||||
							" Spd: "+ to_string_dec_int(entry.velo.speed);
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		replace_entry(entry);
 | 
			
		||||
		
 | 
			
		||||
		logger = std::make_unique<ADSBLogger>();
 | 
			
		||||
        if (logger) {
 | 
			
		||||
                logger->append(u"adsb.txt");
 | 
			
		||||
                // will log each frame in format:
 | 
			
		||||
                // 20171103100227 8DADBEEFDEADBEEFDEADBEEFDEADBEEF ICAO:nnnnnn callsign Alt:nnnnnn Latnnn.nn Lonnnn.nn
 | 
			
		||||
				logger->log_str(logentry);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBRxView::on_tick_second() {
 | 
			
		||||
	// Decay and refresh if needed
 | 
			
		||||
	for (auto& entry : recent) {
 | 
			
		||||
		entry.inc_age();
 | 
			
		||||
		
 | 
			
		||||
		if (details_view) {
 | 
			
		||||
			if (send_updates && (entry.key() == detailed_entry_key)) // Check if the ICAO address match
 | 
			
		||||
				details_view->update(entry);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort the list if it is being displayed
 | 
			
		||||
	if (!send_updates) {
 | 
			
		||||
		sort_entries_by_state();
 | 
			
		||||
		recent_entries_view.set_dirty();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBRxView::ADSBRxView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&recent_entries_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_adsb", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		field_lna.set_value(40);
 | 
			
		||||
		field_vga.set_value(40);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	recent_entries_view.set_parent_rect({ 0, 16, 240, 272 });
 | 
			
		||||
	recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
 | 
			
		||||
		detailed_entry_key = entry.key();
 | 
			
		||||
		details_view = nav.push<ADSBRxDetailsView>(
 | 
			
		||||
			entry,
 | 
			
		||||
			[this]() {
 | 
			
		||||
				send_updates = false;
 | 
			
		||||
			});
 | 
			
		||||
		send_updates = true;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
 | 
			
		||||
		on_tick_second();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	prevFreq = receiver_model.tuning_frequency();
 | 
			
		||||
 | 
			
		||||
	baseband::set_adsb();
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_tuning_frequency(1090000000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
 | 
			
		||||
	receiver_model.set_sampling_rate(2000000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(2500000);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,431 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_geomap.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
#include "database.hpp"
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
#include "adsb.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "crc.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace adsb;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
#define ADSB_DECAY_A 		10		// In seconds
 | 
			
		||||
#define ADSB_DECAY_B 		30
 | 
			
		||||
#define ADSB_DECAY_C 		60		// Can be used for removing old entries, RecentEntries already caps to 64
 | 
			
		||||
 | 
			
		||||
#define AIRCRAFT_ID_L		1		// aircraft ID message type (lowest type id)
 | 
			
		||||
#define AIRCRAFT_ID_H		4		// aircraft ID message type (highest type id)
 | 
			
		||||
 | 
			
		||||
#define SURFACE_POS_L		5		// surface position (lowest type id)
 | 
			
		||||
#define SURFACE_POS_H		8		// surface position (highest type id)
 | 
			
		||||
 | 
			
		||||
#define AIRBORNE_POS_BARO_L	9		// airborne position (lowest type id)
 | 
			
		||||
#define AIRBORNE_POS_BARO_H	18		// airborne position (highest type id)
 | 
			
		||||
 | 
			
		||||
#define AIRBORNE_VEL		19		// airborne velocities
 | 
			
		||||
 | 
			
		||||
#define AIRBORNE_POS_GPS_L	20		// airborne position (lowest type id)
 | 
			
		||||
#define AIRBORNE_POS_GPS_H	22		// airborne position (highest type id)
 | 
			
		||||
 | 
			
		||||
#define RESERVED_L			23		// reserved for other uses
 | 
			
		||||
#define RESERVED_H			31		// reserved for other uses
 | 
			
		||||
 | 
			
		||||
#define VEL_GND_SUBSONIC		1
 | 
			
		||||
#define VEL_GND_SUPERSONIC		2
 | 
			
		||||
#define VEL_AIR_SUBSONIC		3
 | 
			
		||||
#define VEL_AIR_SUPERSONIC		4
 | 
			
		||||
 | 
			
		||||
#define O_E_FRAME_TIMEOUT		20	// timeout between odd and even frames
 | 
			
		||||
 | 
			
		||||
struct AircraftRecentEntry {
 | 
			
		||||
	using Key = uint32_t;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr Key invalid_key = 0xffffffff;
 | 
			
		||||
	
 | 
			
		||||
	uint32_t ICAO_address { };
 | 
			
		||||
	uint16_t hits { 0 };
 | 
			
		||||
	
 | 
			
		||||
	uint16_t age_state { 1 };
 | 
			
		||||
	uint32_t age { 0 };
 | 
			
		||||
	uint32_t amp { 0 };
 | 
			
		||||
	adsb_pos pos { false, 0, 0, 0 };
 | 
			
		||||
	adsb_vel velo { false, 0, 999, 0 };
 | 
			
		||||
	ADSBFrame frame_pos_even { };
 | 
			
		||||
	ADSBFrame frame_pos_odd { };
 | 
			
		||||
	
 | 
			
		||||
	std::string callsign { "        " };
 | 
			
		||||
	std::string time_string { "" };
 | 
			
		||||
	std::string info_string { "" };
 | 
			
		||||
	
 | 
			
		||||
	AircraftRecentEntry(
 | 
			
		||||
		const uint32_t ICAO_address
 | 
			
		||||
	) : ICAO_address { ICAO_address }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return ICAO_address;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_callsign(std::string& new_callsign) {
 | 
			
		||||
		callsign = new_callsign;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void inc_hit() {
 | 
			
		||||
		hits++;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_frame_pos(ADSBFrame& frame, uint32_t parity) {
 | 
			
		||||
		if (!parity)
 | 
			
		||||
			frame_pos_even = frame;
 | 
			
		||||
		else
 | 
			
		||||
			frame_pos_odd = frame;
 | 
			
		||||
		
 | 
			
		||||
		if (!frame_pos_even.empty() && !frame_pos_odd.empty()) {
 | 
			
		||||
			if (abs(frame_pos_even.get_rx_timestamp() - frame_pos_odd.get_rx_timestamp()) < O_E_FRAME_TIMEOUT)
 | 
			
		||||
				pos = decode_frame_pos(frame_pos_even, frame_pos_odd);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_frame_velo(ADSBFrame& frame){
 | 
			
		||||
		velo = decode_frame_velo(frame);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_info_string(std::string& new_info_string) {
 | 
			
		||||
		info_string = new_info_string;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_time_string(std::string& new_time_string) {
 | 
			
		||||
		time_string = new_time_string;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void reset_age() {
 | 
			
		||||
		age = 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void inc_age() {
 | 
			
		||||
		age++;
 | 
			
		||||
		if (age < ADSB_DECAY_A)
 | 
			
		||||
		{
 | 
			
		||||
			age_state = pos.valid ? 0 : 1;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			age_state = (age < ADSB_DECAY_B) ? 2 : 3;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using AircraftRecentEntries = RecentEntries<AircraftRecentEntry>;
 | 
			
		||||
 | 
			
		||||
class ADSBLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::filesystem::path& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	void log_str(std::string& logline);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ADSBRxAircraftDetailsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBRxAircraftDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
 | 
			
		||||
	~ADSBRxAircraftDetailsView();
 | 
			
		||||
 | 
			
		||||
	ADSBRxAircraftDetailsView(const ADSBRxAircraftDetailsView&) = delete;
 | 
			
		||||
	ADSBRxAircraftDetailsView(ADSBRxAircraftDetailsView&&) = delete;
 | 
			
		||||
	ADSBRxAircraftDetailsView& operator=(const ADSBRxAircraftDetailsView&) = delete;
 | 
			
		||||
	ADSBRxAircraftDetailsView& operator=(ADSBRxAircraftDetailsView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void update(const AircraftRecentEntry& entry);
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "AC Details"; };
 | 
			
		||||
 | 
			
		||||
	AircraftRecentEntry get_current_entry() { return entry_copy; }
 | 
			
		||||
 | 
			
		||||
	std::database::AircraftDBRecord aircraft_record = {};
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	AircraftRecentEntry 		entry_copy { 0 };
 | 
			
		||||
	std::function<void(void)> 	on_close_ { };
 | 
			
		||||
	bool 				send_updates { false };
 | 
			
		||||
	std::database 			db = { };
 | 
			
		||||
	std::string 			icao_code = "";
 | 
			
		||||
	int 				return_code = 0;
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
	        { { 0 * 8, 1 * 16 }, "ICAO:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 2 * 16 }, "Registration:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 3 * 16 }, "Manufacturer:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 5 * 16 }, "Model:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 7 * 16 }, "Type:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 8 * 16 }, "Number of engines:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 9 * 16 }, "Engine type:", Color::light_grey() },
 | 
			
		||||
                { { 0 * 8, 11 * 16 }, "Owner:", Color::light_grey() },
 | 
			
		||||
                { { 0 * 8, 13 * 16 }, "Operator:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
        Text text_icao_address {
 | 
			
		||||
		{ 5 * 8, 1 * 16, 6 * 8, 16},
 | 
			
		||||
		"-"	
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
	Text text_registration {
 | 
			
		||||
		{ 13 * 8, 2 * 16, 8 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_manufacturer {
 | 
			
		||||
		{ 0 * 8, 4 * 16, 19 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_model {
 | 
			
		||||
		{ 0 * 8, 6 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_type {
 | 
			
		||||
		{ 5 * 8, 7 * 16, 22 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_number_of_engines {
 | 
			
		||||
		{ 18 * 8, 8 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_engine_type {
 | 
			
		||||
		{ 0 * 8, 10 * 16, 30 * 8, 16},
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_owner {
 | 
			
		||||
		{ 0 * 8, 12 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_operator {
 | 
			
		||||
		{ 0 * 8, 14 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_close {
 | 
			
		||||
		{ 9 * 8, 16 * 16, 12 * 8, 3 * 16 },
 | 
			
		||||
		"Back"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ADSBRxDetailsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
 | 
			
		||||
	~ADSBRxDetailsView();
 | 
			
		||||
 | 
			
		||||
	ADSBRxDetailsView(const ADSBRxDetailsView&) = delete;
 | 
			
		||||
	ADSBRxDetailsView(ADSBRxDetailsView&&) = delete;
 | 
			
		||||
	ADSBRxDetailsView& operator=(const ADSBRxDetailsView&) = delete;
 | 
			
		||||
	ADSBRxDetailsView& operator=(ADSBRxDetailsView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void update(const AircraftRecentEntry& entry);
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Details"; };
 | 
			
		||||
 | 
			
		||||
	AircraftRecentEntry get_current_entry() { return entry_copy; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	std::database::AirlinesDBRecord airline_record = {};
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	AircraftRecentEntry 		entry_copy { 0 };
 | 
			
		||||
	std::function<void(void)> 	on_close_ { };
 | 
			
		||||
	GeoMapView* 			geomap_view { nullptr };
 | 
			
		||||
	ADSBRxAircraftDetailsView* 	aircraft_details_view { nullptr };
 | 
			
		||||
	bool 				send_updates { false };
 | 
			
		||||
	std::database 			db = { };	
 | 
			
		||||
	std::string 			airline_code = "";
 | 
			
		||||
	int 				return_code = 0;
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
	        { { 0 * 8, 1 * 16 }, "ICAO:", Color::light_grey() },
 | 
			
		||||
		{ { 13 * 8, 1 * 16 }, "Callsign:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 2 * 16 }, "Last seen:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 3 * 16 }, "Airline:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 5 * 16 }, "Country:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 13 * 16 }, "Even position frame:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 15 * 16 }, "Odd position frame:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
        Text text_icao_address {
 | 
			
		||||
		{ 5 * 8, 1 * 16, 6 * 8, 16},
 | 
			
		||||
		"-"	
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
	Text text_callsign {
 | 
			
		||||
		{ 22 * 8, 1 * 16, 8 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_last_seen {
 | 
			
		||||
		{ 11 * 8, 2 * 16, 19 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_airline {
 | 
			
		||||
		{ 0 * 8, 4 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_country {
 | 
			
		||||
		{ 8 * 8, 5 * 16, 22 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_infos {
 | 
			
		||||
		{ 0 * 8, 6 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_info2 {
 | 
			
		||||
		{0*8, 7*16, 30*8, 16},
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_frame_pos_even {
 | 
			
		||||
		{ 0 * 8, 14 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_frame_pos_odd {
 | 
			
		||||
		{ 0 * 8, 16 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_aircraft_details {
 | 
			
		||||
		{ 2 * 8, 9 * 16, 12 * 8, 3 * 16 },
 | 
			
		||||
		"A/C details" 
 | 
			
		||||
	};		
 | 
			
		||||
 | 
			
		||||
	Button button_see_map {
 | 
			
		||||
		{ 16 * 8, 9 * 16, 12 * 8, 3 * 16 },
 | 
			
		||||
		"See on map"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
class ADSBRxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBRxView(NavigationView& nav);
 | 
			
		||||
	~ADSBRxView();
 | 
			
		||||
	
 | 
			
		||||
	ADSBRxView(const ADSBRxView&) = delete;
 | 
			
		||||
	ADSBRxView(ADSBRxView&&) = delete;
 | 
			
		||||
	ADSBRxView& operator=(const ADSBRxView&) = delete;
 | 
			
		||||
	ADSBRxView& operator=(ADSBRxView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "ADS-B RX"; };
 | 
			
		||||
 | 
			
		||||
	void replace_entry(AircraftRecentEntry & entry);
 | 
			
		||||
	AircraftRecentEntry find_or_create_entry(uint32_t ICAO_address);
 | 
			
		||||
	void sort_entries_by_state();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	rf::Frequency prevFreq = { 0 };
 | 
			
		||||
	std::unique_ptr<ADSBLogger> logger { };
 | 
			
		||||
	void on_frame(const ADSBFrameMessage * message);
 | 
			
		||||
	void on_tick_second();
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
	
 | 
			
		||||
	const RecentEntriesColumns columns { {
 | 
			
		||||
#if false
 | 
			
		||||
		{ "ICAO", 6 },
 | 
			
		||||
		{ "Callsign", 9 },
 | 
			
		||||
		{ "Hits", 4 },
 | 
			
		||||
		{ "Time", 8 }
 | 
			
		||||
#else
 | 
			
		||||
		{ "ICAO/Call", 9 },
 | 
			
		||||
		{ "Lvl", 3 },
 | 
			
		||||
		{ "Spd", 3 },
 | 
			
		||||
		{ "Amp", 3 },
 | 
			
		||||
		{ "Hit", 3 },
 | 
			
		||||
		{ "Age", 4 }
 | 
			
		||||
#endif
 | 
			
		||||
	} };
 | 
			
		||||
	AircraftRecentEntries recent { };
 | 
			
		||||
	RecentEntriesView<RecentEntries<AircraftRecentEntry>> recent_entries_view { columns, recent };
 | 
			
		||||
	
 | 
			
		||||
	SignalToken signal_token_tick_second { };
 | 
			
		||||
	ADSBRxDetailsView* details_view { nullptr };
 | 
			
		||||
	uint32_t detailed_entry_key { 0 };
 | 
			
		||||
	bool send_updates { false };
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0 * 8, 0 * 8 }, "LNA:   VGA:   AMP:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 4 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 11 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 20 * 8, 4, 10 * 8, 8 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_frame {
 | 
			
		||||
		Message::ID::ADSBFrame,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const ADSBFrameMessage*>(p);
 | 
			
		||||
			this->on_frame(message);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,370 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_adsb_tx.hpp"
 | 
			
		||||
#include "ui_alphanum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "manchester.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace adsb;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
Compass::Compass(
 | 
			
		||||
	const Point parent_pos
 | 
			
		||||
) : Widget { { parent_pos, { 64, 64 } } }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Compass::set_value(uint32_t new_value) {
 | 
			
		||||
	Point center = screen_pos() + Point(32, 32);
 | 
			
		||||
	
 | 
			
		||||
	new_value = range.clip(new_value);
 | 
			
		||||
	
 | 
			
		||||
	display.draw_line(
 | 
			
		||||
		center,
 | 
			
		||||
		center + polar_to_point(value_, 28),
 | 
			
		||||
		Color::dark_grey()
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	display.draw_line(
 | 
			
		||||
		center,
 | 
			
		||||
		center + polar_to_point(new_value, 28),
 | 
			
		||||
		Color::green()
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	value_ = new_value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Compass::paint(Painter&) {
 | 
			
		||||
	display.fill_circle(screen_pos() + Point(32, 32), 32, Color::dark_grey(), Color::black());
 | 
			
		||||
	
 | 
			
		||||
	display.fill_rectangle({ screen_pos() + Point(32 - 2, 0), { 4, 4 } }, Color::black());	// N
 | 
			
		||||
	display.fill_rectangle({ screen_pos() + Point(32 - 2, 64 - 4), { 4, 4 } }, Color::black());	// S
 | 
			
		||||
	display.fill_rectangle({ screen_pos() + Point(0, 32 - 2), { 4, 4 } }, Color::black());	// W
 | 
			
		||||
	display.fill_rectangle({ screen_pos() + Point(64 - 4, 32 - 2), { 4, 4 } }, Color::black());	// E
 | 
			
		||||
	
 | 
			
		||||
	set_value(value_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBPositionView::ADSBPositionView(
 | 
			
		||||
	NavigationView& nav, Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("position");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&geopos,
 | 
			
		||||
		&button_set_map
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	geopos.set_altitude(36000);
 | 
			
		||||
	
 | 
			
		||||
	button_set_map.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.push<GeoMapView>(
 | 
			
		||||
			geopos.altitude(),
 | 
			
		||||
			GeoPos::alt_unit::FEET,
 | 
			
		||||
			geopos.lat(),
 | 
			
		||||
			geopos.lon(),
 | 
			
		||||
			[this](int32_t altitude, float lat, float lon) {
 | 
			
		||||
				geopos.set_altitude(altitude);
 | 
			
		||||
				geopos.set_lat(lat);
 | 
			
		||||
				geopos.set_lon(lon);
 | 
			
		||||
			});
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
 | 
			
		||||
	if (!enabled) return;
 | 
			
		||||
	
 | 
			
		||||
	ADSBFrame temp_frame;
 | 
			
		||||
	
 | 
			
		||||
	encode_frame_pos(temp_frame, ICAO_address, geopos.altitude(),
 | 
			
		||||
		geopos.lat(), geopos.lon(), 0);
 | 
			
		||||
	
 | 
			
		||||
	frame_list.emplace_back(temp_frame);
 | 
			
		||||
	
 | 
			
		||||
	encode_frame_pos(temp_frame, ICAO_address, geopos.altitude(),
 | 
			
		||||
		geopos.lat(), geopos.lon(), 1);
 | 
			
		||||
		
 | 
			
		||||
	frame_list.emplace_back(temp_frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBCallsignView::ADSBCallsignView(
 | 
			
		||||
	NavigationView& nav, Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("callsign");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels_callsign,
 | 
			
		||||
		&button_callsign
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	set_enabled(true);
 | 
			
		||||
	
 | 
			
		||||
	button_callsign.set_text(callsign);
 | 
			
		||||
	
 | 
			
		||||
	button_callsign.on_select = [this, &nav](Button&) {
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			callsign,
 | 
			
		||||
			8,
 | 
			
		||||
			[this](std::string& s) {
 | 
			
		||||
				button_callsign.set_text(s);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBCallsignView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
 | 
			
		||||
	if (!enabled) return;
 | 
			
		||||
	
 | 
			
		||||
	ADSBFrame temp_frame;
 | 
			
		||||
	
 | 
			
		||||
	encode_frame_id(temp_frame, ICAO_address, callsign);
 | 
			
		||||
	
 | 
			
		||||
	frame_list.emplace_back(temp_frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBSpeedView::ADSBSpeedView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("speed");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels_speed,
 | 
			
		||||
		&compass,
 | 
			
		||||
		&field_angle,
 | 
			
		||||
		&field_speed
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	field_angle.set_value(0);
 | 
			
		||||
	field_speed.set_value(400);
 | 
			
		||||
	
 | 
			
		||||
	field_angle.on_change = [this](int32_t v) {
 | 
			
		||||
		compass.set_value(v);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
 | 
			
		||||
	if (!enabled) return;
 | 
			
		||||
	
 | 
			
		||||
	ADSBFrame temp_frame;
 | 
			
		||||
	
 | 
			
		||||
	encode_frame_velo(temp_frame, ICAO_address, field_speed.value(),
 | 
			
		||||
		field_angle.value(), 0);	// TODO: v_rate
 | 
			
		||||
	
 | 
			
		||||
	frame_list.emplace_back(temp_frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBSquawkView::ADSBSquawkView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("squawk");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels_squawk,
 | 
			
		||||
		&field_squawk
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
 | 
			
		||||
	if (!enabled) return;
 | 
			
		||||
	
 | 
			
		||||
	ADSBFrame temp_frame;
 | 
			
		||||
	(void)ICAO_address;
 | 
			
		||||
	
 | 
			
		||||
	encode_frame_squawk(temp_frame, field_squawk.value_dec_u32());
 | 
			
		||||
	
 | 
			
		||||
	frame_list.emplace_back(temp_frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBTXThread::ADSBTXThread(
 | 
			
		||||
	std::vector<ADSBFrame> frames
 | 
			
		||||
) : frames_ {  std::move(frames) }
 | 
			
		||||
{
 | 
			
		||||
	thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ADSBTXThread::static_fn, this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBTXThread::~ADSBTXThread() {
 | 
			
		||||
	if( thread ) {
 | 
			
		||||
		chThdTerminate(thread);
 | 
			
		||||
		chThdWait(thread);
 | 
			
		||||
		thread = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
msg_t ADSBTXThread::static_fn(void* arg) {
 | 
			
		||||
	auto obj = static_cast<ADSBTXThread*>(arg);
 | 
			
		||||
	obj->run();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBTXThread::run() {
 | 
			
		||||
	uint8_t * bin_ptr = shared_memory.bb_data.data;
 | 
			
		||||
	uint8_t * raw_ptr;
 | 
			
		||||
	uint32_t frame_index = 0;	//, plane_index = 0;
 | 
			
		||||
	//uint32_t regen = 0;
 | 
			
		||||
	//float offs = 0;
 | 
			
		||||
	
 | 
			
		||||
	while( !chThdShouldTerminate() ) {
 | 
			
		||||
		/*if (!regen) {
 | 
			
		||||
			regen = 10;
 | 
			
		||||
			
 | 
			
		||||
			encode_frame_id(frames[0], plane_index, "DEMO" + to_string_dec_uint(plane_index));
 | 
			
		||||
			encode_frame_pos(frames[1], plane_index, 5000, plane_lats[plane_index]/8 + offs + 38.5, plane_lons[plane_index]/8 + 125.8, 0);
 | 
			
		||||
			encode_frame_pos(frames[2], plane_index, 5000, plane_lats[plane_index]/8 + offs + 38.5, plane_lons[plane_index]/8 + 125.8, 1);
 | 
			
		||||
			encode_frame_identity(frames[3], plane_index, 1337);
 | 
			
		||||
			
 | 
			
		||||
			if (plane_index == 11)
 | 
			
		||||
				plane_index = 0;
 | 
			
		||||
			else
 | 
			
		||||
				plane_index++;
 | 
			
		||||
			
 | 
			
		||||
			offs += 0.001;
 | 
			
		||||
		}*/
 | 
			
		||||
		
 | 
			
		||||
		memset(bin_ptr, 0, 256);	// 112 bits * 2 parts = 224 should be enough
 | 
			
		||||
		
 | 
			
		||||
		raw_ptr = frames_[frame_index].get_raw_data();
 | 
			
		||||
		
 | 
			
		||||
		// The preamble isn't manchester encoded
 | 
			
		||||
		memcpy(bin_ptr, adsb_preamble, 16);
 | 
			
		||||
		
 | 
			
		||||
		// Convert to binary (1 byte per bit, faster for baseband code)
 | 
			
		||||
		manchester_encode(bin_ptr + 16, raw_ptr, 112, 0);
 | 
			
		||||
	
 | 
			
		||||
		// Display in hex for debug
 | 
			
		||||
		//text_frame.set(to_string_hex_array(frames[0].get_raw_data(), 14));
 | 
			
		||||
	
 | 
			
		||||
		baseband::set_adsb();
 | 
			
		||||
		
 | 
			
		||||
		chThdSleepMilliseconds(50);
 | 
			
		||||
		
 | 
			
		||||
		frame_index++;
 | 
			
		||||
		if (frame_index >= frames_.size()) {
 | 
			
		||||
			frame_index = 0;
 | 
			
		||||
			//if (regen)
 | 
			
		||||
			//	regen--;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBTxView::focus() {
 | 
			
		||||
	tab_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBTxView::~ADSBTxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_adsb", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBTxView::generate_frames() {
 | 
			
		||||
	const uint32_t ICAO_address = sym_icao.value_hex_u64();
 | 
			
		||||
	
 | 
			
		||||
	frames.clear();
 | 
			
		||||
	
 | 
			
		||||
	/* This scheme kinda sucks. Each "tab"'s collect_frames method
 | 
			
		||||
	 * is called to generate its related frame(s). Getting values
 | 
			
		||||
	 * from each widget of each tab would be better ?
 | 
			
		||||
	 * */
 | 
			
		||||
	view_position.collect_frames(ICAO_address, frames);
 | 
			
		||||
	view_callsign.collect_frames(ICAO_address, frames);
 | 
			
		||||
	view_speed.collect_frames(ICAO_address, frames);
 | 
			
		||||
	view_squawk.collect_frames(ICAO_address, frames);
 | 
			
		||||
	
 | 
			
		||||
	// Show how many frames were generated
 | 
			
		||||
	//text_frame.set(to_string_dec_uint(frames.size()) + " frame(s).");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADSBTxView::start_tx() {
 | 
			
		||||
	generate_frames();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(4000000U);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(10000000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_adsb();
 | 
			
		||||
	
 | 
			
		||||
	tx_thread = std::make_unique<ADSBTXThread>(frames);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADSBTxView::ADSBTxView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ { nav }
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_adsb_tx);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&labels,
 | 
			
		||||
		&sym_icao,
 | 
			
		||||
		&view_position,
 | 
			
		||||
		&view_callsign,
 | 
			
		||||
		&view_speed,
 | 
			
		||||
		&view_squawk,
 | 
			
		||||
		&text_frame,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});	
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_adsb", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		start_tx();
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,243 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "adsb.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "ui_geomap.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace adsb;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class Compass : public Widget {
 | 
			
		||||
public:
 | 
			
		||||
	Compass(const Point parent_pos);
 | 
			
		||||
 | 
			
		||||
	void set_value(uint32_t new_value);
 | 
			
		||||
 | 
			
		||||
	void paint(Painter&) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const range_t<uint32_t> range { 0, 359 };
 | 
			
		||||
	uint32_t value_ { 0 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBPositionView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBPositionView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	GeoPos geopos {
 | 
			
		||||
		{ 0, 2 * 16 },
 | 
			
		||||
		GeoPos::FEET
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_set_map {
 | 
			
		||||
		{ 8 * 8, 6 * 16, 14 * 8, 2 * 16 },
 | 
			
		||||
		"Set from map"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBCallsignView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBCallsignView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::string callsign = "TEST1234";
 | 
			
		||||
	
 | 
			
		||||
	Labels labels_callsign {
 | 
			
		||||
		{ { 2 * 8, 5 * 8 }, "Callsign:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_callsign {
 | 
			
		||||
		{ 12 * 8, 2 * 16, 10 * 8, 2 * 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBSpeedView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBSpeedView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels_speed {
 | 
			
		||||
		{ { 1 * 8, 6 * 16 }, "Speed:    kn  Bearing:    *", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Compass compass {
 | 
			
		||||
		{ 21 * 8, 2 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_angle {
 | 
			
		||||
		{ 21 * 8 + 20, 6 * 16 }, 3, { 0, 359 }, 1, ' ', true
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_speed {
 | 
			
		||||
		{ 8 * 8, 6 * 16 }, 3, { 0, 999 }, 5, ' '
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBSquawkView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBSquawkView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels_squawk {
 | 
			
		||||
		{ { 2 * 8, 2 * 16 }, "Squawk:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField field_squawk {
 | 
			
		||||
		{ 10 * 8, 2 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		SymField::SYMFIELD_OCT
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBTXThread {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBTXThread(std::vector<ADSBFrame> frames);
 | 
			
		||||
	~ADSBTXThread();
 | 
			
		||||
 | 
			
		||||
	ADSBTXThread(const ADSBTXThread&) = delete;
 | 
			
		||||
	ADSBTXThread(ADSBTXThread&&) = delete;
 | 
			
		||||
	ADSBTXThread& operator=(const ADSBTXThread&) = delete;
 | 
			
		||||
	ADSBTXThread& operator=(ADSBTXThread&&) = delete;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<ADSBFrame> frames_ { };
 | 
			
		||||
	Thread* thread { nullptr };
 | 
			
		||||
 | 
			
		||||
	static msg_t static_fn(void* arg);
 | 
			
		||||
	
 | 
			
		||||
	void run();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADSBTxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ADSBTxView(NavigationView& nav);
 | 
			
		||||
	~ADSBTxView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "ADS-B TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	/*enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SEQUENCE
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	/*const float plane_lats[12] = {
 | 
			
		||||
		0,
 | 
			
		||||
		-1,
 | 
			
		||||
		-2,
 | 
			
		||||
		-3,
 | 
			
		||||
		-4,
 | 
			
		||||
		-5,
 | 
			
		||||
		-4.5,
 | 
			
		||||
		-5,
 | 
			
		||||
		-4,
 | 
			
		||||
		-3,
 | 
			
		||||
		-2,
 | 
			
		||||
		-1
 | 
			
		||||
	};
 | 
			
		||||
	const float plane_lons[12] = {
 | 
			
		||||
		0,
 | 
			
		||||
		1,
 | 
			
		||||
		1,
 | 
			
		||||
		1,
 | 
			
		||||
		2,
 | 
			
		||||
		1,
 | 
			
		||||
		0,
 | 
			
		||||
		-1,
 | 
			
		||||
		-2,
 | 
			
		||||
		-1,
 | 
			
		||||
		-1,
 | 
			
		||||
		-1
 | 
			
		||||
	};*/
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
	
 | 
			
		||||
	//tx_modes tx_mode = IDLE;
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	std::vector<ADSBFrame> frames { };
 | 
			
		||||
	
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void generate_frames();
 | 
			
		||||
	
 | 
			
		||||
	Rect view_rect = { 0, 7 * 8, 240, 192 };
 | 
			
		||||
	
 | 
			
		||||
	ADSBPositionView view_position { nav_, view_rect };
 | 
			
		||||
	ADSBCallsignView view_callsign { nav_, view_rect };
 | 
			
		||||
	ADSBSpeedView view_speed { view_rect };
 | 
			
		||||
	ADSBSquawkView view_squawk { view_rect };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Position", Color::cyan(), &view_position },
 | 
			
		||||
		{ "Callsign", Color::green(), &view_callsign },
 | 
			
		||||
		{ "Speed", Color::yellow(), &view_speed },
 | 
			
		||||
		{ "Squawk", Color::orange(), &view_squawk }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 4 * 8 }, "ICAO24:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_icao {
 | 
			
		||||
		{ 10 * 8, 4 * 8 },
 | 
			
		||||
		6,
 | 
			
		||||
		SymField::SYMFIELD_HEX
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_frame {
 | 
			
		||||
		{ 1 * 8, 29 * 8, 14 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		1000000,
 | 
			
		||||
		0
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<ADSBTXThread> tx_thread { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,186 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_afsk_rx.hpp"
 | 
			
		||||
#include "ui_modemsetup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "modems.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace modems;
 | 
			
		||||
 | 
			
		||||
void AFSKLogger::log_raw_data(const std::string& data) {
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(datetime, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void AFSKRxView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AFSKRxView::update_freq(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AFSKRxView::AFSKRxView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&text_debug,
 | 
			
		||||
		&button_modem_setup,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_afsk", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
	record_view.set_sampling_rate(24000);
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	update_freq(467225500);	// 462713300
 | 
			
		||||
	auto def_bell202 = &modem_defs[0];
 | 
			
		||||
	persistent_memory::set_modem_baudrate(def_bell202->baudrate);
 | 
			
		||||
	serial_format_t serial_format;
 | 
			
		||||
	serial_format.data_bits = 7;
 | 
			
		||||
	serial_format.parity = EVEN;
 | 
			
		||||
	serial_format.stop_bits = 1;
 | 
			
		||||
	serial_format.bit_order = LSB_FIRST;
 | 
			
		||||
	persistent_memory::set_serial_format(serial_format);
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(100);
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_modem_setup.on_select = [&nav](Button&) {
 | 
			
		||||
		nav.push<ModemSetupView>();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	logger = std::make_unique<AFSKLogger>();
 | 
			
		||||
	if (logger)
 | 
			
		||||
		logger->append("AFSK_LOG.TXT");
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	baseband::set_afsk(persistent_memory::modem_baudrate(), 8, 0, false);
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AFSKRxView::on_data(uint32_t value, bool is_data) {
 | 
			
		||||
	std::string str_console = "\x1B";
 | 
			
		||||
	std::string str_byte = "";
 | 
			
		||||
	
 | 
			
		||||
	if (is_data) {
 | 
			
		||||
		// Colorize differently after message splits
 | 
			
		||||
		str_console += (char)((console_color & 3) + 9);
 | 
			
		||||
		
 | 
			
		||||
		//value = deframe_word(value);
 | 
			
		||||
		
 | 
			
		||||
		value &= 0xFF;											// ABCDEFGH
 | 
			
		||||
		value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);	// EFGHABCD
 | 
			
		||||
		value = ((value & 0xCC) >> 2) | ((value & 0x33) << 2);	// GHEFCDAB
 | 
			
		||||
		value = ((value & 0xAA) >> 1) | ((value & 0x55) << 1);	// HGFEDCBA
 | 
			
		||||
		value &= 0x7F;											// Ignore parity, which is the MSB now
 | 
			
		||||
		
 | 
			
		||||
		if ((value >= 32) && (value < 127)) {
 | 
			
		||||
			str_console += (char)value;							// Printable
 | 
			
		||||
			str_byte += (char)value;
 | 
			
		||||
		} else {
 | 
			
		||||
			str_console += "[" + to_string_hex(value, 2) + "]";	// Not printable
 | 
			
		||||
			str_byte += "[" + to_string_hex(value, 2) + "]";
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		//str_byte = to_string_bin(value & 0xFF, 8) + "  ";
 | 
			
		||||
		
 | 
			
		||||
		console.write(str_console);
 | 
			
		||||
		
 | 
			
		||||
		if (logger) str_log += str_byte;
 | 
			
		||||
		
 | 
			
		||||
		if ((value != 0x7F) && (prev_value == 0x7F)) {
 | 
			
		||||
			// Message split
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
			console_color++;
 | 
			
		||||
			
 | 
			
		||||
			if (logger) {
 | 
			
		||||
				logger->log_raw_data(str_log);
 | 
			
		||||
				str_log = "";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		prev_value = value;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Baudrate estimation
 | 
			
		||||
		text_debug.set("~" + to_string_dec_uint(value));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AFSKRxView::~AFSKRxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_afsk", &app_settings);
 | 
			
		||||
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,125 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_AFSK_RX_H__
 | 
			
		||||
#define __UI_AFSK_RX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"	// DEBUG
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
class AFSKLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::string& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void log_raw_data(const std::string& data);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class AFSKRxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	AFSKRxView(NavigationView& nav);
 | 
			
		||||
	~AFSKRxView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "AFSK RX (beta)"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void on_data(uint32_t value, bool is_data);
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	uint8_t console_color { 0 };
 | 
			
		||||
	uint32_t prev_value { 0 };
 | 
			
		||||
	std::string str_log { "" };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_debug {
 | 
			
		||||
		{ 0 * 8, 1 * 16, 10 * 8, 16 },
 | 
			
		||||
		"DEBUG"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	Button button_modem_setup {
 | 
			
		||||
		{ 12 * 8, 1 * 16, 96, 24 },
 | 
			
		||||
		"Modem setup"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"AFS_????", RecordView::FileType::WAV, 4096, 4
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 4 * 16, 240, 240 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
	void on_data_afsk(const AFSKDataMessage& message);
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<AFSKLogger> logger { };
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::AFSKData,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const AFSKDataMessage*>(p);
 | 
			
		||||
			this->on_data(message->value, message->is_data);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_AFSK_RX_H__*/
 | 
			
		||||
@@ -0,0 +1,422 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_aprs_rx.hpp"
 | 
			
		||||
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
void APRSLogger::log_raw_data(const std::string& data) {
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	
 | 
			
		||||
	log_file.write_entry(datetime, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<APRSRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	char aged_color;
 | 
			
		||||
	Color target_color;
 | 
			
		||||
	// auto entry_age = entry.age;
 | 
			
		||||
	
 | 
			
		||||
	target_color = Color::green();
 | 
			
		||||
 | 
			
		||||
	aged_color = 0x10;
 | 
			
		||||
	std::string entry_string = "\x1B";
 | 
			
		||||
	entry_string += aged_color;
 | 
			
		||||
	
 | 
			
		||||
	entry_string += entry.source_formatted;
 | 
			
		||||
	entry_string.append(10 - entry.source_formatted.size(),' ');
 | 
			
		||||
	entry_string += "       ";
 | 
			
		||||
	entry_string += (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+");
 | 
			
		||||
	entry_string += " ";
 | 
			
		||||
	entry_string += entry.time_string;
 | 
			
		||||
	
 | 
			
		||||
	painter.draw_string(
 | 
			
		||||
		target_rect.location(),
 | 
			
		||||
		style,
 | 
			
		||||
		entry_string
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	if (entry.has_position){
 | 
			
		||||
		painter.draw_bitmap(target_rect.location() + Point(12 * 8, 0), bitmap_target, target_color, style.background);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void APRSRxView::focus() {
 | 
			
		||||
	options_region.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSRxView::update_freq(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect) : View(parent_rect) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_aprs_rx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&options_region,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_aprs", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	record_view.set_sampling_rate(24000);
 | 
			
		||||
 | 
			
		||||
	options_region.on_change = [this](size_t, int32_t i) {		
 | 
			
		||||
		if (i == 0){
 | 
			
		||||
			field_frequency.set_value(144390000);			
 | 
			
		||||
		} else if(i == 1){
 | 
			
		||||
			field_frequency.set_value(144800000);
 | 
			
		||||
		} else if(i == 2){
 | 
			
		||||
			field_frequency.set_value(145175000);
 | 
			
		||||
		} else if(i == 3){
 | 
			
		||||
			field_frequency.set_value(144575000);
 | 
			
		||||
		}		
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(100);
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_region.set_selected_index(0, true);
 | 
			
		||||
	
 | 
			
		||||
	logger = std::make_unique<APRSLogger>();
 | 
			
		||||
	if (logger)
 | 
			
		||||
		logger->append("APRS_RX_LOG.TXT");
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_aprs(1200);
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSRxView::on_packet(const APRSPacketMessage* message){
 | 
			
		||||
	std::string str_console = "\x1B";
 | 
			
		||||
 | 
			
		||||
	aprs::APRSPacket packet = message->packet;
 | 
			
		||||
 | 
			
		||||
	std::string stream_text = packet.get_stream_text();
 | 
			
		||||
	str_console += (char)((console_color++ & 3) + 9);		
 | 
			
		||||
	str_console += stream_text;
 | 
			
		||||
 | 
			
		||||
	if(logger){
 | 
			
		||||
		logger->log_raw_data(stream_text);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	//if(reset_console){ //having more than one console causes issues when switching tabs where one is disabled, and the other enabled breaking the scoll setup.
 | 
			
		||||
	//	console.on_hide();
 | 
			
		||||
	//	console.on_show();
 | 
			
		||||
	//	reset_console = false;
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	console.writeln(str_console);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSRxView::on_data(uint32_t value, bool is_data) {
 | 
			
		||||
	std::string str_console = "\x1B";
 | 
			
		||||
	std::string str_byte = "";
 | 
			
		||||
	
 | 
			
		||||
	if (is_data) {
 | 
			
		||||
		// Colorize differently after message splits
 | 
			
		||||
		str_console += (char)((console_color & 3) + 9);		
 | 
			
		||||
 | 
			
		||||
		if (value == '\n') {
 | 
			
		||||
			// Message split
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
			console_color++;
 | 
			
		||||
			
 | 
			
		||||
			if (logger) {
 | 
			
		||||
				logger->log_raw_data(str_log);
 | 
			
		||||
				str_log = "";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			if ((value >= 32) && (value < 127)) {
 | 
			
		||||
				str_console += (char)value;							// Printable
 | 
			
		||||
				str_byte += (char)value;
 | 
			
		||||
			} else {
 | 
			
		||||
				str_console += "[" + to_string_hex(value, 2) + "]";	// Not printable
 | 
			
		||||
				str_byte += "[" + to_string_hex(value, 2) + "]";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			console.write(str_console);
 | 
			
		||||
		
 | 
			
		||||
			if (logger) str_log += str_byte;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSRxView::on_show(){
 | 
			
		||||
	//some bug where the display scroll area is set to the entire screen when switching from the list tab with details showing back to the stream view.
 | 
			
		||||
	//reset_console = true;	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSRxView::~APRSRxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	settings.save("rx_aprs", &app_settings);
 | 
			
		||||
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::on_show_list() {
 | 
			
		||||
	details_view.hidden(true);
 | 
			
		||||
	recent_entries_view.hidden(false);	
 | 
			
		||||
	send_updates = false;
 | 
			
		||||
	focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::on_show_detail(const APRSRecentEntry& entry) {
 | 
			
		||||
	recent_entries_view.hidden(true);
 | 
			
		||||
	details_view.hidden(false);
 | 
			
		||||
	details_view.set_entry(entry);
 | 
			
		||||
	details_view.update();
 | 
			
		||||
	details_view.focus();
 | 
			
		||||
	detailed_entry_key = entry.key();
 | 
			
		||||
	send_updates = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec) : View(parent_rec),  nav_ {nav}  {
 | 
			
		||||
	add_children({		
 | 
			
		||||
		&recent_entries_view,
 | 
			
		||||
		&details_view
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	hidden(true);
 | 
			
		||||
 | 
			
		||||
	details_view.hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	recent_entries_view.set_parent_rect({0, 0, 240, 280});
 | 
			
		||||
	details_view.set_parent_rect({0, 0, 240, 280});
 | 
			
		||||
	
 | 
			
		||||
	recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
 | 
			
		||||
		this->on_show_detail(entry);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	details_view.on_close = [this]() {
 | 
			
		||||
		this->on_show_list();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*	for(size_t i = 0; i <32 ; i++){
 | 
			
		||||
		std::string id = "test" + i;
 | 
			
		||||
		auto& entry = ::on_packet(recent, i);			
 | 
			
		||||
		entry.set_source_formatted(id);	
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recent_entries_view.set_dirty(); 	*/
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
 | 
			
		||||
	std::string str1 = "test1";
 | 
			
		||||
	std::string str12 = "test2";
 | 
			
		||||
	std::string str13 = "test2";
 | 
			
		||||
 | 
			
		||||
	auto& entry = ::on_packet(recent, 0x1);			
 | 
			
		||||
	entry.set_source_formatted(str1);	
 | 
			
		||||
 | 
			
		||||
	auto& entry2 = ::on_packet(recent, 0x2);			
 | 
			
		||||
	entry2.set_source_formatted(str12);	
 | 
			
		||||
 | 
			
		||||
	auto& entry3 = ::on_packet(recent, 0x2);			
 | 
			
		||||
	entry2.set_source_formatted(str13);	
 | 
			
		||||
 | 
			
		||||
	recent_entries_view.set_dirty(); 	
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::on_pkt(const APRSPacketMessage* message){
 | 
			
		||||
	aprs::APRSPacket packet = message->packet;
 | 
			
		||||
	rtc::RTC datetime;	
 | 
			
		||||
	std::string str_timestamp;
 | 
			
		||||
	std::string source_formatted = packet.get_source_formatted();
 | 
			
		||||
	std::string info_string = packet.get_stream_text();
 | 
			
		||||
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	auto& entry = ::on_packet(recent, packet.get_source());		
 | 
			
		||||
	entry.reset_age();
 | 
			
		||||
	entry.inc_hit();
 | 
			
		||||
	str_timestamp = to_string_datetime(datetime, HMS);
 | 
			
		||||
	entry.set_time_string(str_timestamp);
 | 
			
		||||
	entry.set_info_string(info_string);
 | 
			
		||||
 | 
			
		||||
	entry.set_source_formatted(source_formatted);
 | 
			
		||||
 | 
			
		||||
	if(entry.has_position && !packet.has_position()){ 
 | 
			
		||||
		//maintain position info
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		entry.set_has_position(packet.has_position());
 | 
			
		||||
		entry.set_pos(packet.get_position());
 | 
			
		||||
	}	
 | 
			
		||||
 | 
			
		||||
	if( entry.key() == details_view.entry().key() ) {
 | 
			
		||||
		details_view.set_entry(entry);
 | 
			
		||||
		details_view.update();		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recent_entries_view.set_dirty(); 			
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::on_show(){
 | 
			
		||||
	on_show_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::on_hide(){
 | 
			
		||||
	details_view.hidden(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTableView::focus(){
 | 
			
		||||
	recent_entries_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSTableView::~APRSTableView(){
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSDetailsView::focus() {
 | 
			
		||||
	button_done.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSDetailsView::set_entry(const APRSRecentEntry& entry){
 | 
			
		||||
	entry_copy = entry;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSDetailsView::update() {	
 | 
			
		||||
	if(!hidden()){		
 | 
			
		||||
		//uint32_t age = entry_copy.age;
 | 
			
		||||
		
 | 
			
		||||
		console.clear(true);
 | 
			
		||||
		console.write(entry_copy.info_string);
 | 
			
		||||
 | 
			
		||||
		button_see_map.hidden(!entry_copy.has_position);
 | 
			
		||||
	}	
 | 
			
		||||
	
 | 
			
		||||
	if (send_updates)
 | 
			
		||||
		geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSDetailsView::~APRSDetailsView() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSDetailsView::APRSDetailsView(
 | 
			
		||||
	NavigationView& nav	
 | 
			
		||||
) 
 | 
			
		||||
{	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&console,
 | 
			
		||||
		&button_done,
 | 
			
		||||
		&button_see_map
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_done.on_select = [this, &nav](Button&) {
 | 
			
		||||
		console.clear(true);
 | 
			
		||||
		this->on_close();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_see_map.on_select = [this, &nav](Button&) {
 | 
			
		||||
		geomap_view = nav.push<GeoMapView>(
 | 
			
		||||
			entry_copy.source_formatted,			
 | 
			
		||||
			0, //entry_copy.pos.altitude,
 | 
			
		||||
			GeoPos::alt_unit::FEET,
 | 
			
		||||
			entry_copy.pos.latitude,
 | 
			
		||||
			entry_copy.pos.longitude,			
 | 
			
		||||
			0, /*entry_copy.velo.heading,*/
 | 
			
		||||
			[this]() {
 | 
			
		||||
				send_updates = false;
 | 
			
		||||
				hidden(false);
 | 
			
		||||
				update();
 | 
			
		||||
			});		
 | 
			
		||||
		send_updates = true;
 | 
			
		||||
		hidden(true);
 | 
			
		||||
		
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
APRSRXView::APRSRXView(NavigationView& nav) : nav_ {nav} {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&view_stream,
 | 
			
		||||
		&view_table
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSRXView::focus(){
 | 
			
		||||
	tab_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSRXView::~APRSRXView() {
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,283 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_APRS_RX_H__
 | 
			
		||||
#define __UI_APRS_RX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"	// DEBUG
 | 
			
		||||
#include "ui_geomap.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
 | 
			
		||||
#include "log_file.hpp"
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
class APRSLogger {
 | 
			
		||||
public:
 | 
			
		||||
	Optional<File::Error> append(const std::string& filename) {
 | 
			
		||||
		return log_file.append(filename);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void log_raw_data(const std::string& data);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	LogFile log_file { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
struct APRSRecentEntry {
 | 
			
		||||
	using Key = uint64_t;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr Key invalid_key = 0xffffffffffffffff;
 | 
			
		||||
	
 | 
			
		||||
	uint16_t hits { 0 };
 | 
			
		||||
	uint32_t age { 0 };	
 | 
			
		||||
	
 | 
			
		||||
	uint64_t source { 0 };
 | 
			
		||||
	std::string source_formatted { "        " };
 | 
			
		||||
	std::string time_string { "" };
 | 
			
		||||
	std::string info_string { "" };
 | 
			
		||||
	
 | 
			
		||||
	aprs::aprs_pos pos { 0 , 0 , 0 , 0 };
 | 
			
		||||
	bool has_position = false;
 | 
			
		||||
	APRSRecentEntry(uint64_t src)
 | 
			
		||||
	{
 | 
			
		||||
		source = src;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return source;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_source_formatted(std::string& new_source) {
 | 
			
		||||
		source_formatted = new_source;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void inc_hit() {
 | 
			
		||||
		hits++;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_info_string(std::string& new_info_string) {
 | 
			
		||||
		info_string = new_info_string;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_time_string(std::string& new_time_string) {
 | 
			
		||||
		time_string = new_time_string;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_pos(aprs::aprs_pos pos_in){
 | 
			
		||||
		pos = pos_in;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_has_position(bool has_pos){
 | 
			
		||||
		has_position = has_pos;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void reset_age() {
 | 
			
		||||
		age = 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void inc_age() {
 | 
			
		||||
		age++;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APRSDetailsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	APRSDetailsView(NavigationView&);
 | 
			
		||||
	~APRSDetailsView();
 | 
			
		||||
 | 
			
		||||
	APRSDetailsView(const APRSDetailsView&) = delete;
 | 
			
		||||
	APRSDetailsView(APRSDetailsView&&) = delete;
 | 
			
		||||
	APRSDetailsView& operator=(const APRSDetailsView&) = delete;
 | 
			
		||||
	APRSDetailsView& operator=(APRSDetailsView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void update();			
 | 
			
		||||
	void set_entry(const APRSRecentEntry& entry);
 | 
			
		||||
 | 
			
		||||
	const APRSRecentEntry& entry() const { return entry_copy; };
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Details"; };
 | 
			
		||||
	std::function<void(void)> on_close { };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	APRSRecentEntry entry_copy { 0 };	
 | 
			
		||||
	GeoMapView* geomap_view { nullptr };
 | 
			
		||||
	bool send_updates { false };
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 0 * 16, 240, 224 }		
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 160, 14 * 16, 8 * 8, 3 * 16 },
 | 
			
		||||
		"Close"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_see_map {
 | 
			
		||||
		{ 80, 14 * 16, 8 * 8, 3 * 16 },
 | 
			
		||||
		"Map"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using APRSRecentEntries = RecentEntries<APRSRecentEntry>;
 | 
			
		||||
 | 
			
		||||
class APRSTableView: public View {
 | 
			
		||||
public:
 | 
			
		||||
	APRSTableView(NavigationView& nav, Rect parent_rec);
 | 
			
		||||
	~APRSTableView();
 | 
			
		||||
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void on_pkt(const APRSPacketMessage* message);
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Stations"; };
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	const RecentEntriesColumns columns { {
 | 
			
		||||
		{ "Source", 9 },
 | 
			
		||||
		{ "Loc", 6 },
 | 
			
		||||
		{ "Hits", 4 },
 | 
			
		||||
		{ "Time", 8 }
 | 
			
		||||
	} };
 | 
			
		||||
	APRSRecentEntries recent { };
 | 
			
		||||
	RecentEntriesView<RecentEntries<APRSRecentEntry>> recent_entries_view { columns, recent };
 | 
			
		||||
	APRSDetailsView details_view { nav_ };
 | 
			
		||||
	uint32_t detailed_entry_key { 0 };
 | 
			
		||||
	bool send_updates { false };
 | 
			
		||||
 | 
			
		||||
	void on_show_list();
 | 
			
		||||
	void on_show_detail(const APRSRecentEntry& entry);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APRSRxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	APRSRxView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	~APRSRxView();
 | 
			
		||||
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "APRS RX"; };
 | 
			
		||||
	void on_packet(const APRSPacketMessage* message);
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void on_data(uint32_t value, bool is_data);
 | 
			
		||||
	bool reset_console = false;
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	uint8_t console_color { 0 };
 | 
			
		||||
	std::string str_log { "" };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_region {
 | 
			
		||||
		{ 0 * 8, 0 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{ "NA ", 0 },
 | 
			
		||||
			{ "EUR", 1 },
 | 
			
		||||
			{ "AUS", 2 },
 | 
			
		||||
			{ "NZ ", 3 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 3 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"AFS_????", RecordView::FileType::WAV, 4096, 4
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 2 * 16, 240, 240 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<APRSLogger> logger { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APRSRXView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	APRSRXView(NavigationView& nav);
 | 
			
		||||
	~APRSRXView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "APRS RX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	Rect view_rect = { 0, 3 * 8, 240, 280 };
 | 
			
		||||
	
 | 
			
		||||
	APRSRxView view_stream { nav_, view_rect };
 | 
			
		||||
	APRSTableView view_table { nav_, view_rect };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Stream", Color::cyan(), &view_stream },
 | 
			
		||||
		{ "List", Color::yellow(), &view_table }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::APRSPacket,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const APRSPacketMessage*>(p);
 | 
			
		||||
			this->view_stream.on_packet(message);
 | 
			
		||||
			this->view_table.on_pkt(message);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_APRS_RX_H__*/
 | 
			
		||||
@@ -0,0 +1,140 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_aprs_tx.hpp"
 | 
			
		||||
#include "ui_alphanum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "aprs.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace aprs;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void APRSTXView::focus() {
 | 
			
		||||
	tx_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSTXView::~APRSTXView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_aprs", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTXView::start_tx() {
 | 
			
		||||
	make_aprs_frame(
 | 
			
		||||
		sym_source.value_string().c_str(), num_ssid_source.value(),
 | 
			
		||||
		sym_dest.value_string().c_str(), num_ssid_dest.value(),
 | 
			
		||||
		payload);
 | 
			
		||||
	
 | 
			
		||||
	//uint8_t * bb_data_ptr = shared_memory.bb_data.data;
 | 
			
		||||
	//text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_tuning_frequency(persistent_memory::tuned_frequency());
 | 
			
		||||
	transmitter_model.set_sampling_rate(AFSK_TX_SAMPLERATE);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_afsk_data(
 | 
			
		||||
		AFSK_TX_SAMPLERATE / 1200,
 | 
			
		||||
		1200,
 | 
			
		||||
		2200,
 | 
			
		||||
		1,
 | 
			
		||||
		10000,	//APRS uses fixed 10k bandwidth
 | 
			
		||||
		8
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APRSTXView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	(void)progress;
 | 
			
		||||
	
 | 
			
		||||
	if (done) {
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APRSTXView::APRSTXView(NavigationView& nav) {
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_afsk);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&sym_source,
 | 
			
		||||
		&num_ssid_source,
 | 
			
		||||
		&sym_dest,
 | 
			
		||||
		&num_ssid_dest,
 | 
			
		||||
		&text_payload,
 | 
			
		||||
		&button_set,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_aprs", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	button_set.on_select = [this, &nav](Button&) {
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			payload,
 | 
			
		||||
			30,
 | 
			
		||||
			[this](std::string& s) {
 | 
			
		||||
				text_payload.set(s);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		start_tx();
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,116 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class APRSTXView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	APRSTXView(NavigationView& nav);
 | 
			
		||||
	~APRSTXView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "APRS TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	std::string payload { "" };
 | 
			
		||||
	
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void generate_frame();
 | 
			
		||||
	void generate_frame_pos();
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0 * 8, 1 * 16 }, "Source:       SSID:", Color::light_grey() },	// 6 alphanum + SSID
 | 
			
		||||
		{ { 0 * 8, 2 * 16 }, " Dest.:       SSID:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 4 * 16 }, "Info field:", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_source {
 | 
			
		||||
		{ 7 * 8, 1 * 16 },
 | 
			
		||||
		6,
 | 
			
		||||
		SymField::SYMFIELD_ALPHANUM
 | 
			
		||||
	};
 | 
			
		||||
	NumberField num_ssid_source {
 | 
			
		||||
		{ 19 * 8, 1 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 15 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_dest {
 | 
			
		||||
		{ 7 * 8, 2 * 16 },
 | 
			
		||||
		6,
 | 
			
		||||
		SymField::SYMFIELD_ALPHANUM
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField num_ssid_dest {
 | 
			
		||||
		{ 19 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 15 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_payload {
 | 
			
		||||
		{ 0 * 8, 5 * 16, 30 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_set {
 | 
			
		||||
		{ 0 * 8, 6 * 16, 80, 32 },
 | 
			
		||||
		"Set"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		5000,
 | 
			
		||||
		0 // disable setting bandwith, since APRS used fixed 10k bandwidth
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,369 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_bht_tx.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void BHTView::focus() {
 | 
			
		||||
	tx_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BHTView::start_tx() {
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	
 | 
			
		||||
	if (target_system == XYLOS) {
 | 
			
		||||
		
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_tones);
 | 
			
		||||
		
 | 
			
		||||
		view_xylos.generate_message();
 | 
			
		||||
		
 | 
			
		||||
		//if (tx_mode == SINGLE) {
 | 
			
		||||
			progressbar.set_max(XY_TONE_COUNT);
 | 
			
		||||
		/*} else if (tx_mode == SCAN) {
 | 
			
		||||
			progressbar.set_max(XY_TONE_COUNT * view_xylos.get_scan_remaining());
 | 
			
		||||
		}*/
 | 
			
		||||
		
 | 
			
		||||
		transmitter_model.set_sampling_rate(TONES_SAMPLERATE);
 | 
			
		||||
		transmitter_model.enable();
 | 
			
		||||
		
 | 
			
		||||
		// Setup tones
 | 
			
		||||
		for (size_t c = 0; c < ccir_deltas.size(); c++)
 | 
			
		||||
			baseband::set_tone(c, ccir_deltas[c], XY_TONE_DURATION);
 | 
			
		||||
		
 | 
			
		||||
		baseband::set_tones_config(transmitter_model.channel_bandwidth(), XY_SILENCE, XY_TONE_COUNT, false, false);
 | 
			
		||||
		
 | 
			
		||||
	} else if (target_system == EPAR) {
 | 
			
		||||
		
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_ook);
 | 
			
		||||
		
 | 
			
		||||
		auto bitstream_length = view_EPAR.generate_message();
 | 
			
		||||
		
 | 
			
		||||
		//if (tx_mode == SINGLE) {
 | 
			
		||||
			progressbar.set_max(2 * EPAR_REPEAT_COUNT);
 | 
			
		||||
		/*} else if (tx_mode == SCAN) {
 | 
			
		||||
			progressbar.set_max(2 * EPAR_REPEAT_COUNT * view_EPAR.get_scan_remaining());
 | 
			
		||||
		}*/
 | 
			
		||||
		
 | 
			
		||||
		transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
 | 
			
		||||
		transmitter_model.enable();
 | 
			
		||||
 | 
			
		||||
		baseband::set_ook_data(
 | 
			
		||||
			bitstream_length,
 | 
			
		||||
			EPAR_BIT_DURATION,
 | 
			
		||||
			EPAR_REPEAT_COUNT,
 | 
			
		||||
			encoder_defs[ENCODER_UM3750].pause_symbols
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BHTView::stop_tx() {
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
	tx_mode = IDLE;
 | 
			
		||||
	tx_view.set_transmitting(false);
 | 
			
		||||
	progressbar.set_value(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BHTView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (target_system == XYLOS) {
 | 
			
		||||
		if (done) {
 | 
			
		||||
			if (tx_mode == SINGLE) {
 | 
			
		||||
				if (checkbox_flashing.value()) {
 | 
			
		||||
					// TODO: Thread !
 | 
			
		||||
					chThdSleepMilliseconds(field_speed.value() * 1000);	// Dirty :(
 | 
			
		||||
					view_xylos.flip_relays();
 | 
			
		||||
					start_tx();
 | 
			
		||||
				} else
 | 
			
		||||
					stop_tx();
 | 
			
		||||
			} else if (tx_mode == SCAN) {
 | 
			
		||||
				if (view_xylos.increment_address())
 | 
			
		||||
					start_tx();
 | 
			
		||||
				else
 | 
			
		||||
					stop_tx();	// Reached end of scan range
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
			progressbar.set_value(progress);
 | 
			
		||||
	} else if (target_system == EPAR) {
 | 
			
		||||
		if (done) {
 | 
			
		||||
			
 | 
			
		||||
			if (!view_EPAR.half) {
 | 
			
		||||
				view_EPAR.half = true;
 | 
			
		||||
				start_tx();		// Start second half of transmission
 | 
			
		||||
			} else {
 | 
			
		||||
				view_EPAR.half = false;
 | 
			
		||||
				if (tx_mode == SINGLE) {
 | 
			
		||||
					if (checkbox_flashing.value()) {
 | 
			
		||||
						// TODO: Thread !
 | 
			
		||||
						chThdSleepMilliseconds(field_speed.value() * 1000);	// Dirty :(
 | 
			
		||||
						view_EPAR.flip_relays();
 | 
			
		||||
						start_tx();
 | 
			
		||||
					} else
 | 
			
		||||
						stop_tx();
 | 
			
		||||
				} else if (tx_mode == SCAN) {
 | 
			
		||||
					if (view_EPAR.increment_address())
 | 
			
		||||
						start_tx();
 | 
			
		||||
					else
 | 
			
		||||
						stop_tx();	// Reached end of scan range
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
			progressbar.set_value(progress);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BHTView::~BHTView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_bht", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BHTView::BHTView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&labels,
 | 
			
		||||
		&view_xylos,
 | 
			
		||||
		&view_EPAR,
 | 
			
		||||
		&checkbox_scan,
 | 
			
		||||
		&checkbox_flashing,
 | 
			
		||||
		&field_speed,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_bht", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	field_speed.set_value(1);
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (tx_mode == IDLE) {
 | 
			
		||||
			if (checkbox_scan.value()) {
 | 
			
		||||
				tx_mode = SCAN;
 | 
			
		||||
			} else {
 | 
			
		||||
				tx_mode = SINGLE;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			progressbar.set_value(0);
 | 
			
		||||
			tx_view.set_transmitting(true);
 | 
			
		||||
			target_system = (target_system_t)tab_view.selected();
 | 
			
		||||
			view_EPAR.half = false;
 | 
			
		||||
			
 | 
			
		||||
			start_tx();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		stop_tx();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool EPARView::increment_address() {
 | 
			
		||||
	auto city_code = field_city.value();
 | 
			
		||||
	
 | 
			
		||||
	if (city_code < EPAR_MAX_CITY) {
 | 
			
		||||
		field_city.set_value(city_code + 1);
 | 
			
		||||
		return true;
 | 
			
		||||
	} else
 | 
			
		||||
		return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t EPARView::get_scan_remaining() {
 | 
			
		||||
	return EPAR_MAX_CITY - field_city.value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EPARView::flip_relays() {
 | 
			
		||||
	// Invert first relay's state
 | 
			
		||||
	relay_states[0].set_selected_index(relay_states[0].selected_index() ^ 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t EPARView::generate_message() {
 | 
			
		||||
	// R2, then R1
 | 
			
		||||
	return gen_message_ep(field_city.value(), field_group.selected_index_value(),
 | 
			
		||||
							half ? 0 : 1, relay_states[half].selected_index());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EPARView::EPARView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : View(parent_rect) {
 | 
			
		||||
	
 | 
			
		||||
	hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_city,
 | 
			
		||||
		&field_group
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	field_city.set_value(0);
 | 
			
		||||
	field_group.set_selected_index(2);
 | 
			
		||||
	
 | 
			
		||||
	field_city.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	field_group.on_change = [this](size_t, int32_t) { generate_message(); };
 | 
			
		||||
	
 | 
			
		||||
	const auto relay_state_fn = [this](size_t, OptionsField::value_t) {
 | 
			
		||||
		generate_message();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	size_t n = 0;
 | 
			
		||||
	for (auto& relay_state : relay_states) {
 | 
			
		||||
		relay_state.on_change = relay_state_fn;
 | 
			
		||||
		relay_state.set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(90 + (n * 36)),
 | 
			
		||||
			80,
 | 
			
		||||
			24, 24
 | 
			
		||||
		});
 | 
			
		||||
		relay_state.set_options(relay_options);
 | 
			
		||||
		add_child(&relay_state);
 | 
			
		||||
		n++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EPARView::focus() {
 | 
			
		||||
	field_city.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XylosView::flip_relays() {
 | 
			
		||||
	// Invert first relay's state if not ignored
 | 
			
		||||
	size_t rs = relay_states[0].selected_index();
 | 
			
		||||
	
 | 
			
		||||
	if (rs > 0)
 | 
			
		||||
		relay_states[0].set_selected_index(rs ^ 3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool XylosView::increment_address() {
 | 
			
		||||
	auto city_code = field_city.value();
 | 
			
		||||
	
 | 
			
		||||
	if (city_code < XY_MAX_CITY) {
 | 
			
		||||
		field_city.set_value(city_code + 1);
 | 
			
		||||
		return true;
 | 
			
		||||
	} else
 | 
			
		||||
		return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t XylosView::get_scan_remaining() {
 | 
			
		||||
	return XY_MAX_CITY - field_city.value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XylosView::generate_message() {
 | 
			
		||||
	gen_message_xy(field_header_a.value(), field_header_b.value(), field_city.value(), field_family.value(), 
 | 
			
		||||
					checkbox_wcsubfamily.value(), field_subfamily.value(), checkbox_wcid.value(), field_receiver.value(),
 | 
			
		||||
					relay_states[0].selected_index(), relay_states[1].selected_index(), 
 | 
			
		||||
					relay_states[2].selected_index(), relay_states[3].selected_index());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
XylosView::XylosView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : View(parent_rect) {
 | 
			
		||||
	
 | 
			
		||||
	hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_header_a,
 | 
			
		||||
		&field_header_b,
 | 
			
		||||
		&field_city,
 | 
			
		||||
		&field_family,
 | 
			
		||||
		&field_subfamily,
 | 
			
		||||
		&checkbox_wcsubfamily,
 | 
			
		||||
		&field_receiver,
 | 
			
		||||
		&checkbox_wcid,
 | 
			
		||||
		//&button_seq,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	field_header_a.set_value(0);
 | 
			
		||||
	field_header_b.set_value(0);
 | 
			
		||||
	field_city.set_value(10);
 | 
			
		||||
	field_family.set_value(1);
 | 
			
		||||
	field_subfamily.set_value(1);
 | 
			
		||||
	field_receiver.set_value(1);
 | 
			
		||||
	
 | 
			
		||||
	const auto field_fn = [this](int32_t) {
 | 
			
		||||
		generate_message();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_header_a.on_change = field_fn;
 | 
			
		||||
	field_header_b.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	field_city.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	field_family.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	field_subfamily.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	field_receiver.on_change = [this](int32_t) { generate_message(); };
 | 
			
		||||
	
 | 
			
		||||
	checkbox_wcsubfamily.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		field_subfamily.set_focusable(!v);
 | 
			
		||||
		generate_message();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	checkbox_wcid.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		field_receiver.set_focusable(!v);
 | 
			
		||||
		generate_message();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	checkbox_wcsubfamily.set_value(true);
 | 
			
		||||
	checkbox_wcid.set_value(true);
 | 
			
		||||
	
 | 
			
		||||
	const auto relay_state_fn = [this](size_t, OptionsField::value_t) {
 | 
			
		||||
		generate_message();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	size_t n = 0;
 | 
			
		||||
	for (auto& relay_state : relay_states) {
 | 
			
		||||
		relay_state.on_change = relay_state_fn;
 | 
			
		||||
		relay_state.set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(54 + (n * 36)),
 | 
			
		||||
			134,
 | 
			
		||||
			24, 24
 | 
			
		||||
		});
 | 
			
		||||
		relay_state.set_options(relay_options);
 | 
			
		||||
		add_child(&relay_state);
 | 
			
		||||
		n++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XylosView::focus() {
 | 
			
		||||
	field_city.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,260 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include "bht.hpp"
 | 
			
		||||
#include "bitmap.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "encoders.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class XylosView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	XylosView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void flip_relays();
 | 
			
		||||
	void generate_message();
 | 
			
		||||
	bool increment_address();
 | 
			
		||||
	uint32_t get_scan_remaining();
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 8 * 8, 1 * 8 }, "Header:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 3 * 8 }, "City code:", Color::light_grey() },
 | 
			
		||||
		{ { 7 * 8, 5 * 8 }, "Family:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 7 * 8 + 2 }, "Subfamily:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 11 * 8 }, "Receiver ID:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 14 * 8 }, "Relay:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_header_a {
 | 
			
		||||
		{ 16 * 8, 1 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_header_b {
 | 
			
		||||
		{ 18 * 8, 1 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_city {
 | 
			
		||||
		{ 16 * 8, 3 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, XY_MAX_CITY },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_family {
 | 
			
		||||
		{ 16 * 8, 5 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 0, 9 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_subfamily {
 | 
			
		||||
		{ 16 * 8, 7 * 8 + 2 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 0, 9 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_wcsubfamily {
 | 
			
		||||
		{ 20 * 8, 6 * 8 + 6 },
 | 
			
		||||
		3,
 | 
			
		||||
		"All"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_receiver {
 | 
			
		||||
		{ 16 * 8, 11 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox checkbox_wcid {
 | 
			
		||||
		{ 20 * 8, 10 * 8 + 4 },
 | 
			
		||||
		3,
 | 
			
		||||
		"All"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::array<ImageOptionsField, 4> relay_states { };
 | 
			
		||||
	
 | 
			
		||||
	ImageOptionsField::options_t relay_options = {
 | 
			
		||||
		{ &bitmap_bulb_ignore, 0 },
 | 
			
		||||
		{ &bitmap_bulb_off, 1 },
 | 
			
		||||
		{ &bitmap_bulb_on, 2 }
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EPARView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	EPARView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void flip_relays();
 | 
			
		||||
	size_t generate_message();
 | 
			
		||||
	bool increment_address();
 | 
			
		||||
	uint32_t get_scan_remaining();
 | 
			
		||||
 | 
			
		||||
	bool half { false };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 4 * 8, 1 * 8 }, "City code:", Color::light_grey() },
 | 
			
		||||
		{ { 8 * 8, 3 * 8 }, "Group:", Color::light_grey() },
 | 
			
		||||
		{ { 8 * 8, 7 * 8 }, "Relay:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_city {
 | 
			
		||||
		{ 16 * 8, 1 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 0, EPAR_MAX_CITY },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField field_group {
 | 
			
		||||
		{ 16 * 8, 3 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{
 | 
			
		||||
			{ "A ", 2 },	// See receiver PCB
 | 
			
		||||
			{ "B ", 1 },
 | 
			
		||||
			{ "C ", 0 },
 | 
			
		||||
			{ "TP", 3 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::array<ImageOptionsField, 2> relay_states { };
 | 
			
		||||
	
 | 
			
		||||
	ImageOptionsField::options_t relay_options = {
 | 
			
		||||
		{ &bitmap_bulb_off, 0 },
 | 
			
		||||
		{ &bitmap_bulb_on, 1 }
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BHTView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	BHTView(NavigationView& nav);
 | 
			
		||||
	~BHTView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "BHT Xy/EP TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void stop_tx();
 | 
			
		||||
	
 | 
			
		||||
	enum target_system_t {
 | 
			
		||||
		XYLOS = 0,
 | 
			
		||||
		EPAR = 1
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	target_system_t target_system = { };
 | 
			
		||||
	
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SCAN
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
	
 | 
			
		||||
	Rect view_rect = { 0, 3 * 8, 240, 176 };
 | 
			
		||||
	
 | 
			
		||||
	XylosView view_xylos { view_rect };
 | 
			
		||||
	EPARView view_EPAR { view_rect };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Xylos", Color::cyan(), &view_xylos },
 | 
			
		||||
		{ "EPAR", Color::green(), &view_EPAR }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 29 * 8, 14 * 16 + 4 }, "s", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_scan {
 | 
			
		||||
		{ 1 * 8, 25 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Scan"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_flashing {
 | 
			
		||||
		{ 16 * 8, 25 * 8 },
 | 
			
		||||
		8,
 | 
			
		||||
		"Flashing"
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_speed {
 | 
			
		||||
		{ 26 * 8, 25 * 8 + 4 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 0 * 8, 29 * 8, 30 * 8, 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,174 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_btle_rx.hpp"
 | 
			
		||||
#include "ui_modemsetup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "modems.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace modems;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void BTLERxView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BTLERxView::update_freq(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BTLERxView::BTLERxView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&text_debug,
 | 
			
		||||
		&button_modem_setup,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_btle", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
	record_view.set_sampling_rate(24000);
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	update_freq(2426000000);
 | 
			
		||||
	auto def_bell202 = &modem_defs[0];
 | 
			
		||||
	persistent_memory::set_modem_baudrate(def_bell202->baudrate);
 | 
			
		||||
	serial_format_t serial_format;
 | 
			
		||||
	serial_format.data_bits = 7;
 | 
			
		||||
	serial_format.parity = EVEN;
 | 
			
		||||
	serial_format.stop_bits = 1;
 | 
			
		||||
	serial_format.bit_order = LSB_FIRST;
 | 
			
		||||
	persistent_memory::set_serial_format(serial_format);
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(100);
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_modem_setup.on_select = [&nav](Button&) {
 | 
			
		||||
		nav.push<ModemSetupView>();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false);
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_sampling_rate(4000000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(4000000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BTLERxView::on_data(uint32_t value, bool is_data) {
 | 
			
		||||
	//std::string str_console = "\x1B";
 | 
			
		||||
	std::string str_console = "";
 | 
			
		||||
	if (is_data) {
 | 
			
		||||
		// Colorize differently after message splits
 | 
			
		||||
		//str_console += (char)((console_color & 3) + 9);
 | 
			
		||||
		
 | 
			
		||||
		//value &= 0xFF;											// ABCDEFGH
 | 
			
		||||
		//value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);	// EFGHABCD
 | 
			
		||||
		//value = ((value & 0xCC) >> 2) | ((value & 0x33) << 2);	// GHEFCDAB
 | 
			
		||||
		//value = ((value & 0xAA) >> 1) | ((value & 0x55) << 1);	// HGFEDCBA
 | 
			
		||||
		//value &= 0x7F;											// Ignore parity, which is the MSB now
 | 
			
		||||
		
 | 
			
		||||
		//if ((value >= 32) && (value < 127)) {
 | 
			
		||||
		//	str_console += (char)value;							// Printable
 | 
			
		||||
		//} 
 | 
			
		||||
		
 | 
			
		||||
		//str_console += (char)'A';
 | 
			
		||||
		//str_console += (char)value;
 | 
			
		||||
		//str_console += "[" + to_string_hex(value, 2) + "]";	
 | 
			
		||||
		str_console += ":" + to_string_hex(value, 2) ;	
 | 
			
		||||
		console.write(str_console);
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		/*if ((value != 0x7F) && (prev_value == 0x7F)) {
 | 
			
		||||
			// Message split
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
			console_color++;
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
		}*/
 | 
			
		||||
		//prev_value = value;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Baudrate estimation
 | 
			
		||||
		//text_debug.set("~" + to_string_dec_uint(value)); 
 | 
			
		||||
		if (value == 'A')
 | 
			
		||||
		{console.write("mac");}
 | 
			
		||||
		else if (value == 'B')
 | 
			
		||||
		{console.writeln("");}
 | 
			
		||||
		//console.writeln("");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BTLERxView::~BTLERxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_btle", &app_settings);
 | 
			
		||||
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,113 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_BTLE_RX_H__
 | 
			
		||||
#define __UI_BTLE_RX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"	// DEBUG
 | 
			
		||||
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class BTLERxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	BTLERxView(NavigationView& nav);
 | 
			
		||||
	~BTLERxView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "BTLE RX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void on_data(uint32_t value, bool is_data);
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	uint8_t console_color { 0 };
 | 
			
		||||
	uint32_t prev_value { 0 };
 | 
			
		||||
	std::string str_log { "" };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_debug {
 | 
			
		||||
		{ 0 * 8, 1 * 16, 10 * 8, 16 },
 | 
			
		||||
		"DEBUG"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	Button button_modem_setup {
 | 
			
		||||
		{ 12 * 8, 1 * 16, 96, 24 },
 | 
			
		||||
		"Modem setup"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"AFS_????", RecordView::FileType::WAV, 4096, 4
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 4 * 16, 240, 240 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
	//void on_data_afsk(const AFSKDataMessage& message);
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::AFSKData,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const AFSKDataMessage*>(p);
 | 
			
		||||
			this->on_data(message->value, message->is_data);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_BTLE_RX_H__*/
 | 
			
		||||
@@ -0,0 +1,167 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_coasterp.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void CoasterPagerView::focus() {
 | 
			
		||||
	sym_data.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CoasterPagerView::~CoasterPagerView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_coaster", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CoasterPagerView::generate_frame() {
 | 
			
		||||
	uint8_t frame[19];
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
	
 | 
			
		||||
	// Preamble (8 bytes)
 | 
			
		||||
	for (c = 0; c < 8; c++)
 | 
			
		||||
		frame[c] = 0x55;		// Isn't this 0xAA ?
 | 
			
		||||
	
 | 
			
		||||
	// Sync word
 | 
			
		||||
	frame[8] = 0x2D;
 | 
			
		||||
	frame[9] = 0xD4;
 | 
			
		||||
	
 | 
			
		||||
	// Data length
 | 
			
		||||
	frame[10] = 8;
 | 
			
		||||
	
 | 
			
		||||
	// Data
 | 
			
		||||
	for (c = 0; c < 8; c++)
 | 
			
		||||
		frame[c + 11] = (sym_data.get_sym(c * 2) << 4) | sym_data.get_sym(c * 2 + 1);
 | 
			
		||||
 | 
			
		||||
	// Copy for baseband
 | 
			
		||||
	memcpy(shared_memory.bb_data.data, frame, 19);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CoasterPagerView::start_tx() {
 | 
			
		||||
	generate_frame();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(2280000);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
 | 
			
		||||
	baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	(void)progress;
 | 
			
		||||
	
 | 
			
		||||
	uint16_t address = 0;
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
	
 | 
			
		||||
	if (done) {
 | 
			
		||||
		if (tx_mode == SINGLE) {
 | 
			
		||||
			transmitter_model.disable();
 | 
			
		||||
			tx_mode = IDLE;
 | 
			
		||||
			tx_view.set_transmitting(false);
 | 
			
		||||
		} else if (tx_mode == SCAN) {
 | 
			
		||||
			// Increment address
 | 
			
		||||
			
 | 
			
		||||
			for (c = 0; c < 4; c++) {
 | 
			
		||||
				address <<= 4;
 | 
			
		||||
				address |= sym_data.get_sym(12 + c);
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			address++;
 | 
			
		||||
			
 | 
			
		||||
			for (c = 0; c < 4; c++) {
 | 
			
		||||
				sym_data.set_sym(15 - c, address & 0x0F);
 | 
			
		||||
				address >>= 4;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			start_tx();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CoasterPagerView::CoasterPagerView(NavigationView& nav) {
 | 
			
		||||
	const uint8_t data_init[8] = { 0x44, 0x01, 0x3B, 0x30, 0x30, 0x30, 0x34, 0xBC };
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_fsktx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&sym_data,
 | 
			
		||||
		&checkbox_scan,
 | 
			
		||||
		&text_message,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_coaster", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Bytes to nibbles
 | 
			
		||||
	for (c = 0; c < 16; c++)
 | 
			
		||||
		sym_data.set_sym(c, (data_init[c >> 1] >> ((c & 1) ? 0 : 4)) & 0x0F);
 | 
			
		||||
	
 | 
			
		||||
	checkbox_scan.set_value(false);
 | 
			
		||||
	
 | 
			
		||||
	generate_frame();
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (tx_mode == IDLE) {
 | 
			
		||||
			if (checkbox_scan.value())
 | 
			
		||||
				tx_mode = SCAN;
 | 
			
		||||
			else
 | 
			
		||||
				tx_mode = SINGLE;
 | 
			
		||||
			tx_view.set_transmitting(true);
 | 
			
		||||
			start_tx();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		tx_mode = IDLE;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,102 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class CoasterPagerView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	CoasterPagerView(NavigationView& nav);
 | 
			
		||||
	~CoasterPagerView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "BurgerPgr TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SCAN
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { };
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void generate_frame();
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 3 * 8 }, "Syscall pager TX beta", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 8 * 8 }, "Data:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_data {
 | 
			
		||||
		{ 7 * 8, 8 * 8 },
 | 
			
		||||
		16,		// 14 ? 12 ?
 | 
			
		||||
		SymField::SYMFIELD_HEX
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_scan {
 | 
			
		||||
		{ 10 * 8, 14 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Scan"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*ProgressBar progressbar {
 | 
			
		||||
		{ 5 * 8, 12 * 16, 20 * 8, 16 },
 | 
			
		||||
	};*/
 | 
			
		||||
	Text text_message {
 | 
			
		||||
		{ 5 * 8, 13 * 16, 20 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										395
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_debug.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_debug.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,395 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_debug.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ch.h"
 | 
			
		||||
 | 
			
		||||
#include "radio.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_sd_card_debug.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
#include "irq_controls.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
/* DebugMemoryView *******************************************************/
 | 
			
		||||
 | 
			
		||||
DebugMemoryView::DebugMemoryView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_title,
 | 
			
		||||
		&text_label_m0_core_free,
 | 
			
		||||
		&text_label_m0_core_free_value,
 | 
			
		||||
		&text_label_m0_heap_fragmented_free,
 | 
			
		||||
		&text_label_m0_heap_fragmented_free_value,
 | 
			
		||||
		&text_label_m0_heap_fragments,
 | 
			
		||||
		&text_label_m0_heap_fragments_value,
 | 
			
		||||
		&button_done
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const auto m0_core_free = chCoreStatus();
 | 
			
		||||
	text_label_m0_core_free_value.set(to_string_dec_uint(m0_core_free, 5));
 | 
			
		||||
 | 
			
		||||
	size_t m0_fragmented_free_space = 0;
 | 
			
		||||
	const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
 | 
			
		||||
	text_label_m0_heap_fragmented_free_value.set(to_string_dec_uint(m0_fragmented_free_space, 5));
 | 
			
		||||
	text_label_m0_heap_fragments_value.set(to_string_dec_uint(m0_fragments, 5));
 | 
			
		||||
 | 
			
		||||
	button_done.on_select = [&nav](Button&){ nav.pop(); };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DebugMemoryView::focus() {
 | 
			
		||||
	button_done.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* TemperatureWidget *****************************************************/
 | 
			
		||||
 | 
			
		||||
void TemperatureWidget::paint(Painter& painter) {
 | 
			
		||||
	const auto logger = portapack::temperature_logger;
 | 
			
		||||
 | 
			
		||||
	const auto rect = screen_rect();
 | 
			
		||||
	const Color color_background { 0, 0, 64 };
 | 
			
		||||
	const Color color_foreground = Color::green();
 | 
			
		||||
	const Color color_reticle { 128, 128, 128 };
 | 
			
		||||
 | 
			
		||||
	const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
 | 
			
		||||
	const Rect graph_rect {
 | 
			
		||||
		rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
 | 
			
		||||
		graph_width, rect.height()
 | 
			
		||||
	};
 | 
			
		||||
	const Rect frame_rect {
 | 
			
		||||
		graph_rect.left() - 1, graph_rect.top() - 1,
 | 
			
		||||
		graph_rect.width() + 2, graph_rect.height() + 2
 | 
			
		||||
	};
 | 
			
		||||
	painter.draw_rectangle(frame_rect, color_reticle);
 | 
			
		||||
	painter.fill_rectangle(graph_rect, color_background);
 | 
			
		||||
 | 
			
		||||
	const auto history = logger.history();
 | 
			
		||||
	for(size_t i=0; i<history.size(); i++) {
 | 
			
		||||
		const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
 | 
			
		||||
		const auto sample = history[i];
 | 
			
		||||
		const auto temp = temperature(sample);
 | 
			
		||||
		const auto y = screen_y(temp, graph_rect);
 | 
			
		||||
		const Dim bar_height = graph_rect.bottom() - y;
 | 
			
		||||
		painter.fill_rectangle({ x, y, bar_width, bar_height }, color_foreground);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( !history.empty() ) {
 | 
			
		||||
		const auto sample = history.back();
 | 
			
		||||
		const auto temp = temperature(sample);
 | 
			
		||||
		const auto last_y = screen_y(temp, graph_rect);
 | 
			
		||||
		const Coord x = graph_rect.right() + 8;
 | 
			
		||||
		const Coord y = last_y - 8;
 | 
			
		||||
 | 
			
		||||
		painter.draw_string({ x, y }, style(), temperature_str(temp));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
 | 
			
		||||
	for(auto temp=display_temp_min; temp<=display_temp_max; temp+=10) {
 | 
			
		||||
		const int32_t tick_length = 6;
 | 
			
		||||
		const auto tick_x = graph_rect.left() - tick_length;
 | 
			
		||||
		const auto tick_y = screen_y(temp, graph_rect);
 | 
			
		||||
		painter.fill_rectangle({ tick_x, tick_y, tick_length, 1 }, color_reticle);
 | 
			
		||||
		const auto text_x = graph_rect.left() - temp_len * 8 - 8;
 | 
			
		||||
		const auto text_y = tick_y - 8;
 | 
			
		||||
		painter.draw_string({ text_x, text_y }, style(), temperature_str(temp));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
 | 
			
		||||
	/*It seems to be a temperature difference of 25C*/
 | 
			
		||||
	return -40 +(sensor_value * 4.31)+25;  //max2837 datasheet temp 25ºC has sensor value: 15
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
 | 
			
		||||
	return to_string_dec_int(temperature, temp_len - 1) + "C";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Coord TemperatureWidget::screen_y(
 | 
			
		||||
	const temperature_t temperature,
 | 
			
		||||
	const Rect& rect
 | 
			
		||||
) const {
 | 
			
		||||
	int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
 | 
			
		||||
	const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
 | 
			
		||||
	return y_limit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* TemperatureView *******************************************************/
 | 
			
		||||
 | 
			
		||||
TemperatureView::TemperatureView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_title,
 | 
			
		||||
		&temperature_widget,
 | 
			
		||||
		&button_done,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_done.on_select = [&nav](Button&){ nav.pop(); };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TemperatureView::focus() {
 | 
			
		||||
	button_done.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* RegistersWidget *******************************************************/
 | 
			
		||||
 | 
			
		||||
RegistersWidget::RegistersWidget(
 | 
			
		||||
	RegistersWidgetConfig&& config,
 | 
			
		||||
	std::function<uint32_t(const size_t register_number)>&& reader
 | 
			
		||||
) : Widget { },
 | 
			
		||||
	config(std::move(config)),
 | 
			
		||||
	reader(std::move(reader))
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegistersWidget::update() {
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegistersWidget::paint(Painter& painter) {
 | 
			
		||||
	const Coord left = (size().width() - config.row_width()) / 2;
 | 
			
		||||
 | 
			
		||||
	draw_legend(left, painter);
 | 
			
		||||
	draw_values(left, painter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
 | 
			
		||||
	const auto pos = screen_pos();
 | 
			
		||||
 | 
			
		||||
	for(size_t i=0; i<config.registers_count; i+=config.registers_per_row()) {
 | 
			
		||||
		const Point offset {
 | 
			
		||||
			left, static_cast<int>((i / config.registers_per_row()) * row_height)
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const auto text = to_string_hex(i, config.legend_length());
 | 
			
		||||
		painter.draw_string(
 | 
			
		||||
			pos + offset,
 | 
			
		||||
			style().invert(),
 | 
			
		||||
			text
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegistersWidget::draw_values(
 | 
			
		||||
	const Coord left,
 | 
			
		||||
	Painter& painter
 | 
			
		||||
) {
 | 
			
		||||
	const auto pos = screen_pos();
 | 
			
		||||
 | 
			
		||||
	for(size_t i=0; i<config.registers_count; i++) {
 | 
			
		||||
		const Point offset = {
 | 
			
		||||
			static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
 | 
			
		||||
			static_cast<int>((i / config.registers_per_row()) * row_height)
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const auto value = reader(i);
 | 
			
		||||
 | 
			
		||||
		const auto text = to_string_hex(value, config.value_length());
 | 
			
		||||
		painter.draw_string(
 | 
			
		||||
			pos + offset,
 | 
			
		||||
			style(),
 | 
			
		||||
			text
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* RegistersView *********************************************************/
 | 
			
		||||
 | 
			
		||||
RegistersView::RegistersView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	const std::string& title,
 | 
			
		||||
	RegistersWidgetConfig&& config,
 | 
			
		||||
	std::function<uint32_t(const size_t register_number)>&& reader
 | 
			
		||||
) : registers_widget { std::move(config), std::move(reader) }
 | 
			
		||||
{
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_title,
 | 
			
		||||
		®isters_widget,
 | 
			
		||||
		&button_update,
 | 
			
		||||
		&button_done,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_update.on_select = [this](Button&){
 | 
			
		||||
		this->registers_widget.update();
 | 
			
		||||
	};
 | 
			
		||||
	button_done.on_select = [&nav](Button&){ nav.pop(); };
 | 
			
		||||
 | 
			
		||||
	registers_widget.set_parent_rect({ 0, 48, 240, 192 });
 | 
			
		||||
 | 
			
		||||
	text_title.set_parent_rect({
 | 
			
		||||
		(240 - static_cast<int>(title.size()) * 8) / 2, 16,
 | 
			
		||||
		static_cast<int>(title.size()) * 8, 16
 | 
			
		||||
	});
 | 
			
		||||
	text_title.set(title);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegistersView::focus() {
 | 
			
		||||
	button_done.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ControlsSwitchesWidget ************************************************/
 | 
			
		||||
 | 
			
		||||
void ControlsSwitchesWidget::on_show() {
 | 
			
		||||
	display.fill_rectangle(
 | 
			
		||||
		screen_rect(),
 | 
			
		||||
		Color::black()
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
 | 
			
		||||
	key_event_mask = 1 << toUType(key);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ControlsSwitchesWidget::paint(Painter& painter) {
 | 
			
		||||
	const std::array<Rect, 7> button_rects { {
 | 
			
		||||
		{ 64, 32, 16, 16 }, // Right
 | 
			
		||||
		{  0, 32, 16, 16 }, // Left
 | 
			
		||||
		{ 32, 64, 16, 16 }, // Down
 | 
			
		||||
		{ 32,  0, 16, 16 }, // Up
 | 
			
		||||
		{ 32, 32, 16, 16 }, // Select
 | 
			
		||||
		{ 16, 96, 16, 16 }, // Encoder phase 0
 | 
			
		||||
		{ 48, 96, 16, 16 }, // Encoder phase 1
 | 
			
		||||
	} };
 | 
			
		||||
	const auto pos = screen_pos();
 | 
			
		||||
	auto switches_raw = control::debug::switches();
 | 
			
		||||
	auto switches_debounced = get_switches_state().to_ulong();
 | 
			
		||||
	auto switches_event = key_event_mask;
 | 
			
		||||
 | 
			
		||||
	for(const auto r : button_rects) {
 | 
			
		||||
		const auto c =
 | 
			
		||||
			((switches_event & 1) ?
 | 
			
		||||
				Color::red() :
 | 
			
		||||
				((switches_debounced & 1) ?
 | 
			
		||||
					Color::green() :
 | 
			
		||||
					((switches_raw & 1) ?
 | 
			
		||||
						Color::yellow() :
 | 
			
		||||
						Color::blue()
 | 
			
		||||
					)
 | 
			
		||||
				)
 | 
			
		||||
			);
 | 
			
		||||
		painter.fill_rectangle(r + pos, c);
 | 
			
		||||
		switches_raw >>= 1;
 | 
			
		||||
		switches_debounced >>= 1;
 | 
			
		||||
		switches_event >>= 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ControlsSwitchesWidget::on_frame_sync() {
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* DebugControlsView *****************************************************/
 | 
			
		||||
 | 
			
		||||
DebugControlsView::DebugControlsView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_title,
 | 
			
		||||
		&switches_widget,
 | 
			
		||||
		&button_done,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_done.on_select = [&nav](Button&){ nav.pop(); };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DebugControlsView::focus() {
 | 
			
		||||
	switches_widget.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* DebugPeripheralsMenuView **********************************************/
 | 
			
		||||
 | 
			
		||||
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
 | 
			
		||||
	add_items({
 | 
			
		||||
		{ "RFFC5072",    ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>(
 | 
			
		||||
			"RFFC5072", RegistersWidgetConfig { 31, 16 },
 | 
			
		||||
			[](const size_t register_number) { return radio::debug::first_if::register_read(register_number); }
 | 
			
		||||
		); } },
 | 
			
		||||
		{ "MAX2837",     ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>(
 | 
			
		||||
			"MAX2837", RegistersWidgetConfig { 32, 10 },
 | 
			
		||||
			[](const size_t register_number) { return radio::debug::second_if::register_read(register_number); }
 | 
			
		||||
		); } },
 | 
			
		||||
		{ "Si5351C",     ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>(
 | 
			
		||||
			"Si5351C", RegistersWidgetConfig { 96, 8 },
 | 
			
		||||
			[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }
 | 
			
		||||
		); } },
 | 
			
		||||
		{ audio::debug::codec_name(), ui::Color::dark_cyan(),	&bitmap_icon_peripherals_details,	[&nav](){ nav.push<RegistersView>(
 | 
			
		||||
			audio::debug::codec_name(), RegistersWidgetConfig { audio::debug::reg_count(), audio::debug::reg_bits() },
 | 
			
		||||
			[](const size_t register_number) { return audio::debug::reg_read(register_number); }
 | 
			
		||||
		); } },
 | 
			
		||||
	});
 | 
			
		||||
	set_max_rows(2); // allow wider buttons
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* DebugMenuView *********************************************************/
 | 
			
		||||
 | 
			
		||||
DebugMenuView::DebugMenuView(NavigationView& nav) {
 | 
			
		||||
    if( portapack::persistent_memory::show_gui_return_icon() )
 | 
			
		||||
    {
 | 
			
		||||
        add_items( { { "..", 		ui::Color::light_grey(),&bitmap_icon_previous,	[&nav](){ nav.pop(); } } } );
 | 
			
		||||
    }
 | 
			
		||||
	add_items({
 | 
			
		||||
		{ "Memory", 		ui::Color::dark_cyan(),	&bitmap_icon_memory,	[&nav](){ nav.push<DebugMemoryView>(); } },
 | 
			
		||||
		//{ "Radio State",	ui::Color::white(),	nullptr,	[&nav](){ nav.push<NotImplementedView>(); } },
 | 
			
		||||
		{ "SD Card",		ui::Color::dark_cyan(),	&bitmap_icon_sdcard,	[&nav](){ nav.push<SDCardDebugView>(); } },
 | 
			
		||||
		{ "Peripherals",	ui::Color::dark_cyan(),	&bitmap_icon_peripherals,	[&nav](){ nav.push<DebugPeripheralsMenuView>(); } },
 | 
			
		||||
		{ "Temperature",	ui::Color::dark_cyan(),	&bitmap_icon_temperature,	[&nav](){ nav.push<TemperatureView>(); } },
 | 
			
		||||
		{ "Buttons Test",	ui::Color::dark_cyan(),	&bitmap_icon_controls,	[&nav](){ nav.push<DebugControlsView>(); } },
 | 
			
		||||
	});
 | 
			
		||||
	set_max_rows(2); // allow wider buttons
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
 | 
			
		||||
	
 | 
			
		||||
	std::string debug_text;
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&console,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	for(const auto c : lcr_string) {
 | 
			
		||||
		if ((c < 32) || (c > 126))
 | 
			
		||||
			debug_text += "[" + to_string_dec_uint(c) + "]";
 | 
			
		||||
		else
 | 
			
		||||
			debug_text += c;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	debug_text += "\n\n";
 | 
			
		||||
	debug_text += "Length: " + to_string_dec_uint(lcr_string.length()) + '\n';
 | 
			
		||||
	debug_text += "Checksum: " + to_string_dec_uint(lcr_string.back()) + '\n';
 | 
			
		||||
	
 | 
			
		||||
	console.write(debug_text);
 | 
			
		||||
	
 | 
			
		||||
	button_exit.on_select = [this, &nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
	
 | 
			
		||||
void DebugLCRView::focus() {
 | 
			
		||||
	button_exit.focus();
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										310
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_debug.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_debug.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,310 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_DEBUG_H__
 | 
			
		||||
#define __UI_DEBUG_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_painter.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
 | 
			
		||||
#include "rffc507x.hpp"
 | 
			
		||||
#include "max2837.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class DebugMemoryView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	DebugMemoryView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Memory"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text text_title {
 | 
			
		||||
		{ 96, 96, 48, 16 },
 | 
			
		||||
		"Memory",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_core_free {
 | 
			
		||||
		{ 0, 128, 144, 16 },
 | 
			
		||||
		"M0 Core Free Bytes",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_core_free_value {
 | 
			
		||||
		{ 200, 128, 40, 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_heap_fragmented_free {
 | 
			
		||||
		{ 0, 144, 184, 16 },
 | 
			
		||||
		"M0 Heap Fragmented Free",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_heap_fragmented_free_value {
 | 
			
		||||
		{ 200, 144, 40, 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_heap_fragments {
 | 
			
		||||
		{ 0, 160, 136, 16 },
 | 
			
		||||
		"M0 Heap Fragments",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_label_m0_heap_fragments_value {
 | 
			
		||||
		{ 200, 160, 40, 16 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 72, 192, 96, 24 },
 | 
			
		||||
		"Done"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TemperatureWidget : public Widget {
 | 
			
		||||
public:
 | 
			
		||||
	explicit TemperatureWidget(
 | 
			
		||||
		Rect parent_rect
 | 
			
		||||
	) : Widget { parent_rect }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void paint(Painter& painter) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	using sample_t = uint32_t;
 | 
			
		||||
	using temperature_t = int32_t;
 | 
			
		||||
 | 
			
		||||
	temperature_t temperature(const sample_t sensor_value) const;
 | 
			
		||||
	Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
 | 
			
		||||
 | 
			
		||||
	std::string temperature_str(const temperature_t temperature) const;
 | 
			
		||||
 | 
			
		||||
	static constexpr temperature_t display_temp_min = -10;  //Accomodate negative values, present in cold startup cases
 | 
			
		||||
	static constexpr temperature_t display_temp_scale = 3;
 | 
			
		||||
	static constexpr int bar_width = 1;
 | 
			
		||||
	static constexpr int temp_len = 4; //Now scale shows up to 4 chars ("-10C")
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TemperatureView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	explicit TemperatureView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Temperature"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text text_title {
 | 
			
		||||
		{ 76, 16, 240, 16 },
 | 
			
		||||
		"Temperature",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	TemperatureWidget temperature_widget {
 | 
			
		||||
		{ 0, 40, 240, 180 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 72, 264, 96, 24 },
 | 
			
		||||
		"Done"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RegistersWidgetConfig {
 | 
			
		||||
	size_t registers_count;
 | 
			
		||||
	size_t register_bits;
 | 
			
		||||
 | 
			
		||||
	constexpr size_t legend_length() const {
 | 
			
		||||
		return (registers_count >= 0x10) ? 2 : 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t legend_width() const {
 | 
			
		||||
		return legend_length() * 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t value_length() const {
 | 
			
		||||
		return (register_bits + 3) / 4;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t value_width() const {
 | 
			
		||||
		return value_length() * 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t registers_per_row() const {
 | 
			
		||||
		return (value_length() >= 3) ? 4 : 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t registers_row_length() const {
 | 
			
		||||
		return (registers_per_row() * (value_length() + 1)) - 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t registers_row_width() const {
 | 
			
		||||
		return registers_row_length() * 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t row_width() const {
 | 
			
		||||
		return legend_width() + 8 + registers_row_width();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr size_t rows() const {
 | 
			
		||||
		return registers_count / registers_per_row();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RegistersWidget : public Widget {
 | 
			
		||||
public:
 | 
			
		||||
	RegistersWidget(
 | 
			
		||||
		RegistersWidgetConfig&& config,
 | 
			
		||||
		std::function<uint32_t(const size_t register_number)>&& reader
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	void update();
 | 
			
		||||
 | 
			
		||||
	void paint(Painter& painter) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const RegistersWidgetConfig config;
 | 
			
		||||
	const std::function<uint32_t(const size_t register_number)> reader;
 | 
			
		||||
 | 
			
		||||
	static constexpr size_t row_height = 16;
 | 
			
		||||
 | 
			
		||||
	void draw_legend(const Coord left, Painter& painter);
 | 
			
		||||
	void draw_values(const Coord left, Painter& painter);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RegistersView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	RegistersView(
 | 
			
		||||
		NavigationView& nav,
 | 
			
		||||
		const std::string& title,
 | 
			
		||||
		RegistersWidgetConfig&& config,
 | 
			
		||||
		std::function<uint32_t(const size_t register_number)>&& reader
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	void focus();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text text_title { };
 | 
			
		||||
 | 
			
		||||
	RegistersWidget registers_widget;
 | 
			
		||||
 | 
			
		||||
	Button button_update {
 | 
			
		||||
		{ 16, 256, 96, 24 },
 | 
			
		||||
		"Update"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 128, 256, 96, 24 },
 | 
			
		||||
		"Done"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ControlsSwitchesWidget : public Widget {
 | 
			
		||||
public:
 | 
			
		||||
	ControlsSwitchesWidget(
 | 
			
		||||
		Rect parent_rect
 | 
			
		||||
	) : Widget { parent_rect },
 | 
			
		||||
		key_event_mask(0)
 | 
			
		||||
	{
 | 
			
		||||
		set_focusable(true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	bool on_key(const KeyEvent key) override;
 | 
			
		||||
 | 
			
		||||
	void paint(Painter& painter) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	uint8_t key_event_mask;
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_frame_sync {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			this->on_frame_sync();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void on_frame_sync();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DebugControlsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	explicit DebugControlsView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Buttons Test"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Text text_title {
 | 
			
		||||
		{ 64, 16, 184, 16 },
 | 
			
		||||
		"Controls State",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ControlsSwitchesWidget switches_widget {
 | 
			
		||||
		{ 80, 80, 80, 112 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_done {
 | 
			
		||||
		{ 72, 264, 96, 24 },
 | 
			
		||||
		"Done"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*class DebugLCRView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	DebugLCRView(NavigationView& nav, std::string lcrstring);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "LCR debug"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 8, 16, 224, 240 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_exit {
 | 
			
		||||
		{ 72, 264, 96, 32 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
class DebugPeripheralsMenuView : public BtnGridView {
 | 
			
		||||
public:
 | 
			
		||||
	DebugPeripheralsMenuView(NavigationView& nav);
 | 
			
		||||
	std::string title() const override { return "Peripherals"; };	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DebugMenuView : public BtnGridView {
 | 
			
		||||
public:
 | 
			
		||||
	DebugMenuView(NavigationView& nav);
 | 
			
		||||
	std::string title() const override { return "Debug"; };	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_DEBUG_H__*/
 | 
			
		||||
@@ -0,0 +1,368 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_encoders.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
EncodersConfigView::EncodersConfigView(
 | 
			
		||||
	NavigationView&, Rect parent_rect
 | 
			
		||||
) {
 | 
			
		||||
	using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	std::vector<option_t> enc_options;
 | 
			
		||||
	size_t i;
 | 
			
		||||
	
 | 
			
		||||
	set_parent_rect(parent_rect);
 | 
			
		||||
	hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	// Default encoder def
 | 
			
		||||
	encoder_def = &encoder_defs[0];
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_enctype,
 | 
			
		||||
		&field_clk,
 | 
			
		||||
		&field_frameduration,
 | 
			
		||||
		&symfield_word,
 | 
			
		||||
		&text_format,
 | 
			
		||||
		&waveform
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Load encoder types in option field
 | 
			
		||||
	for (i = 0; i < ENC_TYPES_COUNT; i++)
 | 
			
		||||
		enc_options.emplace_back(std::make_pair(encoder_defs[i].name, i));
 | 
			
		||||
	
 | 
			
		||||
	options_enctype.on_change = [this](size_t index, int32_t) {
 | 
			
		||||
		on_type_change(index);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_enctype.set_options(enc_options);
 | 
			
		||||
	options_enctype.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	symfield_word.on_change = [this]() {
 | 
			
		||||
		generate_frame();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// Selecting input clock changes symbol and word duration
 | 
			
		||||
	field_clk.on_change = [this](int32_t value) {
 | 
			
		||||
		// value is in kHz, new_value is in us
 | 
			
		||||
		int32_t new_value = (encoder_def->clk_per_symbol * 1000000) / (value * 1000);
 | 
			
		||||
		if (new_value != field_frameduration.value())
 | 
			
		||||
			field_frameduration.set_value(new_value * encoder_def->word_length, false);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// Selecting word duration changes input clock and symbol duration
 | 
			
		||||
	field_frameduration.on_change = [this](int32_t value) {
 | 
			
		||||
		// value is in us, new_value is in kHz
 | 
			
		||||
		int32_t new_value = (value * 1000) / (encoder_def->word_length * encoder_def->clk_per_symbol);
 | 
			
		||||
		if (new_value != field_clk.value())
 | 
			
		||||
			field_clk.set_value(1000000 / new_value, false);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersConfigView::focus() {
 | 
			
		||||
	options_enctype.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersConfigView::on_type_change(size_t index) {
 | 
			
		||||
	std::string format_string = "";
 | 
			
		||||
	size_t word_length;
 | 
			
		||||
	char symbol_type;
 | 
			
		||||
 | 
			
		||||
	encoder_def = &encoder_defs[index];
 | 
			
		||||
 | 
			
		||||
	field_clk.set_value(encoder_def->default_speed / 1000);
 | 
			
		||||
	
 | 
			
		||||
	// SymField setup
 | 
			
		||||
	word_length = encoder_def->word_length;
 | 
			
		||||
	symfield_word.set_length(word_length);
 | 
			
		||||
	size_t n = 0, i = 0;
 | 
			
		||||
	while (n < word_length) {
 | 
			
		||||
		symbol_type = encoder_def->word_format[i++];
 | 
			
		||||
		if (symbol_type == 'A') {
 | 
			
		||||
			symfield_word.set_symbol_list(n++, encoder_def->address_symbols);
 | 
			
		||||
			format_string += 'A';
 | 
			
		||||
		} else if (symbol_type == 'D') {
 | 
			
		||||
			symfield_word.set_symbol_list(n++, encoder_def->data_symbols);
 | 
			
		||||
			format_string += 'D';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Ugly :( Pad to erase
 | 
			
		||||
	format_string.append(24 - format_string.size(), ' ');
 | 
			
		||||
	
 | 
			
		||||
	text_format.set(format_string);
 | 
			
		||||
 | 
			
		||||
	generate_frame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersConfigView::on_show() {
 | 
			
		||||
	options_enctype.set_selected_index(0);
 | 
			
		||||
	on_type_change(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersConfigView::draw_waveform() {
 | 
			
		||||
	size_t length = frame_fragments.length();
 | 
			
		||||
 | 
			
		||||
	for (size_t n = 0; n < length; n++)
 | 
			
		||||
		waveform_buffer[n] = (frame_fragments[n] == '0') ? 0 : 1;
 | 
			
		||||
	
 | 
			
		||||
	waveform.set_length(length);
 | 
			
		||||
	waveform.set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersConfigView::generate_frame() {
 | 
			
		||||
	size_t i = 0;
 | 
			
		||||
	
 | 
			
		||||
	frame_fragments.clear();
 | 
			
		||||
	
 | 
			
		||||
	for (auto c : encoder_def->word_format) {
 | 
			
		||||
		if (c == 'S')
 | 
			
		||||
			frame_fragments += encoder_def->sync;
 | 
			
		||||
		else
 | 
			
		||||
			frame_fragments += encoder_def->bit_format[symfield_word.get_sym(i++)];
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	draw_waveform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t EncodersConfigView::repeat_min() {
 | 
			
		||||
	return encoder_def->repeat_min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t EncodersConfigView::samples_per_bit() {
 | 
			
		||||
	return OOK_SAMPLERATE / ((field_clk.value() * 1000) / encoder_def->clk_per_fragment);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t EncodersConfigView::pause_symbols() {
 | 
			
		||||
	return encoder_def->pause_symbols;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersScanView::focus() {
 | 
			
		||||
	field_debug.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EncodersScanView::EncodersScanView(
 | 
			
		||||
	NavigationView&, Rect parent_rect
 | 
			
		||||
) {
 | 
			
		||||
	set_parent_rect(parent_rect);
 | 
			
		||||
	hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_debug,
 | 
			
		||||
		&text_debug,
 | 
			
		||||
		&text_length
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	field_debug.on_change = [this](int32_t value) {
 | 
			
		||||
		uint32_t l;
 | 
			
		||||
		size_t length;
 | 
			
		||||
		
 | 
			
		||||
		de_bruijn debruijn_seq;
 | 
			
		||||
		length = debruijn_seq.init(value);
 | 
			
		||||
		
 | 
			
		||||
		l = 1;
 | 
			
		||||
		l <<= value;
 | 
			
		||||
		l--;
 | 
			
		||||
		if (l > 25)
 | 
			
		||||
			l = 25;
 | 
			
		||||
		text_debug.set(to_string_bin(debruijn_seq.compute(l), 25));
 | 
			
		||||
		
 | 
			
		||||
		text_length.set(to_string_dec_uint(length));
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersView::focus() {
 | 
			
		||||
	tab_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EncodersView::~EncodersView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_ook", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersView::update_progress() {
 | 
			
		||||
	std::string str_buffer;
 | 
			
		||||
	
 | 
			
		||||
	// text_status.set("            ");
 | 
			
		||||
	
 | 
			
		||||
	if (tx_mode == SINGLE) {
 | 
			
		||||
		str_buffer = to_string_dec_uint(repeat_index) + "/" + to_string_dec_uint(repeat_min);
 | 
			
		||||
		text_status.set(str_buffer);
 | 
			
		||||
		progressbar.set_value(repeat_index);
 | 
			
		||||
		
 | 
			
		||||
	/*} else if (tx_mode == SCAN) {
 | 
			
		||||
		strcpy(str, to_string_dec_uint(repeat_index).c_str());
 | 
			
		||||
		strcat(str, "/");
 | 
			
		||||
		strcat(str, to_string_dec_uint(portapack::persistent_memory::afsk_repeats()).c_str());
 | 
			
		||||
		strcat(str, " ");
 | 
			
		||||
		strcat(str, to_string_dec_uint(scan_index + 1).c_str());
 | 
			
		||||
		strcat(str, "/");
 | 
			
		||||
		strcat(str, to_string_dec_uint(scan_count).c_str());
 | 
			
		||||
		text_status.set(str);
 | 
			
		||||
		progress.set_value(scan_progress);*/
 | 
			
		||||
	} else {
 | 
			
		||||
		text_status.set("Ready");
 | 
			
		||||
		progressbar.set_value(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	//char str[16];
 | 
			
		||||
	
 | 
			
		||||
	if (!done) {
 | 
			
		||||
		// Repeating...
 | 
			
		||||
		repeat_index = progress + 1;
 | 
			
		||||
		
 | 
			
		||||
		/*if (tx_mode == SCAN) {
 | 
			
		||||
			scan_progress++;
 | 
			
		||||
			update_progress();
 | 
			
		||||
		} else {*/
 | 
			
		||||
			update_progress();
 | 
			
		||||
		//}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Done transmitting
 | 
			
		||||
		/*if ((tx_mode == SCAN) && (scan_index < (scan_count - 1))) {
 | 
			
		||||
			transmitter_model.disable();
 | 
			
		||||
			if (abort_scan) {
 | 
			
		||||
				// Kill scan process
 | 
			
		||||
				strcpy(str, "Abort @");
 | 
			
		||||
				strcat(str, rgsb);
 | 
			
		||||
				text_status.set(str);
 | 
			
		||||
				progress.set_value(0);
 | 
			
		||||
				tx_mode = IDLE;
 | 
			
		||||
				abort_scan = false;
 | 
			
		||||
				button_scan.set_style(&style_val);
 | 
			
		||||
				button_scan.set_text("SCAN");
 | 
			
		||||
			} else {
 | 
			
		||||
				// Next address
 | 
			
		||||
				scan_index++;
 | 
			
		||||
				strcpy(rgsb, &scan_list[options_scanlist.selected_index()].addresses[scan_index * 5]);
 | 
			
		||||
				scan_progress++;
 | 
			
		||||
				repeat_index = 1;
 | 
			
		||||
				update_progress();
 | 
			
		||||
				start_tx(true);
 | 
			
		||||
			}
 | 
			
		||||
		} else {*/
 | 
			
		||||
			transmitter_model.disable();
 | 
			
		||||
			tx_mode = IDLE;
 | 
			
		||||
			text_status.set("Done");
 | 
			
		||||
			progressbar.set_value(0);
 | 
			
		||||
			tx_view.set_transmitting(false);
 | 
			
		||||
		//}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EncodersView::start_tx(const bool scan) {
 | 
			
		||||
	(void)scan;
 | 
			
		||||
	size_t bitstream_length = 0;
 | 
			
		||||
	
 | 
			
		||||
	repeat_min = view_config.repeat_min();
 | 
			
		||||
	
 | 
			
		||||
	/*if (scan) {
 | 
			
		||||
		if (tx_mode != SCAN) {
 | 
			
		||||
			scan_index = 0;
 | 
			
		||||
			scan_count = scan_list[options_scanlist.selected_index()].count;
 | 
			
		||||
			scan_progress = 1;
 | 
			
		||||
			repeat_index = 1;
 | 
			
		||||
			tx_mode = SCAN;
 | 
			
		||||
			strcpy(rgsb, &scan_list[options_scanlist.selected_index()].addresses[0]);
 | 
			
		||||
			progress.set_max(scan_count * afsk_repeats);
 | 
			
		||||
			update_progress();
 | 
			
		||||
		}
 | 
			
		||||
	} else {*/
 | 
			
		||||
		tx_mode = SINGLE;
 | 
			
		||||
		repeat_index = 1;
 | 
			
		||||
		progressbar.set_max(repeat_min);
 | 
			
		||||
		update_progress();
 | 
			
		||||
	//}
 | 
			
		||||
	
 | 
			
		||||
	view_config.generate_frame();
 | 
			
		||||
	
 | 
			
		||||
	bitstream_length = make_bitstream(view_config.frame_fragments);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
 | 
			
		||||
	transmitter_model.set_rf_amp(true);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_ook_data(
 | 
			
		||||
		bitstream_length,
 | 
			
		||||
		view_config.samples_per_bit(),
 | 
			
		||||
		repeat_min,
 | 
			
		||||
		view_config.pause_symbols()
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EncodersView::EncodersView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ { nav }
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_ook);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&view_config,
 | 
			
		||||
		&view_scan,
 | 
			
		||||
		&text_status,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_ook", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
		start_tx(false);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,230 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "encoders.hpp"
 | 
			
		||||
#include "de_bruijn.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace encoders;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class EncodersConfigView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	EncodersConfigView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	EncodersConfigView(const EncodersConfigView&) = delete;
 | 
			
		||||
	EncodersConfigView(EncodersConfigView&&) = delete;
 | 
			
		||||
	EncodersConfigView& operator=(const EncodersConfigView&) = delete;
 | 
			
		||||
	EncodersConfigView& operator=(EncodersConfigView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	
 | 
			
		||||
	uint8_t repeat_min();
 | 
			
		||||
	uint32_t samples_per_bit();
 | 
			
		||||
	uint32_t pause_symbols();
 | 
			
		||||
	void generate_frame();
 | 
			
		||||
	
 | 
			
		||||
	std::string frame_fragments = "0";
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	//bool abort_scan = false;
 | 
			
		||||
	//uint8_t scan_count;
 | 
			
		||||
	//double scan_progress;
 | 
			
		||||
	//unsigned int scan_index;
 | 
			
		||||
	int16_t waveform_buffer[550];
 | 
			
		||||
	const encoder_def_t * encoder_def { };
 | 
			
		||||
	//uint8_t enc_type = 0;
 | 
			
		||||
 | 
			
		||||
	void draw_waveform();
 | 
			
		||||
	void on_bitfield();
 | 
			
		||||
	void on_type_change(size_t index);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 0 }, "Type:", Color::light_grey() },
 | 
			
		||||
		{ { 16 * 8, 0 }, "Clk:", Color::light_grey() },
 | 
			
		||||
		{ { 24 * 8, 0 }, "kHz", Color::light_grey() },
 | 
			
		||||
		{ { 14 * 8, 2 * 8 }, "Frame:", Color::light_grey() },
 | 
			
		||||
		{ { 26 * 8, 2 * 8 }, "us", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 4 * 8 }, "Symbols:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 11 * 8 }, "Waveform:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_enctype {		// Options are loaded at runtime
 | 
			
		||||
		{ 6 * 8, 0 },
 | 
			
		||||
		7,
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_clk {
 | 
			
		||||
		{ 21 * 8, 0 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 1, 500 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_frameduration {
 | 
			
		||||
		{ 21 * 8, 2 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		{ 300, 99999 },
 | 
			
		||||
		100,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField symfield_word {
 | 
			
		||||
		{ 2 * 8, 6 * 8 },
 | 
			
		||||
		20,
 | 
			
		||||
		SymField::SYMFIELD_DEF
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_format {
 | 
			
		||||
		{ 2 * 8, 8 * 8, 24 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Waveform waveform {
 | 
			
		||||
		{ 0, 14 * 8, 240, 32 },
 | 
			
		||||
		waveform_buffer,
 | 
			
		||||
		0,
 | 
			
		||||
		0,
 | 
			
		||||
		true,
 | 
			
		||||
		Color::yellow()
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EncodersScanView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	EncodersScanView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 1 * 8 }, "Coming soon...", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	NumberField field_debug {
 | 
			
		||||
		{ 1 * 8, 6 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 3, 16 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	Text text_debug {
 | 
			
		||||
		{ 1 * 8, 8 * 8, 24 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	Text text_length {
 | 
			
		||||
		{ 1 * 8, 10 * 8, 24 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EncodersView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	EncodersView(NavigationView& nav);
 | 
			
		||||
	~EncodersView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "OOK TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SCAN
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
	uint8_t repeat_index { 0 };
 | 
			
		||||
	uint8_t repeat_min { 0 };
 | 
			
		||||
	
 | 
			
		||||
	void update_progress();
 | 
			
		||||
	void start_tx(const bool scan);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	/*const Style style_address {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::red(),
 | 
			
		||||
	};
 | 
			
		||||
	const Style style_data {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::blue(),
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	Rect view_rect = { 0, 4 * 8, 240, 168 };
 | 
			
		||||
	
 | 
			
		||||
	EncodersConfigView view_config { nav_, view_rect };
 | 
			
		||||
	EncodersScanView view_scan { nav_, view_rect };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Config", Color::cyan(), &view_config },
 | 
			
		||||
		{ "Scan", Color::green(), &view_scan },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_status {
 | 
			
		||||
		{ 2 * 8, 13 * 16, 128, 16 },
 | 
			
		||||
		"Ready"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 2 * 8, 13 * 16 + 20, 208, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		50000,
 | 
			
		||||
		9
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,341 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_fileman.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
 | 
			
		||||
	current_path = dir_path;
 | 
			
		||||
	
 | 
			
		||||
	text_current.set(dir_path.string().length()? dir_path.string().substr(0, 30 - 6):"(sd root)");
 | 
			
		||||
 | 
			
		||||
	entry_list.clear();
 | 
			
		||||
	
 | 
			
		||||
	auto filtering = (bool)extension_filter.size();
 | 
			
		||||
	
 | 
			
		||||
	// List directories and files, put directories up top
 | 
			
		||||
	if (dir_path.string().length())
 | 
			
		||||
		entry_list.push_back({ u"..", 0, true });
 | 
			
		||||
 | 
			
		||||
	for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) {
 | 
			
		||||
 | 
			
		||||
		// do not display dir / files starting with '.' (hidden / tmp)
 | 
			
		||||
		if (entry.path().string().length() && entry.path().filename().string()[0] != '.') {
 | 
			
		||||
			if (std::filesystem::is_regular_file(entry.status())) {
 | 
			
		||||
				bool matched = true;
 | 
			
		||||
				if (filtering) {
 | 
			
		||||
					auto entry_extension = entry.path().extension().string();
 | 
			
		||||
				
 | 
			
		||||
					for (auto &c: entry_extension)
 | 
			
		||||
						c = toupper(c);
 | 
			
		||||
					
 | 
			
		||||
					if (entry_extension != extension_filter)
 | 
			
		||||
						matched = false;
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				if (matched)
 | 
			
		||||
					entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false });
 | 
			
		||||
			} else if (std::filesystem::is_directory(entry.status())) {
 | 
			
		||||
				entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::filesystem::path FileManBaseView::get_selected_path() {
 | 
			
		||||
	auto selected_path_str = current_path.string();
 | 
			
		||||
	auto entry_path = entry_list[menu_view.highlighted_index()].entry_path.string();
 | 
			
		||||
	
 | 
			
		||||
	if (entry_path == "..") {
 | 
			
		||||
		selected_path_str = get_parent_dir().string();
 | 
			
		||||
	} else {
 | 
			
		||||
		if (selected_path_str.back() != '/')
 | 
			
		||||
			selected_path_str += '/';
 | 
			
		||||
		
 | 
			
		||||
		selected_path_str += entry_path;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return selected_path_str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::filesystem::path FileManBaseView::get_parent_dir() {
 | 
			
		||||
	auto current_path_str = current_path.string();
 | 
			
		||||
	return current_path.string().substr(0, current_path_str.find_last_of('/'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileManBaseView::FileManBaseView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	std::string filter
 | 
			
		||||
) : nav_ (nav),
 | 
			
		||||
	extension_filter { filter }
 | 
			
		||||
{
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&text_current,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	button_exit.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!sdcIsCardInserted(&SDCD1)) {
 | 
			
		||||
		empty_root=true;
 | 
			
		||||
		text_current.set("NO SD CARD!");
 | 
			
		||||
	} else {
 | 
			
		||||
		load_directory_contents(current_path);
 | 
			
		||||
		if (!entry_list.size())
 | 
			
		||||
		{
 | 
			
		||||
			empty_root = true;
 | 
			
		||||
			text_current.set("EMPTY SD CARD!");
 | 
			
		||||
		} else {
 | 
			
		||||
			menu_view.on_left = [&nav, this]() {
 | 
			
		||||
				load_directory_contents(get_parent_dir());
 | 
			
		||||
				refresh_list();
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileManBaseView::focus() {
 | 
			
		||||
	if (empty_root) {
 | 
			
		||||
		button_exit.focus();
 | 
			
		||||
	} else {
 | 
			
		||||
		menu_view.focus();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileManBaseView::refresh_list() {
 | 
			
		||||
	if (on_refresh_widgets)
 | 
			
		||||
		on_refresh_widgets(false);
 | 
			
		||||
 | 
			
		||||
	menu_view.clear();
 | 
			
		||||
	
 | 
			
		||||
	for (size_t n = 0; n < entry_list.size(); n++) {
 | 
			
		||||
		auto entry = &entry_list[n];
 | 
			
		||||
		auto entry_name = entry->entry_path.filename().string().substr(0, 20);
 | 
			
		||||
		
 | 
			
		||||
		if (entry->is_directory) {
 | 
			
		||||
			
 | 
			
		||||
			menu_view.add_item({
 | 
			
		||||
				entry_name,
 | 
			
		||||
				ui::Color::yellow(),
 | 
			
		||||
				&bitmap_icon_dir,
 | 
			
		||||
				[this](){
 | 
			
		||||
					if (on_select_entry)
 | 
			
		||||
						on_select_entry();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			
 | 
			
		||||
		} else {
 | 
			
		||||
			
 | 
			
		||||
			auto file_size = entry->size;
 | 
			
		||||
			size_t suffix_index = 0;
 | 
			
		||||
			
 | 
			
		||||
			while (file_size >= 1024) {
 | 
			
		||||
				file_size /= 1024;
 | 
			
		||||
				suffix_index++;
 | 
			
		||||
			}
 | 
			
		||||
			if (suffix_index > 4)
 | 
			
		||||
				suffix_index = 4;
 | 
			
		||||
			
 | 
			
		||||
			std::string size_str = to_string_dec_uint(file_size) + suffix[suffix_index];
 | 
			
		||||
			
 | 
			
		||||
			auto entry_extension = entry->entry_path.extension().string();
 | 
			
		||||
			for (auto &c: entry_extension)
 | 
			
		||||
				c = toupper(c);
 | 
			
		||||
			
 | 
			
		||||
			// Associate extension to icon and color
 | 
			
		||||
			size_t c;
 | 
			
		||||
			for (c = 0; c < file_types.size() - 1; c++) {
 | 
			
		||||
				if (entry_extension == file_types[c].extension)
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			menu_view.add_item({
 | 
			
		||||
				entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
 | 
			
		||||
				file_types[c].color,
 | 
			
		||||
				file_types[c].icon,
 | 
			
		||||
				[this](){
 | 
			
		||||
					if (on_select_entry)
 | 
			
		||||
						on_select_entry();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	menu_view.set_highlighted(0);	// Refresh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*void FileSaveView::on_save_name() {
 | 
			
		||||
	text_prompt(nav_, &filename_buffer, 8, [this](std::string * buffer) {
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
FileSaveView::FileSaveView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : FileManBaseView(nav)
 | 
			
		||||
{
 | 
			
		||||
	name_buffer.clear();
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_save,
 | 
			
		||||
		&button_save_name,
 | 
			
		||||
		&live_timestamp
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	button_save_name.on_select = [this, &nav](Button&) {
 | 
			
		||||
		on_save_name();
 | 
			
		||||
	};
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
void FileLoadView::refresh_widgets(const bool v) {
 | 
			
		||||
	(void)v; //avoid unused warning
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileLoadView::FileLoadView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	std::string filter
 | 
			
		||||
) : FileManBaseView(nav, filter)
 | 
			
		||||
{
 | 
			
		||||
	on_refresh_widgets = [this](bool v) {
 | 
			
		||||
		refresh_widgets(v);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&menu_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// Resize menu view to fill screen
 | 
			
		||||
	menu_view.set_parent_rect({ 0, 3 * 8, 240, 29 * 8 });
 | 
			
		||||
	
 | 
			
		||||
	refresh_list();
 | 
			
		||||
	
 | 
			
		||||
	on_select_entry = [&nav, this]() {
 | 
			
		||||
		if (entry_list[menu_view.highlighted_index()].is_directory) {
 | 
			
		||||
			load_directory_contents(get_selected_path());
 | 
			
		||||
			refresh_list();
 | 
			
		||||
		} else {
 | 
			
		||||
			nav_.pop();
 | 
			
		||||
			if (on_changed)
 | 
			
		||||
				on_changed(current_path.string() + '/' + entry_list[menu_view.highlighted_index()].entry_path.string());
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileManagerView::on_rename(NavigationView& nav) {
 | 
			
		||||
	text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
 | 
			
		||||
		std::string destination_path = current_path.string();
 | 
			
		||||
		if (destination_path.back() != '/')
 | 
			
		||||
			destination_path += '/';
 | 
			
		||||
		destination_path = destination_path + buffer;
 | 
			
		||||
		rename_file(get_selected_path(), destination_path);
 | 
			
		||||
		load_directory_contents(current_path);
 | 
			
		||||
		refresh_list();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileManagerView::on_delete() {
 | 
			
		||||
	delete_file(get_selected_path());
 | 
			
		||||
	load_directory_contents(current_path);
 | 
			
		||||
	refresh_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileManagerView::refresh_widgets(const bool v) {
 | 
			
		||||
	button_rename.hidden(v);
 | 
			
		||||
	button_new_dir.hidden(v);
 | 
			
		||||
	button_delete.hidden(v);
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileManagerView::~FileManagerView() {
 | 
			
		||||
	// Flush ?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileManagerView::FileManagerView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : FileManBaseView(nav, "")
 | 
			
		||||
{
 | 
			
		||||
	if (!empty_root) {
 | 
			
		||||
		on_refresh_widgets = [this](bool v) {
 | 
			
		||||
			refresh_widgets(v);
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		add_children({
 | 
			
		||||
			&menu_view,
 | 
			
		||||
			&labels,
 | 
			
		||||
			&text_date,
 | 
			
		||||
			&button_rename,
 | 
			
		||||
			&button_new_dir,
 | 
			
		||||
			&button_delete
 | 
			
		||||
		});
 | 
			
		||||
		
 | 
			
		||||
		menu_view.on_highlight = [this]() {
 | 
			
		||||
			text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_path())));
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		refresh_list();
 | 
			
		||||
		
 | 
			
		||||
		on_select_entry = [this]() {
 | 
			
		||||
			if (entry_list[menu_view.highlighted_index()].is_directory) {
 | 
			
		||||
				load_directory_contents(get_selected_path());
 | 
			
		||||
				refresh_list();
 | 
			
		||||
			} else
 | 
			
		||||
				button_rename.focus();
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		button_new_dir.on_select = [this, &nav](Button&) {
 | 
			
		||||
			name_buffer.clear();
 | 
			
		||||
			
 | 
			
		||||
			text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
 | 
			
		||||
				make_new_directory(current_path.string() + '/' + buffer);
 | 
			
		||||
				load_directory_contents(current_path);
 | 
			
		||||
				refresh_list();
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		button_rename.on_select = [this, &nav](Button&) {
 | 
			
		||||
			name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
 | 
			
		||||
			on_rename(nav);
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		button_delete.on_select = [this, &nav](Button&) {
 | 
			
		||||
			// Use display_modal ?
 | 
			
		||||
			nav.push<ModalMessageView>("Delete", "Delete " + entry_list[menu_view.highlighted_index()].entry_path.filename().string() + "\nAre you sure?", YESNO,
 | 
			
		||||
				[this](bool choice) {
 | 
			
		||||
					if (choice)
 | 
			
		||||
						on_delete();
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
		};
 | 
			
		||||
	} 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_painter.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
struct fileman_entry {
 | 
			
		||||
	std::filesystem::path entry_path { };
 | 
			
		||||
	uint32_t size { };
 | 
			
		||||
	bool is_directory { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FileManBaseView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	FileManBaseView(
 | 
			
		||||
		NavigationView& nav,
 | 
			
		||||
		std::string filter
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	void load_directory_contents(const std::filesystem::path& dir_path);
 | 
			
		||||
	std::filesystem::path get_selected_path();
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "File manager"; };
 | 
			
		||||
	
 | 
			
		||||
protected:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr size_t max_filename_length = 30 - 2;
 | 
			
		||||
	
 | 
			
		||||
	const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
 | 
			
		||||
	
 | 
			
		||||
	struct file_assoc_t {
 | 
			
		||||
		std::string extension;
 | 
			
		||||
		const Bitmap* icon;
 | 
			
		||||
		ui::Color color;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const std::vector<file_assoc_t> file_types = {
 | 
			
		||||
		{ ".TXT", &bitmap_icon_file_text, ui::Color::white() },
 | 
			
		||||
		{ ".PNG", &bitmap_icon_file_image, ui::Color::green() },
 | 
			
		||||
		{ ".BMP", &bitmap_icon_file_image, ui::Color::green() },
 | 
			
		||||
		{ ".C8",  &bitmap_icon_file_iq, ui::Color::blue() },
 | 
			
		||||
		{ ".C16", &bitmap_icon_file_iq, ui::Color::blue() },
 | 
			
		||||
		{ ".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
 | 
			
		||||
		{ "", &bitmap_icon_file, ui::Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	bool empty_root { false };
 | 
			
		||||
	std::function<void(void)> on_select_entry { nullptr };
 | 
			
		||||
	std::function<void(bool)> on_refresh_widgets { nullptr };
 | 
			
		||||
	std::vector<fileman_entry> entry_list { };
 | 
			
		||||
	std::filesystem::path current_path { u"" };
 | 
			
		||||
	std::string extension_filter { "" };
 | 
			
		||||
	
 | 
			
		||||
	void change_category(int32_t category_id);
 | 
			
		||||
	std::filesystem::path get_parent_dir();
 | 
			
		||||
	void refresh_list();
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0, 0 }, "Path:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	Text text_current {
 | 
			
		||||
		{ 6 * 8, 0 * 8, 24 * 8, 16 },
 | 
			
		||||
		"",
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MenuView menu_view {
 | 
			
		||||
		{ 0, 2 * 8, 240, 26 * 8 },
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_exit {
 | 
			
		||||
		{ 16 * 8, 34 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*class FileSaveView : public FileManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	FileSaveView(NavigationView& nav);
 | 
			
		||||
	~FileSaveView();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::string name_buffer { };
 | 
			
		||||
	
 | 
			
		||||
	void on_save_name();
 | 
			
		||||
	
 | 
			
		||||
	Text text_save {
 | 
			
		||||
		{ 4 * 8, 15 * 8, 8 * 8, 16 },
 | 
			
		||||
		"Save as:",
 | 
			
		||||
	};
 | 
			
		||||
	Button button_save_name {
 | 
			
		||||
		{ 4 * 8, 18 * 8, 12 * 8, 32 },
 | 
			
		||||
		"Name (set)"
 | 
			
		||||
	};
 | 
			
		||||
	LiveDateTime live_timestamp {
 | 
			
		||||
		{ 17 * 8, 24 * 8, 11 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
class FileLoadView : public FileManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	std::function<void(std::filesystem::path)> on_changed { };
 | 
			
		||||
	
 | 
			
		||||
	FileLoadView(NavigationView& nav, std::string filter);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void refresh_widgets(const bool v);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FileManagerView : public FileManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	FileManagerView(NavigationView& nav);
 | 
			
		||||
	~FileManagerView();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::string name_buffer { };
 | 
			
		||||
	
 | 
			
		||||
	void refresh_widgets(const bool v);
 | 
			
		||||
	void on_rename(NavigationView& nav);
 | 
			
		||||
	void on_delete();
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_date {
 | 
			
		||||
		{ 8 * 8, 26 * 8 , 19 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_rename {
 | 
			
		||||
		{ 0 * 8, 29 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Rename"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_delete {
 | 
			
		||||
		{ 16 * 8, 29 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Delete"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_new_dir {
 | 
			
		||||
		{ 0 * 8, 34 * 8, 14 * 8, 32 },
 | 
			
		||||
		"New dir"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,350 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_freqman.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
static int32_t last_category_id { 0 };
 | 
			
		||||
 | 
			
		||||
FreqManBaseView::FreqManBaseView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	file_list = get_freqman_files();
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_category,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	if (file_list.size()) {
 | 
			
		||||
		add_child(&options_category);
 | 
			
		||||
		populate_categories();
 | 
			
		||||
	} else
 | 
			
		||||
		error_ = ERROR_NOFILES;
 | 
			
		||||
	
 | 
			
		||||
	// initialize
 | 
			
		||||
	change_category(last_category_id);
 | 
			
		||||
	
 | 
			
		||||
	// Default function
 | 
			
		||||
	on_change_category = [this](int32_t category_id) {
 | 
			
		||||
		change_category(category_id);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_exit.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void FreqManBaseView::focus() {
 | 
			
		||||
	button_exit.focus();
 | 
			
		||||
	
 | 
			
		||||
	if (error_ == ERROR_ACCESS) {
 | 
			
		||||
		nav_.display_modal("Error", "File acces error", ABORT, nullptr);
 | 
			
		||||
	} else if (error_ == ERROR_NOFILES) {
 | 
			
		||||
		nav_.display_modal("Error", "No database files\nin /freqman", ABORT, nullptr);
 | 
			
		||||
	} else {
 | 
			
		||||
		options_category.focus();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FreqManBaseView::populate_categories() {
 | 
			
		||||
	categories.clear();
 | 
			
		||||
	
 | 
			
		||||
	for (size_t n = 0; n < file_list.size(); n++)
 | 
			
		||||
		categories.emplace_back(std::make_pair(file_list[n].substr(0, 14), n));
 | 
			
		||||
	
 | 
			
		||||
	// Alphabetical sort
 | 
			
		||||
	std::sort(categories.begin(), categories.end(), [](auto &left, auto &right) {
 | 
			
		||||
		return left.first < right.first;
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	options_category.set_options(categories);
 | 
			
		||||
	options_category.set_selected_index(last_category_id);
 | 
			
		||||
	
 | 
			
		||||
	options_category.on_change = [this](size_t category_id, int32_t) {
 | 
			
		||||
		if (on_change_category)
 | 
			
		||||
			on_change_category(category_id);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FreqManBaseView::change_category(int32_t category_id) {
 | 
			
		||||
	
 | 
			
		||||
	if (!file_list.size()) return;
 | 
			
		||||
	
 | 
			
		||||
	last_category_id = current_category_id = category_id;
 | 
			
		||||
	
 | 
			
		||||
	if (!load_freqman_file(file_list[categories[current_category_id].second], database))
 | 
			
		||||
		error_ = ERROR_ACCESS;
 | 
			
		||||
	else
 | 
			
		||||
		refresh_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FreqManBaseView::refresh_list() {
 | 
			
		||||
	if (!database.size()) {
 | 
			
		||||
		if (on_refresh_widgets)
 | 
			
		||||
			on_refresh_widgets(true);
 | 
			
		||||
	} else {
 | 
			
		||||
		if (on_refresh_widgets)
 | 
			
		||||
			on_refresh_widgets(false);
 | 
			
		||||
	
 | 
			
		||||
		menu_view.clear();
 | 
			
		||||
		
 | 
			
		||||
		for (size_t n = 0; n < database.size(); n++) {
 | 
			
		||||
			menu_view.add_item({
 | 
			
		||||
				freqman_item_string(database[n], 30),
 | 
			
		||||
				ui::Color::white(),
 | 
			
		||||
				nullptr,
 | 
			
		||||
				[this](){
 | 
			
		||||
					if (on_select_frequency)
 | 
			
		||||
						on_select_frequency();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		menu_view.set_highlighted(0);	// Refresh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencySaveView::save_current_file() {
 | 
			
		||||
	if (database.size() > FREQMAN_MAX_PER_FILE) {
 | 
			
		||||
		nav_.display_modal(
 | 
			
		||||
			"Error", "Too many entries, maximum is\n" FREQMAN_MAX_PER_FILE_STR ". Trim list ?",
 | 
			
		||||
			YESNO,
 | 
			
		||||
			[this](bool choice) {
 | 
			
		||||
				if (choice) {
 | 
			
		||||
					database.resize(FREQMAN_MAX_PER_FILE);
 | 
			
		||||
					save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
				}
 | 
			
		||||
				nav_.pop();
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencySaveView::on_save_name() {
 | 
			
		||||
	text_prompt(nav_, desc_buffer, 28, [this](std::string& buffer) {
 | 
			
		||||
		database.push_back({ value_, 0, buffer, SINGLE });
 | 
			
		||||
		save_current_file();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencySaveView::on_save_timestamp() {
 | 
			
		||||
	database.push_back({ value_, 0, live_timestamp.string(), SINGLE });
 | 
			
		||||
	save_current_file();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FrequencySaveView::FrequencySaveView(
 | 
			
		||||
	NavigationView& nav,
 | 
			
		||||
	const rf::Frequency value
 | 
			
		||||
) : FreqManBaseView(nav),
 | 
			
		||||
	value_ (value)
 | 
			
		||||
{
 | 
			
		||||
	desc_buffer.reserve(28);
 | 
			
		||||
	
 | 
			
		||||
	// Todo: add back ?
 | 
			
		||||
	/*for (size_t n = 0; n < database.size(); n++) {
 | 
			
		||||
		if (database[n].value == value_) {
 | 
			
		||||
			error_ = ERROR_DUPLICATE;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}*/
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&big_display,
 | 
			
		||||
		&button_save_name,
 | 
			
		||||
		&button_save_timestamp,
 | 
			
		||||
		&live_timestamp
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	big_display.set(value);
 | 
			
		||||
	
 | 
			
		||||
	button_save_name.on_select = [this, &nav](Button&) {
 | 
			
		||||
		on_save_name();
 | 
			
		||||
	};
 | 
			
		||||
	button_save_timestamp.on_select = [this, &nav](Button&) {
 | 
			
		||||
		on_save_timestamp();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyLoadView::refresh_widgets(const bool v) {
 | 
			
		||||
	menu_view.hidden(v);
 | 
			
		||||
	text_empty.hidden(!v);
 | 
			
		||||
	//display.fill_rectangle(menu_view.screen_rect(), Color::black());
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FrequencyLoadView::FrequencyLoadView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : FreqManBaseView(nav)
 | 
			
		||||
{
 | 
			
		||||
	on_refresh_widgets = [this](bool v) {
 | 
			
		||||
		refresh_widgets(v);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&menu_view,
 | 
			
		||||
		&text_empty
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// Resize menu view to fill screen
 | 
			
		||||
	menu_view.set_parent_rect({ 0, 3 * 8, 240, 30 * 8 });
 | 
			
		||||
	
 | 
			
		||||
	// Just to allow exit on left
 | 
			
		||||
	menu_view.on_left = [&nav, this]() {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	change_category(last_category_id);
 | 
			
		||||
	refresh_list();
 | 
			
		||||
	
 | 
			
		||||
	on_select_frequency = [&nav, this]() {
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
		
 | 
			
		||||
		auto& entry = database[menu_view.highlighted_index()];
 | 
			
		||||
		
 | 
			
		||||
		if (entry.type == RANGE) {
 | 
			
		||||
			// User chose a frequency range entry
 | 
			
		||||
			if (on_range_loaded)
 | 
			
		||||
				on_range_loaded(entry.frequency_a, entry.frequency_b);
 | 
			
		||||
			else if (on_frequency_loaded)
 | 
			
		||||
				on_frequency_loaded(entry.frequency_a);
 | 
			
		||||
			// TODO: Maybe return center of range if user choses a range when the app needs a unique frequency, instead of frequency_a ?
 | 
			
		||||
		} else {
 | 
			
		||||
			// User chose an unique frequency entry
 | 
			
		||||
			if (on_frequency_loaded)
 | 
			
		||||
				on_frequency_loaded(entry.frequency_a);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
 | 
			
		||||
	database[menu_view.highlighted_index()].frequency_a = f;
 | 
			
		||||
	save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
	refresh_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
 | 
			
		||||
	text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
 | 
			
		||||
		database[menu_view.highlighted_index()].description = buffer;
 | 
			
		||||
		refresh_list();
 | 
			
		||||
		save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyManagerView::on_new_category(NavigationView& nav) {
 | 
			
		||||
	text_prompt(nav, desc_buffer, 12, [this](std::string& buffer) {
 | 
			
		||||
		File freqman_file;
 | 
			
		||||
		create_freqman_file(buffer, freqman_file);
 | 
			
		||||
	});
 | 
			
		||||
	populate_categories();
 | 
			
		||||
	refresh_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyManagerView::on_delete() {
 | 
			
		||||
	database.erase(database.begin() + menu_view.highlighted_index());
 | 
			
		||||
	save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
	refresh_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FrequencyManagerView::refresh_widgets(const bool v) {
 | 
			
		||||
	button_edit_freq.hidden(v);
 | 
			
		||||
	button_edit_desc.hidden(v);
 | 
			
		||||
	button_delete.hidden(v);
 | 
			
		||||
	menu_view.hidden(v);
 | 
			
		||||
	text_empty.hidden(!v);
 | 
			
		||||
	//display.fill_rectangle(menu_view.screen_rect(), Color::black());
 | 
			
		||||
	set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FrequencyManagerView::~FrequencyManagerView() {
 | 
			
		||||
	//save_freqman_file(file_list[categories[current_category_id].second], database);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FrequencyManagerView::FrequencyManagerView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : FreqManBaseView(nav)
 | 
			
		||||
{
 | 
			
		||||
	on_refresh_widgets = [this](bool v) {
 | 
			
		||||
		refresh_widgets(v);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button_new_category,
 | 
			
		||||
		&menu_view,
 | 
			
		||||
		&text_empty,
 | 
			
		||||
		&button_edit_freq,
 | 
			
		||||
		&button_edit_desc,
 | 
			
		||||
		&button_delete
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// Just to allow exit on left
 | 
			
		||||
	menu_view.on_left = [&nav, this]() {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	change_category(last_category_id);
 | 
			
		||||
	refresh_list();
 | 
			
		||||
	
 | 
			
		||||
	on_select_frequency = [this]() {
 | 
			
		||||
		button_edit_freq.focus();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_new_category.on_select = [this, &nav](Button&) {
 | 
			
		||||
		desc_buffer = "";
 | 
			
		||||
		on_new_category(nav);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_edit_freq.on_select = [this, &nav](Button&) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.highlighted_index()].frequency_a);
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			on_edit_freq(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_edit_desc.on_select = [this, &nav](Button&) {
 | 
			
		||||
		desc_buffer = database[menu_view.highlighted_index()].description;
 | 
			
		||||
		on_edit_desc(nav);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_delete.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.push<ModalMessageView>("Confirm", "Are you sure ?", YESNO,
 | 
			
		||||
			[this](bool choice) {
 | 
			
		||||
				if (choice)
 | 
			
		||||
					on_delete();
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,175 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_painter.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "freqman.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
	
 | 
			
		||||
class FreqManBaseView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	FreqManBaseView(
 | 
			
		||||
		NavigationView& nav
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
protected:
 | 
			
		||||
	using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	using options_t = std::vector<option_t>;
 | 
			
		||||
	
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	freqman_error error_ { NO_ERROR };
 | 
			
		||||
	options_t categories { };
 | 
			
		||||
	std::function<void(int32_t category_id)> on_change_category { nullptr };
 | 
			
		||||
	std::function<void(void)> on_select_frequency { nullptr };
 | 
			
		||||
	std::function<void(bool)> on_refresh_widgets { nullptr };
 | 
			
		||||
	std::vector<std::string> file_list { };
 | 
			
		||||
	int32_t current_category_id { 0 };
 | 
			
		||||
	
 | 
			
		||||
	void populate_categories();
 | 
			
		||||
	void change_category(int32_t category_id);
 | 
			
		||||
	void refresh_list();
 | 
			
		||||
	
 | 
			
		||||
	freqman_db database { };
 | 
			
		||||
	
 | 
			
		||||
	Labels label_category {
 | 
			
		||||
		{ { 0, 4 }, "Category:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_category {
 | 
			
		||||
		{ 9 * 8, 4 },
 | 
			
		||||
		14,
 | 
			
		||||
		{ }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MenuView menu_view {
 | 
			
		||||
		{ 0, 3 * 8, 240, 23 * 8 },
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	Text text_empty {
 | 
			
		||||
		{ 7 * 8, 12 * 8, 16 * 8, 16 },
 | 
			
		||||
		"Empty category !",
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_exit {
 | 
			
		||||
		{ 20 * 8, 34 * 8, 10 * 8, 4 * 8 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FrequencySaveView : public FreqManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	FrequencySaveView(NavigationView& nav, const rf::Frequency value);
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Save frequency"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	std::string desc_buffer { };
 | 
			
		||||
	rf::Frequency value_ { };
 | 
			
		||||
	
 | 
			
		||||
	void on_save_name();
 | 
			
		||||
	void on_save_timestamp();
 | 
			
		||||
	void save_current_file();
 | 
			
		||||
	
 | 
			
		||||
	BigFrequency big_display {
 | 
			
		||||
		{ 4, 2 * 16, 28 * 8, 32 },
 | 
			
		||||
		0
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 12 * 8 }, "Save as:", Color::white() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save_name {
 | 
			
		||||
		{ 1 * 8, 17 * 8, 12 * 8, 48 },
 | 
			
		||||
		"Name (set)"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_save_timestamp {
 | 
			
		||||
		{ 1 * 8, 25 * 8, 12 * 8, 48 },
 | 
			
		||||
		"Timestamp:"
 | 
			
		||||
	};
 | 
			
		||||
	LiveDateTime live_timestamp {
 | 
			
		||||
		{ 14 * 8, 27 * 8, 16 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FrequencyLoadView : public FreqManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	std::function<void(rf::Frequency)> on_frequency_loaded { };
 | 
			
		||||
	std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded { };
 | 
			
		||||
	
 | 
			
		||||
	FrequencyLoadView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Load frequency"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void refresh_widgets(const bool v);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FrequencyManagerView : public FreqManBaseView {
 | 
			
		||||
public:
 | 
			
		||||
	FrequencyManagerView(NavigationView& nav);
 | 
			
		||||
	~FrequencyManagerView();
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Freq. manager"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	std::string desc_buffer { };
 | 
			
		||||
	
 | 
			
		||||
	void refresh_widgets(const bool v);
 | 
			
		||||
	void on_edit_freq(rf::Frequency f);
 | 
			
		||||
	void on_edit_desc(NavigationView& nav);
 | 
			
		||||
	void on_new_category(NavigationView& nav);
 | 
			
		||||
	void on_delete();
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 4 * 8 + 4, 26 * 8 }, "Edit:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_new_category {
 | 
			
		||||
		{ 23 * 8, 2, 7 * 8, 20 },
 | 
			
		||||
		"New"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_edit_freq {
 | 
			
		||||
		{ 0 * 8, 29 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Frequency"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_edit_desc {
 | 
			
		||||
		{ 0 * 8, 34 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Description"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_delete {
 | 
			
		||||
		{ 18 * 8, 27 * 8, 12 * 8, 32 },
 | 
			
		||||
		"Delete"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,392 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_jammer.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_freqman.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void RangeView::focus() {
 | 
			
		||||
	check_enabled.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern constexpr Style RangeView::style_info;
 | 
			
		||||
 | 
			
		||||
void RangeView::update_start(rf::Frequency f) {
 | 
			
		||||
	// Change everything except max
 | 
			
		||||
	frequency_range.min = f;
 | 
			
		||||
	button_start.set_text(to_string_short_freq(f));
 | 
			
		||||
	
 | 
			
		||||
	center = (frequency_range.min + frequency_range.max) / 2;
 | 
			
		||||
	width = abs(frequency_range.max - frequency_range.min);
 | 
			
		||||
	
 | 
			
		||||
	button_center.set_text(to_string_short_freq(center));
 | 
			
		||||
	button_width.set_text(to_string_short_freq(width));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeView::update_stop(rf::Frequency f) {
 | 
			
		||||
	// Change everything except min
 | 
			
		||||
	frequency_range.max = f;
 | 
			
		||||
	button_stop.set_text(to_string_short_freq(f));
 | 
			
		||||
	
 | 
			
		||||
	center = (frequency_range.min + frequency_range.max) / 2;
 | 
			
		||||
	width = abs(frequency_range.max - frequency_range.min);
 | 
			
		||||
	
 | 
			
		||||
	button_center.set_text(to_string_short_freq(center));
 | 
			
		||||
	button_width.set_text(to_string_short_freq(width));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeView::update_center(rf::Frequency f) {
 | 
			
		||||
	// Change min/max/center, keep width
 | 
			
		||||
	center = f;
 | 
			
		||||
	button_center.set_text(to_string_short_freq(center));
 | 
			
		||||
 | 
			
		||||
	rf::Frequency min = center - (width / 2);
 | 
			
		||||
	rf::Frequency max = min + width;
 | 
			
		||||
	
 | 
			
		||||
	frequency_range.min = min;
 | 
			
		||||
	button_start.set_text(to_string_short_freq(min));
 | 
			
		||||
	
 | 
			
		||||
	frequency_range.max = max;
 | 
			
		||||
	button_stop.set_text(to_string_short_freq(max));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeView::update_width(uint32_t w) {
 | 
			
		||||
	// Change min/max/width, keep center
 | 
			
		||||
	width = w;
 | 
			
		||||
	
 | 
			
		||||
	button_width.set_text(to_string_short_freq(width));
 | 
			
		||||
	
 | 
			
		||||
	rf::Frequency min = center - (width / 2);
 | 
			
		||||
	rf::Frequency max = min + width;
 | 
			
		||||
	
 | 
			
		||||
	frequency_range.min = min;
 | 
			
		||||
	button_start.set_text(to_string_short_freq(min));
 | 
			
		||||
	
 | 
			
		||||
	frequency_range.max = max;
 | 
			
		||||
	button_stop.set_text(to_string_short_freq(max));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeView::paint(Painter&) {
 | 
			
		||||
	// Draw lines and arrows
 | 
			
		||||
	Rect r;
 | 
			
		||||
	Point p;
 | 
			
		||||
	Coord c;
 | 
			
		||||
	
 | 
			
		||||
	r = button_center.screen_rect();
 | 
			
		||||
	p = r.center() + Point(0, r.height() / 2);
 | 
			
		||||
	
 | 
			
		||||
	display.draw_line(p, p + Point(0, 10), Color::grey());
 | 
			
		||||
	
 | 
			
		||||
	r = button_width.screen_rect();
 | 
			
		||||
	c = r.top() + (r.height() / 2);
 | 
			
		||||
	
 | 
			
		||||
	p = {r.left() - 64, c};
 | 
			
		||||
	display.draw_line({r.left(), c}, p, Color::grey());
 | 
			
		||||
	display.draw_line(p, p + Point(10, -10), Color::grey());
 | 
			
		||||
	display.draw_line(p, p + Point(10, 10), Color::grey());
 | 
			
		||||
	
 | 
			
		||||
	p = {r.right() + 64, c};
 | 
			
		||||
	display.draw_line({r.right(), c}, p, Color::grey());
 | 
			
		||||
	display.draw_line(p, p + Point(-10, -10), Color::grey());
 | 
			
		||||
	display.draw_line(p, p + Point(-10, 10), Color::grey());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RangeView::RangeView(NavigationView& nav) {
 | 
			
		||||
	hidden(true);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&check_enabled,
 | 
			
		||||
		&button_load_range,
 | 
			
		||||
		&button_start,
 | 
			
		||||
		&button_stop,
 | 
			
		||||
		&button_center,
 | 
			
		||||
		&button_width
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	check_enabled.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		frequency_range.enabled = v;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_start.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(frequency_range.min);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			update_start(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_stop.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(frequency_range.max);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			update_stop(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_center.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(center);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			update_center(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_width.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(width);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			update_width(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_load_range.on_select = [this, &nav](Button&) {
 | 
			
		||||
		auto load_view = nav.push<FrequencyLoadView>();
 | 
			
		||||
		load_view->on_frequency_loaded = [this](rf::Frequency value) {
 | 
			
		||||
			update_center(value);
 | 
			
		||||
			update_width(100000);	// 100kHz default jamming bandwidth when loading unique frequency
 | 
			
		||||
		};
 | 
			
		||||
		load_view->on_range_loaded = [this](rf::Frequency start, rf::Frequency stop) {
 | 
			
		||||
			update_start(start);
 | 
			
		||||
			update_stop(stop);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	check_enabled.set_value(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JammerView::focus() {
 | 
			
		||||
	tab_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JammerView::~JammerView() {
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JammerView::on_retune(const rf::Frequency freq, const uint32_t range) {
 | 
			
		||||
	if (freq) {
 | 
			
		||||
		transmitter_model.set_tuning_frequency(freq);
 | 
			
		||||
		text_range_number.set(to_string_dec_uint(range, 2));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) {
 | 
			
		||||
	jammer_channels[i].enabled = true;
 | 
			
		||||
	jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
 | 
			
		||||
	jammer_channels[i].center = center;
 | 
			
		||||
	jammer_channels[i].duration = 30720 * duration;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern constexpr Style JammerView::style_val;
 | 
			
		||||
extern constexpr Style JammerView::style_cancel;
 | 
			
		||||
 | 
			
		||||
void JammerView::start_tx() {
 | 
			
		||||
	uint32_t c, i = 0;
 | 
			
		||||
	size_t num_channels;
 | 
			
		||||
	rf::Frequency start_freq, range_bw, range_bw_sub, ch_width;
 | 
			
		||||
	bool out_of_ranges = false;
 | 
			
		||||
	
 | 
			
		||||
	size_t hop_value = options_hop.selected_index_value();
 | 
			
		||||
	
 | 
			
		||||
	// Disable all channels by default
 | 
			
		||||
	for (c = 0; c < JAMMER_MAX_CH; c++)
 | 
			
		||||
		jammer_channels[c].enabled = false;
 | 
			
		||||
	
 | 
			
		||||
	// Generate jamming channels with JAMMER_MAX_CH maximum width
 | 
			
		||||
	// Convert ranges min/max to center/bw
 | 
			
		||||
	for (size_t r = 0; r < 3; r++) {
 | 
			
		||||
		
 | 
			
		||||
		if (range_views[r]->frequency_range.enabled) {
 | 
			
		||||
			range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min);
 | 
			
		||||
			
 | 
			
		||||
			// Get lower bound
 | 
			
		||||
			if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max)
 | 
			
		||||
				start_freq = range_views[r]->frequency_range.min;
 | 
			
		||||
			else
 | 
			
		||||
				start_freq = range_views[r]->frequency_range.max;
 | 
			
		||||
			
 | 
			
		||||
			if (range_bw >= JAMMER_CH_WIDTH) {
 | 
			
		||||
				// Split range in multiple channels
 | 
			
		||||
				num_channels = 0;
 | 
			
		||||
				range_bw_sub = range_bw;
 | 
			
		||||
				
 | 
			
		||||
				do {
 | 
			
		||||
					range_bw_sub -= JAMMER_CH_WIDTH;
 | 
			
		||||
					num_channels++;
 | 
			
		||||
				} while (range_bw_sub >= JAMMER_CH_WIDTH);
 | 
			
		||||
				
 | 
			
		||||
				ch_width = range_bw / num_channels;
 | 
			
		||||
				
 | 
			
		||||
				for (c = 0; c < num_channels; c++) {
 | 
			
		||||
					if (i >= JAMMER_MAX_CH) {
 | 
			
		||||
						out_of_ranges = true;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
					set_jammer_channel(i, ch_width, start_freq + (ch_width / 2) + (ch_width * c), hop_value);
 | 
			
		||||
					i++;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// Range fits in a single channel
 | 
			
		||||
				if (i >= JAMMER_MAX_CH) {
 | 
			
		||||
					out_of_ranges = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					set_jammer_channel(i, range_bw, start_freq + (range_bw / 2), hop_value);
 | 
			
		||||
					i++;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (!out_of_ranges && i) {
 | 
			
		||||
		text_range_total.set("/" + to_string_dec_uint(i, 2));
 | 
			
		||||
		
 | 
			
		||||
		jamming = true;
 | 
			
		||||
		button_transmit.set_style(&style_cancel);
 | 
			
		||||
		button_transmit.set_text("STOP");
 | 
			
		||||
		
 | 
			
		||||
		transmitter_model.set_sampling_rate(3072000U);
 | 
			
		||||
		transmitter_model.set_rf_amp(true);
 | 
			
		||||
		transmitter_model.set_baseband_bandwidth(3500000U);
 | 
			
		||||
		transmitter_model.set_tx_gain(47);
 | 
			
		||||
		transmitter_model.enable();
 | 
			
		||||
 | 
			
		||||
		baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
 | 
			
		||||
		mscounter = 0; //euquiq: Reset internal ms counter for do_timer()
 | 
			
		||||
	} else {
 | 
			
		||||
		if (out_of_ranges)
 | 
			
		||||
			nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz.");
 | 
			
		||||
		else
 | 
			
		||||
			nav_.display_modal("Error", "No range enabled.");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JammerView::stop_tx() {
 | 
			
		||||
	button_transmit.set_style(&style_val);
 | 
			
		||||
	button_transmit.set_text("START");
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	radio::disable();
 | 
			
		||||
	baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
 | 
			
		||||
	jamming = false;
 | 
			
		||||
	cooling = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//called each 1/60th of second
 | 
			
		||||
void JammerView::on_timer() {
 | 
			
		||||
	if (++mscounter == 60) {
 | 
			
		||||
		mscounter = 0;
 | 
			
		||||
		if (jamming) 
 | 
			
		||||
		{
 | 
			
		||||
			if (cooling) 
 | 
			
		||||
			{
 | 
			
		||||
				if (++seconds >= field_timepause.value()) 
 | 
			
		||||
				{	//Re-start TX
 | 
			
		||||
					transmitter_model.enable();
 | 
			
		||||
					button_transmit.set_text("STOP");
 | 
			
		||||
					baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
 | 
			
		||||
					
 | 
			
		||||
					int32_t jitter_amount = field_jitter.value();
 | 
			
		||||
					if (jitter_amount) 
 | 
			
		||||
					{
 | 
			
		||||
						lfsr_v = lfsr_iterate(lfsr_v);
 | 
			
		||||
						jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
 | 
			
		||||
						mscounter += jitter_amount;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					cooling = false;
 | 
			
		||||
					seconds = 0;
 | 
			
		||||
				}
 | 
			
		||||
			} 
 | 
			
		||||
			else 
 | 
			
		||||
			{
 | 
			
		||||
				if (++seconds >= field_timetx.value()) //Start cooling period:
 | 
			
		||||
				{
 | 
			
		||||
					transmitter_model.disable();
 | 
			
		||||
					button_transmit.set_text("PAUSED");
 | 
			
		||||
					baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
 | 
			
		||||
					
 | 
			
		||||
					int32_t jitter_amount = field_jitter.value();
 | 
			
		||||
					if (jitter_amount) 
 | 
			
		||||
					{
 | 
			
		||||
						lfsr_v = lfsr_iterate(lfsr_v);
 | 
			
		||||
						jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
 | 
			
		||||
						mscounter += jitter_amount;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					cooling = true;
 | 
			
		||||
					seconds = 0;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
	
 | 
			
		||||
JammerView::JammerView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ { nav }
 | 
			
		||||
{
 | 
			
		||||
	Rect view_rect = { 0, 3 * 8, 240, 80 };
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_jammer);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&view_range_a,
 | 
			
		||||
		&view_range_b,
 | 
			
		||||
		&view_range_c,
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_type,
 | 
			
		||||
		&text_range_number,
 | 
			
		||||
		&text_range_total,
 | 
			
		||||
		&options_speed,
 | 
			
		||||
		&options_hop,
 | 
			
		||||
		&field_timetx,
 | 
			
		||||
		&field_timepause,
 | 
			
		||||
		&field_jitter,
 | 
			
		||||
		&button_transmit
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	view_range_a.set_parent_rect(view_rect);
 | 
			
		||||
	view_range_b.set_parent_rect(view_rect);
 | 
			
		||||
	view_range_c.set_parent_rect(view_rect);
 | 
			
		||||
	
 | 
			
		||||
	options_type.set_selected_index(3);		// Rand CW
 | 
			
		||||
	options_speed.set_selected_index(3);	// 10kHz
 | 
			
		||||
	options_hop.set_selected_index(1);		// 50ms
 | 
			
		||||
	button_transmit.set_style(&style_val);
 | 
			
		||||
 | 
			
		||||
	field_timetx.set_value(30);
 | 
			
		||||
	field_timepause.set_value(1);
 | 
			
		||||
 | 
			
		||||
	button_transmit.on_select = [this](Button&) {
 | 
			
		||||
		if (jamming || cooling)
 | 
			
		||||
			stop_tx();
 | 
			
		||||
		else
 | 
			
		||||
			start_tx();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,254 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "jammer.hpp"
 | 
			
		||||
#include "lfsr_random.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace jammer;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class RangeView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	RangeView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void paint(Painter&) override;
 | 
			
		||||
	
 | 
			
		||||
	jammer_range_t frequency_range { false, 0, 0 };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void update_start(rf::Frequency f);
 | 
			
		||||
	void update_stop(rf::Frequency f);
 | 
			
		||||
	void update_center(rf::Frequency f);
 | 
			
		||||
	void update_width(uint32_t w);
 | 
			
		||||
	
 | 
			
		||||
	uint32_t width { };
 | 
			
		||||
	rf::Frequency center { };
 | 
			
		||||
 | 
			
		||||
	static constexpr Style style_info {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::grey(),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 8 * 8 + 4 }, "Start", Color::light_grey() },
 | 
			
		||||
		{ { 23 * 8, 8 * 8 + 4 }, "Stop", Color::light_grey() },
 | 
			
		||||
		{ { 12 * 8, 5 * 8 - 4}, "Center", Color::light_grey() },
 | 
			
		||||
		{ { 12 * 8 + 4, 13 * 8 }, "Width", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_enabled {
 | 
			
		||||
		{ 1 * 8, 4 },
 | 
			
		||||
		12,
 | 
			
		||||
		"Enable range"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_load_range {
 | 
			
		||||
		{ 18 * 8, 4, 12 * 8, 24 },
 | 
			
		||||
		"Load range"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_start {
 | 
			
		||||
		{ 0 * 8, 11 * 8, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	Button button_stop {
 | 
			
		||||
		{ 19 * 8, 11 * 8, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	Button button_center {
 | 
			
		||||
		{ 76, 4 * 15 - 4, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	Button button_width {
 | 
			
		||||
		{ 76, 8 * 15, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class JammerView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	JammerView(NavigationView& nav);
 | 
			
		||||
	~JammerView();
 | 
			
		||||
	
 | 
			
		||||
	JammerView(const JammerView&) = delete;
 | 
			
		||||
	JammerView(JammerView&&) = delete;
 | 
			
		||||
	JammerView& operator=(const JammerView&) = delete;
 | 
			
		||||
	JammerView& operator=(JammerView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Jammer TX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void on_timer();
 | 
			
		||||
	void stop_tx();
 | 
			
		||||
	void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
 | 
			
		||||
	void on_retune(const rf::Frequency freq, const uint32_t range);
 | 
			
		||||
	
 | 
			
		||||
	JammerChannel * jammer_channels = (JammerChannel*)shared_memory.bb_data.data;
 | 
			
		||||
	bool jamming { false };
 | 
			
		||||
	bool cooling { false };		//euquiq: Indicates jammer in cooldown
 | 
			
		||||
	uint16_t seconds = 0;		//euquiq: seconds counter for toggling tx / cooldown
 | 
			
		||||
	int16_t mscounter = 0;		//euquiq: Internal ms counter for do_timer()
 | 
			
		||||
	lfsr_word_t lfsr_v = 1;		//euquiq: Used to generate "random" Jitter
 | 
			
		||||
	
 | 
			
		||||
	static constexpr Style style_val {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::green(),
 | 
			
		||||
	};
 | 
			
		||||
	static constexpr Style style_cancel {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::red(),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RangeView view_range_a { nav_ };
 | 
			
		||||
	RangeView view_range_b { nav_ };
 | 
			
		||||
	RangeView view_range_c { nav_ };
 | 
			
		||||
	
 | 
			
		||||
	std::array<RangeView*, 3> range_views { { &view_range_a, &view_range_b, &view_range_c } };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Range 1", Color::white(), range_views[0] },
 | 
			
		||||
		{ "Range 2", Color::white(), range_views[1] },
 | 
			
		||||
		{ "Range 3", Color::white(), range_views[2] },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 23 * 8 }, "Type:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 25 * 8 }, "Speed:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 27 * 8 }, "Hop:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 29 * 8 }, "TX:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 31 * 8 }, "Sle3p:", Color::light_grey() }, 	//euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
 | 
			
		||||
		{ { 0 * 8, 33 * 8 }, "Jitter:", Color::light_grey() },	//Maybe the repository curator can keep the "mystype" for some versions. 
 | 
			
		||||
		{ { 11 * 8, 29 * 8 }, "Secs.", Color::light_grey() }, 
 | 
			
		||||
		{ { 11 * 8, 31 * 8 }, "Secs.", Color::light_grey() },
 | 
			
		||||
		{ { 11 * 8, 33 * 8 }, "/60", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_type {
 | 
			
		||||
		{ 7 * 8, 23 * 8 },
 | 
			
		||||
		8,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Rand FSK", 0 },
 | 
			
		||||
			{ "FM tone", 1 },
 | 
			
		||||
			{ "CW sweep", 2 },
 | 
			
		||||
			{ "Rand CW", 3 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_range_number {
 | 
			
		||||
		{ 16 * 8, 23 * 8, 2 * 8, 16 },
 | 
			
		||||
		"--"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_range_total {
 | 
			
		||||
		{ 18 * 8, 23 * 8, 3 * 8, 16 },
 | 
			
		||||
		"/--"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_speed {
 | 
			
		||||
		{ 7 * 8, 25 * 8 },
 | 
			
		||||
		6,
 | 
			
		||||
		{
 | 
			
		||||
			{ "10Hz  ", 10 },
 | 
			
		||||
			{ "100Hz ", 100 },
 | 
			
		||||
			{ "1kHz  ", 1000 },
 | 
			
		||||
			{ "10kHz ", 10000 },
 | 
			
		||||
			{ "100kHz", 100000 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_hop {
 | 
			
		||||
		{ 7 * 8, 27 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		{
 | 
			
		||||
			{ "10ms ", 1 },
 | 
			
		||||
			{ "50ms ", 5 },
 | 
			
		||||
			{ "100ms", 10 },
 | 
			
		||||
			{ "1s   ", 100 },
 | 
			
		||||
			{ "2s   ", 200 },
 | 
			
		||||
			{ "5s   ", 500 },
 | 
			
		||||
			{ "10s  ", 1000 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_timetx {
 | 
			
		||||
		{ 7 * 8, 29 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 1, 180 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_timepause {
 | 
			
		||||
		{ 8 * 8, 31 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 60 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_jitter {
 | 
			
		||||
		{ 8 * 8, 33 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 60 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_transmit {
 | 
			
		||||
		{ 148, 212, 80, 80},
 | 
			
		||||
		"START"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_retune {
 | 
			
		||||
		Message::ID::Retune,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RetuneMessage*>(p);
 | 
			
		||||
			this->on_retune(message->freq, message->range);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		MessageHandlerRegistration message_handler_frame_sync {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			this->on_timer();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,259 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_keyfob.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
uint8_t KeyfobView::subaru_get_checksum() {
 | 
			
		||||
	uint8_t checksum = 0;
 | 
			
		||||
	
 | 
			
		||||
	for (size_t i = 0; i < 9; i++) {
 | 
			
		||||
		checksum ^= (frame[i] & 0x0F);			// 00 11 22 33 44 55 66 77 88 9-
 | 
			
		||||
		checksum ^= ((frame[i] >> 4) & 0x0F);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	checksum ^= ((frame[9] >> 4) & 0x0F);
 | 
			
		||||
	checksum++;
 | 
			
		||||
	checksum &= 0x0F;
 | 
			
		||||
	
 | 
			
		||||
	return checksum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool KeyfobView::subaru_is_valid() {
 | 
			
		||||
	if (frame[0] != 0x55)
 | 
			
		||||
		return false;
 | 
			
		||||
	
 | 
			
		||||
	if (subaru_get_checksum() != (frame[9] & 0x0F))
 | 
			
		||||
		return false;
 | 
			
		||||
	
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t KeyfobView::subaru_get_code() {
 | 
			
		||||
	// 77777777 88888888 9999
 | 
			
		||||
	return (frame[7] << 12) | (frame[8] << 4) | (frame[9] >> 4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::subaru_set_code(const uint16_t code) {
 | 
			
		||||
	frame[7] = (code >> 12) & 0xFF;
 | 
			
		||||
	frame[8] = (code >> 4) & 0xFF;
 | 
			
		||||
	frame[9] &= 0x0F;
 | 
			
		||||
	frame[9] |= (code & 0x0F) << 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t KeyfobView::subaru_get_command() {
 | 
			
		||||
	uint32_t command_a = frame[5] & 0x0F;
 | 
			
		||||
	uint32_t command_b = frame[6] & 0x0F;
 | 
			
		||||
	
 | 
			
		||||
	if (command_a != command_b)
 | 
			
		||||
		return -1;
 | 
			
		||||
	
 | 
			
		||||
	return command_a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::subaru_set_command(const uint32_t command) {
 | 
			
		||||
	frame[5] &= 0xF0;
 | 
			
		||||
	frame[5] |= command;
 | 
			
		||||
	frame[6] &= 0xF0;
 | 
			
		||||
	frame[6] |= command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::generate_payload(size_t& bitstream_length) {
 | 
			
		||||
	for (size_t i = 0; i < (10 * 8); i++) {
 | 
			
		||||
		if (frame[i >> 3] & (1 << (7 - (i & 7))))
 | 
			
		||||
			bitstream_append(bitstream_length, 2, 0b10);
 | 
			
		||||
		else
 | 
			
		||||
			bitstream_append(bitstream_length, 2, 0b01);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t KeyfobView::generate_frame() {
 | 
			
		||||
	size_t bitstream_length = 0;
 | 
			
		||||
	uint64_t payload;
 | 
			
		||||
	
 | 
			
		||||
	// Symfield word to frame
 | 
			
		||||
	payload = field_payload_a.value_hex_u64();
 | 
			
		||||
	for (size_t i = 0; i < 5; i++) {
 | 
			
		||||
		frame[4 - i] = payload & 0xFF;
 | 
			
		||||
		payload >>= 8;
 | 
			
		||||
	}
 | 
			
		||||
	payload = field_payload_b.value_hex_u64();
 | 
			
		||||
	for (size_t i = 0; i < 5; i++) {
 | 
			
		||||
		frame[9 - i] = payload & 0xFF;
 | 
			
		||||
		payload >>= 8;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Recompute checksum
 | 
			
		||||
	frame[9] = (frame[9] & 0xF0) | subaru_get_checksum();
 | 
			
		||||
	update_symfields();
 | 
			
		||||
	
 | 
			
		||||
	// Preamble: 128x 01
 | 
			
		||||
	for (size_t i = 0; i < 128; i++)
 | 
			
		||||
		bitstream_append(bitstream_length, 2, 0b01);
 | 
			
		||||
 | 
			
		||||
	// Space: 4x 0
 | 
			
		||||
	bitstream_append(bitstream_length, 4, 0b0000);
 | 
			
		||||
 | 
			
		||||
	// Payload
 | 
			
		||||
	generate_payload(bitstream_length);
 | 
			
		||||
 | 
			
		||||
	// Space: 8x 0
 | 
			
		||||
	bitstream_append(bitstream_length, 8, 0b00000000);
 | 
			
		||||
 | 
			
		||||
	// Payload again
 | 
			
		||||
	generate_payload(bitstream_length);
 | 
			
		||||
	
 | 
			
		||||
	return bitstream_length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::focus() {
 | 
			
		||||
	options_make.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
KeyfobView::~KeyfobView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	settings.save("tx_keyfob", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::update_progress(const uint32_t progress) {
 | 
			
		||||
	text_status.set("Repeat #" + to_string_dec_uint(progress));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (!done) {
 | 
			
		||||
		// Repeating...
 | 
			
		||||
		update_progress(progress + 1);
 | 
			
		||||
		progressbar.set_value(progress);
 | 
			
		||||
	} else {
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		text_status.set("Done");
 | 
			
		||||
		progressbar.set_value(0);
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::on_make_change(size_t index) {
 | 
			
		||||
	(void)index;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DEBUG
 | 
			
		||||
void KeyfobView::update_symfields() {
 | 
			
		||||
	for (size_t i = 0; i < 5; i++) {
 | 
			
		||||
		field_payload_a.set_sym(i << 1, frame[i] >> 4);
 | 
			
		||||
		field_payload_a.set_sym((i << 1) + 1, frame[i] & 0x0F);
 | 
			
		||||
	}
 | 
			
		||||
	for (size_t i = 0; i < 5; i++) {
 | 
			
		||||
		field_payload_b.set_sym(i << 1, frame[5 + i] >> 4);
 | 
			
		||||
		field_payload_b.set_sym((i << 1) + 1, frame[5 + i] & 0x0F);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::on_command_change(uint32_t value) {
 | 
			
		||||
	subaru_set_command(value);
 | 
			
		||||
	update_symfields();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KeyfobView::start_tx() {
 | 
			
		||||
	progressbar.set_max(repeats - 1);
 | 
			
		||||
	
 | 
			
		||||
	update_progress(1);
 | 
			
		||||
	
 | 
			
		||||
	size_t bitstream_length = generate_frame();
 | 
			
		||||
 | 
			
		||||
	transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_ook_data(
 | 
			
		||||
		bitstream_length,
 | 
			
		||||
		subaru_samples_per_bit,
 | 
			
		||||
		repeats,
 | 
			
		||||
		200		// Pause symbols
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
KeyfobView::KeyfobView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ { nav }
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_ook);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_make,
 | 
			
		||||
		&options_command,
 | 
			
		||||
		&field_payload_a,
 | 
			
		||||
		&field_payload_b,
 | 
			
		||||
		&text_status,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_keyfob", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	frame[0] = 0x55;
 | 
			
		||||
	update_symfields();
 | 
			
		||||
	
 | 
			
		||||
	options_make.on_change = [this](size_t index, int32_t) {
 | 
			
		||||
		on_make_change(index);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_command.on_change = [this](size_t, int32_t value) {
 | 
			
		||||
		on_command_change(value);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_make.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_tuning_frequency(433920000);	// Fixed 433.92MHz
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		start_tx();
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,136 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "encoders.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace encoders;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class KeyfobView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	KeyfobView(NavigationView& nav);
 | 
			
		||||
	~KeyfobView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Key fob TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	// 1013210ns / bit
 | 
			
		||||
	static constexpr uint32_t subaru_samples_per_bit = (OOK_SAMPLERATE * 0.00101321);
 | 
			
		||||
	static constexpr uint32_t repeats = 4;
 | 
			
		||||
	
 | 
			
		||||
	uint8_t frame[10] { };
 | 
			
		||||
	
 | 
			
		||||
	void generate_payload(size_t& bitstream_length);
 | 
			
		||||
	size_t generate_frame();
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	void update_progress(const uint32_t progress);
 | 
			
		||||
	void on_make_change(size_t index);
 | 
			
		||||
	void on_command_change(uint32_t value);
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	void update_symfields();
 | 
			
		||||
	
 | 
			
		||||
	uint8_t subaru_get_checksum();
 | 
			
		||||
	bool subaru_is_valid();
 | 
			
		||||
	uint16_t subaru_get_code();
 | 
			
		||||
	void subaru_set_code(const uint16_t code);
 | 
			
		||||
	int32_t subaru_get_command();
 | 
			
		||||
	void subaru_set_command(const uint32_t command);
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 5 * 8, 1 * 16 }, "Make:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 2 * 16 }, "Command:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 4 * 16 }, "Payload:       #####", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 7 * 16 }, "Checksum is fixed just", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 8 * 16 }, "before transmission.", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_make {
 | 
			
		||||
		{ 10 * 8, 1 * 16 },
 | 
			
		||||
		8,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Subaru", 0 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_command {
 | 
			
		||||
		{ 10 * 8, 2 * 16 },
 | 
			
		||||
		6,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Lock", 1 },
 | 
			
		||||
			{ "Unlock", 2 },
 | 
			
		||||
			{ "Trunk", 11 },
 | 
			
		||||
			{ "Panic", 10 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField field_payload_a {
 | 
			
		||||
		{ 2 * 8, 5 * 16 },
 | 
			
		||||
		10,
 | 
			
		||||
		SymField::SYMFIELD_HEX
 | 
			
		||||
	};
 | 
			
		||||
	SymField field_payload_b {
 | 
			
		||||
		{ 13 * 8, 5 * 16 },
 | 
			
		||||
		10,
 | 
			
		||||
		SymField::SYMFIELD_HEX
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_status {
 | 
			
		||||
		{ 2 * 8, 13 * 16, 128, 16 },
 | 
			
		||||
		"Ready"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 2 * 8, 13 * 16 + 20, 208, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		0,
 | 
			
		||||
		15,
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										298
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_lcr.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_lcr.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,298 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_lcr.hpp"
 | 
			
		||||
#include "ui_modemsetup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "lcr.hpp"
 | 
			
		||||
#include "modems.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "serializer.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void LCRView::focus() {
 | 
			
		||||
	button_set_rgsb.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LCRView::~LCRView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_lcr", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// Recap: frequency @ baudrate
 | 
			
		||||
final_str = to_string_short_freq(persistent_memory::tuned_frequency());
 | 
			
		||||
final_str += '@';
 | 
			
		||||
final_str += to_string_dec_int(persistent_memory::modem_baudrate(), 4);
 | 
			
		||||
final_str += "bps ";
 | 
			
		||||
//final_str += modem_defs[persistent_memory::modem_def_index()].name;
 | 
			
		||||
text_recap.set(final_str);*/
 | 
			
		||||
 | 
			
		||||
void LCRView::update_progress() {
 | 
			
		||||
	if (tx_mode == IDLE) {
 | 
			
		||||
		text_status.set("Ready");
 | 
			
		||||
		progress.set_value(0);
 | 
			
		||||
	} else {
 | 
			
		||||
		std::string progress_str = to_string_dec_uint(repeat_index) + "/" + to_string_dec_uint(persistent_memory::modem_repeat()) +
 | 
			
		||||
			" " + to_string_dec_uint(scan_index + 1) + "/" + to_string_dec_uint(scan_count);
 | 
			
		||||
			
 | 
			
		||||
		text_status.set(progress_str);
 | 
			
		||||
		
 | 
			
		||||
		if (tx_mode == SINGLE)
 | 
			
		||||
			progress.set_value(repeat_index);
 | 
			
		||||
		else if (tx_mode == SCAN)
 | 
			
		||||
			progress.set_value(scan_progress);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LCRView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (!done) {
 | 
			
		||||
		// Repeating...
 | 
			
		||||
		repeat_index = progress + 1;
 | 
			
		||||
		
 | 
			
		||||
		if (tx_mode == SCAN)
 | 
			
		||||
			scan_progress++;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Done transmitting
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		
 | 
			
		||||
		if ((tx_mode == SCAN) && (scan_index < (scan_count - 1))) {
 | 
			
		||||
			// Next address
 | 
			
		||||
			scan_index++;
 | 
			
		||||
			scan_progress++;
 | 
			
		||||
			repeat_index = 1;
 | 
			
		||||
			start_tx(true);
 | 
			
		||||
		} else {
 | 
			
		||||
			tx_mode = IDLE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	update_progress();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LCRView::start_tx(const bool scan) {
 | 
			
		||||
	uint32_t repeats = persistent_memory::modem_repeat();
 | 
			
		||||
	
 | 
			
		||||
	if (scan) {
 | 
			
		||||
		if (tx_mode != SCAN) {
 | 
			
		||||
			scan_index = 0;
 | 
			
		||||
			scan_count = scan_list[options_scanlist.selected_index()].count;
 | 
			
		||||
			scan_progress = 1;
 | 
			
		||||
			repeat_index = 1;
 | 
			
		||||
			tx_mode = SCAN;
 | 
			
		||||
			progress.set_max(scan_count * repeats);
 | 
			
		||||
			update_progress();
 | 
			
		||||
		}
 | 
			
		||||
		rgsb = scan_list[options_scanlist.selected_index()].addresses[scan_index];
 | 
			
		||||
		button_set_rgsb.set_text(rgsb);
 | 
			
		||||
	} else {
 | 
			
		||||
		tx_mode = SINGLE;
 | 
			
		||||
		repeat_index = 1;
 | 
			
		||||
		scan_count = 1;
 | 
			
		||||
		scan_index = 0;
 | 
			
		||||
		progress.set_max(repeats);
 | 
			
		||||
		update_progress();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	std::vector<std::string> litterals_list;
 | 
			
		||||
	
 | 
			
		||||
	for (size_t i = 0; i < LCR_MAX_AM; i++) {
 | 
			
		||||
		if (checkboxes[i].value())
 | 
			
		||||
			litterals_list.push_back(litteral[i]);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	modems::generate_data(lcr::generate_message(rgsb, litterals_list, options_ec.selected_index()), lcr_message_data);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.set_tuning_frequency(persistent_memory::tuned_frequency());
 | 
			
		||||
	transmitter_model.set_sampling_rate(AFSK_TX_SAMPLERATE);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
 | 
			
		||||
	memcpy(shared_memory.bb_data.data, lcr_message_data, sizeof(lcr_message_data));
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_afsk_data(
 | 
			
		||||
		AFSK_TX_SAMPLERATE / persistent_memory::modem_baudrate(),
 | 
			
		||||
		persistent_memory::afsk_mark_freq(),
 | 
			
		||||
		persistent_memory::afsk_space_freq(),
 | 
			
		||||
		repeats,
 | 
			
		||||
		transmitter_model.channel_bandwidth(),
 | 
			
		||||
		serializer::symbol_count(persistent_memory::serial_format())
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LCRView::on_button_set_am(NavigationView& nav, int16_t button_id) {
 | 
			
		||||
	text_prompt(
 | 
			
		||||
		nav,
 | 
			
		||||
		litteral[button_id],
 | 
			
		||||
		7,
 | 
			
		||||
		[this, button_id](std::string& buffer) {
 | 
			
		||||
			texts[button_id].set(buffer);
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LCRView::LCRView(NavigationView& nav) {
 | 
			
		||||
	std::string label;
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_afsk);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_ec,
 | 
			
		||||
		&button_set_rgsb,
 | 
			
		||||
		&button_modem_setup,
 | 
			
		||||
		&text_status,
 | 
			
		||||
		&progress,
 | 
			
		||||
		&options_scanlist,
 | 
			
		||||
		&check_scan,
 | 
			
		||||
		&button_clear,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_lcr", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options_scanlist.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	const auto button_set_am_fn = [this, &nav](Button& button) {
 | 
			
		||||
		on_button_set_am(nav, button.id);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	for (size_t n = 0; n < LCR_MAX_AM; n++) {
 | 
			
		||||
		Button* button = &buttons[n];
 | 
			
		||||
		button->on_select = button_set_am_fn;
 | 
			
		||||
		button->id = n;
 | 
			
		||||
		button->set_text("AM " + to_string_dec_uint(n + 1));
 | 
			
		||||
		button->set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(40),
 | 
			
		||||
			static_cast<Coord>(n * 32 + 64),
 | 
			
		||||
			48, 24
 | 
			
		||||
		});
 | 
			
		||||
		add_child(button);
 | 
			
		||||
		
 | 
			
		||||
		Checkbox* checkbox = &checkboxes[n];
 | 
			
		||||
		checkbox->set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(8),
 | 
			
		||||
			static_cast<Coord>(n * 32 + 64),
 | 
			
		||||
			48, 24
 | 
			
		||||
		});
 | 
			
		||||
		checkbox->set_value(false);
 | 
			
		||||
		add_child(checkbox);
 | 
			
		||||
		
 | 
			
		||||
		Rectangle* rectangle = &rectangles[n];
 | 
			
		||||
		rectangle->set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(98),
 | 
			
		||||
			static_cast<Coord>(n * 32 + 66),
 | 
			
		||||
			68, 20
 | 
			
		||||
		});
 | 
			
		||||
		rectangle->set_color(ui::Color::grey());
 | 
			
		||||
		rectangle->set_outline(true);
 | 
			
		||||
		add_child(rectangle);
 | 
			
		||||
		
 | 
			
		||||
		Text* text = &texts[n];
 | 
			
		||||
		text->set_parent_rect({
 | 
			
		||||
			static_cast<Coord>(104),
 | 
			
		||||
			static_cast<Coord>(n * 32 + 68),
 | 
			
		||||
			7 * 8, 16
 | 
			
		||||
		});
 | 
			
		||||
		add_child(text);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	button_set_rgsb.set_text(rgsb);
 | 
			
		||||
	options_ec.set_selected_index(0);	// Auto
 | 
			
		||||
	checkboxes[0].set_value(true);
 | 
			
		||||
	
 | 
			
		||||
	button_set_rgsb.on_select = [this, &nav](Button&) {
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			rgsb,
 | 
			
		||||
			4,
 | 
			
		||||
			[this](std::string& buffer) {
 | 
			
		||||
				button_set_rgsb.set_text(buffer);
 | 
			
		||||
			});
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_modem_setup.on_select = [this, &nav](Button&) {
 | 
			
		||||
		if (tx_mode == IDLE)
 | 
			
		||||
			nav.push<ModemSetupView>();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_clear.on_select = [this, &nav](Button&) {
 | 
			
		||||
		options_ec.set_selected_index(0);	// Auto
 | 
			
		||||
		for (size_t n = 0; n < LCR_MAX_AM; n++) {
 | 
			
		||||
			litteral[n] = "       ";
 | 
			
		||||
			checkboxes[n].set_value(true);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (check_scan.value()) {
 | 
			
		||||
			if (tx_mode == IDLE) {
 | 
			
		||||
				start_tx(true);
 | 
			
		||||
				tx_view.set_transmitting(true);
 | 
			
		||||
			} else {
 | 
			
		||||
				// Kill scan process
 | 
			
		||||
				baseband::kill_afsk();
 | 
			
		||||
				tx_view.set_transmitting(false);
 | 
			
		||||
				transmitter_model.disable();
 | 
			
		||||
				text_status.set("Abort @" + rgsb);
 | 
			
		||||
				progress.set_value(0);
 | 
			
		||||
				tx_mode = IDLE;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if (tx_mode == IDLE) {
 | 
			
		||||
				start_tx(false);
 | 
			
		||||
				tx_view.set_transmitting(true);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		tx_mode = IDLE;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										173
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_lcr.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_lcr.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
	
 | 
			
		||||
#define LCR_MAX_AM 5
 | 
			
		||||
 | 
			
		||||
class LCRView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	LCRView(NavigationView& nav);
 | 
			
		||||
	~LCRView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "TEDI/LCR TX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	struct scan_list_t {
 | 
			
		||||
		uint8_t count;
 | 
			
		||||
		const std::string * addresses;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const scan_list_t scan_list[2] = {
 | 
			
		||||
		{ 36, &RGSB_list_Lille[0] },
 | 
			
		||||
		{ 20, &RGSB_list_Reims[0] }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const std::string RGSB_list_Lille[36] = {
 | 
			
		||||
		"AI10",	"AI20", "AI30",	"AI40",
 | 
			
		||||
		"AI50",	"AI60", "AI70",	"AJ10",
 | 
			
		||||
		"AJ20",	"AJ30", "AJ40",	"AJ50",
 | 
			
		||||
		"AJ60",	"AJ70", "AK10",
 | 
			
		||||
		"EAA0", "EAB0",	"EAC0",	"EAD0",
 | 
			
		||||
		"EbA0",	"EbB0",	"EbC0",	"EbD0",
 | 
			
		||||
		"EbE0",	"EbF0",	"EbG0",	"EbH0",
 | 
			
		||||
		"EbI0",	"EbJ0",	"EbK0",	"EbL0",
 | 
			
		||||
		"EbM0",	"EbN0",	"EbO0",	"EbP0",
 | 
			
		||||
		"EbS0"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const std::string  RGSB_list_Reims[20] = {
 | 
			
		||||
		"AI10",	"AI20", "AI30",	"AI40",
 | 
			
		||||
		"AI50",	"AI60", "AI70",
 | 
			
		||||
		"AJ10", "AJ20",	"AJ30", "AJ40",
 | 
			
		||||
		"AJ50", "AJ60",	"AJ70",
 | 
			
		||||
		"AK10", "AK20", "AK50", "AK60",
 | 
			
		||||
		"AK70", 
 | 
			
		||||
		"AP10"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SCAN
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
	uint8_t scan_count { 0 }, scan_index { 0 };
 | 
			
		||||
	uint32_t scan_progress { 0 };
 | 
			
		||||
	std::array<std::string, LCR_MAX_AM> litteral { { "       " } };
 | 
			
		||||
	std::string rgsb { "AI10" };
 | 
			
		||||
	uint16_t lcr_message_data[256];
 | 
			
		||||
	uint8_t repeat_index { 0 };
 | 
			
		||||
	
 | 
			
		||||
	void update_progress();
 | 
			
		||||
	void start_tx(const bool scan);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	void on_button_set_am(NavigationView& nav, int16_t button_id);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0, 8 }, "EC:     RGSB:", Color::light_grey() },
 | 
			
		||||
		{ { 17 * 8, 4 * 8 }, "List:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::array<Button, LCR_MAX_AM> buttons { };
 | 
			
		||||
	std::array<Checkbox, LCR_MAX_AM> checkboxes { };
 | 
			
		||||
	std::array<Rectangle, LCR_MAX_AM> rectangles { };
 | 
			
		||||
	std::array<Text, LCR_MAX_AM> texts { };
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_ec {
 | 
			
		||||
		{ 3 * 8, 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Auto", 0 },
 | 
			
		||||
			{ "Jour", 1 },
 | 
			
		||||
			{ "Nuit", 2 },
 | 
			
		||||
			{ "S ? ", 3 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_set_rgsb {
 | 
			
		||||
		{ 13 * 8, 4, 8 * 8, 24 },
 | 
			
		||||
		"RGSB"
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_scan {
 | 
			
		||||
		{ 22 * 8, 4 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Scan"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_modem_setup {
 | 
			
		||||
		{ 1 * 8, 4 * 8 + 2, 14 * 8, 24 },
 | 
			
		||||
		"Modem setup"
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_scanlist {
 | 
			
		||||
		{ 22 * 8, 4 * 8 },
 | 
			
		||||
		6,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Reims ", 1 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_clear {
 | 
			
		||||
		{ 22 * 8, 8 * 8, 7 * 8, 19 * 8 },
 | 
			
		||||
		"CLEAR"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_status {
 | 
			
		||||
		{ 2 * 8, 27 * 8 + 4, 26 * 8, 16 },
 | 
			
		||||
		"Ready"
 | 
			
		||||
	};
 | 
			
		||||
	ProgressBar progress {
 | 
			
		||||
		{ 2 * 8, 29 * 8 + 4, 26 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,336 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2020 euquiq
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_looking_glass_app.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
void GlassView::focus() {
 | 
			
		||||
	field_marker.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GlassView::~GlassView() {
 | 
			
		||||
    receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::on_lna_changed(int32_t v_db) {
 | 
			
		||||
	receiver_model.set_lna(v_db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::on_vga_changed(int32_t v_db) {
 | 
			
		||||
	receiver_model.set_vga(v_db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::add_spectrum_pixel(Color color)
 | 
			
		||||
{
 | 
			
		||||
    spectrum_row[pixel_index++] = color;
 | 
			
		||||
    if (pixel_index == 240) //got an entire waterfall line
 | 
			
		||||
    { 
 | 
			
		||||
        const auto draw_y = display.scroll(1); //Scroll 1 pixel down
 | 
			
		||||
        display.draw_pixels( {{0, draw_y}, {240, 1}}, spectrum_row); //new line at top
 | 
			
		||||
        pixel_index = 0; //Start New cascade line
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Apparently, the spectrum object returns an array of 256 bins
 | 
			
		||||
//Each having the radio signal power for it's corresponding frequency slot
 | 
			
		||||
void GlassView::on_channel_spectrum(const ChannelSpectrum &spectrum)
 | 
			
		||||
{
 | 
			
		||||
    baseband::spectrum_streaming_stop();
 | 
			
		||||
 | 
			
		||||
    // Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
 | 
			
		||||
    // Spectrum.db has 256 bins. Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
 | 
			
		||||
    // All things said and done, we actually need 240 of those bins:
 | 
			
		||||
    for(uint8_t bin = 0; bin < 240; bin++) 
 | 
			
		||||
    {
 | 
			
		||||
        if (bin < 120) {
 | 
			
		||||
            if (spectrum.db[134 + bin] > max_power)
 | 
			
		||||
                max_power = spectrum.db[134 + bin];
 | 
			
		||||
        }         
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (spectrum.db[bin - 118] > max_power)
 | 
			
		||||
                    max_power = spectrum.db[bin - 118];  
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bins_Hz_size += each_bin_size;   //add this bin Hz count into the "pixel fulfilled bag of Hz"
 | 
			
		||||
 | 
			
		||||
        if (bins_Hz_size >= marker_pixel_step) //new pixel fullfilled
 | 
			
		||||
        {
 | 
			
		||||
            if (min_color_power < max_power)
 | 
			
		||||
                add_spectrum_pixel(spectrum_rgb3_lut[max_power]); //Pixel will represent max_power
 | 
			
		||||
            else
 | 
			
		||||
                add_spectrum_pixel(0);  //Filtered out, show black
 | 
			
		||||
 | 
			
		||||
            max_power = 0;
 | 
			
		||||
 | 
			
		||||
            if (!pixel_index) //Received indication that a waterfall line has been completed
 | 
			
		||||
            {
 | 
			
		||||
                bins_Hz_size = 0;  //Since this is an entire pixel line, we don't carry "Pixels into next bin"
 | 
			
		||||
                break;
 | 
			
		||||
            } else {
 | 
			
		||||
                bins_Hz_size -= marker_pixel_step; //reset bins size, but carrying the eventual excess Hz into next pixel
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (pixel_index) 
 | 
			
		||||
        f_center += LOOKING_GLASS_SLICE_WIDTH; //Move into the next bandwidth slice NOTE: spectrum.sampling_rate = LOOKING_GLASS_SLICE_WIDTH
 | 
			
		||||
    else
 | 
			
		||||
        f_center = f_center_ini; //Start a new sweep
 | 
			
		||||
 | 
			
		||||
    receiver_model.set_tuning_frequency(f_center); //tune rx for this slice
 | 
			
		||||
    baseband::spectrum_streaming_start();          //Do the RX
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::on_hide()
 | 
			
		||||
{
 | 
			
		||||
    baseband::spectrum_streaming_stop();
 | 
			
		||||
    display.scroll_disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::on_show()
 | 
			
		||||
{
 | 
			
		||||
    display.scroll_set_area( 109, 319); //Restart scroll on the correct coordinates
 | 
			
		||||
    baseband::spectrum_streaming_start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::on_range_changed()
 | 
			
		||||
{
 | 
			
		||||
    f_min = field_frequency_min.value();
 | 
			
		||||
    f_max = field_frequency_max.value();
 | 
			
		||||
    search_span = f_max - f_min;
 | 
			
		||||
 | 
			
		||||
    field_marker.set_range(f_min, f_max);              //Move the marker between range
 | 
			
		||||
    field_marker.set_value(f_min + (search_span / 2)); //Put MARKER AT MIDDLE RANGE
 | 
			
		||||
    text_range.set(to_string_dec_uint(search_span));
 | 
			
		||||
 | 
			
		||||
    f_min = (f_min)*MHZ_DIV; //Transpose into full frequency realm
 | 
			
		||||
    f_max = (f_max)*MHZ_DIV;
 | 
			
		||||
    search_span = search_span * MHZ_DIV;
 | 
			
		||||
 | 
			
		||||
    marker_pixel_step = search_span / 240;                                        //Each pixel value in Hz
 | 
			
		||||
    text_marker_pm.set(to_string_dec_uint((marker_pixel_step / X2_MHZ_DIV) + 1)); // Give idea of +/- marker precision
 | 
			
		||||
 | 
			
		||||
    int32_t marker_step = marker_pixel_step / MHZ_DIV;
 | 
			
		||||
    if (!marker_step) 
 | 
			
		||||
        field_marker.set_step(1); //in case selected range is less than 240 (pixels)
 | 
			
		||||
    else
 | 
			
		||||
        field_marker.set_step(marker_step); //step needs to be a pixel wide.
 | 
			
		||||
 | 
			
		||||
    f_center_ini = f_min + (LOOKING_GLASS_SLICE_WIDTH / 2);    //Initial center frequency for sweep
 | 
			
		||||
    f_center_ini += LOOKING_GLASS_SLICE_WIDTH;                 //euquiq: Why do I need to move the center ???!!! (shift needed for marker accuracy)
 | 
			
		||||
 | 
			
		||||
    PlotMarker(field_marker.value()); //Refresh marker on screen
 | 
			
		||||
 | 
			
		||||
    f_center = f_center_ini;                        //Reset sweep into first slice
 | 
			
		||||
    pixel_index = 0;                                //reset pixel counter
 | 
			
		||||
    max_power = 0;
 | 
			
		||||
    bins_Hz_size = 0;                               //reset amount of Hz filled up by pixels
 | 
			
		||||
    
 | 
			
		||||
    baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, field_trigger.value());   
 | 
			
		||||
    receiver_model.set_tuning_frequency(f_center_ini); //tune rx for this slice
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::PlotMarker(rf::Frequency fpos)
 | 
			
		||||
{
 | 
			
		||||
    int64_t pos = fpos * MHZ_DIV;
 | 
			
		||||
    pos -= f_min;
 | 
			
		||||
    pos = pos / marker_pixel_step; //Real pixel 
 | 
			
		||||
 | 
			
		||||
    portapack::display.fill_rectangle({0, 100, 240, 8}, Color::black()); //Clear old marker and whole marker rectangle btw
 | 
			
		||||
    portapack::display.fill_rectangle({(int16_t)pos - 2, 100, 5, 3}, Color::red()); //Red marker middle
 | 
			
		||||
    portapack::display.fill_rectangle({(int16_t)pos - 1, 103, 3, 3}, Color::red()); //Red marker middle
 | 
			
		||||
    portapack::display.fill_rectangle({(int16_t)pos, 106, 1, 2}, Color::red()); //Red marker middle
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GlassView::GlassView(
 | 
			
		||||
    NavigationView &nav) : nav_(nav)
 | 
			
		||||
{
 | 
			
		||||
    baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
 | 
			
		||||
 | 
			
		||||
    add_children({&labels,
 | 
			
		||||
                  &field_frequency_min,
 | 
			
		||||
                  &field_frequency_max,
 | 
			
		||||
                  &field_lna,
 | 
			
		||||
                  &field_vga,
 | 
			
		||||
                  &text_range,
 | 
			
		||||
                  &filter_config,
 | 
			
		||||
                  &field_rf_amp,
 | 
			
		||||
                  &range_presets,
 | 
			
		||||
                  &field_marker,
 | 
			
		||||
                  &text_marker_pm,
 | 
			
		||||
                  &field_trigger
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
    load_Presets(); //Load available presets from TXT files (or default)
 | 
			
		||||
    
 | 
			
		||||
    field_frequency_min.set_value(presets_db[0].min);   //Defaults to first preset
 | 
			
		||||
    field_frequency_min.on_change = [this](int32_t v) {
 | 
			
		||||
        if (v >= field_frequency_max.value())
 | 
			
		||||
            field_frequency_max.set_value(v + 240);
 | 
			
		||||
        this->on_range_changed();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    field_frequency_max.set_value(presets_db[0].max);   //Defaults to first preset
 | 
			
		||||
    field_frequency_max.on_change = [this](int32_t v) {
 | 
			
		||||
        if (v <= field_frequency_min.value())
 | 
			
		||||
            field_frequency_min.set_value(v - 240);
 | 
			
		||||
        this->on_range_changed();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    field_lna.set_value(receiver_model.lna());
 | 
			
		||||
    field_lna.on_change = [this](int32_t v) {
 | 
			
		||||
        this->on_lna_changed(v);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    field_vga.set_value(receiver_model.vga());
 | 
			
		||||
    field_vga.on_change = [this](int32_t v_db) {
 | 
			
		||||
        this->on_vga_changed(v_db);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    filter_config.set_selected_index(0);
 | 
			
		||||
	filter_config.on_change = [this](size_t n, OptionsField::value_t v) {
 | 
			
		||||
		(void)n;
 | 
			
		||||
		min_color_power = v;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	range_presets.on_change = [this](size_t n, OptionsField::value_t v) {
 | 
			
		||||
		(void)n;
 | 
			
		||||
		field_frequency_min.set_value(presets_db[v].min,false);
 | 
			
		||||
        field_frequency_max.set_value(presets_db[v].max,false);
 | 
			
		||||
        this->on_range_changed();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
    field_marker.on_change = [this](int32_t v) {
 | 
			
		||||
        PlotMarker(v); //Refresh marker on screen
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    field_marker.on_select = [this](NumberField&) {
 | 
			
		||||
        f_center = field_marker.value();
 | 
			
		||||
        f_center = f_center * MHZ_DIV;
 | 
			
		||||
        receiver_model.set_tuning_frequency(f_center); //Center tune rx in marker freq.
 | 
			
		||||
        receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
 | 
			
		||||
        nav_.pop();
 | 
			
		||||
		nav_.push<AnalogAudioView>(); //Jump into audio view
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    field_trigger.set_value(32);   //Defaults to 32, as normal triggering resolution
 | 
			
		||||
    field_trigger.on_change = [this](int32_t v) {
 | 
			
		||||
        baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, v);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    display.scroll_set_area( 109, 319);
 | 
			
		||||
    baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, field_trigger.value());	//trigger:
 | 
			
		||||
    // Discord User jteich:  WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
 | 
			
		||||
    // it keeps adding the output of the fft to the buffer until "trigger" number of calls are made, 
 | 
			
		||||
    //at which time it pushes the buffer up with channel_spectrum.feed
 | 
			
		||||
 | 
			
		||||
    on_range_changed();
 | 
			
		||||
 | 
			
		||||
    receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
 | 
			
		||||
	receiver_model.set_sampling_rate(LOOKING_GLASS_SLICE_WIDTH); //20mhz
 | 
			
		||||
    receiver_model.set_baseband_bandwidth(LOOKING_GLASS_SLICE_WIDTH); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz
 | 
			
		||||
    receiver_model.set_squelch_level(0);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::load_Presets() {
 | 
			
		||||
	File presets_file; 		//LOAD /WHIPCALC/ANTENNAS.TXT from microSD
 | 
			
		||||
	auto result = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
 | 
			
		||||
	presets_db.clear();			//Start with fresh db
 | 
			
		||||
	if (result.is_valid()) {
 | 
			
		||||
		presets_Default(); 		//There is no txt, store a default range
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		std::string line;		//There is a txt file
 | 
			
		||||
		char one_char[1];		//Read it char by char
 | 
			
		||||
		for (size_t pointer=0; pointer < presets_file.size();pointer++) {
 | 
			
		||||
			presets_file.seek(pointer);
 | 
			
		||||
			presets_file.read(one_char, 1);
 | 
			
		||||
			if ((int)one_char[0] > 31) {			//ascii space upwards
 | 
			
		||||
				line += one_char[0];				//Add it to the textline
 | 
			
		||||
			}
 | 
			
		||||
			else if (one_char[0] == '\n') {			//New Line
 | 
			
		||||
				txtline_process(line);				//make sense of this textline
 | 
			
		||||
				line.clear();						//Ready for next textline
 | 
			
		||||
			} 
 | 
			
		||||
		}
 | 
			
		||||
		if (line.length() > 0) txtline_process(line);	//Last line had no newline at end ?
 | 
			
		||||
		if (!presets_db.size()) presets_Default();		//no antenna on txt, use default
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    populate_Presets();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::txtline_process(std::string& line) 
 | 
			
		||||
{
 | 
			
		||||
	if (line.find("#") != std::string::npos) return;	//Line is just a comment
 | 
			
		||||
 | 
			
		||||
	size_t comma = line.find(",");          //Get first comma position
 | 
			
		||||
    if (comma == std::string::npos) return; //No comma at all
 | 
			
		||||
 | 
			
		||||
	size_t previous = 0;
 | 
			
		||||
	preset_entry new_preset;
 | 
			
		||||
 | 
			
		||||
    new_preset.min = std::stoi(line.substr(0,comma));
 | 
			
		||||
    if (!new_preset.min) return;    //No frequency!
 | 
			
		||||
    
 | 
			
		||||
    previous = comma + 1;
 | 
			
		||||
    comma = line.find(",",previous);		//Search for next delimiter
 | 
			
		||||
    if (comma == std::string::npos) return; //No comma at all
 | 
			
		||||
 | 
			
		||||
    new_preset.max = std::stoi(line.substr(previous,comma - previous));
 | 
			
		||||
    if (!new_preset.max) return;    //No frequency!
 | 
			
		||||
 | 
			
		||||
    new_preset.label = line.substr(comma + 1);
 | 
			
		||||
    if (new_preset.label.size() == 0) return; //No label ?
 | 
			
		||||
    
 | 
			
		||||
    presets_db.push_back(new_preset);   //Add this preset.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::populate_Presets() 
 | 
			
		||||
{
 | 
			
		||||
    using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	using options_t = std::vector<option_t>;
 | 
			
		||||
    options_t entries;
 | 
			
		||||
 | 
			
		||||
	for (preset_entry preset : presets_db) 
 | 
			
		||||
        {	//go thru all available presets
 | 
			
		||||
            entries.emplace_back(preset.label,entries.size());
 | 
			
		||||
        }
 | 
			
		||||
    range_presets.set_options(entries);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GlassView::presets_Default() 
 | 
			
		||||
{
 | 
			
		||||
        presets_db.clear();
 | 
			
		||||
        presets_db.push_back({2320, 2560, "DEFAULT WIFI 2.4GHz"});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,187 @@
 | 
			
		||||
/*
 | 
			
		||||
  * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
  * Copyright (C) 2020 euquiq
 | 
			
		||||
  *
 | 
			
		||||
  * This file is part of PortaPack.
 | 
			
		||||
  *
 | 
			
		||||
  * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
  * it under the terms of the GNU General Public License as published by
 | 
			
		||||
  * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
  * any later version.
 | 
			
		||||
  *
 | 
			
		||||
  * This program is distributed in the hope that it will be useful,
 | 
			
		||||
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
  * GNU General Public License for more details.
 | 
			
		||||
  *
 | 
			
		||||
  * You should have received a copy of the GNU General Public License
 | 
			
		||||
  * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
  * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
  * Boston, MA 02110-1301, USA.
 | 
			
		||||
  */
 | 
			
		||||
 | 
			
		||||
 #include "ui.hpp"
 | 
			
		||||
 #include "portapack.hpp"
 | 
			
		||||
 #include "baseband_api.hpp"
 | 
			
		||||
 #include "receiver_model.hpp"
 | 
			
		||||
 #include "ui_widget.hpp"
 | 
			
		||||
 #include "ui_navigation.hpp"
 | 
			
		||||
 #include "ui_receiver.hpp"
 | 
			
		||||
 #include "string_format.hpp"
 | 
			
		||||
 #include "analog_audio_app.hpp"
 | 
			
		||||
 #include "spectrum_color_lut.hpp"
 | 
			
		||||
 | 
			
		||||
 namespace ui
 | 
			
		||||
 {
 | 
			
		||||
     #define LOOKING_GLASS_SLICE_WIDTH	20000000 // Each slice bandwidth 20 MHz
 | 
			
		||||
     #define MHZ_DIV	            1000000
 | 
			
		||||
     #define X2_MHZ_DIV	        2000000
 | 
			
		||||
 | 
			
		||||
     class GlassView : public View
 | 
			
		||||
     {
 | 
			
		||||
     public:
 | 
			
		||||
         GlassView(NavigationView &nav);
 | 
			
		||||
 | 
			
		||||
	 GlassView( const GlassView &);
 | 
			
		||||
	 GlassView& operator=(const GlassView &nav);
 | 
			
		||||
 | 
			
		||||
         ~GlassView();
 | 
			
		||||
         std::string title() const override { return "Looking Glass"; };
 | 
			
		||||
 | 
			
		||||
 	    void on_show() override;
 | 
			
		||||
 	    void on_hide() override;
 | 
			
		||||
         void focus() override;
 | 
			
		||||
 | 
			
		||||
     private:
 | 
			
		||||
     	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
        struct preset_entry
 | 
			
		||||
        {
 | 
			
		||||
            rf::Frequency min{};
 | 
			
		||||
            rf::Frequency max{};
 | 
			
		||||
            std::string label{};
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        std::vector<preset_entry> presets_db{};
 | 
			
		||||
 | 
			
		||||
         void on_channel_spectrum(const ChannelSpectrum& spectrum);
 | 
			
		||||
         void do_timers();
 | 
			
		||||
         void on_range_changed();
 | 
			
		||||
         void on_lna_changed(int32_t v_db);
 | 
			
		||||
 	    void on_vga_changed(int32_t v_db);
 | 
			
		||||
         void add_spectrum_pixel(Color color);
 | 
			
		||||
        void PlotMarker(rf::Frequency pos);
 | 
			
		||||
        void load_Presets();
 | 
			
		||||
        void txtline_process(std::string& line);
 | 
			
		||||
        void populate_Presets();
 | 
			
		||||
        void presets_Default();
 | 
			
		||||
 | 
			
		||||
         rf::Frequency f_min { 0 }, f_max { 0 };
 | 
			
		||||
         rf::Frequency search_span { 0 };
 | 
			
		||||
         rf::Frequency f_center { 0 };
 | 
			
		||||
         rf::Frequency f_center_ini { 0 };
 | 
			
		||||
         rf::Frequency marker_pixel_step { 0 };
 | 
			
		||||
         rf::Frequency each_bin_size { LOOKING_GLASS_SLICE_WIDTH  / 240 };
 | 
			
		||||
         rf::Frequency bins_Hz_size { 0 };
 | 
			
		||||
         uint8_t min_color_power { 0 };
 | 
			
		||||
         uint32_t pixel_index { 0 };
 | 
			
		||||
         std::array<Color, 240> spectrum_row = { 0 };
 | 
			
		||||
         ChannelSpectrumFIFO* fifo { nullptr }; 
 | 
			
		||||
         uint8_t max_power = 0;
 | 
			
		||||
 | 
			
		||||
        Labels labels{
 | 
			
		||||
            {{0, 0}, "MIN:     MAX:     LNA   VGA  ", Color::light_grey()},
 | 
			
		||||
            {{0, 1 * 16}, " RANGE:     FILTER:      AMP:", Color::light_grey()},
 | 
			
		||||
            {{0, 2 * 16}, "PRESET:", Color::light_grey()},
 | 
			
		||||
            {{0, 3 * 16}, "MARKER:     MHz +/-    MHz", Color::light_grey()},
 | 
			
		||||
             {{0, 4 * 16}, "RESOLUTION:    (fft trigger)", Color::light_grey()}
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
         NumberField field_frequency_min {
 | 
			
		||||
             { 4 * 8, 0 * 16 },
 | 
			
		||||
             4,
 | 
			
		||||
             { 0, 6960 },
 | 
			
		||||
             240,
 | 
			
		||||
             ' '
 | 
			
		||||
         };
 | 
			
		||||
 | 
			
		||||
         NumberField field_frequency_max {
 | 
			
		||||
             { 13 * 8, 0 * 16 },
 | 
			
		||||
             4,
 | 
			
		||||
             { 240, 7200 },
 | 
			
		||||
             240,
 | 
			
		||||
             ' '
 | 
			
		||||
         };
 | 
			
		||||
 | 
			
		||||
         LNAGainField field_lna {
 | 
			
		||||
             { 21 * 8, 0 * 16 }
 | 
			
		||||
         };
 | 
			
		||||
 | 
			
		||||
         VGAGainField field_vga {
 | 
			
		||||
             { 27 * 8, 0 * 16 }
 | 
			
		||||
         };
 | 
			
		||||
 | 
			
		||||
         Text text_range{
 | 
			
		||||
            {7 * 8, 1 * 16, 4 * 8, 16},
 | 
			
		||||
            ""};
 | 
			
		||||
 | 
			
		||||
        OptionsField filter_config{
 | 
			
		||||
            {19 * 8, 1 * 16},
 | 
			
		||||
            4,
 | 
			
		||||
            {
 | 
			
		||||
                {"OFF ", 0},
 | 
			
		||||
                {"MID ", 118}, //85 +25 (110) + a bit more to kill all blue
 | 
			
		||||
                {"HIGH", 202}, //168 + 25 (193)
 | 
			
		||||
            }};
 | 
			
		||||
 | 
			
		||||
        RFAmpField field_rf_amp{
 | 
			
		||||
            {29 * 8, 1 * 16}};
 | 
			
		||||
 | 
			
		||||
        OptionsField range_presets{
 | 
			
		||||
            {7 * 8, 2 * 16},
 | 
			
		||||
            20,
 | 
			
		||||
            {
 | 
			
		||||
                {" NONE (WIFI 2.4GHz)", 0},
 | 
			
		||||
            }};
 | 
			
		||||
 | 
			
		||||
        NumberField field_marker{
 | 
			
		||||
            {7 * 8, 3 * 16},
 | 
			
		||||
            4,
 | 
			
		||||
            {0, 7200},
 | 
			
		||||
            25,
 | 
			
		||||
            ' '};
 | 
			
		||||
 | 
			
		||||
        Text text_marker_pm{
 | 
			
		||||
            {20 * 8, 3 * 16, 2 * 8, 16},
 | 
			
		||||
            ""};
 | 
			
		||||
 | 
			
		||||
        NumberField field_trigger{
 | 
			
		||||
             {11 * 8, 4 * 16},
 | 
			
		||||
             3,
 | 
			
		||||
             {2, 128},
 | 
			
		||||
             2,
 | 
			
		||||
             ' '};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
     MessageHandlerRegistration message_handler_spectrum_config {
 | 
			
		||||
 		Message::ID::ChannelSpectrumConfig,
 | 
			
		||||
 		[this](const Message* const p) {
 | 
			
		||||
 			const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
 | 
			
		||||
 			this->fifo = message.fifo;
 | 
			
		||||
 		}
 | 
			
		||||
 	};
 | 
			
		||||
 	MessageHandlerRegistration message_handler_frame_sync {
 | 
			
		||||
 		Message::ID::DisplayFrameSync,
 | 
			
		||||
 		[this](const Message* const) {
 | 
			
		||||
 			if( this->fifo ) {
 | 
			
		||||
 				ChannelSpectrum channel_spectrum;
 | 
			
		||||
 				while( fifo->out(channel_spectrum) ) {
 | 
			
		||||
 					this->on_channel_spectrum(channel_spectrum);
 | 
			
		||||
 				}
 | 
			
		||||
 			}
 | 
			
		||||
 		}
 | 
			
		||||
 	};
 | 
			
		||||
 | 
			
		||||
     };
 | 
			
		||||
 } 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										554
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_mictx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										554
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_mictx.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,554 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_mictx.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
 | 
			
		||||
#include "wm8731.hpp"
 | 
			
		||||
using wolfson::wm8731::WM8731;
 | 
			
		||||
 | 
			
		||||
#include "tonesets.hpp"
 | 
			
		||||
#include "portapack_hal.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "irq_controls.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace tonekey;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
WM8731 audio_codec_wm8731 { i2c0, 0x1a };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void MicTXView::focus() {
 | 
			
		||||
	switch(focused_ui) {
 | 
			
		||||
		case 0:
 | 
			
		||||
			field_frequency.focus();
 | 
			
		||||
			break;
 | 
			
		||||
		case 1:
 | 
			
		||||
			field_rxfrequency.focus();
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			field_va.focus();
 | 
			
		||||
			break;
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::update_vumeter() {
 | 
			
		||||
	vumeter.set_value(audio_level);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::on_tx_progress(const bool done) {
 | 
			
		||||
	// Roger beep played, stop transmitting
 | 
			
		||||
	if (done)
 | 
			
		||||
		set_tx(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::configure_baseband() {
 | 
			
		||||
	baseband::set_audiotx_config(
 | 
			
		||||
		sampling_rate / 20,		// Update vu-meter at 20Hz
 | 
			
		||||
		transmitting ? transmitter_model.channel_bandwidth() : 0,
 | 
			
		||||
		mic_gain,
 | 
			
		||||
		TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
 | 
			
		||||
		enable_am,
 | 
			
		||||
		enable_dsb,
 | 
			
		||||
		enable_usb,
 | 
			
		||||
		enable_lsb
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::set_tx(bool enable) {
 | 
			
		||||
	if (enable) {
 | 
			
		||||
		if (rx_enabled)  //If audio RX is enabled
 | 
			
		||||
			rxaudio(false); //Then turn off audio RX
 | 
			
		||||
		transmitting = true;
 | 
			
		||||
		configure_baseband();
 | 
			
		||||
		transmitter_model.set_tuning_frequency(tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(tx_gain);
 | 
			
		||||
		transmitter_model.set_rf_amp(rf_amp);
 | 
			
		||||
		transmitter_model.enable();
 | 
			
		||||
		portapack::pin_i2s0_rx_sda.mode(3);		// This is already done in audio::init but gets changed by the CPLD overlay reprogramming
 | 
			
		||||
	} else {
 | 
			
		||||
		if (transmitting && rogerbeep_enabled) {
 | 
			
		||||
			baseband::request_beep();	//Transmit the roger beep
 | 
			
		||||
			transmitting = false;		//And flag the end of the transmission so ...
 | 
			
		||||
		} else { // (if roger beep was enabled, this will be executed after the beep ends transmitting.
 | 
			
		||||
			transmitting = false;
 | 
			
		||||
			configure_baseband();
 | 
			
		||||
			transmitter_model.disable();
 | 
			
		||||
			if (rx_enabled)  //If audio RX is enabled and we've been transmitting
 | 
			
		||||
				rxaudio(true); //Turn back on audio RX
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::do_timing() {
 | 
			
		||||
	if (va_enabled) {
 | 
			
		||||
		if (!transmitting) {
 | 
			
		||||
			// Attack
 | 
			
		||||
			if (audio_level >= va_level) {
 | 
			
		||||
				if ((attack_timer >> 8) >= attack_ms) {
 | 
			
		||||
					decay_timer = 0;
 | 
			
		||||
					attack_timer = 0;
 | 
			
		||||
					set_tx(true);
 | 
			
		||||
				} else {
 | 
			
		||||
					attack_timer += lcd_frame_duration;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				attack_timer = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Decay
 | 
			
		||||
			if (audio_level < va_level) {
 | 
			
		||||
				if ((decay_timer >> 8) >= decay_ms) {
 | 
			
		||||
					decay_timer = 0;
 | 
			
		||||
					attack_timer = 0;
 | 
			
		||||
					set_tx(false);
 | 
			
		||||
				} else {
 | 
			
		||||
					decay_timer += lcd_frame_duration;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				decay_timer = 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Check for PTT release
 | 
			
		||||
		const auto switches_state = get_switches_state();
 | 
			
		||||
		if (!switches_state[4] && transmitting && !button_touch)		// Select button
 | 
			
		||||
			set_tx(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Hmmmm. Maybe useless now.
 | 
			
		||||
void MicTXView::on_tuning_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
	//if ( rx_enabled )
 | 
			
		||||
		receiver_model.set_tuning_frequency(f); //Update freq also for RX
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
void MicTXView::rxaudio(bool is_on) {
 | 
			
		||||
	if (is_on) {
 | 
			
		||||
		audio::input::stop();
 | 
			
		||||
		baseband::shutdown();
 | 
			
		||||
		
 | 
			
		||||
		if (enable_am || enable_usb || enable_lsb || enable_dsb) {
 | 
			
		||||
			baseband::run_image(portapack::spi_flash::image_tag_am_audio);
 | 
			
		||||
			receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);	
 | 
			
		||||
			if (options_mode.selected_index() < 4)
 | 
			
		||||
				receiver_model.set_am_configuration(options_mode.selected_index() - 1);
 | 
			
		||||
			else
 | 
			
		||||
				receiver_model.set_am_configuration(0);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
 | 
			
		||||
			receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		receiver_model.set_sampling_rate(3072000);
 | 
			
		||||
		receiver_model.set_baseband_bandwidth(1750000);	
 | 
			
		||||
//		receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
 | 
			
		||||
		receiver_model.set_tuning_frequency(rx_frequency); // Now with seperate controls!
 | 
			
		||||
		receiver_model.set_lna(rx_lna);
 | 
			
		||||
		receiver_model.set_vga(rx_vga);
 | 
			
		||||
		receiver_model.set_rf_amp(rx_amp);
 | 
			
		||||
		receiver_model.enable();
 | 
			
		||||
		audio::output::start();
 | 
			
		||||
	} else {	//These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
 | 
			
		||||
		receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //This fixes something with AM RX...
 | 
			
		||||
		receiver_model.disable();
 | 
			
		||||
		baseband::shutdown();
 | 
			
		||||
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
 | 
			
		||||
		audio::output::stop();
 | 
			
		||||
		audio::input::start();	// set up audio input =  mic config of any audio coded AK4951/WM8731, (in WM8731 parameter will be ignored)
 | 
			
		||||
		portapack::pin_i2s0_rx_sda.mode(3);
 | 
			
		||||
		configure_baseband();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::on_headphone_volume_changed(int32_t v) {
 | 
			
		||||
	//if (rx_enabled) {
 | 
			
		||||
		const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
 | 
			
		||||
		receiver_model.set_headphone_volume(new_volume);
 | 
			
		||||
	//}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicTXView::set_ptt_visibility(bool v) {
 | 
			
		||||
	tx_button.hidden(!v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MicTXView::MicTXView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
	portapack::pin_i2s0_rx_sda.mode(3);		// This is already done in audio::init but gets changed by the CPLD overlay reprogramming
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	if (true ) {       // Temporary , disabling ALC feature , (pending to solve -No Audio in Mic app ,in some H2/H2+ WM /QFP100 CPLS users-  if ( audio_codec_wm8731.detected() ) {
 | 
			
		||||
	add_children({		
 | 
			
		||||
		&labels_WM8731,		// we have audio codec WM8731, same MIC menu  as original.
 | 
			
		||||
		&vumeter,
 | 
			
		||||
		&options_gain,		// MIC GAIN float factor on the GUI.
 | 
			
		||||
//		&check_va,
 | 
			
		||||
		&field_va,
 | 
			
		||||
		&field_va_level,
 | 
			
		||||
		&field_va_attack,
 | 
			
		||||
		&field_va_decay,
 | 
			
		||||
		&field_bw,
 | 
			
		||||
		&field_rfgain,
 | 
			
		||||
		&field_rfamp,
 | 
			
		||||
		&options_mode,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&options_tone_key,
 | 
			
		||||
		&check_rogerbeep,
 | 
			
		||||
		&check_rxactive,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&field_rxbw,
 | 
			
		||||
		&field_squelch,
 | 
			
		||||
		&field_rxfrequency,
 | 
			
		||||
		&field_rxlna,
 | 
			
		||||
		&field_rxvga,
 | 
			
		||||
		&field_rxamp,
 | 
			
		||||
		&tx_button
 | 
			
		||||
	});
 | 
			
		||||
    
 | 
			
		||||
	} else {			
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels_AK4951,		// we have audio codec AK4951, enable Automatic Level Control
 | 
			
		||||
		&vumeter,
 | 
			
		||||
		&options_gain,
 | 
			
		||||
		&options_ak4951_alc_mode,
 | 
			
		||||
//		&check_va,
 | 
			
		||||
		&field_va,
 | 
			
		||||
		&field_va_level,
 | 
			
		||||
		&field_va_attack,
 | 
			
		||||
		&field_va_decay,
 | 
			
		||||
		&field_bw,
 | 
			
		||||
		&field_rfgain,
 | 
			
		||||
		&field_rfamp,
 | 
			
		||||
		&options_mode,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&options_tone_key,
 | 
			
		||||
		&check_rogerbeep,
 | 
			
		||||
		&check_rxactive,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&field_rxbw,
 | 
			
		||||
		&field_squelch,
 | 
			
		||||
		&field_rxfrequency,
 | 
			
		||||
		&field_rxlna,
 | 
			
		||||
		&field_rxvga,
 | 
			
		||||
		&field_rxamp,
 | 
			
		||||
		&tx_button
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tone_keys_populate(options_tone_key);
 | 
			
		||||
	options_tone_key.on_change = [this](size_t i, int32_t) {
 | 
			
		||||
		tone_key_index = i;
 | 
			
		||||
	};
 | 
			
		||||
	options_tone_key.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	options_gain.on_change = [this](size_t, int32_t v) {
 | 
			
		||||
		mic_gain = v / 10.0;
 | 
			
		||||
		configure_baseband();
 | 
			
		||||
	};
 | 
			
		||||
	options_gain.set_selected_index(1);		// x1.0
 | 
			
		||||
	
 | 
			
		||||
	options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
 | 
			
		||||
	      ak4951_alc_GUI_selected  = v;
 | 
			
		||||
		  audio::input::start();
 | 
			
		||||
	};
 | 
			
		||||
	// options_ak4951_alc_mode.set_selected_index(0);
 | 
			
		||||
	 
 | 
			
		||||
	tx_frequency = transmitter_model.tuning_frequency();
 | 
			
		||||
	field_frequency.set_value(transmitter_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		tx_frequency = f;
 | 
			
		||||
		if(!rx_enabled)
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		focused_ui = 0;
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(tx_frequency);
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			tx_frequency = f;
 | 
			
		||||
			if(!rx_enabled)
 | 
			
		||||
				transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
			set_dirty();
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_bw.on_change = [this](uint32_t v) {
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(v * 1000);
 | 
			
		||||
	};
 | 
			
		||||
	field_bw.set_value(10);
 | 
			
		||||
	
 | 
			
		||||
	tx_gain = transmitter_model.tx_gain();
 | 
			
		||||
	field_rfgain.on_change = [this](int32_t v) {
 | 
			
		||||
		tx_gain = v;
 | 
			
		||||
		
 | 
			
		||||
	};
 | 
			
		||||
	field_rfgain.set_value(tx_gain);
 | 
			
		||||
 | 
			
		||||
	rf_amp = transmitter_model.rf_amp();
 | 
			
		||||
	field_rfamp.on_change = [this](int32_t v) {
 | 
			
		||||
		rf_amp = (bool)v;
 | 
			
		||||
	};
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);
 | 
			
		||||
	
 | 
			
		||||
	options_mode.on_change = [this](size_t, int32_t v) {
 | 
			
		||||
		enable_am = false;
 | 
			
		||||
		enable_usb = false;
 | 
			
		||||
		enable_lsb = false;
 | 
			
		||||
		enable_dsb = false;
 | 
			
		||||
		switch(v) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				enable_am = false;
 | 
			
		||||
				enable_usb = false;
 | 
			
		||||
				enable_lsb = false;
 | 
			
		||||
				enable_dsb = false;
 | 
			
		||||
				field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
 | 
			
		||||
				//if (rx_enabled)
 | 
			
		||||
				rxaudio(rx_enabled); 					//Update now if we have RX audio on
 | 
			
		||||
				options_tone_key.hidden(0);				// we are in FM mode , we should have active the Key-tones & CTCSS option.
 | 
			
		||||
				field_rxbw.hidden(0);					// we are in FM mode,  we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K) 
 | 
			
		||||
				break;
 | 
			
		||||
			case 1:
 | 
			
		||||
				enable_am = true;
 | 
			
		||||
				rxaudio(rx_enabled); 					//Update now if we have RX audio on
 | 
			
		||||
				options_tone_key.set_selected_index(0);	// we are NOT in FM mode ,  we reset the possible previous key-tones &CTCSS selection.
 | 
			
		||||
				set_dirty();							// Refresh display	
 | 
			
		||||
				options_tone_key.hidden(1);				// we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
 | 
			
		||||
				field_rxbw.hidden(1);					// we hide the NFM BW selection in other modes non_FM  (no meaning in AM/DSB/SSB)
 | 
			
		||||
				check_rogerbeep.hidden(0);				// make visible again the "rogerbeep" selection.
 | 
			
		||||
				break;
 | 
			
		||||
			case 2:
 | 
			
		||||
				enable_usb = true;
 | 
			
		||||
				rxaudio(rx_enabled); 					//Update now if we have RX audio on
 | 
			
		||||
				check_rogerbeep.set_value(false);		// reset the possible activation of roger beep, because it is not compatible with SSB , by now. 
 | 
			
		||||
				check_rogerbeep.hidden(1);				// hide that roger beep selection. 
 | 
			
		||||
				set_dirty();							// Refresh display
 | 
			
		||||
				break;
 | 
			
		||||
			case 3:
 | 
			
		||||
				enable_lsb = true;
 | 
			
		||||
				rxaudio(rx_enabled); 					//Update now if we have RX audio on
 | 
			
		||||
				check_rogerbeep.set_value(false);		// reset the possible activation of roger beep, because it is not compatible with SSB , by now. 
 | 
			
		||||
				check_rogerbeep.hidden(1);				// hide that roger beep selection. 
 | 
			
		||||
				set_dirty();							// Refresh display
 | 
			
		||||
				break;
 | 
			
		||||
			case 4:
 | 
			
		||||
				enable_dsb = true;
 | 
			
		||||
				rxaudio(rx_enabled); 					//Update now if we have RX audio on
 | 
			
		||||
				check_rogerbeep.hidden(0);				// make visible again the "rogerbeep" selection.
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		//configure_baseband();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	/*
 | 
			
		||||
	check_va.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		va_enabled = v;
 | 
			
		||||
		text_ptt.hidden(v);			//hide / show PTT text
 | 
			
		||||
		check_rxactive.hidden(v); 	//hide / show the RX AUDIO
 | 
			
		||||
		set_dirty();				//Refresh display
 | 
			
		||||
	};
 | 
			
		||||
	*/
 | 
			
		||||
	field_va.set_selected_index(1);
 | 
			
		||||
	field_va.on_change = [this](size_t, int32_t v) {
 | 
			
		||||
		switch(v) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				va_enabled = 0;
 | 
			
		||||
				this->set_ptt_visibility(0);
 | 
			
		||||
				check_rxactive.hidden(0);
 | 
			
		||||
				ptt_enabled = 0;
 | 
			
		||||
				break;
 | 
			
		||||
			case 1:
 | 
			
		||||
				va_enabled = 0;
 | 
			
		||||
				this->set_ptt_visibility(1);
 | 
			
		||||
				check_rxactive.hidden(0);
 | 
			
		||||
				ptt_enabled = 1;
 | 
			
		||||
				break;
 | 
			
		||||
			case 2:
 | 
			
		||||
				if (!rx_enabled) {
 | 
			
		||||
					va_enabled = 1;
 | 
			
		||||
					this->set_ptt_visibility(0);
 | 
			
		||||
					check_rxactive.hidden(1);
 | 
			
		||||
					ptt_enabled = 0;
 | 
			
		||||
				} else {
 | 
			
		||||
					field_va.set_selected_index(1);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		set_dirty();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	check_rogerbeep.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		rogerbeep_enabled = v;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_va_level.on_change = [this](int32_t v) {
 | 
			
		||||
		va_level = v;
 | 
			
		||||
		vumeter.set_mark(v);
 | 
			
		||||
	};
 | 
			
		||||
	field_va_level.set_value(40);
 | 
			
		||||
	
 | 
			
		||||
	field_va_attack.on_change = [this](int32_t v) {
 | 
			
		||||
		attack_ms = v;
 | 
			
		||||
	};
 | 
			
		||||
	field_va_attack.set_value(500);
 | 
			
		||||
	
 | 
			
		||||
	field_va_decay.on_change = [this](int32_t v) {
 | 
			
		||||
		decay_ms = v;
 | 
			
		||||
	};
 | 
			
		||||
	field_va_decay.set_value(1000);
 | 
			
		||||
 | 
			
		||||
	check_rxactive.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
//		vumeter.set_value(0);	//Start with a clean vumeter
 | 
			
		||||
		rx_enabled = v;
 | 
			
		||||
//		check_va.hidden(v); 	//Hide or show voice activation
 | 
			
		||||
		rxaudio(v);				//Activate-Deactivate audio rx accordingly
 | 
			
		||||
		set_dirty();			//Refresh interface
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
 | 
			
		||||
	field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v);	};
 | 
			
		||||
 | 
			
		||||
	field_rxbw.on_change = [this](size_t, int32_t v) {
 | 
			
		||||
		switch(v) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				receiver_model.set_nbfm_configuration(0);
 | 
			
		||||
				break;
 | 
			
		||||
			case 1:
 | 
			
		||||
				receiver_model.set_nbfm_configuration(1);
 | 
			
		||||
				break;
 | 
			
		||||
			case 2:
 | 
			
		||||
				receiver_model.set_nbfm_configuration(2);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	field_rxbw.set_selected_index(2);
 | 
			
		||||
 | 
			
		||||
	field_squelch.on_change = [this](int32_t v) { 
 | 
			
		||||
		receiver_model.set_squelch_level(100 - v);	
 | 
			
		||||
	};
 | 
			
		||||
	field_squelch.set_value(0);
 | 
			
		||||
	receiver_model.set_squelch_level(0);
 | 
			
		||||
 | 
			
		||||
	rx_frequency = receiver_model.tuning_frequency();
 | 
			
		||||
	field_rxfrequency.set_value(rx_frequency);
 | 
			
		||||
	field_rxfrequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_rxfrequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		rx_frequency = f;
 | 
			
		||||
		if(rx_enabled)
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_rxfrequency.on_edit = [this, &nav]() {
 | 
			
		||||
		focused_ui = 1;
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(rx_frequency);
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			rx_frequency = f;
 | 
			
		||||
			if(rx_enabled)
 | 
			
		||||
				receiver_model.set_tuning_frequency(f);
 | 
			
		||||
			this->field_rxfrequency.set_value(f);
 | 
			
		||||
			set_dirty();
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	rx_lna = receiver_model.lna();
 | 
			
		||||
	field_rxlna.on_change = [this](int32_t v) {
 | 
			
		||||
		rx_lna = v;
 | 
			
		||||
		if(rx_enabled)
 | 
			
		||||
			receiver_model.set_lna(v);
 | 
			
		||||
	};
 | 
			
		||||
	field_rxlna.set_value(rx_lna);
 | 
			
		||||
 | 
			
		||||
	rx_vga = receiver_model.vga();
 | 
			
		||||
	field_rxvga.on_change = [this](int32_t v) {
 | 
			
		||||
		rx_vga = v;
 | 
			
		||||
		if(rx_enabled)
 | 
			
		||||
			receiver_model.set_vga(v);
 | 
			
		||||
	};
 | 
			
		||||
	field_rxvga.set_value(rx_vga);
 | 
			
		||||
 | 
			
		||||
	rx_amp = receiver_model.rf_amp();
 | 
			
		||||
	field_rxamp.on_change = [this](int32_t v) {
 | 
			
		||||
		rx_amp = v;
 | 
			
		||||
		if(rx_enabled)
 | 
			
		||||
			receiver_model.set_rf_amp(rx_amp);
 | 
			
		||||
	};
 | 
			
		||||
	field_rxamp.set_value(rx_amp);
 | 
			
		||||
 | 
			
		||||
	tx_button.on_select = [this](Button&) {
 | 
			
		||||
		if(ptt_enabled && !transmitting) {
 | 
			
		||||
			set_tx(true);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	tx_button.on_touch_release = [this](Button&) {
 | 
			
		||||
		if(button_touch) {
 | 
			
		||||
			button_touch = false;
 | 
			
		||||
			set_tx(false);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	tx_button.on_touch_press = [this](Button&) {
 | 
			
		||||
		if(!transmitting) {
 | 
			
		||||
			button_touch = true;
 | 
			
		||||
		}	
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	transmitter_model.set_sampling_rate(sampling_rate);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	
 | 
			
		||||
	set_tx(false);
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	audio::input::start();		// originally , audio::input::start();  (we added parameter)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MicTXView::~MicTXView() {
 | 
			
		||||
	audio::input::stop();
 | 
			
		||||
	transmitter_model.set_tuning_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	if (rx_enabled) //Also turn off audio rx if enabled
 | 
			
		||||
		rxaudio(false);
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										375
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_mictx.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_mictx.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,375 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_MICTX_H__
 | 
			
		||||
#define __UI_MICTX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "tone_key.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class MicTXView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	MicTXView(NavigationView& nav);
 | 
			
		||||
	~MicTXView();
 | 
			
		||||
 | 
			
		||||
	MicTXView(const MicTXView&) = delete;
 | 
			
		||||
	MicTXView(MicTXView&&) = delete;
 | 
			
		||||
	MicTXView& operator=(const MicTXView&) = delete;
 | 
			
		||||
	MicTXView& operator=(MicTXView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	// PTT: Enable through KeyEvent (only works with presses), disable by polling :(
 | 
			
		||||
	// This is the old "RIGHT BUTTON" method.
 | 
			
		||||
	/*
 | 
			
		||||
	bool on_key(const KeyEvent key) {
 | 
			
		||||
		if ((key == KeyEvent::Right) && (!va_enabled) && ptt_enabled) {
 | 
			
		||||
			set_tx(true);
 | 
			
		||||
			return true;
 | 
			
		||||
		} else
 | 
			
		||||
			return false;
 | 
			
		||||
	};
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Microphone"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static constexpr uint32_t sampling_rate = 1536000U;
 | 
			
		||||
	static constexpr uint32_t lcd_frame_duration = (256 * 1000UL) / 60;	// 1 frame @ 60fps in ms .8 fixed point  /60
 | 
			
		||||
	
 | 
			
		||||
	void update_vumeter();
 | 
			
		||||
	void do_timing();
 | 
			
		||||
	void set_tx(bool enable);
 | 
			
		||||
//	void on_tuning_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_tx_progress(const bool done);
 | 
			
		||||
	void configure_baseband();
 | 
			
		||||
 | 
			
		||||
	void rxaudio(bool is_on);
 | 
			
		||||
	void on_headphone_volume_changed(int32_t v);
 | 
			
		||||
 | 
			
		||||
	void set_ptt_visibility(bool v);
 | 
			
		||||
	
 | 
			
		||||
	bool transmitting { false };
 | 
			
		||||
	bool va_enabled { false };
 | 
			
		||||
	bool ptt_enabled { true };
 | 
			
		||||
	bool rogerbeep_enabled { false };
 | 
			
		||||
	bool rx_enabled { false };
 | 
			
		||||
	uint32_t tone_key_index { };
 | 
			
		||||
	float mic_gain { 1.0 };
 | 
			
		||||
    uint8_t  ak4951_alc_GUI_selected { 0 }; 
 | 
			
		||||
	uint32_t audio_level { 0 };
 | 
			
		||||
	uint32_t va_level { };
 | 
			
		||||
	uint32_t attack_ms { };
 | 
			
		||||
	uint32_t decay_ms { };
 | 
			
		||||
	uint32_t attack_timer { 0 };
 | 
			
		||||
	uint32_t decay_timer { 0 };
 | 
			
		||||
	int32_t tx_gain { 47 };
 | 
			
		||||
    bool rf_amp { false };
 | 
			
		||||
	int32_t rx_lna { 32 };
 | 
			
		||||
	int32_t rx_vga { 32 };
 | 
			
		||||
	bool rx_amp { false };
 | 
			
		||||
	rf::Frequency tx_frequency { 0 };
 | 
			
		||||
	rf::Frequency rx_frequency { 0 };
 | 
			
		||||
	int32_t focused_ui { 2 };
 | 
			
		||||
	bool button_touch { false };
 | 
			
		||||
 | 
			
		||||
	//AM TX Stuff
 | 
			
		||||
	bool enable_am { false };
 | 
			
		||||
	bool enable_dsb { false };
 | 
			
		||||
	bool enable_usb { false };
 | 
			
		||||
	bool enable_lsb { false };
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	Labels labels_WM8731 {		
 | 
			
		||||
		{ { 3 * 8, 1 * 8 }, "MIC-GAIN:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 3 * 8 }, "F:", Color::light_grey() },
 | 
			
		||||
		{ { 15 * 8, 3 * 8 }, "BW:   FM kHz", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
 | 
			
		||||
		{ {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
 | 
			
		||||
		{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() },
 | 
			
		||||
		{ {12 * 8, 10 * 8 }, "ATT:", Color::light_grey() },
 | 
			
		||||
		{ {20 * 8, 10 * 8 }, "DEC:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
 | 
			
		||||
		{ { 7 * 8, 23 * 8 }, "VOL:", Color::light_grey() },
 | 
			
		||||
		{ {15 * 8, 23 * 8 }, "FM RXBW:", Color::light_grey() },
 | 
			
		||||
		{ {17 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
 | 
			
		||||
		{ { 5 * 8, 25 * 8 }, "F:", Color::light_grey() },
 | 
			
		||||
		{ { 5 * 8, 27 * 8 }, "LNA:", Color::light_grey()},
 | 
			
		||||
		{ {12 * 8, 27 * 8 }, "VGA:", Color::light_grey()},
 | 
			
		||||
		{ {19 * 8, 27 * 8 }, "AMP:", Color::light_grey()}
 | 
			
		||||
	};
 | 
			
		||||
	Labels labels_AK4951 {
 | 
			
		||||
		{ { 3 * 8, 1 * 8 }, "MIC-GAIN:", Color::light_grey() },
 | 
			
		||||
		{ { 17 * 8, 1 * 8 }, "ALC", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 3 * 8 }, "F:", Color::light_grey() },
 | 
			
		||||
		{ { 15 * 8, 3 * 8 }, "BW:   FM kHz", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
 | 
			
		||||
		{ {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
 | 
			
		||||
		{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() },
 | 
			
		||||
		{ {12 * 8, 10 * 8 }, "ATT:", Color::light_grey() },
 | 
			
		||||
		{ {20 * 8, 10 * 8 }, "DEC:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
 | 
			
		||||
		{ { 7 * 8, 23 * 8 }, "VOL:", Color::light_grey() },
 | 
			
		||||
		{ {15 * 8, 23 * 8 }, "FM RXBW:", Color::light_grey() },
 | 
			
		||||
		{ {17 * 8, 25 * 8 }, "SQ:", Color::light_grey() },
 | 
			
		||||
		{ { 5 * 8, 25 * 8 }, "F:", Color::light_grey() },
 | 
			
		||||
		{ { 5 * 8, 27 * 8 }, "LNA:", Color::light_grey()},
 | 
			
		||||
		{ {12 * 8, 27 * 8 }, "VGA:", Color::light_grey()},
 | 
			
		||||
		{ {19 * 8, 27 * 8 }, "AMP:", Color::light_grey()}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VuMeter vumeter {
 | 
			
		||||
		{ 0 * 8, 1 * 8, 2 * 8, 33 * 8 },
 | 
			
		||||
		12,
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_gain {
 | 
			
		||||
		{ 12 * 8, 1 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		{
 | 
			
		||||
			{ "x0.5", 5 },
 | 
			
		||||
			{ "x1.0", 10 },
 | 
			
		||||
			{ "x1.5", 15 },
 | 
			
		||||
			{ "x2.0", 20 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_ak4951_alc_mode {
 | 
			
		||||
		{ 20 * 8, 1 * 8 },				// Coordinates are: int:x (px), int:y (px)				
 | 
			
		||||
		11,
 | 
			
		||||
		{
 | 
			
		||||
			{ " OFF-20kHz", 0 },	// Nothing changed from ORIGINAL,keeping ALL programm. AK4951 Dig. block->OFF)
 | 
			
		||||
			{ "+12dB-6kHz", 1 },	// ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "+09dB-6kHz", 2 },	// ALC-> on, (+09dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "+06dB-6kHz", 3 },	// ALC-> on, (+06dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "+03dB-2kHz", 4 },	// ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1,fb:1,7K, k=1,8)
 | 
			
		||||
			{ "+03dB-4kHz", 5 },	// ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4,fb~2,4k, k=1,8)
 | 
			
		||||
			{ "+03dB-6kHz", 6 },	// ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "+00dB-6kHz", 7 },	// ALC-> on, (+00dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "-03dB-6kHz", 8 },	// ALC-> on, (-03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "-06dB-6kHz", 9 },	// ALC-> on, (-06dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
 | 
			
		||||
			{ "-09dB-6kHz", 10 },	// ALC-> on, (-09dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz - Pre-amp MIC -3dB (18dB's) 
 | 
			
		||||
			{ "-12dB-6kHz", 11 },	// ALC-> on, (-12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz - Pre-amp MIC -6dB (15dB's) 	
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 5 * 8, 3 * 8 },
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_bw {
 | 
			
		||||
		{ 18 * 8, 3 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 0, 150 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_rfgain {
 | 
			
		||||
		{ 8 * 8, 5 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 47 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_rfamp {
 | 
			
		||||
		{ 15 * 8, 5 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 14 },
 | 
			
		||||
		14,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_mode {
 | 
			
		||||
		{ 24 * 8, 5 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{ "FM", 0 },
 | 
			
		||||
			{ "AM", 1 },
 | 
			
		||||
			{ "USB", 2 },
 | 
			
		||||
			{ "LSB", 3 },
 | 
			
		||||
			{ "DSB", 4 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	/*
 | 
			
		||||
	Checkbox check_va {
 | 
			
		||||
		{ 3 * 8, (10 * 8) - 4 },
 | 
			
		||||
		7,
 | 
			
		||||
		"Voice activation",
 | 
			
		||||
		false
 | 
			
		||||
	};
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	OptionsField field_va {
 | 
			
		||||
		{ 17 * 8, 8 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{
 | 
			
		||||
			{" OFF", 0},
 | 
			
		||||
			{" PTT", 1},
 | 
			
		||||
			{"AUTO", 2}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_va_level {
 | 
			
		||||
		{ 8 * 8, 10 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 0, 255 },
 | 
			
		||||
		2,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_va_attack {
 | 
			
		||||
		{ 16 * 8, 10 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 0, 999 },
 | 
			
		||||
		20,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_va_decay {
 | 
			
		||||
		{ 24 * 8, 10 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ 0, 9999 },
 | 
			
		||||
		100,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_tone_key {
 | 
			
		||||
		{ 10 * 8, ( 15 * 8 ) - 2 },
 | 
			
		||||
		23,
 | 
			
		||||
		{ }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Checkbox check_rogerbeep {
 | 
			
		||||
		{ 3 * 8, ( 16 * 8 ) + 4 },
 | 
			
		||||
		10,
 | 
			
		||||
		"Roger beep",
 | 
			
		||||
		false
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Checkbox check_rxactive {
 | 
			
		||||
		{ 3 * 8, ( 21 * 8 ) - 4 },
 | 
			
		||||
		8,
 | 
			
		||||
		"RX audio listening",
 | 
			
		||||
		false
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_volume {
 | 
			
		||||
		{ 11* 8, 23 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField field_rxbw {
 | 
			
		||||
	       { 23* 8, 23 * 8},
 | 
			
		||||
	       3,
 | 
			
		||||
	       {
 | 
			
		||||
	       		{"8k5", 0},
 | 
			
		||||
	       		{"11k", 1},
 | 
			
		||||
	       		{"16k", 2}
 | 
			
		||||
	       	}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_squelch {
 | 
			
		||||
		{ 20 * 8, 25 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FrequencyField field_rxfrequency {
 | 
			
		||||
		{ 7 * 8, 25 * 8 },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_rxlna {
 | 
			
		||||
		{ 9 * 8, 27 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 40 },
 | 
			
		||||
		8,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_rxvga {
 | 
			
		||||
		{ 16 * 8, 27 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 62 },
 | 
			
		||||
		2,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_rxamp {
 | 
			
		||||
		{ 23 * 8, 27 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{ 0, 1 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button tx_button {
 | 
			
		||||
		{ 10 * 8, 30 * 8, 10 * 8, 5 * 8 },
 | 
			
		||||
		"TX",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_lcd_sync {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			this->do_timing();
 | 
			
		||||
			this->update_vumeter();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_audio_level {
 | 
			
		||||
		Message::ID::AudioLevelReport,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const AudioLevelReportMessage*>(p);
 | 
			
		||||
			this->audio_level = message->value;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_MICTX_H__*/
 | 
			
		||||
@@ -0,0 +1,110 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_modemsetup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace modems;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void ModemSetupView::focus() {
 | 
			
		||||
	field_baudrate.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ModemSetupView::ModemSetupView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
	using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	using options_t = std::vector<option_t>;
 | 
			
		||||
	options_t modem_options;
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_baudrate,
 | 
			
		||||
		&field_mark,
 | 
			
		||||
		&field_space,
 | 
			
		||||
		&field_repeat,
 | 
			
		||||
		&options_modem,
 | 
			
		||||
		&button_set_modem,
 | 
			
		||||
		&sym_format,
 | 
			
		||||
		&button_save
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// Only list AFSK modems for now
 | 
			
		||||
	for (size_t i = 0; i < MODEM_DEF_COUNT; i++) {
 | 
			
		||||
		if (modem_defs[i].modulation == AFSK)
 | 
			
		||||
			modem_options.emplace_back(std::make_pair(modem_defs[i].name, i));
 | 
			
		||||
	}
 | 
			
		||||
	options_modem.set_options(modem_options);
 | 
			
		||||
	options_modem.set_selected_index(0);
 | 
			
		||||
	
 | 
			
		||||
	sym_format.set_symbol_list(0, "6789");		// Data bits
 | 
			
		||||
	sym_format.set_symbol_list(1, "NEo");		// Parity
 | 
			
		||||
	sym_format.set_symbol_list(2, "012");		// Stop bits
 | 
			
		||||
	sym_format.set_symbol_list(3, "ML");		// MSB/LSB first
 | 
			
		||||
	
 | 
			
		||||
	sym_format.set_sym(0, persistent_memory::serial_format().data_bits - 6);
 | 
			
		||||
	sym_format.set_sym(1, persistent_memory::serial_format().parity);
 | 
			
		||||
	sym_format.set_sym(2, persistent_memory::serial_format().stop_bits);
 | 
			
		||||
	sym_format.set_sym(3, persistent_memory::serial_format().bit_order);
 | 
			
		||||
	
 | 
			
		||||
	field_mark.set_value(persistent_memory::afsk_mark_freq());
 | 
			
		||||
	field_space.set_value(persistent_memory::afsk_space_freq());
 | 
			
		||||
	field_repeat.set_value(persistent_memory::modem_repeat());
 | 
			
		||||
	
 | 
			
		||||
	field_baudrate.set_value(persistent_memory::modem_baudrate());
 | 
			
		||||
	
 | 
			
		||||
	button_set_modem.on_select = [this, &nav](Button&) {
 | 
			
		||||
		size_t modem_def_index = options_modem.selected_index();
 | 
			
		||||
		
 | 
			
		||||
		field_mark.set_value(modem_defs[modem_def_index].mark_freq);
 | 
			
		||||
		field_space.set_value(modem_defs[modem_def_index].space_freq);
 | 
			
		||||
		field_baudrate.set_value(modem_defs[modem_def_index].baudrate);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_save.on_select = [this,&nav](Button&) {
 | 
			
		||||
		serial_format_t serial_format;
 | 
			
		||||
		
 | 
			
		||||
		persistent_memory::set_afsk_mark(field_mark.value());
 | 
			
		||||
		persistent_memory::set_afsk_space(field_space.value());
 | 
			
		||||
		
 | 
			
		||||
		persistent_memory::set_modem_baudrate(field_baudrate.value());
 | 
			
		||||
		persistent_memory::set_modem_repeat(field_repeat.value());
 | 
			
		||||
		
 | 
			
		||||
		serial_format.data_bits = sym_format.get_sym(0) + 6;
 | 
			
		||||
		serial_format.parity = (parity_enum)sym_format.get_sym(1);
 | 
			
		||||
		serial_format.stop_bits = sym_format.get_sym(2);
 | 
			
		||||
		serial_format.bit_order = (order_enum)sym_format.get_sym(3);
 | 
			
		||||
		
 | 
			
		||||
		persistent_memory::set_serial_format(serial_format);
 | 
			
		||||
		
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,108 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "modems.hpp"
 | 
			
		||||
#include "serializer.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class ModemSetupView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ModemSetupView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Modem setup"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 11 * 8 }, "Baudrate:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 13 * 8 }, "Mark:      Hz", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 15 * 8 }, "Space:     Hz", Color::light_grey() },
 | 
			
		||||
		{ { 140, 15 * 8 }, "Repeat:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 6 * 8 }, "Modem preset:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 22 * 8 }, "Serial format:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_baudrate {
 | 
			
		||||
		{ 11 * 8, 11 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		{ 50, 9600 },
 | 
			
		||||
		25,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_mark {
 | 
			
		||||
		{ 8 * 8, 13 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		{ 100, 15000 },
 | 
			
		||||
		25,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_space {
 | 
			
		||||
		{ 8 * 8, 15 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		{ 100, 15000 },
 | 
			
		||||
		25,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_repeat {
 | 
			
		||||
		{ 204, 15 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_modem {
 | 
			
		||||
		{ 15 * 8, 6 * 8 },
 | 
			
		||||
		7,
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_format {
 | 
			
		||||
		{ 16 * 8, 22 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		SymField::SYMFIELD_DEF
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_set_modem {
 | 
			
		||||
		{ 23 * 8, 6 * 8 - 4, 6 * 8, 24 },
 | 
			
		||||
		"SET"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 9 * 8, 250, 96, 40 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										284
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_morse.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_morse.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,284 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_morse.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "hackrf_gpio.hpp"
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace morse;
 | 
			
		||||
using namespace hackrf::one;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
static WORKING_AREA(ookthread_wa, 256);
 | 
			
		||||
 | 
			
		||||
static msg_t ookthread_fn(void * arg) {
 | 
			
		||||
	uint32_t v = 0, delay = 0;
 | 
			
		||||
	uint8_t * message_symbols = shared_memory.bb_data.tones_data.message;
 | 
			
		||||
	uint8_t symbol;
 | 
			
		||||
	MorseView * arg_c = (MorseView*)arg;
 | 
			
		||||
	
 | 
			
		||||
	chRegSetThreadName("ookthread");
 | 
			
		||||
	
 | 
			
		||||
	for (uint32_t i = 0; i < arg_c->symbol_count; i++) {
 | 
			
		||||
		if (chThdShouldTerminate()) break;
 | 
			
		||||
		
 | 
			
		||||
		symbol = message_symbols[i];
 | 
			
		||||
		
 | 
			
		||||
		v = (symbol < 2) ? 1 : 0;	// TX on for dot or dash, off for pause
 | 
			
		||||
		delay = morse_symbols[symbol];
 | 
			
		||||
		
 | 
			
		||||
		gpio_tx.write(v);
 | 
			
		||||
		arg_c->on_tx_progress(i, false);
 | 
			
		||||
		
 | 
			
		||||
		chThdSleepMilliseconds(delay * arg_c->time_unit_ms);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	gpio_tx.write(0);				// Ensure TX is off
 | 
			
		||||
	arg_c->on_tx_progress(0, true);
 | 
			
		||||
	chThdExit(0);
 | 
			
		||||
	
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static msg_t loopthread_fn(void * arg) {
 | 
			
		||||
	MorseView * arg_c = (MorseView*)arg;
 | 
			
		||||
	uint32_t	wait = arg_c->loop;
 | 
			
		||||
 | 
			
		||||
	chRegSetThreadName("loopthread");
 | 
			
		||||
 | 
			
		||||
	for (uint32_t i = 0; i < wait; i++) {
 | 
			
		||||
		if (chThdShouldTerminate()) break;
 | 
			
		||||
 | 
			
		||||
		arg_c->on_loop_progress(i, false);
 | 
			
		||||
		chThdSleepMilliseconds(1000);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	arg_c->on_loop_progress(0, true);
 | 
			
		||||
	chThdExit(0);
 | 
			
		||||
	
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::on_set_text(NavigationView& nav) {
 | 
			
		||||
	text_prompt(nav, buffer, 28);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::focus() {
 | 
			
		||||
	button_message.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MorseView::~MorseView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_morse", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::paint(Painter&) {
 | 
			
		||||
	message = buffer;
 | 
			
		||||
	text_message.set(message);
 | 
			
		||||
	update_tx_duration();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MorseView::start_tx() {
 | 
			
		||||
	// Re-generate message, just in case
 | 
			
		||||
	update_tx_duration();
 | 
			
		||||
	
 | 
			
		||||
	if (!symbol_count) {
 | 
			
		||||
		nav_.display_modal("Error", "Message too long,\nmust be < 256 symbols.", INFO, nullptr);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_max(symbol_count);
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(1536000U);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	if (modulation == CW) {
 | 
			
		||||
		ookthread = chThdCreateStatic(ookthread_wa, sizeof(ookthread_wa), NORMALPRIO + 10, ookthread_fn, this);
 | 
			
		||||
	} else if (modulation == FM) {
 | 
			
		||||
		baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::update_tx_duration() {
 | 
			
		||||
	uint32_t duration_ms;
 | 
			
		||||
	
 | 
			
		||||
	time_unit_ms = 1200 / field_speed.value();
 | 
			
		||||
	symbol_count = morse_encode(message, time_unit_ms, field_tone.value(), &time_units);
 | 
			
		||||
 | 
			
		||||
	if (symbol_count) {
 | 
			
		||||
		duration_ms = time_units * time_unit_ms;
 | 
			
		||||
		text_tx_duration.set(to_string_dec_uint(duration_ms / 1000) + "." + to_string_dec_uint((duration_ms / 100) % 10, 1) + "s   ");
 | 
			
		||||
	} else {
 | 
			
		||||
		text_tx_duration.set("-");		// Error
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (done) {
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		progressbar.set_value(0);
 | 
			
		||||
 | 
			
		||||
		if (loop && run) {
 | 
			
		||||
			text_tx_duration.set("wait");
 | 
			
		||||
			progressbar.set_max(loop);
 | 
			
		||||
			progressbar.set_value(0);
 | 
			
		||||
			
 | 
			
		||||
			if (loopthread) {
 | 
			
		||||
				chThdRelease(loopthread);
 | 
			
		||||
				loopthread = nullptr;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			loopthread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO, loopthread_fn, this);
 | 
			
		||||
		} else {
 | 
			
		||||
			tx_view.set_transmitting(false);
 | 
			
		||||
		}
 | 
			
		||||
	} else
 | 
			
		||||
		progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::on_loop_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (done) {
 | 
			
		||||
		start_tx();
 | 
			
		||||
	} else
 | 
			
		||||
		progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MorseView::set_foxhunt(size_t i) {
 | 
			
		||||
	message = foxhunt_codes[i];
 | 
			
		||||
	buffer = message.c_str();
 | 
			
		||||
	text_message.set(message);
 | 
			
		||||
	update_tx_duration();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MorseView::MorseView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_tones);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&checkbox_foxhunt,
 | 
			
		||||
		&options_foxhunt,
 | 
			
		||||
		&field_speed,
 | 
			
		||||
		&field_tone,
 | 
			
		||||
		&options_modulation,
 | 
			
		||||
		&options_loop,
 | 
			
		||||
		&text_tx_duration,
 | 
			
		||||
		&text_message,
 | 
			
		||||
		&button_message,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_morse", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Default settings
 | 
			
		||||
	field_speed.set_value(15);					// 15wps
 | 
			
		||||
	field_tone.set_value(700);					// 700Hz FM tone
 | 
			
		||||
	options_modulation.set_selected_index(0);	// CW mode
 | 
			
		||||
	options_loop.set_selected_index(0);			// Off
 | 
			
		||||
	
 | 
			
		||||
	checkbox_foxhunt.on_select = [this](Checkbox&, bool value) {
 | 
			
		||||
		foxhunt_mode = value;
 | 
			
		||||
		
 | 
			
		||||
		if (foxhunt_mode)
 | 
			
		||||
			set_foxhunt(options_foxhunt.selected_index_value());
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_foxhunt.on_change = [this](size_t i, int32_t) {
 | 
			
		||||
		if (foxhunt_mode)
 | 
			
		||||
			set_foxhunt(i);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_modulation.on_change = [this](size_t i, int32_t) {
 | 
			
		||||
		modulation = (modulation_t)i;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	options_loop.on_change = [this](size_t i, uint32_t n) {
 | 
			
		||||
		(void)i; //avoid unused warning
 | 
			
		||||
		loop = n;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_speed.on_change = [this](int32_t) {
 | 
			
		||||
		update_tx_duration();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_message.on_select = [this, &nav](Button&) {
 | 
			
		||||
		this->on_set_text(nav);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (start_tx()) {
 | 
			
		||||
			run = true;
 | 
			
		||||
			tx_view.set_transmitting(true);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		run = false;
 | 
			
		||||
		if (ookthread) chThdTerminate(ookthread);
 | 
			
		||||
 | 
			
		||||
		if (loopthread) {
 | 
			
		||||
			chThdTerminate(loopthread);
 | 
			
		||||
			chThdWait(loopthread);
 | 
			
		||||
			loopthread = nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		baseband::kill_tone();
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										196
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_morse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_morse.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __MORSE_TX_H__
 | 
			
		||||
#define __MORSE_TX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "volume.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "morse.hpp"
 | 
			
		||||
 | 
			
		||||
#include <ch.h>
 | 
			
		||||
 | 
			
		||||
using namespace morse;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class MorseView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	MorseView(NavigationView& nav);
 | 
			
		||||
	~MorseView();
 | 
			
		||||
	
 | 
			
		||||
	MorseView(const MorseView&) = delete;
 | 
			
		||||
	MorseView(MorseView&&) = delete;
 | 
			
		||||
	MorseView& operator=(const MorseView&) = delete;
 | 
			
		||||
	MorseView& operator=(MorseView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void paint(Painter& painter) override;
 | 
			
		||||
	
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	void on_loop_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Morse TX"; };
 | 
			
		||||
	
 | 
			
		||||
	uint32_t time_unit_ms { 0 };
 | 
			
		||||
	size_t symbol_count { 0 };
 | 
			
		||||
	uint32_t loop { 0 };
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	std::string buffer { "PORTAPACK" };
 | 
			
		||||
	std::string message { };
 | 
			
		||||
	uint32_t time_units { 0 };
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	enum modulation_t {
 | 
			
		||||
		CW = 0,
 | 
			
		||||
		FM = 1
 | 
			
		||||
	};
 | 
			
		||||
	modulation_t modulation { CW };
 | 
			
		||||
	
 | 
			
		||||
	bool start_tx();
 | 
			
		||||
	void update_tx_duration();
 | 
			
		||||
	void on_set_text(NavigationView& nav);
 | 
			
		||||
	void set_foxhunt(size_t i);
 | 
			
		||||
	
 | 
			
		||||
	Thread * ookthread { nullptr };
 | 
			
		||||
	Thread * loopthread { nullptr };
 | 
			
		||||
	bool foxhunt_mode { false };
 | 
			
		||||
	bool run { false };
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 4 * 8, 6 * 8 }, "Speed:   wps", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 8 * 8 }, "Tone:    Hz", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 10 * 8 }, "Modulation:", Color::light_grey() },
 | 
			
		||||
		{ { 4 * 8, 12 * 8 }, "Loop:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 25 * 8 }, "TX will last", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_foxhunt {
 | 
			
		||||
		{ 4 * 8, 16 },
 | 
			
		||||
		8,
 | 
			
		||||
		"Foxhunt:"
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_foxhunt {
 | 
			
		||||
		{ 17 * 8, 16 + 4 },
 | 
			
		||||
		7,
 | 
			
		||||
		{
 | 
			
		||||
			{ "1 (MOE)", 0 },
 | 
			
		||||
			{ "2 (MOI)", 1 },
 | 
			
		||||
			{ "3 (MOS)", 2 },
 | 
			
		||||
			{ "4 (MOH)", 3 },
 | 
			
		||||
			{ "5 (MO5)", 4 },
 | 
			
		||||
			{ "6 (MON)", 5 },
 | 
			
		||||
			{ "7 (MOD)", 6 },
 | 
			
		||||
			{ "8 (MOB)", 7 },
 | 
			
		||||
			{ "9 (MO6)", 8 },
 | 
			
		||||
			{ "X (MO) ", 9 },
 | 
			
		||||
			{ "T (S) ", 10 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_speed {
 | 
			
		||||
		{ 10 * 8, 6 * 8 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 10, 45 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_tone {
 | 
			
		||||
		{ 9 * 8, 8 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ 100, 9999 },
 | 
			
		||||
		20,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_modulation {
 | 
			
		||||
		{ 15 * 8, 10 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		{
 | 
			
		||||
			{ "CW", 0 },
 | 
			
		||||
			{ "FM", 1 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_loop {
 | 
			
		||||
		{ 9 * 8, 12 * 8 },
 | 
			
		||||
		6,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Off", 0 },
 | 
			
		||||
			{ "5 sec", 5 },
 | 
			
		||||
			{ "10 sec", 10 },
 | 
			
		||||
			{ "30 sec", 30 },
 | 
			
		||||
			{ "1 min", 60 },
 | 
			
		||||
			{ "3 min", 180 },
 | 
			
		||||
			{ "5 min", 300 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_tx_duration {
 | 
			
		||||
		{ 14 * 8, 25 * 8, 4 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_message {
 | 
			
		||||
		{ 1 * 8, 15 * 8, 28 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_message {
 | 
			
		||||
		{ 1 * 8, 17 * 8, 12 * 8, 28 },
 | 
			
		||||
		"Set message"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 2 * 8, 28 * 8, 208, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		12
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__MORSE_TX_H__*/
 | 
			
		||||
@@ -0,0 +1,178 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_nrf_rx.hpp"
 | 
			
		||||
#include "ui_modemsetup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "modems.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace modems;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void NRFRxView::focus() {
 | 
			
		||||
	field_frequency.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NRFRxView::update_freq(rf::Frequency f) {
 | 
			
		||||
	receiver_model.set_tuning_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NRFRxView::NRFRxView(NavigationView& nav) {
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_nrf_rx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&channel,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&text_debug,
 | 
			
		||||
		&button_modem_setup,
 | 
			
		||||
		&record_view,
 | 
			
		||||
		&console
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("rx_nrf", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		field_lna.set_value(app_settings.lna);
 | 
			
		||||
		field_vga.set_value(app_settings.vga);
 | 
			
		||||
		field_rf_amp.set_value(app_settings.rx_amp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	record_view.on_error = [&nav](std::string message) {
 | 
			
		||||
		nav.display_modal("Error", message);
 | 
			
		||||
	};
 | 
			
		||||
	record_view.set_sampling_rate(24000);
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	update_freq(2480000000);
 | 
			
		||||
	auto def_bell202 = &modem_defs[0];
 | 
			
		||||
	persistent_memory::set_modem_baudrate(def_bell202->baudrate);
 | 
			
		||||
	serial_format_t serial_format;
 | 
			
		||||
	serial_format.data_bits = 7;
 | 
			
		||||
	serial_format.parity = EVEN;
 | 
			
		||||
	serial_format.stop_bits = 1;
 | 
			
		||||
	serial_format.bit_order = LSB_FIRST;
 | 
			
		||||
	persistent_memory::set_serial_format(serial_format);
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(receiver_model.tuning_frequency());
 | 
			
		||||
	field_frequency.set_step(100);
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		update_freq(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			update_freq(f);
 | 
			
		||||
			field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_modem_setup.on_select = [&nav](Button&) {
 | 
			
		||||
		nav.push<ModemSetupView>();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	// Auto-configure modem for LCR RX (will be removed later)
 | 
			
		||||
	baseband::set_nrf(persistent_memory::modem_baudrate(), 8, 0, false);
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	audio::output::start();
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_sampling_rate(4000000);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(4000000);
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NRFRxView::on_data(uint32_t value, bool is_data) {
 | 
			
		||||
	//std::string str_console = "\x1B";
 | 
			
		||||
	std::string str_console = "";
 | 
			
		||||
	if (is_data) {
 | 
			
		||||
		// Colorize differently after message splits
 | 
			
		||||
		//str_console += (char)((console_color & 3) + 9);
 | 
			
		||||
		
 | 
			
		||||
		//value &= 0xFF;											// ABCDEFGH
 | 
			
		||||
		//value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);	// EFGHABCD
 | 
			
		||||
		//value = ((value & 0xCC) >> 2) | ((value & 0x33) << 2);	// GHEFCDAB
 | 
			
		||||
		//value = ((value & 0xAA) >> 1) | ((value & 0x55) << 1);	// HGFEDCBA
 | 
			
		||||
		//value &= 0x7F;											// Ignore parity, which is the MSB now
 | 
			
		||||
		
 | 
			
		||||
		//if ((value >= 32) && (value < 127)) {
 | 
			
		||||
		//	str_console += (char)value;							// Printable
 | 
			
		||||
		//} 
 | 
			
		||||
		
 | 
			
		||||
		//str_console += (char)'A';
 | 
			
		||||
		//str_console += (char)value;
 | 
			
		||||
		//str_console += "[" + to_string_hex(value, 2) + "]";	
 | 
			
		||||
		str_console += " " + to_string_hex(value, 2) ;	
 | 
			
		||||
		console.write(str_console);
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		/*if ((value != 0x7F) && (prev_value == 0x7F)) {
 | 
			
		||||
			// Message split
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
			console_color++;
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
		}*/
 | 
			
		||||
		//prev_value = value;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Baudrate estimation
 | 
			
		||||
		//text_debug.set("~" + to_string_dec_uint(value)); 
 | 
			
		||||
		if (value == 'A')
 | 
			
		||||
		{console.write("addr:");}
 | 
			
		||||
		else if (value == 'B')
 | 
			
		||||
		{console.write(" data:");}
 | 
			
		||||
		else if (value == 'C')
 | 
			
		||||
		{
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
			console.writeln("");
 | 
			
		||||
		}
 | 
			
		||||
		//console.writeln("");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NRFRxView::~NRFRxView() {
 | 
			
		||||
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.rx_frequency = field_frequency.value();
 | 
			
		||||
	settings.save("rx_nrf", &app_settings);
 | 
			
		||||
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,112 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2017 Furrtek
 | 
			
		||||
 * Copyright (C) 2020 Shao
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_NRF_RX_H__
 | 
			
		||||
#define __UI_NRF_RX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "ui_record_view.hpp"	// DEBUG
 | 
			
		||||
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class NRFRxView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	NRFRxView(NavigationView& nav);
 | 
			
		||||
	~NRFRxView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "NRF RX"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	void on_data(uint32_t value, bool is_data);
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	uint8_t console_color { 0 };
 | 
			
		||||
	uint32_t prev_value { 0 };
 | 
			
		||||
	std::string str_log { "" };
 | 
			
		||||
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 13 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 15 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 21 * 8, 0, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	Channel channel {
 | 
			
		||||
		{ 21 * 8, 5, 6 * 8, 4 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 0 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_debug {
 | 
			
		||||
		{ 0 * 8, 1 * 16, 10 * 8, 16 },
 | 
			
		||||
		"DEBUG"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	Button button_modem_setup {
 | 
			
		||||
		{ 12 * 8, 1 * 16, 96, 24 },
 | 
			
		||||
		"Modem setup"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	RecordView record_view {
 | 
			
		||||
		{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
 | 
			
		||||
		u"AFS_????", RecordView::FileType::WAV, 4096, 4
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Console console {
 | 
			
		||||
		{ 0, 4 * 16, 240, 240 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void update_freq(rf::Frequency f);
 | 
			
		||||
	//void on_data_afsk(const AFSKDataMessage& message);
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_packet {
 | 
			
		||||
		Message::ID::AFSKData,
 | 
			
		||||
		[this](Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const AFSKDataMessage*>(p);
 | 
			
		||||
			this->on_data(message->value, message->is_data);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_NRF_RX_H__*/
 | 
			
		||||
@@ -0,0 +1,305 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_numbers.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "hackrf_hal.hpp"
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
// TODO: This app takes way too much space, find a way to shrink/simplify or make it an SD card module (loadable)
 | 
			
		||||
 | 
			
		||||
void NumbersStationView::focus() {
 | 
			
		||||
	if (file_error)
 | 
			
		||||
		nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr);
 | 
			
		||||
	else
 | 
			
		||||
		button_exit.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NumbersStationView::~NumbersStationView() {
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NumbersStationView::wav_file_t * NumbersStationView::get_wav(uint32_t index) {
 | 
			
		||||
	return ¤t_voice->available_wavs[index];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NumbersStationView::prepare_audio() {
 | 
			
		||||
	uint8_t code;
 | 
			
		||||
	wav_file_t * wav_file;
 | 
			
		||||
	
 | 
			
		||||
	if (sample_counter >= sample_length) {
 | 
			
		||||
		if (segment == ANNOUNCE) {
 | 
			
		||||
			if (!announce_loop) {
 | 
			
		||||
				code_index = 0;
 | 
			
		||||
				segment = MESSAGE;
 | 
			
		||||
			} else {
 | 
			
		||||
				wav_file = get_wav(11);
 | 
			
		||||
				reader->open(current_voice->dir + file_names[wav_file->index].name + ".wav");
 | 
			
		||||
				sample_length = wav_file->length;
 | 
			
		||||
				announce_loop--;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (segment == MESSAGE) {
 | 
			
		||||
			if (code_index == 25) {
 | 
			
		||||
				transmitter_model.disable();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			code = symfield_code.get_sym(code_index);
 | 
			
		||||
			
 | 
			
		||||
			if (code >= 10) {
 | 
			
		||||
				memset(audio_buffer, 0, 1024);
 | 
			
		||||
				if (code == 10) {
 | 
			
		||||
					pause = 11025;		// p: 0.25s @ 44100Hz
 | 
			
		||||
				} else if (code == 11) {
 | 
			
		||||
					pause = 33075;		// P: 0.75s @ 44100Hz
 | 
			
		||||
				} else if (code == 12) {
 | 
			
		||||
					transmitter_model.disable();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				wav_file = get_wav(code);
 | 
			
		||||
				reader->open(current_voice->dir + file_names[code].name + ".wav");
 | 
			
		||||
				sample_length = wav_file->length;
 | 
			
		||||
			}
 | 
			
		||||
			code_index++;
 | 
			
		||||
		}		
 | 
			
		||||
		sample_counter = 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (!pause) {
 | 
			
		||||
		auto bytes_read = reader->read(audio_buffer, 1024).value();
 | 
			
		||||
		
 | 
			
		||||
		// Unsigned to signed, pretty stupid :/
 | 
			
		||||
		for (size_t n = 0; n < bytes_read; n++)
 | 
			
		||||
			audio_buffer[n] -= 0x80;
 | 
			
		||||
		for (size_t n = bytes_read; n < 1024; n++)
 | 
			
		||||
			audio_buffer[n] = 0;
 | 
			
		||||
		
 | 
			
		||||
		sample_counter += 1024;
 | 
			
		||||
	} else {
 | 
			
		||||
		if (pause >= 1024) {
 | 
			
		||||
			pause -= 1024;
 | 
			
		||||
		} else {
 | 
			
		||||
			sample_counter = sample_length;
 | 
			
		||||
			pause = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_fifo_data(audio_buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NumbersStationView::start_tx() {
 | 
			
		||||
	//sample_length = sound_sizes[10];		// Announce
 | 
			
		||||
	sample_counter = sample_length;
 | 
			
		||||
	
 | 
			
		||||
	code_index = 0;
 | 
			
		||||
	announce_loop = 2;
 | 
			
		||||
	segment = ANNOUNCE;
 | 
			
		||||
	
 | 
			
		||||
	prepare_audio();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(1536000U);
 | 
			
		||||
	transmitter_model.set_rf_amp(true);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_audiotx_data(
 | 
			
		||||
		(1536000 / 44100) - 1,		// TODO: Read wav file's samplerate
 | 
			
		||||
		12000,
 | 
			
		||||
		1,
 | 
			
		||||
		false,
 | 
			
		||||
		0
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NumbersStationView::on_tick_second() {
 | 
			
		||||
	armed_blink = not armed_blink;
 | 
			
		||||
	
 | 
			
		||||
	if (armed_blink)
 | 
			
		||||
		check_armed.set_style(&style_red);
 | 
			
		||||
	else
 | 
			
		||||
		check_armed.set_style(&style());
 | 
			
		||||
	
 | 
			
		||||
	check_armed.set_dirty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NumbersStationView::on_voice_changed(size_t index) {
 | 
			
		||||
	std::string code_list = "";
 | 
			
		||||
	
 | 
			
		||||
	for (const auto& wavs : voices[index].available_wavs)
 | 
			
		||||
		code_list += wavs.code;
 | 
			
		||||
	
 | 
			
		||||
	for (uint32_t c = 0; c < 25; c++)
 | 
			
		||||
		symfield_code.set_symbol_list(c, code_list);
 | 
			
		||||
	
 | 
			
		||||
	current_voice = &voices[index];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NumbersStationView::check_wav_validity(const std::string dir, const std::string file) {
 | 
			
		||||
	if (reader->open("/numbers/" + dir + "/" + file)) {
 | 
			
		||||
		// Check format (mono, 8 bits)
 | 
			
		||||
		if ((reader->channels() == 1) && (reader->bits_per_sample() == 8))
 | 
			
		||||
			return true;
 | 
			
		||||
		else
 | 
			
		||||
			return false;
 | 
			
		||||
	} else
 | 
			
		||||
		return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NumbersStationView::NumbersStationView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	std::vector<std::filesystem::path> directory_list;
 | 
			
		||||
	using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	using options_t = std::vector<option_t>;
 | 
			
		||||
	options_t voice_options;
 | 
			
		||||
	voice_t temp_voice { };
 | 
			
		||||
	bool valid;
 | 
			
		||||
	uint32_t c;
 | 
			
		||||
	//uint8_t y, m, d, dayofweek;
 | 
			
		||||
	
 | 
			
		||||
	reader = std::make_unique<WAVFileReader>();
 | 
			
		||||
	
 | 
			
		||||
	// Search for valid voice directories
 | 
			
		||||
	directory_list = scan_root_directories("/numbers");
 | 
			
		||||
	if (!directory_list.size()) {
 | 
			
		||||
		file_error = true;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	for (const auto& dir : directory_list) {
 | 
			
		||||
		c = 0;
 | 
			
		||||
		for (const auto& file_name : file_names) {
 | 
			
		||||
			valid = check_wav_validity(dir.string(), file_name.name + ".wav");
 | 
			
		||||
			if ((!valid) && (file_name.required)) {
 | 
			
		||||
				temp_voice.available_wavs.clear();
 | 
			
		||||
				break;	// Invalid required file means invalid voice
 | 
			
		||||
			} else if (valid) {
 | 
			
		||||
				temp_voice.available_wavs.push_back({ file_name.code, c++, 0, 0 });	// TODO: Needs length and samplerate
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (!temp_voice.available_wavs.empty()) {
 | 
			
		||||
			// Voice can be used, are there accent files ?
 | 
			
		||||
			c = 0;
 | 
			
		||||
			for (const auto& file_name : file_names) {
 | 
			
		||||
				valid = check_wav_validity(dir.string(), file_name.name + "a.wav");
 | 
			
		||||
				if ((!valid) && (file_name.required)) {
 | 
			
		||||
					c = 0;
 | 
			
		||||
					break;	// Invalid required file means accents can't be used
 | 
			
		||||
				} else if (valid) {
 | 
			
		||||
					c++;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			temp_voice.accent = c ? true : false;
 | 
			
		||||
			temp_voice.dir = dir.string();
 | 
			
		||||
			
 | 
			
		||||
			voices.push_back(temp_voice);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (voices.empty()) {
 | 
			
		||||
		file_error = true;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&symfield_code,
 | 
			
		||||
		&check_armed,
 | 
			
		||||
		&options_voices,
 | 
			
		||||
		&text_voice_flags,
 | 
			
		||||
		//&button_tx_now,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	for (const auto& voice : voices)
 | 
			
		||||
		voice_options.emplace_back(voice.dir.substr(0, 4), 0);
 | 
			
		||||
	
 | 
			
		||||
	options_voices.set_options(voice_options);
 | 
			
		||||
	options_voices.on_change = [this](size_t i, int32_t) {
 | 
			
		||||
		this->on_voice_changed(i);
 | 
			
		||||
	};
 | 
			
		||||
	options_voices.set_selected_index(0);
 | 
			
		||||
 | 
			
		||||
	check_armed.set_value(false);
 | 
			
		||||
	
 | 
			
		||||
	check_armed.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		if (v) {
 | 
			
		||||
			armed_blink = false;
 | 
			
		||||
			signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
 | 
			
		||||
				this->on_tick_second();
 | 
			
		||||
			};
 | 
			
		||||
		} else {
 | 
			
		||||
			check_armed.set_style(&style());
 | 
			
		||||
			rtc_time::signal_tick_second -= signal_token_tick_second;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	symfield_code.set_sym(0, 10);
 | 
			
		||||
	symfield_code.set_sym(1, 3);
 | 
			
		||||
	symfield_code.set_sym(2, 4);
 | 
			
		||||
	symfield_code.set_sym(3, 11);
 | 
			
		||||
	symfield_code.set_sym(4, 6);
 | 
			
		||||
	symfield_code.set_sym(5, 1);
 | 
			
		||||
	symfield_code.set_sym(6, 9);
 | 
			
		||||
	symfield_code.set_sym(7, 7);
 | 
			
		||||
	symfield_code.set_sym(8, 8);
 | 
			
		||||
	symfield_code.set_sym(9, 0);
 | 
			
		||||
	symfield_code.set_sym(10, 12);	// End
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	
 | 
			
		||||
	// Thanks, Sakamoto-sama !
 | 
			
		||||
	y = datetime.year();
 | 
			
		||||
	m = datetime.month();
 | 
			
		||||
	d = datetime.day();
 | 
			
		||||
	y -= m < 3;
 | 
			
		||||
	dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7;
 | 
			
		||||
	
 | 
			
		||||
	text_title.set(day_of_week[dayofweek]);
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
	button_exit.on_select = [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,180 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * 
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_NUMBERS_H__
 | 
			
		||||
#define __UI_NUMBERS_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "clock_manager.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "utility.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
#include "io_wave.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class NumbersStationView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	NumbersStationView(NavigationView& nav);
 | 
			
		||||
	~NumbersStationView();
 | 
			
		||||
	
 | 
			
		||||
	NumbersStationView(const NumbersStationView&) = delete;
 | 
			
		||||
	NumbersStationView(NumbersStationView&&) = delete;
 | 
			
		||||
	NumbersStationView& operator=(const NumbersStationView&) = delete;
 | 
			
		||||
	NumbersStationView& operator=(NumbersStationView&&) = delete;
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Station"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	// Sequencing state machine
 | 
			
		||||
	enum segments {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		ANNOUNCE,
 | 
			
		||||
		MESSAGE,
 | 
			
		||||
		SIGNOFF
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Style style_red {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::red()
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	typedef struct {
 | 
			
		||||
		char code;
 | 
			
		||||
		uint32_t index;
 | 
			
		||||
		uint32_t length;
 | 
			
		||||
		uint32_t samplerate;
 | 
			
		||||
	} wav_file_t;
 | 
			
		||||
	
 | 
			
		||||
	struct voice_t {
 | 
			
		||||
		std::string dir;
 | 
			
		||||
		std::vector<wav_file_t> available_wavs;
 | 
			
		||||
		bool accent;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::vector<voice_t> voices { };
 | 
			
		||||
	voice_t * current_voice { };
 | 
			
		||||
	
 | 
			
		||||
	struct wav_file_list_t {
 | 
			
		||||
		std::string name;
 | 
			
		||||
		bool required;
 | 
			
		||||
		char code;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const std::vector<wav_file_list_t> file_names = {
 | 
			
		||||
		{ "0", true, '0' },
 | 
			
		||||
		{ "1", true, '1' },
 | 
			
		||||
		{ "2", true, '2' },
 | 
			
		||||
		{ "3", true, '3' },
 | 
			
		||||
		{ "4", true, '4' },
 | 
			
		||||
		{ "5", true, '5' },
 | 
			
		||||
		{ "6", true, '6' },
 | 
			
		||||
		{ "7", true, '7' },
 | 
			
		||||
		{ "8", true, '8' },
 | 
			
		||||
		{ "9", true, '9' },
 | 
			
		||||
		{ "announce", false, 'A' }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	segments segment { IDLE };
 | 
			
		||||
	bool armed { false };
 | 
			
		||||
	bool file_error { false };
 | 
			
		||||
	
 | 
			
		||||
	// const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
 | 
			
		||||
	// const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<WAVFileReader> reader { };
 | 
			
		||||
	
 | 
			
		||||
	uint8_t code_index { 0 }, announce_loop { 0 };
 | 
			
		||||
	uint32_t sample_counter { 0 };
 | 
			
		||||
	uint32_t sample_length { 0 };
 | 
			
		||||
	int8_t audio_buffer[1024] { };
 | 
			
		||||
	uint32_t pause { 0 };
 | 
			
		||||
	bool armed_blink { false };
 | 
			
		||||
	SignalToken signal_token_tick_second { };
 | 
			
		||||
 | 
			
		||||
	wav_file_t * get_wav(uint32_t index);
 | 
			
		||||
	bool check_wav_validity(const std::string dir, const std::string file);
 | 
			
		||||
	void on_voice_changed(size_t index);
 | 
			
		||||
	void on_tick_second();
 | 
			
		||||
	void prepare_audio();
 | 
			
		||||
	void start_tx();
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 5 * 8 }, "Voice:     Flags:", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 8 * 8 }, "Code:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_voices {
 | 
			
		||||
		{ 8 * 8, 1 * 8 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ }
 | 
			
		||||
	};
 | 
			
		||||
	Text text_voice_flags {
 | 
			
		||||
		{ 19 * 8, 1 * 8, 2 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};	
 | 
			
		||||
	
 | 
			
		||||
	SymField symfield_code {
 | 
			
		||||
		{ 1 * 8, 10 * 8 },
 | 
			
		||||
		25,
 | 
			
		||||
		SymField::SYMFIELD_DEF
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_armed {
 | 
			
		||||
		{ 2 * 8, 13 * 16 },
 | 
			
		||||
		5,
 | 
			
		||||
		"Armed"
 | 
			
		||||
	};
 | 
			
		||||
	/*Button button_tx_now {
 | 
			
		||||
		{ 18 * 8, 13 * 16, 10 * 8, 32 },
 | 
			
		||||
		"TX now"
 | 
			
		||||
	};*/
 | 
			
		||||
	Button button_exit {
 | 
			
		||||
		{ 21 * 8, 16 * 16, 64, 32 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RequestSignalMessage*>(p);
 | 
			
		||||
			if (message->signal == RequestSignalMessage::Signal::FillRequest) {
 | 
			
		||||
				this->prepare_audio();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_NUMBERS_H__*/
 | 
			
		||||
@@ -0,0 +1,192 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_nuoptix.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ch.h"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "lfsr_random.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void NuoptixView::focus() {
 | 
			
		||||
	number_timecode.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NuoptixView::~NuoptixView() {
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NuoptixView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (done)
 | 
			
		||||
		transmit(false);
 | 
			
		||||
	else
 | 
			
		||||
		progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NuoptixView::transmit(bool setup) {
 | 
			
		||||
	uint8_t mod, tone_code;
 | 
			
		||||
	uint8_t c;
 | 
			
		||||
	uint8_t dtmf_message[6];
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	
 | 
			
		||||
	if (!tx_mode) {
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (tx_mode == IMPROVISE)
 | 
			
		||||
		timecode = lfsr_iterate(timecode) % 1999;	// Could be 9999 but that would be one long audio track !
 | 
			
		||||
	
 | 
			
		||||
	if (setup) {
 | 
			
		||||
		progressbar.set_max(6 * 2);
 | 
			
		||||
		
 | 
			
		||||
		if (tx_mode == IMPROVISE) {
 | 
			
		||||
			// Seed from RTC
 | 
			
		||||
			rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
			timecode = datetime.day() + datetime.second();
 | 
			
		||||
		} else {
 | 
			
		||||
			timecode = number_timecode.value();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		transmitter_model.set_sampling_rate(1536000U);
 | 
			
		||||
		transmitter_model.set_rf_amp(true);
 | 
			
		||||
		transmitter_model.set_lna(40);
 | 
			
		||||
		transmitter_model.set_vga(40);
 | 
			
		||||
		transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
		transmitter_model.enable();
 | 
			
		||||
		
 | 
			
		||||
		dtmf_message[0] = '*';			// "Pre-tone for restart" method #1
 | 
			
		||||
		dtmf_message[1] = 'A';			// "Restart" method #1
 | 
			
		||||
	} else {
 | 
			
		||||
		dtmf_message[0] = '#';
 | 
			
		||||
		dtmf_message[1] = (timecode / 1000) % 10;
 | 
			
		||||
		chThdSleepMilliseconds(92);		// 141-49ms
 | 
			
		||||
		number_timecode.set_value(timecode);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_value(0);
 | 
			
		||||
	
 | 
			
		||||
	dtmf_message[2] = (timecode / 100) % 10;
 | 
			
		||||
	dtmf_message[3] = (timecode / 10) % 10;
 | 
			
		||||
	dtmf_message[4] = timecode % 10;
 | 
			
		||||
	
 | 
			
		||||
	mod = 0;
 | 
			
		||||
	for (c = 1; c < 5; c++)
 | 
			
		||||
		if (dtmf_message[c] <= 9)
 | 
			
		||||
			mod += dtmf_message[c];
 | 
			
		||||
	
 | 
			
		||||
	mod = 10 - (mod % 10);
 | 
			
		||||
	if (mod == 10) mod = 0;		// Is this right ?
 | 
			
		||||
	
 | 
			
		||||
	text_mod.set("Mod: " + to_string_dec_uint(mod));
 | 
			
		||||
	
 | 
			
		||||
	dtmf_message[5] = mod;
 | 
			
		||||
	
 | 
			
		||||
	for (c = 0; c < 6; c++) {
 | 
			
		||||
		tone_code = dtmf_message[c];
 | 
			
		||||
		
 | 
			
		||||
		if (tone_code == 'A')
 | 
			
		||||
			tone_code = 10;
 | 
			
		||||
		else if (tone_code == 'B')
 | 
			
		||||
			tone_code = 11;
 | 
			
		||||
		else if (tone_code == 'C')
 | 
			
		||||
			tone_code = 12;
 | 
			
		||||
		else if (tone_code == 'D')
 | 
			
		||||
			tone_code = 13;
 | 
			
		||||
		else if (tone_code == '#')
 | 
			
		||||
			tone_code = 14;
 | 
			
		||||
		else if (tone_code == '*')
 | 
			
		||||
			tone_code = 15;
 | 
			
		||||
		
 | 
			
		||||
		shared_memory.bb_data.tones_data.message[c * 2] = tone_code;
 | 
			
		||||
		shared_memory.bb_data.tones_data.message[c * 2 + 1] = 0xFF;		// Silence
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	for (c = 0; c < 16; c++) {
 | 
			
		||||
		baseband::set_tone(c * 2, dtmf_deltas[c][0], NUOPTIX_TONE_LENGTH);
 | 
			
		||||
		baseband::set_tone(c * 2 + 1, dtmf_deltas[c][1], NUOPTIX_TONE_LENGTH);
 | 
			
		||||
	}
 | 
			
		||||
	shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH;		// 49ms tone, 49ms space
 | 
			
		||||
	
 | 
			
		||||
	audio::set_rate(audio::Rate::Hz_24000);
 | 
			
		||||
	baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);	
 | 
			
		||||
	
 | 
			
		||||
	timecode++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NuoptixView::NuoptixView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_tones);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&number_timecode,
 | 
			
		||||
		&text_timecode,
 | 
			
		||||
		&text_mod,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	number_timecode.set_value(1);
 | 
			
		||||
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
		tx_mode = NORMAL;
 | 
			
		||||
		transmit(true);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		tx_mode = IDLE;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*button_impro.on_select = [this](Button&){
 | 
			
		||||
		if (tx_mode == IMPROVISE) {
 | 
			
		||||
			tx_mode = IDLE;
 | 
			
		||||
			button_impro.set_text("IMPROVISE");
 | 
			
		||||
		} else if (tx_mode == IDLE) {
 | 
			
		||||
			tx_mode = IMPROVISE;
 | 
			
		||||
			button_impro.set_text("STOP");
 | 
			
		||||
			transmit(true);
 | 
			
		||||
		}
 | 
			
		||||
	};*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,110 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_NUOPTIX_H__
 | 
			
		||||
#define __UI_NUOPTIX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
#include "tonesets.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "volume.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
 | 
			
		||||
#define NUOPTIX_TONE_LENGTH	((TONES_SAMPLERATE * 0.049) - 1)	// 49ms
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
	
 | 
			
		||||
class NuoptixView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	NuoptixView(NavigationView& nav);
 | 
			
		||||
	~NuoptixView();
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Nuoptix sync"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		NORMAL,
 | 
			
		||||
		IMPROVISE
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode { IDLE };
 | 
			
		||||
	
 | 
			
		||||
	void on_tuning_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void transmit(bool setup);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	
 | 
			
		||||
	uint32_t timecode { 0 };
 | 
			
		||||
	
 | 
			
		||||
	Text text_timecode {
 | 
			
		||||
		{ 10 * 8, 2 * 16, 9 * 8, 16 },
 | 
			
		||||
		"Timecode:"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField number_timecode {
 | 
			
		||||
		{ 13 * 8, 3 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ 1, 9999 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_mod {
 | 
			
		||||
		{ 10 * 8, 5 * 16, 6 * 8, 16 },
 | 
			
		||||
		"Mod: "
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 16, 14 * 16, 208, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*Button button_impro {
 | 
			
		||||
		{ 64, 184, 112, 40 },
 | 
			
		||||
		"IMPROVISE"
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		15
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_NUOPTIX_H__*/
 | 
			
		||||
@@ -0,0 +1,328 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 * Copyleft  (ↄ) 2022 NotPike
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_playlist.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_fileman.hpp"
 | 
			
		||||
#include "io_file.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void PlaylistView::set_ready() {
 | 
			
		||||
	ready_signal = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	void PlaylistView::load_file(std::filesystem::path playlist_path) {
 | 
			
		||||
		File playlist_file;
 | 
			
		||||
		
 | 
			
		||||
		auto error = playlist_file.open(playlist_path.string());
 | 
			
		||||
		if (!error.is_valid()) {
 | 
			
		||||
			std::string line;
 | 
			
		||||
			char one_char[1];
 | 
			
		||||
			for (size_t pointer =0 ; pointer < playlist_file.size(); pointer++) {
 | 
			
		||||
				playlist_file.seek(pointer);
 | 
			
		||||
				playlist_file.read(one_char, 1);
 | 
			
		||||
				if ((int) one_char[0] >= ' ') {
 | 
			
		||||
					line += one_char[0];
 | 
			
		||||
				} else if (one_char[0] == '\n') {
 | 
			
		||||
					txtline_process(line); 
 | 
			
		||||
					line.clear();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (line.length() > 0) {
 | 
			
		||||
				txtline_process(line); 
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		playlist_masterdb = playlist_db;
 | 
			
		||||
		return ;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	void PlaylistView::txtline_process(std::string &line) {
 | 
			
		||||
		playlist_entry new_item;
 | 
			
		||||
				rf::Frequency f;
 | 
			
		||||
				size_t previous = 0;
 | 
			
		||||
				size_t current = line.find(',');
 | 
			
		||||
				std::string freqs = line.substr(0, current);
 | 
			
		||||
				previous = current +1;
 | 
			
		||||
				current = line.find(',', previous);
 | 
			
		||||
				std::string file = line.substr(previous, current - previous);
 | 
			
		||||
				previous = current +1;
 | 
			
		||||
				current = line.find(',', previous);
 | 
			
		||||
				uint32_t sample_rate = strtoll(line.substr(previous).c_str(), nullptr,  10);
 | 
			
		||||
 | 
			
		||||
				f = strtoll(freqs.c_str(), nullptr, 0);
 | 
			
		||||
				new_item.replay_frequency = f;
 | 
			
		||||
				new_item.replay_file = "/" + file;
 | 
			
		||||
				new_item.sample_rate = sample_rate;
 | 
			
		||||
				new_item.next_delay = 0;
 | 
			
		||||
 | 
			
		||||
					playlist_db.push_back(std::move(new_item));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate) {
 | 
			
		||||
	File data_file;	
 | 
			
		||||
	// Get file size
 | 
			
		||||
	auto data_open_error = data_file.open("/" + new_file_path.string());
 | 
			
		||||
	if (data_open_error.is_valid()) {
 | 
			
		||||
		file_error();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	file_path = new_file_path;
 | 
			
		||||
	field_frequency.set_value(replay_frequency);
 | 
			
		||||
	
 | 
			
		||||
	sample_rate = replay_sample_rate;
 | 
			
		||||
	
 | 
			
		||||
	text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
 | 
			
		||||
	
 | 
			
		||||
	auto file_size = data_file.size();
 | 
			
		||||
	auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_max(file_size);
 | 
			
		||||
	text_filename.set(file_path.filename().string().substr(0, 12));
 | 
			
		||||
	text_duration.set(to_string_time_ms(duration));
 | 
			
		||||
	
 | 
			
		||||
	button_play.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::on_tx_progress(const uint32_t progress) {
 | 
			
		||||
	progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::focus() {
 | 
			
		||||
	button_open.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::file_error() {
 | 
			
		||||
	nav_.display_modal("Error", "File "+file_path.string() +" read error. " +file_path.string());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PlaylistView::is_active() const {
 | 
			
		||||
	return (bool)replay_thread;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PlaylistView::loop() const {
 | 
			
		||||
	return (bool) playlist_db.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::toggle() {
 | 
			
		||||
	if( is_active() ) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		start();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::start() {
 | 
			
		||||
	stop(false);
 | 
			
		||||
 | 
			
		||||
		playlist_entry item = playlist_db.front();
 | 
			
		||||
		playlist_db.pop_front();
 | 
			
		||||
	//		playlist_entry item = playlist_db[0];
 | 
			
		||||
//	for (playlist_entry item : playlist_db) {
 | 
			
		||||
	//	file_path = item.replay_file;
 | 
			
		||||
	//	rf::Frequency replay_frequency = strtoll(item.replay_frequency.c_str(),nullptr,10);
 | 
			
		||||
		on_file_changed(item.replay_file, item.replay_frequency, item.sample_rate);
 | 
			
		||||
		on_target_frequency_changed(item.replay_frequency);
 | 
			
		||||
 | 
			
		||||
		std::unique_ptr<stream::Reader> reader;
 | 
			
		||||
		
 | 
			
		||||
		auto p = std::make_unique<FileReader>();
 | 
			
		||||
		auto open_error = p->open(file_path);
 | 
			
		||||
		if( open_error.is_valid() ) {
 | 
			
		||||
			file_error();
 | 
			
		||||
			return;                               // Fixes TX bug if there's a file error
 | 
			
		||||
		} else {
 | 
			
		||||
			reader = std::move(p);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if( reader ) {
 | 
			
		||||
			button_play.set_bitmap(&bitmap_stop);
 | 
			
		||||
			baseband::set_sample_rate(sample_rate * 8);
 | 
			
		||||
			
 | 
			
		||||
			replay_thread = std::make_unique<ReplayThread>(
 | 
			
		||||
				std::move(reader),
 | 
			
		||||
				read_size, buffer_count,
 | 
			
		||||
				&ready_signal,
 | 
			
		||||
				[](uint32_t return_code) {
 | 
			
		||||
					ReplayThreadDoneMessage message { return_code };
 | 
			
		||||
					EventDispatcher::send_message(message);
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
		field_rfgain.on_change = [this](int32_t v) {
 | 
			
		||||
			tx_gain = v;
 | 
			
		||||
		};  
 | 
			
		||||
		field_rfgain.set_value(tx_gain);
 | 
			
		||||
		receiver_model.set_tx_gain(tx_gain); 
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		field_rfamp.on_change = [this](int32_t v) {
 | 
			
		||||
			rf_amp = (bool)v;
 | 
			
		||||
		};
 | 
			
		||||
		field_rfamp.set_value(rf_amp ? 14 : 0);
 | 
			
		||||
 | 
			
		||||
		//Enable Bias Tee if selected
 | 
			
		||||
		radio::set_antenna_bias(portapack::get_antenna_bias());
 | 
			
		||||
			
 | 
			
		||||
		radio::enable({
 | 
			
		||||
			receiver_model.tuning_frequency(),
 | 
			
		||||
			sample_rate * 8,
 | 
			
		||||
			baseband_bandwidth,
 | 
			
		||||
			rf::Direction::Transmit,
 | 
			
		||||
			rf_amp,         //  previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps  
 | 
			
		||||
			static_cast<int8_t>(receiver_model.lna()),
 | 
			
		||||
			static_cast<int8_t>(receiver_model.vga())
 | 
			
		||||
		});  
 | 
			
		||||
 | 
			
		||||
//	}
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
void PlaylistView::stop(const bool do_loop) {
 | 
			
		||||
	if( is_active() ) {
 | 
			
		||||
		replay_thread.reset();
 | 
			
		||||
	}
 | 
			
		||||
	if (do_loop) {
 | 
			
		||||
		if (playlist_db.size() > 0 ) {
 | 
			
		||||
			start();
 | 
			
		||||
		} else {
 | 
			
		||||
			playlist_db = playlist_masterdb;
 | 
			
		||||
			start();
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		radio::set_antenna_bias(false);    //Turn off Bias Tee
 | 
			
		||||
		radio::disable();
 | 
			
		||||
		button_play.set_bitmap(&bitmap_play);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	ready_signal = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::handle_replay_thread_done(const uint32_t return_code) {
 | 
			
		||||
	if (return_code == ReplayThread::END_OF_FILE) {
 | 
			
		||||
			stop(true);
 | 
			
		||||
	} else if (return_code == ReplayThread::READ_ERROR) {
 | 
			
		||||
		stop(false);
 | 
			
		||||
		file_error();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_value(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlaylistView::PlaylistView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	tx_gain = 35;field_rfgain.set_value(tx_gain);  // Initial default  value (-12 dB's max ).
 | 
			
		||||
	field_rfamp.set_value(rf_amp ? 14 : 0);  // Initial default value True. (TX RF amp on , +14dB's)
 | 
			
		||||
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_replay);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button_open,
 | 
			
		||||
		&text_filename,
 | 
			
		||||
		&text_sample_rate,
 | 
			
		||||
		&text_duration,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&field_frequency,
 | 
			
		||||
		&field_rfgain, 
 | 
			
		||||
		&field_rfamp,       // let's not use common rf_amp
 | 
			
		||||
		&check_loop,
 | 
			
		||||
		&button_play,
 | 
			
		||||
		&waterfall,
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	field_frequency.set_value(target_frequency());
 | 
			
		||||
	field_frequency.set_step(receiver_model.frequency_step());
 | 
			
		||||
	field_frequency.on_change = [this](rf::Frequency f) {
 | 
			
		||||
		this->on_target_frequency_changed(f);
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency.on_edit = [this, &nav]() {
 | 
			
		||||
		// TODO: Provide separate modal method/scheme?
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->on_target_frequency_changed(f);
 | 
			
		||||
			this->field_frequency.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency.set_step(5000);
 | 
			
		||||
	
 | 
			
		||||
	button_play.on_select = [this](ImageButton&) {
 | 
			
		||||
		this->toggle();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_open.on_select = [this, &nav](Button&) {
 | 
			
		||||
		auto open_view = nav.push<FileLoadView>(".TXT");
 | 
			
		||||
		open_view->on_changed = [this](std::filesystem::path new_file_path) {
 | 
			
		||||
			load_file(new_file_path);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlaylistView::~PlaylistView() {
 | 
			
		||||
	radio::disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::on_hide() {
 | 
			
		||||
	stop(false);
 | 
			
		||||
	// TODO: Terrible kludge because widget system doesn't notify Waterfall that
 | 
			
		||||
	// it's being shown or hidden.
 | 
			
		||||
	waterfall.on_hide();
 | 
			
		||||
	View::on_hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::set_parent_rect(const Rect new_parent_rect) {
 | 
			
		||||
	View::set_parent_rect(new_parent_rect);
 | 
			
		||||
 | 
			
		||||
	const ui::Rect waterfall_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height };
 | 
			
		||||
	waterfall.set_parent_rect(waterfall_rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::on_target_frequency_changed(rf::Frequency f) {
 | 
			
		||||
	set_target_frequency(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlaylistView::set_target_frequency(const rf::Frequency new_value) {
 | 
			
		||||
	persistent_memory::set_tuned_frequency(new_value);;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rf::Frequency PlaylistView::target_frequency() const {
 | 
			
		||||
	return persistent_memory::tuned_frequency();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "replay_thread.hpp"
 | 
			
		||||
#include "ui_spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <deque>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class PlaylistView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	PlaylistView(NavigationView& nav);
 | 
			
		||||
	~PlaylistView();
 | 
			
		||||
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
	void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Playlist"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr ui::Dim header_height = 3 * 16;
 | 
			
		||||
	
 | 
			
		||||
	struct playlist_entry {
 | 
			
		||||
		rf::Frequency replay_frequency { 0 };
 | 
			
		||||
		std::string replay_file{};
 | 
			
		||||
		uint32_t sample_rate{};
 | 
			
		||||
		uint32_t next_delay{};
 | 
			
		||||
	};
 | 
			
		||||
	std::deque<playlist_entry> playlist_db{};
 | 
			
		||||
	std::deque<playlist_entry> playlist_masterdb{};
 | 
			
		||||
	uint32_t sample_rate = 0;
 | 
			
		||||
	int32_t tx_gain { 47 };
 | 
			
		||||
	bool rf_amp { true }; // aux private var to store temporal, Replay App rf_amp user selection.
 | 
			
		||||
	static constexpr uint32_t baseband_bandwidth = 2500000;
 | 
			
		||||
	const size_t read_size { 16384 };
 | 
			
		||||
	const size_t buffer_count { 3 };
 | 
			
		||||
	void load_file(std::filesystem::path playlist_path);
 | 
			
		||||
	void txtline_process(std::string &);
 | 
			
		||||
	void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate);
 | 
			
		||||
	void on_target_frequency_changed(rf::Frequency f);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress);
 | 
			
		||||
	
 | 
			
		||||
	void set_target_frequency(const rf::Frequency new_value);
 | 
			
		||||
	rf::Frequency target_frequency() const;
 | 
			
		||||
 | 
			
		||||
	void toggle();
 | 
			
		||||
	void start();
 | 
			
		||||
	void stop(const bool do_loop);
 | 
			
		||||
	bool is_active() const;
 | 
			
		||||
	bool loop() const;
 | 
			
		||||
	void set_ready();
 | 
			
		||||
	void handle_replay_thread_done(const uint32_t return_code);
 | 
			
		||||
	void file_error();
 | 
			
		||||
 | 
			
		||||
	std::filesystem::path file_path { };
 | 
			
		||||
	std::unique_ptr<ReplayThread> replay_thread { };
 | 
			
		||||
	bool ready_signal { false };
 | 
			
		||||
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 10 * 8, 2 * 16 }, "GAIN   A:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_open {
 | 
			
		||||
		{ 0 * 8, 0 * 16, 10 * 8, 2 * 16 },
 | 
			
		||||
		"Open file"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	Text text_filename {
 | 
			
		||||
		{ 11 * 8, 0 * 16, 12 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_sample_rate {
 | 
			
		||||
		{ 24 * 8, 0 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_duration {
 | 
			
		||||
		{ 11 * 8, 1 * 16, 6 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 18 * 8, 1 * 16, 12 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	FrequencyField field_frequency {
 | 
			
		||||
		{ 0 * 8, 2 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_rfgain {
 | 
			
		||||
		{ 14 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 47 },
 | 
			
		||||
		1,
 | 
			
		||||
		' '	
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_rfamp {     // previously  I was using "RFAmpField field_rf_amp" but that is general Receiver amp setting.
 | 
			
		||||
		{ 19 * 8, 2 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 14 },                // this time we will display GUI , 0 or 14 dBs same as Mic App
 | 
			
		||||
		14,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_loop {
 | 
			
		||||
		{ 21 * 8, 2 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		"Loop",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	ImageButton button_play {
 | 
			
		||||
		{ 28 * 8, 2 * 16, 2 * 8, 1 * 16 },
 | 
			
		||||
		&bitmap_play,
 | 
			
		||||
		Color::green(),
 | 
			
		||||
		Color::black()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	spectrum::WaterfallWidget waterfall { };
 | 
			
		||||
 | 
			
		||||
	MessageHandlerRegistration message_handler_replay_thread_error {
 | 
			
		||||
		Message::ID::ReplayThreadDone,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
 | 
			
		||||
			this->handle_replay_thread_done(message.return_code);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_fifo_signal {
 | 
			
		||||
		Message::ID::RequestSignal,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = static_cast<const RequestSignalMessage*>(p);
 | 
			
		||||
			if (message->signal == RequestSignalMessage::Signal::FillRequest) {
 | 
			
		||||
				this->set_ready();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,195 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_pocsag_tx.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace pocsag;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void POCSAGTXView::focus() {
 | 
			
		||||
	field_address.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POCSAGTXView::~POCSAGTXView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_pocsag", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
 | 
			
		||||
	if (done) {
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		progressbar.set_value(0);
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
	} else
 | 
			
		||||
		progressbar.set_value(progress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool POCSAGTXView::start_tx() {
 | 
			
		||||
	uint32_t total_frames, i, codeword, bi, address;
 | 
			
		||||
	pocsag::BitRate bitrate;
 | 
			
		||||
	std::vector<uint32_t> codewords;
 | 
			
		||||
	
 | 
			
		||||
	address = field_address.value_dec_u32();
 | 
			
		||||
	if (address > 0x1FFFFFU) {
 | 
			
		||||
		nav_.display_modal("Bad address", "Address must be less\nthan 2097152.");
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	MessageType type = (MessageType)options_type.selected_index_value();
 | 
			
		||||
	
 | 
			
		||||
	if (type == MessageType::NUMERIC_ONLY) {
 | 
			
		||||
		// Check for invalid characters
 | 
			
		||||
		if (message.find_first_not_of("0123456789SU -][") != std::string::npos) {
 | 
			
		||||
			nav_.display_modal("Bad message", "A numeric only message must\nonly contain:\n0123456789SU][- or space.");
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	MessageType phase = (MessageType)options_phase.selected_index_value();
 | 
			
		||||
	
 | 
			
		||||
	pocsag_encode(type, BCH_code, options_function.selected_index_value(), message, address, codewords);
 | 
			
		||||
	
 | 
			
		||||
	total_frames = codewords.size() / 2;
 | 
			
		||||
	
 | 
			
		||||
	progressbar.set_max(total_frames);
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(2280000);
 | 
			
		||||
	transmitter_model.set_rf_amp(true);
 | 
			
		||||
	transmitter_model.set_lna(40);
 | 
			
		||||
	transmitter_model.set_vga(40);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	uint8_t * data_ptr = shared_memory.bb_data.data;
 | 
			
		||||
	
 | 
			
		||||
	bi = 0;
 | 
			
		||||
	for (i = 0; i < codewords.size(); i++) {
 | 
			
		||||
		if (phase == 0)
 | 
			
		||||
			codeword = ~(codewords[i]);
 | 
			
		||||
		else
 | 
			
		||||
		codeword = codewords[i];
 | 
			
		||||
		
 | 
			
		||||
		data_ptr[bi++] = (codeword >> 24) & 0xFF;
 | 
			
		||||
		data_ptr[bi++] = (codeword >> 16) & 0xFF;
 | 
			
		||||
		data_ptr[bi++] = (codeword >> 8) & 0xFF;
 | 
			
		||||
		data_ptr[bi++] = codeword & 0xFF;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	bitrate = pocsag_bitrates[options_bitrate.selected_index()];
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_fsk_data(
 | 
			
		||||
		codewords.size() * 32,
 | 
			
		||||
		2280000 / bitrate,
 | 
			
		||||
		4500,
 | 
			
		||||
		64
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGTXView::paint(Painter&) {
 | 
			
		||||
	message = buffer;
 | 
			
		||||
	text_message.set(message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void POCSAGTXView::on_set_text(NavigationView& nav) {
 | 
			
		||||
	text_prompt(nav, buffer, 30);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POCSAGTXView::POCSAGTXView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_fsktx);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&options_bitrate,
 | 
			
		||||
		&field_address,
 | 
			
		||||
		&options_type,
 | 
			
		||||
		&options_function,
 | 
			
		||||
		&options_phase,
 | 
			
		||||
		&text_message,
 | 
			
		||||
		&button_message,
 | 
			
		||||
		&progressbar,
 | 
			
		||||
		&tx_view
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_pocsag", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options_bitrate.set_selected_index(1);	// 1200bps
 | 
			
		||||
	options_type.set_selected_index(0);		// Address only
 | 
			
		||||
	
 | 
			
		||||
	// TODO: set_value for whole symfield
 | 
			
		||||
	uint32_t reload_address = persistent_memory::pocsag_last_address();
 | 
			
		||||
	for (uint32_t c = 0; c < 7; c++) {
 | 
			
		||||
		field_address.set_sym(6 - c, reload_address % 10);
 | 
			
		||||
		reload_address /= 10;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	options_type.on_change = [this](size_t, int32_t i) {
 | 
			
		||||
		if (i == 2)
 | 
			
		||||
			options_function.set_selected_index(3);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_message.on_select = [this, &nav](Button&) {
 | 
			
		||||
		this->on_set_text(nav);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			transmitter_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		if (start_tx())
 | 
			
		||||
			tx_view.set_transmitting(true);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,160 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __POCSAG_TX_H__
 | 
			
		||||
#define __POCSAG_TX_H__
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "bch_code.hpp"
 | 
			
		||||
#include "message.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "pocsag.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace pocsag;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class POCSAGTXView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	POCSAGTXView(NavigationView& nav);
 | 
			
		||||
	~POCSAGTXView();
 | 
			
		||||
	
 | 
			
		||||
	POCSAGTXView(const POCSAGTXView&) = delete;
 | 
			
		||||
	POCSAGTXView(POCSAGTXView&&) = delete;
 | 
			
		||||
	POCSAGTXView& operator=(const POCSAGTXView&) = delete;
 | 
			
		||||
	POCSAGTXView& operator=(POCSAGTXView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	void paint(Painter&) override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "POCSAG TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::string buffer { "PORTAPACK" };
 | 
			
		||||
	std::string message { };
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	BCHCode BCH_code {
 | 
			
		||||
		{ 1, 0, 1, 0, 0, 1 },
 | 
			
		||||
		5, 31, 21, 2
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
	void on_set_text(NavigationView& nav);
 | 
			
		||||
	void on_tx_progress(const uint32_t progress, const bool done);
 | 
			
		||||
	bool start_tx();
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 3 * 8, 4 * 8 }, "Bitrate:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 6 * 8 }, "Address:", Color::light_grey() },
 | 
			
		||||
		{ { 6 * 8, 8 * 8 }, "Type:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 10 * 8 }, "Function:", Color::light_grey() },
 | 
			
		||||
		{ { 5 * 8, 12 * 8 }, "Phase:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 14 * 8 }, "Message:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_bitrate {
 | 
			
		||||
		{ 11 * 8, 4 * 8 },
 | 
			
		||||
		8,
 | 
			
		||||
		{
 | 
			
		||||
			{ "512 bps ", 0 },
 | 
			
		||||
			{ "1200 bps", 1 },
 | 
			
		||||
			{ "2400 bps", 2 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	SymField field_address {
 | 
			
		||||
		{ 11 * 8, 6 * 8 },
 | 
			
		||||
		7,
 | 
			
		||||
		SymField::SYMFIELD_DEC
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_type {
 | 
			
		||||
		{ 11 * 8, 8 * 8 },
 | 
			
		||||
		12,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Address only", MessageType::ADDRESS_ONLY },
 | 
			
		||||
			{ "Numeric only", MessageType::NUMERIC_ONLY },
 | 
			
		||||
			{ "Alphanumeric", MessageType::ALPHANUMERIC }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_function {
 | 
			
		||||
		{ 11 * 8, 10 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{
 | 
			
		||||
			{ "A", 0 },
 | 
			
		||||
			{ "B", 1 },
 | 
			
		||||
			{ "C", 2 },
 | 
			
		||||
			{ "D", 3 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_phase {
 | 
			
		||||
		{ 11 * 8, 12 * 8 },
 | 
			
		||||
		1,
 | 
			
		||||
		{
 | 
			
		||||
			{ "P", 0 },
 | 
			
		||||
			{ "N", 1 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_message {
 | 
			
		||||
		{ 0 * 8, 16 * 8, 16 * 8, 16 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_message {
 | 
			
		||||
		{ 0 * 8, 18 * 8, 14 * 8, 32 },
 | 
			
		||||
		"Set message"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progressbar {
 | 
			
		||||
		{ 16, 200, 208, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		10000,
 | 
			
		||||
		9
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_tx_progress {
 | 
			
		||||
		Message::ID::TXProgress,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
 | 
			
		||||
			this->on_tx_progress(message.progress, message.done);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__POCSAG_TX_H__*/
 | 
			
		||||
							
								
								
									
										277
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_rds.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_rds.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,277 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_rds.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
using namespace rds;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
RDSPSNView::RDSPSNView(
 | 
			
		||||
	NavigationView& nav, 
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("PSN");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&text_psn,
 | 
			
		||||
		&button_set,
 | 
			
		||||
		&check_mono_stereo,
 | 
			
		||||
		&check_TA,
 | 
			
		||||
		&check_MS
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	set_enabled(true);
 | 
			
		||||
	
 | 
			
		||||
	check_TA.set_value(true);
 | 
			
		||||
	
 | 
			
		||||
	check_mono_stereo.on_select = [this](Checkbox&, bool value) {
 | 
			
		||||
		mono_stereo = value;
 | 
			
		||||
	};
 | 
			
		||||
	check_TA.on_select = [this](Checkbox&, bool value) {
 | 
			
		||||
		TA = value;
 | 
			
		||||
	};
 | 
			
		||||
	check_MS.on_select = [this](Checkbox&, bool value) {
 | 
			
		||||
		MS = value;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_set.on_select = [this, &nav](Button&) {
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			PSN,
 | 
			
		||||
			8,
 | 
			
		||||
			[this](std::string& s) {
 | 
			
		||||
				text_psn.set(s);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSRadioTextView::RDSRadioTextView(
 | 
			
		||||
	NavigationView& nav, 
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("Radiotext");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button_set,
 | 
			
		||||
		&text_radiotext
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	button_set.on_select = [this, &nav](Button&){
 | 
			
		||||
		text_prompt(
 | 
			
		||||
			nav,
 | 
			
		||||
			radiotext,
 | 
			
		||||
			28,
 | 
			
		||||
			[this](std::string& s) {
 | 
			
		||||
				text_radiotext.set(s);
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSDateTimeView::RDSDateTimeView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("date & time");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSAudioView::RDSAudioView(
 | 
			
		||||
	Rect parent_rect
 | 
			
		||||
) : OptionTabView(parent_rect)
 | 
			
		||||
{
 | 
			
		||||
	set_type("audio");
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSThread::RDSThread(
 | 
			
		||||
	std::vector<RDSGroup>** frames
 | 
			
		||||
) : frames_ { std::move(frames) }
 | 
			
		||||
{
 | 
			
		||||
	thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, RDSThread::static_fn, this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSThread::~RDSThread() {
 | 
			
		||||
	if( thread ) {
 | 
			
		||||
		chThdTerminate(thread);
 | 
			
		||||
		chThdWait(thread);
 | 
			
		||||
		thread = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
msg_t RDSThread::static_fn(void* arg) {
 | 
			
		||||
	auto obj = static_cast<RDSThread*>(arg);
 | 
			
		||||
	obj->run();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RDSThread::run() {
 | 
			
		||||
	std::vector<RDSGroup>* frame_ptr;
 | 
			
		||||
	size_t block_count, c;
 | 
			
		||||
	uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data;
 | 
			
		||||
	uint32_t frame_index = 0;
 | 
			
		||||
	
 | 
			
		||||
	while( !chThdShouldTerminate() ) {
 | 
			
		||||
 | 
			
		||||
		do {
 | 
			
		||||
			frame_ptr = frames_[frame_index];
 | 
			
		||||
			
 | 
			
		||||
			if (frame_index == 2) {
 | 
			
		||||
				frame_index = 0;
 | 
			
		||||
			} else {
 | 
			
		||||
				frame_index++;
 | 
			
		||||
			}
 | 
			
		||||
		} while(!(block_count = frame_ptr->size() * 4));
 | 
			
		||||
		
 | 
			
		||||
		for (c = 0; c < block_count; c++)
 | 
			
		||||
			tx_data_u32[c] = frame_ptr->at(c >> 2).block[c & 3];
 | 
			
		||||
	
 | 
			
		||||
		baseband::set_rds_data(block_count * 26);
 | 
			
		||||
		
 | 
			
		||||
		chThdSleepMilliseconds(1000);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RDSView::focus() {
 | 
			
		||||
	tab_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSView::~RDSView() {
 | 
			
		||||
	// save app settings
 | 
			
		||||
	app_settings.tx_frequency = transmitter_model.tuning_frequency();	
 | 
			
		||||
	settings.save("tx_rds", &app_settings);
 | 
			
		||||
 | 
			
		||||
	transmitter_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RDSView::start_tx() {
 | 
			
		||||
	rds_flags.PI_code = sym_pi_code.value_hex_u64();
 | 
			
		||||
	rds_flags.PTY = options_pty.selected_index_value();
 | 
			
		||||
	rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
 | 
			
		||||
	rds_flags.TP = check_TP.value();
 | 
			
		||||
	rds_flags.TA = view_PSN.TA;
 | 
			
		||||
	rds_flags.MS = view_PSN.MS;
 | 
			
		||||
	
 | 
			
		||||
	if (view_PSN.is_enabled())
 | 
			
		||||
		gen_PSN(frame_psn, view_PSN.PSN, &rds_flags);
 | 
			
		||||
	else
 | 
			
		||||
		frame_psn.clear();
 | 
			
		||||
	
 | 
			
		||||
	if (view_radiotext.is_enabled())
 | 
			
		||||
		gen_RadioText(frame_radiotext, view_radiotext.radiotext, 0, &rds_flags);
 | 
			
		||||
	else
 | 
			
		||||
		frame_radiotext.clear();
 | 
			
		||||
	
 | 
			
		||||
	// DEBUG
 | 
			
		||||
	if (view_datetime.is_enabled())
 | 
			
		||||
		gen_ClockTime(frame_datetime, &rds_flags, 2016, 12, 1, 9, 23, 2);
 | 
			
		||||
	else
 | 
			
		||||
		frame_datetime.clear();
 | 
			
		||||
	
 | 
			
		||||
	transmitter_model.set_sampling_rate(2280000U);
 | 
			
		||||
	transmitter_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	transmitter_model.enable();
 | 
			
		||||
	
 | 
			
		||||
	tx_thread = std::make_unique<RDSThread>(frames);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RDSView::RDSView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ { nav }
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_rds);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&tab_view,
 | 
			
		||||
		&labels,
 | 
			
		||||
		&sym_pi_code,
 | 
			
		||||
		&check_TP,
 | 
			
		||||
		&options_pty,
 | 
			
		||||
		&view_PSN,
 | 
			
		||||
		&view_radiotext,
 | 
			
		||||
		&view_datetime,
 | 
			
		||||
		&view_audio,
 | 
			
		||||
		&tx_view,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// load app settings
 | 
			
		||||
	auto rc = settings.load("tx_rds", &app_settings);
 | 
			
		||||
	if(rc == SETTINGS_OK) {
 | 
			
		||||
		transmitter_model.set_rf_amp(app_settings.tx_amp);
 | 
			
		||||
		transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
 | 
			
		||||
		transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
 | 
			
		||||
		transmitter_model.set_tx_gain(app_settings.tx_gain);		
 | 
			
		||||
	}	
 | 
			
		||||
	check_TP.set_value(true);
 | 
			
		||||
	
 | 
			
		||||
	sym_pi_code.set_sym(0, 0xF);
 | 
			
		||||
	sym_pi_code.set_sym(1, 0x3);
 | 
			
		||||
	sym_pi_code.set_sym(2, 0xE);
 | 
			
		||||
	sym_pi_code.set_sym(3, 0x0);
 | 
			
		||||
	sym_pi_code.on_change = [this]() {
 | 
			
		||||
		rds_flags.PI_code = sym_pi_code.value_hex_u64();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	options_pty.set_selected_index(0);				// None
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_edit_frequency = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			receiver_model.set_tuning_frequency(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_start = [this]() {
 | 
			
		||||
		start_tx();
 | 
			
		||||
		tx_view.set_transmitting(true);
 | 
			
		||||
		txing = true;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_view.on_stop = [this]() {
 | 
			
		||||
		// Kill tx_thread here ?
 | 
			
		||||
		tx_view.set_transmitting(false);
 | 
			
		||||
		transmitter_model.disable();
 | 
			
		||||
		txing = false;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
							
								
								
									
										342
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_rds.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								Software/portapack-mayhem/firmware/application/apps/ui_rds.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,342 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "ui_tabview.hpp"
 | 
			
		||||
#include "app_settings.hpp"
 | 
			
		||||
#include "rds.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace rds;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class RDSPSNView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	RDSPSNView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	std::string PSN { "TEST1234" };
 | 
			
		||||
	bool mono_stereo { false };
 | 
			
		||||
	bool TA { false };
 | 
			
		||||
	bool MS { false };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 3 * 8 }, "Program Service Name", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 7 * 8 }, "PSN:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_set {
 | 
			
		||||
		{ 18 * 8, 3 * 16, 80, 32 },
 | 
			
		||||
		"Set"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_psn {
 | 
			
		||||
		 { 6 * 8, 3 * 16 + 8, 8 * 8, 16 },
 | 
			
		||||
		 ""
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_mono_stereo {
 | 
			
		||||
		{ 2 * 8, 12 * 8 },
 | 
			
		||||
		6,
 | 
			
		||||
		"Stereo"
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_MS {
 | 
			
		||||
		{ 14 * 8, 12 * 8 },
 | 
			
		||||
		5,
 | 
			
		||||
		"Music"
 | 
			
		||||
	};
 | 
			
		||||
	Checkbox check_TA {
 | 
			
		||||
		{ 2 * 8, 16 * 8 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Traffic announcement"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RDSRadioTextView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	RDSRadioTextView(NavigationView& nav, Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
	std::string radiotext { "Radiotext test ABCD1234" };
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 3 * 8 }, "Radiotext", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 6 * 8 }, "Text:", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text text_radiotext {
 | 
			
		||||
		{ 1 * 8, 4 * 16, 28 * 8, 16 },
 | 
			
		||||
		"-"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_set {
 | 
			
		||||
		{ 88, 6 * 16, 64, 32 },
 | 
			
		||||
		"Set"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RDSDateTimeView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	RDSDateTimeView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 44, 5 * 16 }, "Not yet implemented", Color::red() }
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RDSAudioView : public OptionTabView {
 | 
			
		||||
public:
 | 
			
		||||
	RDSAudioView(Rect parent_rect);
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 44, 5 * 16 }, "Not yet implemented", Color::red() }
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RDSThread {
 | 
			
		||||
public:
 | 
			
		||||
	RDSThread(std::vector<RDSGroup>** frames);
 | 
			
		||||
	~RDSThread();
 | 
			
		||||
 | 
			
		||||
	RDSThread(const RDSThread&) = delete;
 | 
			
		||||
	RDSThread(RDSThread&&) = delete;
 | 
			
		||||
	RDSThread& operator=(const RDSThread&) = delete;
 | 
			
		||||
	RDSThread& operator=(RDSThread&&) = delete;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<RDSGroup>** frames_ { };
 | 
			
		||||
	Thread* thread { nullptr };
 | 
			
		||||
 | 
			
		||||
	static msg_t static_fn(void* arg);
 | 
			
		||||
	
 | 
			
		||||
	void run();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RDSView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	RDSView(NavigationView& nav);
 | 
			
		||||
	~RDSView();
 | 
			
		||||
 | 
			
		||||
	RDSView(const RDSView&) = delete;
 | 
			
		||||
	RDSView(RDSView&&) = delete;
 | 
			
		||||
	RDSView& operator=(const RDSView&) = delete;
 | 
			
		||||
	RDSView& operator=(RDSView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "RDS TX"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	RDS_flags rds_flags { };
 | 
			
		||||
	
 | 
			
		||||
	// app save settings
 | 
			
		||||
	std::app_settings 		settings { }; 		
 | 
			
		||||
	std::app_settings::AppSettings 	app_settings { };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	std::vector<RDSGroup> frame_psn { };
 | 
			
		||||
	std::vector<RDSGroup> frame_radiotext { };
 | 
			
		||||
	std::vector<RDSGroup> frame_datetime { };
 | 
			
		||||
	std::vector<RDSGroup>* frames[3] { &frame_psn, &frame_radiotext, &frame_datetime };
 | 
			
		||||
	
 | 
			
		||||
	bool txing = false;
 | 
			
		||||
	
 | 
			
		||||
	uint16_t message_length { 0 };
 | 
			
		||||
	
 | 
			
		||||
	void start_tx();
 | 
			
		||||
 | 
			
		||||
	Rect view_rect = { 0, 8 * 8, 240, 192 };
 | 
			
		||||
	
 | 
			
		||||
	RDSPSNView view_PSN { nav_, view_rect };
 | 
			
		||||
	RDSRadioTextView view_radiotext { nav_, view_rect };
 | 
			
		||||
	RDSDateTimeView view_datetime { view_rect };
 | 
			
		||||
	RDSAudioView view_audio { view_rect };
 | 
			
		||||
	
 | 
			
		||||
	TabView tab_view {
 | 
			
		||||
		{ "Name", Color::cyan(), &view_PSN },
 | 
			
		||||
		{ "Text", Color::green(), &view_radiotext },
 | 
			
		||||
		{ "Time", Color::yellow(), &view_datetime },
 | 
			
		||||
		{ "Audio", Color::orange(), &view_audio }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0 * 8, 28 }, "Program type:", Color::light_grey() },
 | 
			
		||||
		//{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
 | 
			
		||||
		{ { 2 * 8, 28 + 16 }, "Program ID:", Color::light_grey() },
 | 
			
		||||
		//{ { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	OptionsField options_pty {
 | 
			
		||||
		{ 13 * 8, 28 },
 | 
			
		||||
		8,
 | 
			
		||||
		{
 | 
			
		||||
			{ "None", 0 },
 | 
			
		||||
			{ "News", 1 },
 | 
			
		||||
			{ "Affairs", 2 },
 | 
			
		||||
			{ "Info", 3 },
 | 
			
		||||
			{ "Sport", 4 },
 | 
			
		||||
			{ "Educate", 5 },
 | 
			
		||||
			{ "Drama", 6 },
 | 
			
		||||
			{ "Culture", 7 },
 | 
			
		||||
			{ "Science", 8 },
 | 
			
		||||
			{ "Varied", 9 },
 | 
			
		||||
			{ "Pop", 10 },
 | 
			
		||||
			{ "Rock", 11 },
 | 
			
		||||
			{ "Easy", 12 },
 | 
			
		||||
			{ "Light", 13 },
 | 
			
		||||
			{ "Classics", 14 },
 | 
			
		||||
			{ "Other", 15 },
 | 
			
		||||
			{ "Weather", 16 },
 | 
			
		||||
			{ "Finance", 17 },
 | 
			
		||||
			{ "Children", 18 },
 | 
			
		||||
			{ "Social", 19 },
 | 
			
		||||
			{ "Religion", 20 },
 | 
			
		||||
			{ "PhoneIn", 21 },
 | 
			
		||||
			{ "Travel", 22 },
 | 
			
		||||
			{ "Leisure", 23 },
 | 
			
		||||
			{ "Jazz", 24 },
 | 
			
		||||
			{ "Country", 25 },
 | 
			
		||||
			{ "National", 26 },
 | 
			
		||||
			{ "Oldies", 27 },
 | 
			
		||||
			{ "Folk", 28 },
 | 
			
		||||
			{ "Docs", 29 },
 | 
			
		||||
			{ "AlarmTst", 30 },
 | 
			
		||||
			{ "Alarm", 31 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*OptionsField options_countrycode {
 | 
			
		||||
		{ 17 * 8, 16 + 8 },
 | 
			
		||||
		11,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Albania", 	9 },
 | 
			
		||||
			{ "Algeria", 	2 },
 | 
			
		||||
			{ "Andorra", 	3 },
 | 
			
		||||
			{ "Austria", 	10 },
 | 
			
		||||
			{ "Azores", 	8 },
 | 
			
		||||
			{ "Belgium", 	6 },
 | 
			
		||||
			{ "Belarus", 	15 },
 | 
			
		||||
			{ "Bosnia", 	15 },
 | 
			
		||||
			{ "Bulgaria", 	8 },
 | 
			
		||||
			{ "Canaries", 	14 },
 | 
			
		||||
			{ "Croatia", 	12 },
 | 
			
		||||
			{ "Cyprus", 	2 },
 | 
			
		||||
			{ "Czech-Rep", 	2 },
 | 
			
		||||
			{ "Denmark", 	9 },
 | 
			
		||||
			{ "Egypt", 		15 },
 | 
			
		||||
			{ "Estonia", 	2 },
 | 
			
		||||
			{ "Faroe", 		9 },
 | 
			
		||||
			{ "Finland", 	6 },
 | 
			
		||||
			{ "France", 	15 },
 | 
			
		||||
			{ "Germany 1", 	13 },
 | 
			
		||||
			{ "Germany 2", 	1 },
 | 
			
		||||
			{ "Gibraltar", 	10 },
 | 
			
		||||
			{ "Greece", 	1 },
 | 
			
		||||
			{ "Hungary", 	11 },
 | 
			
		||||
			{ "Iceland", 	10 },
 | 
			
		||||
			{ "Iraq", 		11 },
 | 
			
		||||
			{ "Ireland", 	2 },
 | 
			
		||||
			{ "Israel", 	4 },
 | 
			
		||||
			{ "Italy", 		5 },
 | 
			
		||||
			{ "Jordan", 	5 },
 | 
			
		||||
			{ "Latvia", 	9 },
 | 
			
		||||
			{ "Lebanon", 	10 },
 | 
			
		||||
			{ "Libya", 		13 },
 | 
			
		||||
			{ "Liechtenst.", 9 },
 | 
			
		||||
			{ "Lithuania", 	12 },
 | 
			
		||||
			{ "Luxembourg", 7 },
 | 
			
		||||
			{ "Macedonia", 	4 },
 | 
			
		||||
			{ "Madeira", 	8 },
 | 
			
		||||
			{ "Malta", 		12 },
 | 
			
		||||
			{ "Moldova", 	1 },
 | 
			
		||||
			{ "Monaco", 	11 },
 | 
			
		||||
			{ "Morocco", 	1 },
 | 
			
		||||
			{ "Netherlands", 8 },
 | 
			
		||||
			{ "Norway", 	15 },
 | 
			
		||||
			{ "Palestine", 	8 },
 | 
			
		||||
			{ "Poland", 	3 },
 | 
			
		||||
			{ "Portugal", 	8 },
 | 
			
		||||
			{ "Romania", 	14 },
 | 
			
		||||
			{ "Russia", 	7 },
 | 
			
		||||
			{ "San Marino", 3 },
 | 
			
		||||
			{ "Slovakia", 	5 },
 | 
			
		||||
			{ "Slovenia", 	9 },
 | 
			
		||||
			{ "Spain", 		14 },
 | 
			
		||||
			{ "Sweden", 	14 },
 | 
			
		||||
			{ "Switzerland", 4 },
 | 
			
		||||
			{ "Syria", 		6 },
 | 
			
		||||
			{ "Tunisia", 	7 },
 | 
			
		||||
			{ "Turkey", 	3 },
 | 
			
		||||
			{ "Ukraine", 	6 },
 | 
			
		||||
			{ "U.K.", 		12 },
 | 
			
		||||
			{ "Vatican", 	4 },
 | 
			
		||||
			{ "Yugoslavia", 13 }
 | 
			
		||||
		}
 | 
			
		||||
	};*/
 | 
			
		||||
	
 | 
			
		||||
	SymField sym_pi_code {
 | 
			
		||||
		{ 13 * 8, 28 + 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		SymField::SYMFIELD_HEX
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	/*OptionsField options_coverage {
 | 
			
		||||
		{ 17 * 8, 32 + 8 },
 | 
			
		||||
		12,
 | 
			
		||||
		{
 | 
			
		||||
			{ "Local", 0 },
 | 
			
		||||
			{ "Internat.", 1 },
 | 
			
		||||
			{ "National", 2 },
 | 
			
		||||
			{ "Sup-regional", 3 },
 | 
			
		||||
			{ "R11", 4 },
 | 
			
		||||
			{ "R12", 5 },
 | 
			
		||||
			{ "R13", 6 },
 | 
			
		||||
			{ "R14", 7 },
 | 
			
		||||
			{ "R15", 8 },
 | 
			
		||||
			{ "R16", 9 },
 | 
			
		||||
			{ "R17", 10 },
 | 
			
		||||
			{ "R18", 11 },
 | 
			
		||||
			{ "R19", 12 },
 | 
			
		||||
			{ "R110", 13 },
 | 
			
		||||
			{ "R111", 14 },
 | 
			
		||||
			{ "R112", 15 }
 | 
			
		||||
		}
 | 
			
		||||
	};*/
 | 
			
		||||
 | 
			
		||||
	Checkbox check_TP {
 | 
			
		||||
		{ 23 * 8, 4 * 8 },
 | 
			
		||||
		2,
 | 
			
		||||
		"TP"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	TransmitterView tx_view {
 | 
			
		||||
		16 * 16,
 | 
			
		||||
		50000,
 | 
			
		||||
		9
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<RDSThread> tx_thread { };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_remote.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void RemoteView::focus() {
 | 
			
		||||
	button.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RemoteView::~RemoteView() {
 | 
			
		||||
	//transmitter_model.disable();
 | 
			
		||||
	//baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RemoteView::RemoteView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&button
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	button.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_transmitter.hpp"
 | 
			
		||||
#include "transmitter_model.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class RemoteView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	RemoteView(NavigationView& nav);
 | 
			
		||||
	~RemoteView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Custom remote"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	/*enum tx_modes {
 | 
			
		||||
		IDLE = 0,
 | 
			
		||||
		SINGLE,
 | 
			
		||||
		SCAN
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	tx_modes tx_mode = IDLE;
 | 
			
		||||
 | 
			
		||||
	struct remote_layout_t {
 | 
			
		||||
		Point position;
 | 
			
		||||
		std::string text;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const std::array<remote_layout_t, 32> remote_layout { };*/
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 0 }, "Work in progress...", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button {
 | 
			
		||||
		{ 60, 64, 120, 32 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,567 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_scanner.hpp"
 | 
			
		||||
#include "ui_fileman.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
ScannerThread::ScannerThread(
 | 
			
		||||
	std::vector<rf::Frequency> frequency_list
 | 
			
		||||
) : frequency_list_ {  std::move(frequency_list) }
 | 
			
		||||
{
 | 
			
		||||
	thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ScannerThread::static_fn, this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ScannerThread::~ScannerThread() {
 | 
			
		||||
	stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::stop() {
 | 
			
		||||
	if( thread ) {
 | 
			
		||||
		chThdTerminate(thread);
 | 
			
		||||
		chThdWait(thread);
 | 
			
		||||
		thread = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::set_scanning(const bool v) {
 | 
			
		||||
	_scanning = v;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ScannerThread::is_scanning() {
 | 
			
		||||
	return _scanning;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::set_freq_lock(const uint32_t v) {
 | 
			
		||||
	_freq_lock = v;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t ScannerThread::is_freq_lock() {
 | 
			
		||||
	return _freq_lock;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::set_freq_del(const uint32_t v) {
 | 
			
		||||
	_freq_del = v;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::change_scanning_direction() {
 | 
			
		||||
	_fwd = !_fwd;
 | 
			
		||||
	chThdSleepMilliseconds(300);	//Give some pause after reversing scanning direction
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
msg_t ScannerThread::static_fn(void* arg) {
 | 
			
		||||
	auto obj = static_cast<ScannerThread*>(arg);
 | 
			
		||||
	obj->run();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerThread::run() {
 | 
			
		||||
	if (frequency_list_.size())	{					//IF THERE IS A FREQUENCY LIST ...	
 | 
			
		||||
		RetuneMessage message { };
 | 
			
		||||
		uint32_t frequency_index = frequency_list_.size();
 | 
			
		||||
		bool restart_scan = false;					//Flag whenever scanning is restarting after a pause
 | 
			
		||||
		while( !chThdShouldTerminate() ) {
 | 
			
		||||
			if (_scanning) {						//Scanning
 | 
			
		||||
				if (_freq_lock == 0) {				//normal scanning (not performing freq_lock)
 | 
			
		||||
					if (!restart_scan) {			//looping at full speed
 | 
			
		||||
						if (_fwd) {					//forward
 | 
			
		||||
							frequency_index++;
 | 
			
		||||
							if (frequency_index >= frequency_list_.size())
 | 
			
		||||
								frequency_index = 0;	
 | 
			
		||||
 | 
			
		||||
						} else {					//reverse
 | 
			
		||||
							if (frequency_index < 1)
 | 
			
		||||
								frequency_index = frequency_list_.size();	
 | 
			
		||||
							frequency_index--;
 | 
			
		||||
						}
 | 
			
		||||
						receiver_model.set_tuning_frequency(frequency_list_[frequency_index]);	// Retune
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
						restart_scan=false;			//Effectively skipping first retuning, giving system time
 | 
			
		||||
				} 
 | 
			
		||||
				message.range = frequency_index;	//Inform freq (for coloring purposes also!)
 | 
			
		||||
				EventDispatcher::send_message(message);
 | 
			
		||||
			} 
 | 
			
		||||
			else {									//NOT scanning 									
 | 
			
		||||
				if (_freq_del != 0) {				//There is a frequency to delete
 | 
			
		||||
					for (uint16_t i = 0; i < frequency_list_.size(); i++) {	//Search for the freq to delete
 | 
			
		||||
						if (frequency_list_[i] == _freq_del) 
 | 
			
		||||
						{							//found: Erase it
 | 
			
		||||
							frequency_list_.erase(frequency_list_.begin() + i);
 | 
			
		||||
							if (i==0)				//set scan index one place back to compensate
 | 
			
		||||
								i=frequency_list_.size();
 | 
			
		||||
							else
 | 
			
		||||
								i--;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					_freq_del = 0;					//deleted.
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					restart_scan=true;					//Flag the need for skipping a cycle when restarting scan
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			chThdSleepMilliseconds(50);				//Needed to (eventually) stabilize the receiver into new freq
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::handle_retune(uint32_t i) {
 | 
			
		||||
	switch (scan_thread->is_freq_lock())
 | 
			
		||||
	{
 | 
			
		||||
	case 0:										//NO FREQ LOCK, ONGOING STANDARD SCANNING
 | 
			
		||||
		text_cycle.set( to_string_dec_uint(i + 1,3) );
 | 
			
		||||
		current_index = i;		//since it is an ongoing scan, this is a new index
 | 
			
		||||
		if (description_list[current_index].size() > 0) desc_cycle.set( description_list[current_index] );	//Show new description	
 | 
			
		||||
		break;
 | 
			
		||||
	case 1:										//STARTING LOCK FREQ
 | 
			
		||||
		big_display.set_style(&style_yellow);
 | 
			
		||||
		break;
 | 
			
		||||
	case MAX_FREQ_LOCK:							//FREQ IS STRONG: GREEN and scanner will pause when on_statistics_update()
 | 
			
		||||
		big_display.set_style(&style_green);
 | 
			
		||||
		break;
 | 
			
		||||
	default:	//freq lock is checking the signal, do not update display
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	big_display.set(frequency_list[current_index]);	//UPDATE the big Freq after 0, 1 or MAX_FREQ_LOCK (at least, for color synching)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::focus() {
 | 
			
		||||
	field_mode.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ScannerView::~ScannerView() {
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::show_max() {		//show total number of freqs to scan
 | 
			
		||||
	if (frequency_list.size() == MAX_DB_ENTRY) {
 | 
			
		||||
		text_max.set_style(&style_red);
 | 
			
		||||
		text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)");
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		text_max.set_style(&style_grey);
 | 
			
		||||
		text_max.set( "/ " + to_string_dec_uint(frequency_list.size()));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ScannerView::ScannerView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
	) : nav_ { nav } , loaded_file_name { "SCANNER" }
 | 
			
		||||
{
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_rf_amp,
 | 
			
		||||
		&field_volume,
 | 
			
		||||
		&field_bw,
 | 
			
		||||
		&field_squelch,
 | 
			
		||||
		&field_wait,
 | 
			
		||||
		&button_load,
 | 
			
		||||
		&rssi,
 | 
			
		||||
		&text_cycle,
 | 
			
		||||
		&text_max,
 | 
			
		||||
		&desc_cycle,
 | 
			
		||||
		&big_display,
 | 
			
		||||
		&button_manual_start,
 | 
			
		||||
		&button_manual_end,
 | 
			
		||||
		&field_mode,
 | 
			
		||||
		&step_mode,
 | 
			
		||||
		&button_manual_scan,
 | 
			
		||||
		&button_pause,
 | 
			
		||||
		&button_dir,
 | 
			
		||||
		&button_audio_app,
 | 
			
		||||
		&button_mic_app,
 | 
			
		||||
		&button_add,
 | 
			
		||||
		&button_remove
 | 
			
		||||
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	def_step = change_mode(AM);	//Start on AM
 | 
			
		||||
	field_mode.set_by_value(AM);	//Reflect the mode into the manual selector
 | 
			
		||||
 | 
			
		||||
	//HELPER: Pre-setting a manual range, based on stored frequency
 | 
			
		||||
	rf::Frequency stored_freq = persistent_memory::tuned_frequency();
 | 
			
		||||
	frequency_range.min = stored_freq - 1000000;
 | 
			
		||||
	button_manual_start.set_text(to_string_short_freq(frequency_range.min));
 | 
			
		||||
	frequency_range.max = stored_freq + 1000000;
 | 
			
		||||
	button_manual_end.set_text(to_string_short_freq(frequency_range.max));
 | 
			
		||||
 | 
			
		||||
	button_load.on_select = [this, &nav](Button&) {
 | 
			
		||||
		// load txt files from the FREQMAN folder
 | 
			
		||||
		auto open_view = nav.push<FileLoadView>(".TXT");
 | 
			
		||||
		open_view->on_changed = [this](std::filesystem::path new_file_path) {
 | 
			
		||||
 | 
			
		||||
			std::string dir_filter = "FREQMAN/";
 | 
			
		||||
			std::string str_file_path = new_file_path.string();
 | 
			
		||||
 | 
			
		||||
			if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
 | 
			
		||||
				scan_pause();
 | 
			
		||||
				// get the filename without txt extension so we can use load_freqman_file fcn
 | 
			
		||||
				std::string str_file_name = new_file_path.stem().string();
 | 
			
		||||
				frequency_file_load(str_file_name, true);
 | 
			
		||||
			} else {
 | 
			
		||||
				nav_.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_manual_start.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			frequency_range.min = f;
 | 
			
		||||
			button_manual_start.set_text(to_string_short_freq(f));
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_manual_end.on_select = [this, &nav](Button& button) {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(frequency_range.max);
 | 
			
		||||
		new_view->on_changed = [this, &button](rf::Frequency f) {
 | 
			
		||||
			frequency_range.max = f;
 | 
			
		||||
			button_manual_end.set_text(to_string_short_freq(f));
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_pause.on_select = [this](Button&) {
 | 
			
		||||
		if ( userpause )
 | 
			
		||||
			user_resume();
 | 
			
		||||
		else {
 | 
			
		||||
			scan_pause();
 | 
			
		||||
			button_pause.set_text("RESUME");	//PAUSED, show resume
 | 
			
		||||
			userpause=true;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_audio_app.on_select = [this](Button&) {
 | 
			
		||||
		scan_thread->stop();
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
		nav_.push<AnalogAudioView>();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_mic_app.on_select = [this](Button&) {
 | 
			
		||||
		scan_thread->stop();
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
		nav_.push<MicTXView>();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_remove.on_select = [this](Button&) {
 | 
			
		||||
		if (frequency_list.size() > current_index) {
 | 
			
		||||
			if (scan_thread->is_scanning())			//STOP Scanning if necessary
 | 
			
		||||
				scan_thread->set_scanning(false);
 | 
			
		||||
			scan_thread->set_freq_del(frequency_list[current_index]);
 | 
			
		||||
			description_list.erase(description_list.begin() + current_index);
 | 
			
		||||
			frequency_list.erase(frequency_list.begin() + current_index);
 | 
			
		||||
			show_max();								//UPDATE new list size on screen
 | 
			
		||||
			desc_cycle.set(" ");					//Clean up description (cosmetic detail)
 | 
			
		||||
			scan_thread->set_freq_lock(0); 			//Reset the scanner lock
 | 
			
		||||
			if ( userpause ) 						//If user-paused, resume
 | 
			
		||||
				user_resume();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_manual_scan.on_select = [this](Button&) {
 | 
			
		||||
		if (!frequency_range.min || !frequency_range.max) {
 | 
			
		||||
			nav_.display_modal("Error", "Both START and END freqs\nneed a value");
 | 
			
		||||
		} else if (frequency_range.min > frequency_range.max) {
 | 
			
		||||
			nav_.display_modal("Error", "END freq\nis lower than START");
 | 
			
		||||
		} else {
 | 
			
		||||
		audio::output::stop();
 | 
			
		||||
		scan_thread->stop();	//STOP SCANNER THREAD
 | 
			
		||||
		frequency_list.clear();
 | 
			
		||||
		description_list.clear();
 | 
			
		||||
		def_step = step_mode.selected_index_value();		//Use def_step from manual selector
 | 
			
		||||
 | 
			
		||||
		description_list.push_back(
 | 
			
		||||
			"M" + to_string_short_freq(frequency_range.min) + ">"
 | 
			
		||||
	 		+ to_string_short_freq(frequency_range.max) + "S" 
 | 
			
		||||
	 		+ to_string_short_freq(def_step).erase(0,1) //euquiq: lame kludge to reduce spacing in step freq
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		rf::Frequency frequency = frequency_range.min;
 | 
			
		||||
		while (frequency_list.size() < MAX_DB_ENTRY &&  frequency <= frequency_range.max) { //add manual range				
 | 
			
		||||
			frequency_list.push_back(frequency);
 | 
			
		||||
			description_list.push_back("");				//If empty, will keep showing the last description
 | 
			
		||||
			frequency+=def_step;
 | 
			
		||||
		}
 | 
			
		||||
		show_max();
 | 
			
		||||
		if ( userpause ) 						//If user-paused, resume
 | 
			
		||||
			user_resume();
 | 
			
		||||
		big_display.set_style(&style_grey);		//Back to grey color
 | 
			
		||||
		start_scan_thread(); //RESTART SCANNER THREAD
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_mode.on_change = [this](size_t, OptionsField::value_t v) {
 | 
			
		||||
		receiver_model.disable();
 | 
			
		||||
		baseband::shutdown();
 | 
			
		||||
		change_mode(v);
 | 
			
		||||
		if ( !scan_thread->is_scanning() ) 						//for some motive, audio output gets stopped.
 | 
			
		||||
			audio::output::start();								//So if scan was stopped we resume audio
 | 
			
		||||
		receiver_model.enable(); 
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_dir.on_select = [this](Button&) {
 | 
			
		||||
		scan_thread->change_scanning_direction();
 | 
			
		||||
		if ( userpause ) 						//If user-paused, resume
 | 
			
		||||
			user_resume();
 | 
			
		||||
		big_display.set_style(&style_grey);		//Back to grey color
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_add.on_select = [this](Button&) {  //frequency_list[current_index]
 | 
			
		||||
		File scanner_file;
 | 
			
		||||
		std::string freq_file_path = "FREQMAN/" + loaded_file_name + ".TXT";
 | 
			
		||||
		auto result = scanner_file.open(freq_file_path);	//First search if freq is already in txt
 | 
			
		||||
		if (!result.is_valid()) {
 | 
			
		||||
			std::string frequency_to_add = "f=" 
 | 
			
		||||
				+ to_string_dec_uint(frequency_list[current_index] / 1000) 
 | 
			
		||||
				+ to_string_dec_uint(frequency_list[current_index] % 1000UL, 3, '0');
 | 
			
		||||
			char one_char[1];		//Read it char by char
 | 
			
		||||
			std::string line;		//and put read line in here
 | 
			
		||||
			bool found=false;
 | 
			
		||||
			for (size_t pointer=0; pointer < scanner_file.size();pointer++) {
 | 
			
		||||
 | 
			
		||||
				scanner_file.seek(pointer);
 | 
			
		||||
				scanner_file.read(one_char, 1);
 | 
			
		||||
				if ((int)one_char[0] > 31) {			//ascii space upwards
 | 
			
		||||
					line += one_char[0];				//Add it to the textline
 | 
			
		||||
				}
 | 
			
		||||
				else if (one_char[0] == '\n') {			//New Line
 | 
			
		||||
					if (line.compare(0, frequency_to_add.size(),frequency_to_add) == 0) {
 | 
			
		||||
						found=true;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
					line.clear();						//Ready for next textline
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (found) {
 | 
			
		||||
				nav_.display_modal("Error", "Frequency already exists");
 | 
			
		||||
				big_display.set(frequency_list[current_index]);		//After showing an error
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				scanner_file.append(freq_file_path); //Second: append if it is not there
 | 
			
		||||
				scanner_file.write_line(frequency_to_add + ",d=ADD FQ");
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		{
 | 
			
		||||
			nav_.display_modal("Error", "Cannot open " + loaded_file_name + ".TXT\nfor appending freq.");
 | 
			
		||||
			big_display.set(frequency_list[current_index]);		//After showing an error
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	//PRE-CONFIGURATION:
 | 
			
		||||
	field_wait.on_change = [this](int32_t v) {	wait = v;	}; 	field_wait.set_value(5);
 | 
			
		||||
	field_squelch.on_change = [this](int32_t v) {	squelch = v;	}; 	field_squelch.set_value(-10);
 | 
			
		||||
	field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
 | 
			
		||||
	field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v);	};
 | 
			
		||||
 | 
			
		||||
	// LEARN FREQUENCIES
 | 
			
		||||
	std::string scanner_txt = "SCANNER";
 | 
			
		||||
	frequency_file_load(scanner_txt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::frequency_file_load(std::string file_name, bool stop_all_before) {
 | 
			
		||||
 | 
			
		||||
	// stop everything running now if required
 | 
			
		||||
	if (stop_all_before) {
 | 
			
		||||
		scan_thread->stop();
 | 
			
		||||
		frequency_list.clear(); // clear the existing frequency list (expected behavior)
 | 
			
		||||
		description_list.clear();
 | 
			
		||||
		def_step = step_mode.selected_index_value();		//Use def_step from manual selector
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ( load_freqman_file(file_name, database)  ) {
 | 
			
		||||
		loaded_file_name = file_name; // keep loaded filename in memory
 | 
			
		||||
		for(auto& entry : database) {									// READ LINE PER LINE
 | 
			
		||||
			if (frequency_list.size() < MAX_DB_ENTRY) {					//We got space!
 | 
			
		||||
				if (entry.type == RANGE)  {								//RANGE	
 | 
			
		||||
					switch (entry.step) {
 | 
			
		||||
						case AM_US:	def_step = 10000;  	break ;
 | 
			
		||||
						case AM_EUR:def_step = 9000;  	break ;
 | 
			
		||||
						case NFM_1: def_step = 12500;  	break ;
 | 
			
		||||
						case NFM_2: def_step = 6250;	break ;	
 | 
			
		||||
						case FM_1:	def_step = 100000; 	break ;
 | 
			
		||||
						case FM_2:	def_step = 50000; 	break ;
 | 
			
		||||
						case N_1:	def_step = 25000;  	break ;
 | 
			
		||||
						case N_2:	def_step = 250000; 	break ;
 | 
			
		||||
						case AIRBAND:   def_step= 8330;  	break ;
 | 
			
		||||
						case ERROR_STEP:
 | 
			
		||||
						case STEP_DEF:
 | 
			
		||||
						default:
 | 
			
		||||
							def_step = step_mode.selected_index_value(); //Use def_step from manual selector
 | 
			
		||||
								 			break ;		
 | 
			
		||||
					}
 | 
			
		||||
					frequency_list.push_back(entry.frequency_a);		//Store starting freq and description
 | 
			
		||||
					description_list.push_back("R" + to_string_short_freq(entry.frequency_a)
 | 
			
		||||
						+ ">" + to_string_short_freq(entry.frequency_b)
 | 
			
		||||
						+ " S" + to_string_short_freq(def_step).erase(0,1) //euquiq: lame kludge to reduce spacing in step freq
 | 
			
		||||
						);
 | 
			
		||||
					while (frequency_list.size() < MAX_DB_ENTRY && entry.frequency_a <= entry.frequency_b) { //add the rest of the range
 | 
			
		||||
						entry.frequency_a+=def_step;
 | 
			
		||||
						frequency_list.push_back(entry.frequency_a);
 | 
			
		||||
						description_list.push_back("");				//Token (keep showing the last description)
 | 
			
		||||
					}
 | 
			
		||||
				} else if ( entry.type == SINGLE)  {
 | 
			
		||||
					frequency_list.push_back(entry.frequency_a);
 | 
			
		||||
					description_list.push_back("S: " + entry.description);
 | 
			
		||||
				}
 | 
			
		||||
				show_max();
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				break; //No more space: Stop reading the txt file !
 | 
			
		||||
			}		
 | 
			
		||||
		}
 | 
			
		||||
	} 
 | 
			
		||||
	else 
 | 
			
		||||
	{
 | 
			
		||||
		loaded_file_name = "SCANNER"; // back to the default frequency file
 | 
			
		||||
		desc_cycle.set(" NO " + file_name + ".TXT FILE ..." );
 | 
			
		||||
	}
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	step_mode.set_by_value(def_step); //Impose the default step into the manual step selector
 | 
			
		||||
	start_scan_thread();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::on_statistics_update(const ChannelStatistics& statistics) {
 | 
			
		||||
	if ( !userpause ) 									//Scanning not user-paused
 | 
			
		||||
	{
 | 
			
		||||
		if (timer >= (wait * 10) ) 
 | 
			
		||||
		{
 | 
			
		||||
			timer = 0;
 | 
			
		||||
			scan_resume();
 | 
			
		||||
		} 
 | 
			
		||||
		else if (!timer) 
 | 
			
		||||
		{
 | 
			
		||||
			if (statistics.max_db > squelch ) {  		//There is something on the air...(statistics.max_db > -squelch) 
 | 
			
		||||
				if (scan_thread->is_freq_lock() >= MAX_FREQ_LOCK) { //checking time reached
 | 
			
		||||
					scan_pause();
 | 
			
		||||
					timer++;	
 | 
			
		||||
				} else {
 | 
			
		||||
					scan_thread->set_freq_lock( scan_thread->is_freq_lock() + 1 ); //in lock period, still analyzing the signal
 | 
			
		||||
				}
 | 
			
		||||
			} else {									//There is NOTHING on the air
 | 
			
		||||
				if (scan_thread->is_freq_lock() > 0) {	//But are we already in freq_lock ?
 | 
			
		||||
					big_display.set_style(&style_grey);	//Back to grey color
 | 
			
		||||
					scan_thread->set_freq_lock(0); 		//Reset the scanner lock, since there is no signal
 | 
			
		||||
				}				
 | 
			
		||||
			}
 | 
			
		||||
		} 
 | 
			
		||||
		else 	//Ongoing wait time
 | 
			
		||||
		{
 | 
			
		||||
				timer++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::scan_pause() {
 | 
			
		||||
	if (scan_thread->is_scanning()) {
 | 
			
		||||
		scan_thread->set_freq_lock(0); 		//Reset the scanner lock (because user paused, or MAX_FREQ_LOCK reached) for next freq scan	
 | 
			
		||||
		scan_thread->set_scanning(false); // WE STOP SCANNING
 | 
			
		||||
		audio::output::start();
 | 
			
		||||
		on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::scan_resume() {
 | 
			
		||||
	audio::output::stop();
 | 
			
		||||
	big_display.set_style(&style_grey);		//Back to grey color
 | 
			
		||||
	if (!scan_thread->is_scanning())
 | 
			
		||||
		scan_thread->set_scanning(true);   // RESUME!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::user_resume() {
 | 
			
		||||
	timer = wait * 10;					//Will trigger a scan_resume() on_statistics_update, also advancing to next freq.
 | 
			
		||||
	button_pause.set_text("PAUSE");		//Show button for pause
 | 
			
		||||
	userpause=false;					//Resume scanning
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::on_headphone_volume_changed(int32_t v) {
 | 
			
		||||
	const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
 | 
			
		||||
	receiver_model.set_headphone_volume(new_volume);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thread->stop();  After this do a start_scan_thread()
 | 
			
		||||
	using option_t = std::pair<std::string, int32_t>;
 | 
			
		||||
	using options_t = std::vector<option_t>;
 | 
			
		||||
	options_t bw;
 | 
			
		||||
	field_bw.on_change = [this](size_t n, OptionsField::value_t) { 
 | 
			
		||||
		(void)n;  //avoid unused warning 
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	switch (new_mod) {
 | 
			
		||||
	case NFM:	//bw 16k (2) default
 | 
			
		||||
		bw.emplace_back("8k5", 0);
 | 
			
		||||
		bw.emplace_back("11k", 0);
 | 
			
		||||
		bw.emplace_back("16k", 0);			
 | 
			
		||||
		field_bw.set_options(bw);
 | 
			
		||||
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
 | 
			
		||||
		receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
 | 
			
		||||
		field_bw.set_selected_index(2);
 | 
			
		||||
		receiver_model.set_nbfm_configuration(field_bw.selected_index());
 | 
			
		||||
		field_bw.on_change = [this](size_t n, OptionsField::value_t) { 	receiver_model.set_nbfm_configuration(n); };
 | 
			
		||||
		receiver_model.set_sampling_rate(3072000);	receiver_model.set_baseband_bandwidth(1750000);	
 | 
			
		||||
		break;
 | 
			
		||||
	case AM:
 | 
			
		||||
		bw.emplace_back("DSB", 0);
 | 
			
		||||
		bw.emplace_back("USB", 0);
 | 
			
		||||
		bw.emplace_back("LSB", 0);
 | 
			
		||||
		bw.emplace_back("CW ", 0);
 | 
			
		||||
		field_bw.set_options(bw);
 | 
			
		||||
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_am_audio);
 | 
			
		||||
		receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
 | 
			
		||||
		field_bw.set_selected_index(0);
 | 
			
		||||
		receiver_model.set_am_configuration(field_bw.selected_index());
 | 
			
		||||
		field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n);	};		
 | 
			
		||||
		receiver_model.set_sampling_rate(3072000);	receiver_model.set_baseband_bandwidth(1750000); 
 | 
			
		||||
		break;
 | 
			
		||||
	case WFM:
 | 
			
		||||
		bw.emplace_back("16k", 0);
 | 
			
		||||
		field_bw.set_options(bw);
 | 
			
		||||
 | 
			
		||||
		baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
 | 
			
		||||
		receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
 | 
			
		||||
		field_bw.set_selected_index(0);
 | 
			
		||||
		receiver_model.set_wfm_configuration(field_bw.selected_index());
 | 
			
		||||
		field_bw.on_change = [this](size_t n, OptionsField::value_t) {	receiver_model.set_wfm_configuration(n); };
 | 
			
		||||
		receiver_model.set_sampling_rate(3072000);	receiver_model.set_baseband_bandwidth(2000000);	
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return mod_step[new_mod];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScannerView::start_scan_thread() {
 | 
			
		||||
	receiver_model.enable(); 
 | 
			
		||||
	receiver_model.set_squelch_level(0);
 | 
			
		||||
	scan_thread = std::make_unique<ScannerThread>(frequency_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,311 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2018 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "freqman.hpp"
 | 
			
		||||
#include "analog_audio_app.hpp"
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "ui_mictx.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "file.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define MAX_DB_ENTRY 500
 | 
			
		||||
#define MAX_FREQ_LOCK 10 		//50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
enum modulation_type { AM = 0,WFM,NFM };
 | 
			
		||||
	
 | 
			
		||||
string const mod_name[3] = {"AM", "WFM", "NFM"};
 | 
			
		||||
size_t const mod_step[3] = {9000, 100000, 12500 };
 | 
			
		||||
 | 
			
		||||
class ScannerThread {
 | 
			
		||||
public:
 | 
			
		||||
	ScannerThread(std::vector<rf::Frequency> frequency_list);
 | 
			
		||||
	~ScannerThread();
 | 
			
		||||
 | 
			
		||||
	void set_scanning(const bool v);
 | 
			
		||||
	bool is_scanning();
 | 
			
		||||
 | 
			
		||||
	void set_freq_lock(const uint32_t v);
 | 
			
		||||
	uint32_t is_freq_lock();
 | 
			
		||||
 | 
			
		||||
	void set_freq_del(const uint32_t v);
 | 
			
		||||
 | 
			
		||||
	void change_scanning_direction();
 | 
			
		||||
 | 
			
		||||
	void stop();
 | 
			
		||||
 | 
			
		||||
	ScannerThread(const ScannerThread&) = delete;
 | 
			
		||||
	ScannerThread(ScannerThread&&) = delete;
 | 
			
		||||
	ScannerThread& operator=(const ScannerThread&) = delete;
 | 
			
		||||
	ScannerThread& operator=(ScannerThread&&) = delete;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<rf::Frequency> frequency_list_ { };
 | 
			
		||||
	Thread* thread { nullptr };
 | 
			
		||||
	
 | 
			
		||||
	bool _scanning { true };
 | 
			
		||||
	bool _fwd { true };
 | 
			
		||||
	uint32_t _freq_lock { 0 };
 | 
			
		||||
	uint32_t _freq_del { 0 };
 | 
			
		||||
	static msg_t static_fn(void* arg);
 | 
			
		||||
	void run();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ScannerView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ScannerView(NavigationView& nav);
 | 
			
		||||
	~ScannerView();
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	void big_display_freq(rf::Frequency f);
 | 
			
		||||
 | 
			
		||||
	const Style style_grey {		// scanning
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::grey(),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const Style style_yellow {		//Found signal
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::dark_yellow(),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Style style_green {		//Found signal
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::green(),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Style style_red {		//erasing freq
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::red(),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Scanner"; };
 | 
			
		||||
	std::vector<rf::Frequency> frequency_list{ };
 | 
			
		||||
	std::vector<string> description_list { };
 | 
			
		||||
 | 
			
		||||
//void set_parent_rect(const Rect new_parent_rect) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	void start_scan_thread();
 | 
			
		||||
	size_t change_mode(uint8_t mod_type);
 | 
			
		||||
	void show_max();
 | 
			
		||||
	void scan_pause();
 | 
			
		||||
	void scan_resume();
 | 
			
		||||
	void user_resume();
 | 
			
		||||
	void frequency_file_load(std::string file_name, bool stop_all_before = false);
 | 
			
		||||
 | 
			
		||||
	void on_statistics_update(const ChannelStatistics& statistics);
 | 
			
		||||
	void on_headphone_volume_changed(int32_t v);
 | 
			
		||||
	void handle_retune(uint32_t i);
 | 
			
		||||
 | 
			
		||||
	jammer::jammer_range_t frequency_range { false, 0, 0 };  //perfect for manual scan task too...
 | 
			
		||||
	int32_t squelch { 0 };
 | 
			
		||||
	uint32_t timer { 0 };
 | 
			
		||||
	uint32_t wait { 0 };
 | 
			
		||||
	size_t	def_step { 0 };
 | 
			
		||||
	freqman_db database { };
 | 
			
		||||
	std::string loaded_file_name;
 | 
			
		||||
	uint32_t current_index { 0 };
 | 
			
		||||
	bool userpause { false };
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 0 * 8, 0 * 16 }, "LNA:   VGA:   AMP:  VOL:", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, 1* 16 }, "BW:    SQUELCH:   db WAIT:", Color::light_grey() },
 | 
			
		||||
		{ { 3 * 8, 10 * 16 }, "START        END     MANUAL", Color::light_grey() },
 | 
			
		||||
		{ { 0 * 8, (26 * 8) + 4 }, "MODE:", Color::light_grey() },
 | 
			
		||||
		{ { 11 * 8, (26 * 8) + 4 }, "STEP:", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 4 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 11 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	RFAmpField field_rf_amp {
 | 
			
		||||
		{ 18 * 8, 0 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_volume {
 | 
			
		||||
		{ 24 * 8, 0 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField field_bw {
 | 
			
		||||
		{ 3 * 8, 1 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ }
 | 
			
		||||
	};		
 | 
			
		||||
 | 
			
		||||
	NumberField field_squelch {
 | 
			
		||||
		{ 15 * 8, 1 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
 		{ -90, 20 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_wait {
 | 
			
		||||
		{ 26 * 8, 1 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		' ',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	RSSI rssi {
 | 
			
		||||
		{ 0 * 16, 2 * 16, 15 * 16, 8 },
 | 
			
		||||
	}; 
 | 
			
		||||
 | 
			
		||||
	Text text_cycle {
 | 
			
		||||
		{ 0, 3 * 16, 3 * 8, 16 },  
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_max {
 | 
			
		||||
		{ 4 * 8, 3 * 16, 18 * 8, 16 },  
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Text desc_cycle {
 | 
			
		||||
		{0, 4 * 16, 240, 16 },	   
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	BigFrequency big_display {		//Show frequency in glamour
 | 
			
		||||
		{ 4, 6 * 16, 28 * 8, 52 },
 | 
			
		||||
		0
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_manual_start {
 | 
			
		||||
		{ 0 * 8, 11 * 16, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_manual_end {
 | 
			
		||||
		{ 12 * 8, 11 * 16, 11 * 8, 28 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_manual_scan {
 | 
			
		||||
		{ 24 * 8, 11 * 16, 6 * 8, 28 },
 | 
			
		||||
		"SCAN"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField field_mode {
 | 
			
		||||
		{ 5 * 8, (26 * 8) + 4 },
 | 
			
		||||
		6,
 | 
			
		||||
		{
 | 
			
		||||
			{ " AM  ", 0 },
 | 
			
		||||
			{ " WFM ", 1 },
 | 
			
		||||
			{ " NFM ", 2 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField step_mode {
 | 
			
		||||
		{ 17 * 8, (26 * 8) + 4 },
 | 
			
		||||
		12,
 | 
			
		||||
		{
 | 
			
		||||
			{ "5kHz (SA AM)", 	5000 },
 | 
			
		||||
			{ "9kHz (EU AM)", 	9000 },
 | 
			
		||||
			{ "10kHz(US AM)", 	10000 },
 | 
			
		||||
			{ "50kHz (FM1)", 	50000 },
 | 
			
		||||
			{ "100kHz(FM2)", 	100000 },
 | 
			
		||||
			{ "6.25kHz(NFM)",	6250 },
 | 
			
		||||
			{ "12.5kHz(NFM)",	12500 },
 | 
			
		||||
			{ "25kHz (N1)",		25000 },
 | 
			
		||||
			{ "250kHz (N2)",	250000 },
 | 
			
		||||
			{ "8.33kHz(AIR)",	8330 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_pause {
 | 
			
		||||
		{ 0, (15 * 16) - 4, 72, 28 },
 | 
			
		||||
		"PAUSE"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_dir {
 | 
			
		||||
		{ 0,  (35 * 8) - 4, 72, 28 },
 | 
			
		||||
		"FW><RV"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_audio_app {
 | 
			
		||||
		{ 84, (15 * 16) - 4, 72, 28 },
 | 
			
		||||
		"AUDIO"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_mic_app {
 | 
			
		||||
		{ 84,  (35 * 8) - 4, 72, 28 },
 | 
			
		||||
		"MIC TX"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_add {
 | 
			
		||||
		{ 168, (15 * 16) - 4, 72, 28 },
 | 
			
		||||
		"ADD FQ"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_load {
 | 
			
		||||
		{ 24 * 8, 3 * 16 - 8, 6 * 8, 22 },
 | 
			
		||||
		"Load"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_remove {
 | 
			
		||||
		{ 168, (35 * 8) - 4, 72, 28 },
 | 
			
		||||
		"DEL FQ"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	std::unique_ptr<ScannerThread> scan_thread { };
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_retune {
 | 
			
		||||
		Message::ID::Retune,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const RetuneMessage*>(p);
 | 
			
		||||
			this->handle_retune(message.range);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_stats {
 | 
			
		||||
		Message::ID::ChannelStatistics,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,111 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_script.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void ScriptView::on_frequency_select() {
 | 
			
		||||
	//button_edit_freq.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScriptView::on_edit_freq(rf::Frequency f) {
 | 
			
		||||
	(void)f;
 | 
			
		||||
	//frequencies[menu_view.highlighted()].value = f;
 | 
			
		||||
	setup_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScriptView::on_edit_desc(NavigationView& nav) {
 | 
			
		||||
	(void)nav;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScriptView::on_delete() {
 | 
			
		||||
	//frequencies.erase(frequencies.begin() + menu_view.highlighted());
 | 
			
		||||
	setup_list();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScriptView::setup_list() {
 | 
			
		||||
	//size_t n;
 | 
			
		||||
	
 | 
			
		||||
	menu_view.clear();
 | 
			
		||||
	
 | 
			
		||||
	/*for (n = 0; n < frequencies.size(); n++) {
 | 
			
		||||
		menu_view.add_item({ freqman_item_string(frequencies[n]), ui::Color::white(), nullptr, [this](){ on_frequency_select(); } });
 | 
			
		||||
	}*/
 | 
			
		||||
	
 | 
			
		||||
	menu_view.set_parent_rect({ 0, 0, 240, 168 });
 | 
			
		||||
	menu_view.set_highlighted(menu_view.highlighted());		// Refresh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScriptView::focus() {
 | 
			
		||||
	menu_view.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ScriptView::ScriptView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) {
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&menu_view,
 | 
			
		||||
		&text_edit,
 | 
			
		||||
		&button_edit_freq,
 | 
			
		||||
		&button_edit_desc,
 | 
			
		||||
		&button_del,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	setup_list();
 | 
			
		||||
	
 | 
			
		||||
	button_edit_freq.on_select = [this, &nav](Button&) {
 | 
			
		||||
		/*auto new_view = nav.push<FrequencyKeypadView>(frequencies[menu_view.highlighted()].value);
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			on_edit_freq(f);
 | 
			
		||||
		};*/
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_edit_desc.on_select = [this, &nav](Button&) {
 | 
			
		||||
		on_edit_desc(nav);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_del.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.push<ModalMessageView>("Confirm", "Are you sure?", YESNO,
 | 
			
		||||
			[this](bool choice) {
 | 
			
		||||
				if (choice) {
 | 
			
		||||
					on_delete();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	button_exit.on_select = [this, &nav](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,94 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui.hpp"
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_painter.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_textentry.hpp"
 | 
			
		||||
#include "rtc_time.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
	
 | 
			
		||||
enum script_keyword {
 | 
			
		||||
	STOP = 0,
 | 
			
		||||
	WAIT_N,
 | 
			
		||||
	WAIT_RTC,
 | 
			
		||||
	IF,
 | 
			
		||||
	LOOP,
 | 
			
		||||
	END,
 | 
			
		||||
	TX,
 | 
			
		||||
	RX
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct script_line {
 | 
			
		||||
	script_keyword keyword;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ScriptView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	ScriptView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
 | 
			
		||||
	std::string title() const override { return "Script editor"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void on_frequency_select();
 | 
			
		||||
	void on_edit_freq(rf::Frequency f);
 | 
			
		||||
	void on_edit_desc(NavigationView& nav);
 | 
			
		||||
	void on_delete();
 | 
			
		||||
	void setup_list();
 | 
			
		||||
	
 | 
			
		||||
	std::vector<script_line> script { };
 | 
			
		||||
 | 
			
		||||
	MenuView menu_view {
 | 
			
		||||
		{ 0, 0, 240, 168 },
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text text_edit {
 | 
			
		||||
		{ 16, 194, 5 * 8, 16 },
 | 
			
		||||
		"Edit:"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_edit_freq {
 | 
			
		||||
		{ 16, 194 + 16, 88, 32 },
 | 
			
		||||
		"Frequency"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_edit_desc {
 | 
			
		||||
		{ 16, 194 + 16 + 34, 88, 32 },
 | 
			
		||||
		"Description"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_del {
 | 
			
		||||
		{ 160, 192, 72, 64 },
 | 
			
		||||
		"Delete"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_exit {
 | 
			
		||||
		{ 160, 264, 72, 32 },
 | 
			
		||||
		"Exit"
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_sd_wipe.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
Thread* WipeSDView::thread { nullptr };
 | 
			
		||||
 | 
			
		||||
WipeSDView::WipeSDView(NavigationView& nav) : nav_ (nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_info,
 | 
			
		||||
		&progress,
 | 
			
		||||
		&dummy
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WipeSDView::~WipeSDView() {
 | 
			
		||||
	if (thread)
 | 
			
		||||
		chThdTerminate(thread);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WipeSDView::focus() {
 | 
			
		||||
	BlockDeviceInfo block_device_info;
 | 
			
		||||
	
 | 
			
		||||
	dummy.focus();
 | 
			
		||||
	
 | 
			
		||||
	if (!confirmed) {
 | 
			
		||||
		nav_.push<ModalMessageView>("Warning !", "Wipe FAT of SD card?", YESCANCEL, [this](bool choice) {
 | 
			
		||||
				if (choice)
 | 
			
		||||
					confirmed = true;
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		if (sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS) {
 | 
			
		||||
			thread = chThdCreateFromHeap(NULL, 2048, NORMALPRIO, WipeSDView::static_fn, this);
 | 
			
		||||
		} else {
 | 
			
		||||
			nav_.pop();		// Just silently abort for now
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_SD_WIPE_H__
 | 
			
		||||
#define __UI_SD_WIPE_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "ff.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
class WipeSDView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	WipeSDView(NavigationView& nav);
 | 
			
		||||
	~WipeSDView();
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Wipe SD Card"; };	
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
	
 | 
			
		||||
	bool confirmed = false;
 | 
			
		||||
	static Thread* thread;
 | 
			
		||||
	
 | 
			
		||||
	static msg_t static_fn(void* arg) {
 | 
			
		||||
		auto obj = static_cast<WipeSDView*>(arg);
 | 
			
		||||
		obj->run();
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void run() {
 | 
			
		||||
		lfsr_word_t v = 1;
 | 
			
		||||
		//DIR d;
 | 
			
		||||
		const auto buffer = std::make_unique<std::array<uint8_t, 512>>();
 | 
			
		||||
 | 
			
		||||
		//f_opendir(&d, (TCHAR*)u"");
 | 
			
		||||
 | 
			
		||||
		uint32_t count = 512;	//sd_card::fs.n_fats * sd_card::fs.fsize;
 | 
			
		||||
		progress.set_max(count);
 | 
			
		||||
 | 
			
		||||
		for (uint32_t c = 0; c < count; c++) {
 | 
			
		||||
			progress.set_value(c);
 | 
			
		||||
			
 | 
			
		||||
			lfsr_fill(v,
 | 
			
		||||
				reinterpret_cast<lfsr_word_t*>(buffer->data()),
 | 
			
		||||
				sizeof(*buffer.get()) / sizeof(lfsr_word_t));
 | 
			
		||||
				
 | 
			
		||||
			if (disk_write(sd_card::fs.drv, buffer->data(), sd_card::fs.fatbase + c, 1) != RES_OK)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		nav_.pop();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	Text text_info {
 | 
			
		||||
		{ 10 * 8, 16 * 8, 10 * 8, 16 },
 | 
			
		||||
		"Working..."
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progress {
 | 
			
		||||
		{ 2 * 8, 19 * 8, 26 * 8, 24 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button dummy {
 | 
			
		||||
		{ 240, 0, 0, 0 },
 | 
			
		||||
		""
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_SD_WIPE_H__*/
 | 
			
		||||
@@ -0,0 +1,433 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_search.hpp"
 | 
			
		||||
 | 
			
		||||
#include "baseband_api.hpp"
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
void RecentEntriesTable<SearchRecentEntries>::draw(
 | 
			
		||||
	const Entry& entry,
 | 
			
		||||
	const Rect& target_rect,
 | 
			
		||||
	Painter& painter,
 | 
			
		||||
	const Style& style
 | 
			
		||||
) {
 | 
			
		||||
	std::string str_duration = "";
 | 
			
		||||
	
 | 
			
		||||
	if (entry.duration < 600)
 | 
			
		||||
		str_duration = to_string_dec_uint(entry.duration / 10) + "." + to_string_dec_uint(entry.duration % 10) + "s";
 | 
			
		||||
	else
 | 
			
		||||
		str_duration = to_string_dec_uint(entry.duration / 600) + "m" + to_string_dec_uint((entry.duration / 10) % 60) + "s";
 | 
			
		||||
		
 | 
			
		||||
	str_duration.resize(target_rect.width() / 8, ' ');
 | 
			
		||||
	
 | 
			
		||||
	painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::focus() {
 | 
			
		||||
	field_frequency_min.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SearchView::~SearchView() {
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
	baseband::shutdown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::do_detection() {
 | 
			
		||||
	uint8_t power_max = 0;
 | 
			
		||||
	int32_t bin_max = -1;
 | 
			
		||||
	uint32_t slice_max = 0;
 | 
			
		||||
	uint32_t snap_value;
 | 
			
		||||
	uint8_t power;
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	std::string str_approx, str_timestamp;
 | 
			
		||||
	
 | 
			
		||||
	// Display spectrum
 | 
			
		||||
	bin_skip_acc = 0;
 | 
			
		||||
	pixel_index = 0;
 | 
			
		||||
	display.draw_pixels(
 | 
			
		||||
		{ { 0, 88 }, { (Dim)spectrum_row.size(), 1 } },
 | 
			
		||||
		spectrum_row
 | 
			
		||||
	);
 | 
			
		||||
	
 | 
			
		||||
	mean_power = mean_acc / (SEARCH_BIN_NB_NO_DC * slices_nb);
 | 
			
		||||
	mean_acc = 0;
 | 
			
		||||
	
 | 
			
		||||
	overall_power_max = 0;
 | 
			
		||||
	
 | 
			
		||||
	// Find max power over threshold for all slices
 | 
			
		||||
	for (size_t slice = 0; slice < slices_nb; slice++) {
 | 
			
		||||
		power = slices[slice].max_power;
 | 
			
		||||
		if (power > overall_power_max)
 | 
			
		||||
			overall_power_max = power;
 | 
			
		||||
		
 | 
			
		||||
		if ((power >= mean_power + power_threshold) && (power > power_max)) {
 | 
			
		||||
			power_max = power;
 | 
			
		||||
			bin_max = slices[slice].max_index;
 | 
			
		||||
			slice_max = slice;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// Lock / release
 | 
			
		||||
	if ((bin_max >= last_bin - 2) && (bin_max <= last_bin + 2) && (bin_max > -1) && (slice_max == last_slice)) {
 | 
			
		||||
		
 | 
			
		||||
		// Staying around the same bin
 | 
			
		||||
		if (detect_timer >= DETECT_DELAY) {
 | 
			
		||||
			if ((bin_max != locked_bin) || (!locked)) {
 | 
			
		||||
				
 | 
			
		||||
				if (!locked) {
 | 
			
		||||
					resolved_frequency = slices[slice_max].center_frequency + (SEARCH_BIN_WIDTH * (bin_max - 128));
 | 
			
		||||
					
 | 
			
		||||
					if (check_snap.value()) {
 | 
			
		||||
						snap_value = options_snap.selected_index_value();
 | 
			
		||||
						resolved_frequency = round(resolved_frequency / snap_value) * snap_value;
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					// Check range
 | 
			
		||||
					if ((resolved_frequency >= f_min) && (resolved_frequency <= f_max)) {
 | 
			
		||||
						
 | 
			
		||||
						duration = 0;
 | 
			
		||||
						
 | 
			
		||||
						auto& entry = ::on_packet(recent, resolved_frequency);
 | 
			
		||||
						
 | 
			
		||||
						rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
						str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
 | 
			
		||||
										to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
 | 
			
		||||
										to_string_dec_uint(datetime.second(), 2, '0');
 | 
			
		||||
						entry.set_time(str_timestamp);
 | 
			
		||||
						recent_entries_view.set_dirty();
 | 
			
		||||
 | 
			
		||||
						text_infos.set("Locked ! ");
 | 
			
		||||
						big_display.set_style(&style_locked);
 | 
			
		||||
						
 | 
			
		||||
						locked = true;
 | 
			
		||||
						locked_bin = bin_max;
 | 
			
		||||
						
 | 
			
		||||
						// TODO
 | 
			
		||||
						/*nav_.pop();
 | 
			
		||||
						receiver_model.disable();
 | 
			
		||||
						baseband::shutdown();
 | 
			
		||||
						nav_.pop();*/
 | 
			
		||||
 | 
			
		||||
						/*if (options_goto.selected_index() == 1)
 | 
			
		||||
							nav_.push<AnalogAudioView>(false);
 | 
			
		||||
						else if (options_goto.selected_index() == 2)
 | 
			
		||||
							nav_.push<POCSAGAppView>();
 | 
			
		||||
						*/
 | 
			
		||||
					} else
 | 
			
		||||
						text_infos.set("Out of range");
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				big_display.set(resolved_frequency);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		release_timer = 0;
 | 
			
		||||
	} else {
 | 
			
		||||
		detect_timer = 0;
 | 
			
		||||
		if (locked) {
 | 
			
		||||
			if (release_timer >= RELEASE_DELAY) {
 | 
			
		||||
				locked = false;
 | 
			
		||||
				
 | 
			
		||||
				auto& entry = ::on_packet(recent, resolved_frequency);
 | 
			
		||||
				entry.set_duration(duration);
 | 
			
		||||
				recent_entries_view.set_dirty();
 | 
			
		||||
				
 | 
			
		||||
				text_infos.set("Listening");
 | 
			
		||||
				big_display.set_style(&style_grey);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	last_bin = bin_max;
 | 
			
		||||
	last_slice = slice_max;
 | 
			
		||||
	search_counter++;
 | 
			
		||||
	
 | 
			
		||||
	// Refresh red tick
 | 
			
		||||
	portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Color::black());
 | 
			
		||||
	if (bin_max > -1) {
 | 
			
		||||
		last_tick_pos = (Coord)(bin_max / slices_nb);
 | 
			
		||||
		portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Color::red());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::add_spectrum_pixel(Color color) {
 | 
			
		||||
	// Is avoiding floats really necessary ?
 | 
			
		||||
	bin_skip_acc += bin_skip_frac;
 | 
			
		||||
	if (bin_skip_acc < 0x10000) 
 | 
			
		||||
		return;
 | 
			
		||||
	
 | 
			
		||||
	bin_skip_acc -= 0x10000;
 | 
			
		||||
	
 | 
			
		||||
	if (pixel_index < 240)
 | 
			
		||||
		spectrum_row[pixel_index++] = color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
 | 
			
		||||
	uint8_t max_power = 0;
 | 
			
		||||
	int16_t max_bin = 0;
 | 
			
		||||
	uint8_t power;
 | 
			
		||||
	size_t bin;
 | 
			
		||||
	
 | 
			
		||||
	baseband::spectrum_streaming_stop();
 | 
			
		||||
	
 | 
			
		||||
	// Add pixels to spectrum display and find max power for this slice
 | 
			
		||||
	// Center 12 bins are ignored (DC spike is blanked)
 | 
			
		||||
	// Leftmost and rightmost 2 bins are ignored
 | 
			
		||||
	for (bin = 0; bin < 256; bin++) {
 | 
			
		||||
 | 
			
		||||
		if ((bin < 2) || (bin > 253) || ((bin >= 122) && (bin < 134))) {
 | 
			
		||||
			power = 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (bin < 128)
 | 
			
		||||
				power = spectrum.db[128 + bin];
 | 
			
		||||
			else
 | 
			
		||||
				power = spectrum.db[bin - 128];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		add_spectrum_pixel(spectrum_rgb3_lut[power]);
 | 
			
		||||
		
 | 
			
		||||
		mean_acc += power;
 | 
			
		||||
		if (power > max_power) {
 | 
			
		||||
			max_power = power;
 | 
			
		||||
			max_bin = bin;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	slices[slice_counter].max_power = max_power;
 | 
			
		||||
	slices[slice_counter].max_index = max_bin;
 | 
			
		||||
	
 | 
			
		||||
	if (slices_nb > 1) {
 | 
			
		||||
		// Slice sequence
 | 
			
		||||
		if (slice_counter >= slices_nb) {
 | 
			
		||||
			do_detection();
 | 
			
		||||
			slice_counter = 0;
 | 
			
		||||
		} else
 | 
			
		||||
			slice_counter++;
 | 
			
		||||
		receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency);
 | 
			
		||||
		baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);	// Clear
 | 
			
		||||
	} else {
 | 
			
		||||
		// Unique slice
 | 
			
		||||
		do_detection();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	baseband::spectrum_streaming_start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_show() {
 | 
			
		||||
	baseband::spectrum_streaming_start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_hide() {
 | 
			
		||||
	baseband::spectrum_streaming_stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_range_changed() {
 | 
			
		||||
	rf::Frequency slices_span, center_frequency;
 | 
			
		||||
	int64_t offset;
 | 
			
		||||
	size_t slice;
 | 
			
		||||
 | 
			
		||||
	f_min = field_frequency_min.value();
 | 
			
		||||
	f_max = field_frequency_max.value();
 | 
			
		||||
	search_span = abs(f_max - f_min);
 | 
			
		||||
	
 | 
			
		||||
	if (search_span > SEARCH_SLICE_WIDTH) {
 | 
			
		||||
		// ex: 100M~115M (15M span):
 | 
			
		||||
		// slices_nb = (115M-100M)/2.5M = 6
 | 
			
		||||
		slices_nb = (search_span + SEARCH_SLICE_WIDTH - 1) / SEARCH_SLICE_WIDTH;
 | 
			
		||||
		if (slices_nb > 32) {
 | 
			
		||||
			text_slices.set("!!");
 | 
			
		||||
			slices_nb = 32;
 | 
			
		||||
		} else {
 | 
			
		||||
			text_slices.set(to_string_dec_uint(slices_nb, 2, ' '));
 | 
			
		||||
		}
 | 
			
		||||
		// slices_span = 6 * 2.5M = 15M
 | 
			
		||||
		slices_span = slices_nb * SEARCH_SLICE_WIDTH;
 | 
			
		||||
		// offset = 0 + 2.5/2 = 1.25M
 | 
			
		||||
		offset = ((search_span - slices_span) / 2) + (SEARCH_SLICE_WIDTH / 2);
 | 
			
		||||
		// slice_start = 100M + 1.25M = 101.25M
 | 
			
		||||
		center_frequency = std::min(f_min, f_max) + offset;
 | 
			
		||||
		
 | 
			
		||||
		for (slice = 0; slice < slices_nb; slice++) {
 | 
			
		||||
			slices[slice].center_frequency = center_frequency;
 | 
			
		||||
			center_frequency += SEARCH_SLICE_WIDTH;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		slices[0].center_frequency = (f_max + f_min) / 2;
 | 
			
		||||
		receiver_model.set_tuning_frequency(slices[0].center_frequency);
 | 
			
		||||
 | 
			
		||||
		slices_nb = 1;
 | 
			
		||||
		text_slices.set(" 1");
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	bin_skip_frac = 0xF000 / slices_nb;
 | 
			
		||||
 | 
			
		||||
	slice_counter = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_lna_changed(int32_t v_db) {
 | 
			
		||||
	receiver_model.set_lna(v_db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::on_vga_changed(int32_t v_db) {
 | 
			
		||||
	receiver_model.set_vga(v_db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SearchView::do_timers() {
 | 
			
		||||
	
 | 
			
		||||
	if (timing_div >= 60) {
 | 
			
		||||
		// ~1Hz
 | 
			
		||||
		
 | 
			
		||||
		timing_div = 0;
 | 
			
		||||
		
 | 
			
		||||
		// Update scan rate
 | 
			
		||||
		text_rate.set(to_string_dec_uint(search_counter, 3));
 | 
			
		||||
		search_counter = 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (timing_div % 12 == 0) {
 | 
			
		||||
		// ~5Hz
 | 
			
		||||
		
 | 
			
		||||
		// Update power levels
 | 
			
		||||
		text_mean.set(to_string_dec_uint(mean_power, 3));
 | 
			
		||||
		
 | 
			
		||||
		vu_max.set_value(overall_power_max);
 | 
			
		||||
		vu_max.set_mark(mean_power + power_threshold);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (timing_div % 6 == 0) {
 | 
			
		||||
		// ~10Hz
 | 
			
		||||
		
 | 
			
		||||
		// Update timing indicator
 | 
			
		||||
		if (locked) {
 | 
			
		||||
			progress_timers.set_max(RELEASE_DELAY);
 | 
			
		||||
			progress_timers.set_value(RELEASE_DELAY - release_timer);
 | 
			
		||||
		} else {
 | 
			
		||||
			progress_timers.set_max(DETECT_DELAY);
 | 
			
		||||
			progress_timers.set_value(detect_timer);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Increment timers
 | 
			
		||||
		if (detect_timer < DETECT_DELAY) detect_timer++;
 | 
			
		||||
		if (release_timer < RELEASE_DELAY) release_timer++;
 | 
			
		||||
		
 | 
			
		||||
		if (locked) duration++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timing_div++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SearchView::SearchView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) : nav_ (nav)
 | 
			
		||||
{
 | 
			
		||||
	baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_frequency_min,
 | 
			
		||||
		&field_frequency_max,
 | 
			
		||||
		&field_lna,
 | 
			
		||||
		&field_vga,
 | 
			
		||||
		&field_threshold,
 | 
			
		||||
		&text_mean,
 | 
			
		||||
		&text_slices,
 | 
			
		||||
		&text_rate,
 | 
			
		||||
		&text_infos,
 | 
			
		||||
		&vu_max,
 | 
			
		||||
		&progress_timers,
 | 
			
		||||
		&check_snap,
 | 
			
		||||
		&options_snap,
 | 
			
		||||
		&big_display,
 | 
			
		||||
		&recent_entries_view
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
 | 
			
		||||
	
 | 
			
		||||
	recent_entries_view.set_parent_rect({ 0, 28 * 8, 240, 12 * 8 });
 | 
			
		||||
	recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
 | 
			
		||||
		nav.push<FrequencyKeypadView>(entry.frequency);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	text_mean.set_style(&style_grey);
 | 
			
		||||
	text_slices.set_style(&style_grey);
 | 
			
		||||
	text_rate.set_style(&style_grey);
 | 
			
		||||
	progress_timers.set_style(&style_grey);
 | 
			
		||||
	big_display.set_style(&style_grey);
 | 
			
		||||
	
 | 
			
		||||
	check_snap.set_value(true);
 | 
			
		||||
	options_snap.set_selected_index(1);		// 12.5kHz
 | 
			
		||||
	
 | 
			
		||||
	field_threshold.set_value(80);
 | 
			
		||||
	field_threshold.on_change = [this](int32_t value) {
 | 
			
		||||
		power_threshold = value;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_frequency_min.set_value(receiver_model.tuning_frequency() - 1000000);
 | 
			
		||||
	field_frequency_min.set_step(100000);
 | 
			
		||||
	field_frequency_min.on_change = [this](rf::Frequency) {
 | 
			
		||||
		this->on_range_changed();
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency_min.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->field_frequency_min.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_frequency_max.set_value(receiver_model.tuning_frequency() + 1000000);
 | 
			
		||||
	field_frequency_max.set_step(100000);
 | 
			
		||||
	field_frequency_max.on_change = [this](rf::Frequency) {
 | 
			
		||||
		this->on_range_changed();
 | 
			
		||||
	};
 | 
			
		||||
	field_frequency_max.on_edit = [this, &nav]() {
 | 
			
		||||
		auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
 | 
			
		||||
		new_view->on_changed = [this](rf::Frequency f) {
 | 
			
		||||
			this->field_frequency_max.set_value(f);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	field_lna.set_value(receiver_model.lna());
 | 
			
		||||
	field_lna.on_change = [this](int32_t v) {
 | 
			
		||||
		this->on_lna_changed(v);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_vga.set_value(receiver_model.vga());
 | 
			
		||||
	field_vga.on_change = [this](int32_t v_db) {
 | 
			
		||||
		this->on_vga_changed(v_db);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	progress_timers.set_max(DETECT_DELAY);
 | 
			
		||||
	
 | 
			
		||||
	on_range_changed();
 | 
			
		||||
 | 
			
		||||
	receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
 | 
			
		||||
	receiver_model.set_sampling_rate(SEARCH_SLICE_WIDTH);
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(2500000);
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,252 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "receiver_model.hpp"
 | 
			
		||||
 | 
			
		||||
#include "spectrum_color_lut.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "recent_entries.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
#define SEARCH_SLICE_WIDTH	2500000					// Search slice bandwidth
 | 
			
		||||
#define SEARCH_BIN_NB			256					// FFT power bins
 | 
			
		||||
#define SEARCH_BIN_NB_NO_DC	(SEARCH_BIN_NB - 16)	// Bins after trimming
 | 
			
		||||
#define SEARCH_BIN_WIDTH		(SEARCH_SLICE_WIDTH / SEARCH_BIN_NB)
 | 
			
		||||
 | 
			
		||||
#define DETECT_DELAY		5	// In 100ms units
 | 
			
		||||
#define RELEASE_DELAY		6
 | 
			
		||||
 | 
			
		||||
struct SearchRecentEntry {
 | 
			
		||||
	using Key = rf::Frequency;
 | 
			
		||||
	
 | 
			
		||||
	static constexpr Key invalid_key = 0xffffffff;
 | 
			
		||||
	
 | 
			
		||||
	rf::Frequency frequency;
 | 
			
		||||
	uint32_t duration { 0 };	// In 100ms units
 | 
			
		||||
	std::string time { "" };
 | 
			
		||||
 | 
			
		||||
	SearchRecentEntry(
 | 
			
		||||
	) : SearchRecentEntry { 0 }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	SearchRecentEntry(
 | 
			
		||||
		const rf::Frequency frequency
 | 
			
		||||
	) : frequency { frequency }
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Key key() const {
 | 
			
		||||
		return frequency;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_time(std::string& new_time) {
 | 
			
		||||
		time = new_time;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void set_duration(uint32_t new_duration) {
 | 
			
		||||
		duration = new_duration;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using SearchRecentEntries = RecentEntries<SearchRecentEntry>;
 | 
			
		||||
 | 
			
		||||
class SearchView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SearchView(NavigationView& nav);
 | 
			
		||||
	~SearchView();
 | 
			
		||||
	
 | 
			
		||||
	SearchView(const SearchView&) = delete;
 | 
			
		||||
	SearchView(SearchView&&) = delete;
 | 
			
		||||
	SearchView& operator=(const SearchView&) = delete;
 | 
			
		||||
	SearchView& operator=(SearchView&&) = delete;
 | 
			
		||||
	
 | 
			
		||||
	void on_show() override;
 | 
			
		||||
	void on_hide() override;
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Search"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationView& nav_;
 | 
			
		||||
 | 
			
		||||
	const Style style_grey {		// For informations and lost signal
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::grey(),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	const Style style_locked {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::green(),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	struct slice_t {
 | 
			
		||||
		rf::Frequency center_frequency;
 | 
			
		||||
		uint8_t max_power;
 | 
			
		||||
		int16_t max_index;
 | 
			
		||||
		uint8_t power;
 | 
			
		||||
		int16_t index;
 | 
			
		||||
	} slices[32];
 | 
			
		||||
	
 | 
			
		||||
	uint32_t bin_skip_acc { 0 }, bin_skip_frac { };
 | 
			
		||||
	uint32_t pixel_index { 0 };
 | 
			
		||||
	std::array<Color, 240> spectrum_row = { 0 };
 | 
			
		||||
	ChannelSpectrumFIFO* fifo { nullptr };
 | 
			
		||||
	rf::Frequency f_min { 0 }, f_max { 0 };
 | 
			
		||||
	uint8_t detect_timer { 0 }, release_timer { 0 }, timing_div { 0 };
 | 
			
		||||
	uint8_t overall_power_max { 0 };
 | 
			
		||||
	uint32_t mean_power { 0 }, mean_acc { 0 };
 | 
			
		||||
	uint32_t duration { 0 };
 | 
			
		||||
	uint32_t power_threshold { 80 };	// Todo: Put this in persistent / settings
 | 
			
		||||
	rf::Frequency slice_start { 0 };
 | 
			
		||||
	uint8_t slices_nb { 0 };
 | 
			
		||||
	uint8_t slice_counter { 0 };
 | 
			
		||||
	int16_t last_bin { 0 };
 | 
			
		||||
	uint32_t last_slice { 0 };
 | 
			
		||||
	Coord last_tick_pos { 0 };
 | 
			
		||||
	rf::Frequency search_span { 0 }, resolved_frequency { 0 };
 | 
			
		||||
	uint16_t locked_bin { 0 };
 | 
			
		||||
	uint8_t search_counter { 0 };
 | 
			
		||||
	bool locked { false };
 | 
			
		||||
	
 | 
			
		||||
	void on_channel_spectrum(const ChannelSpectrum& spectrum);
 | 
			
		||||
	void on_range_changed();
 | 
			
		||||
	void do_detection();
 | 
			
		||||
	void on_lna_changed(int32_t v_db);
 | 
			
		||||
	void on_vga_changed(int32_t v_db);
 | 
			
		||||
	void do_timers();
 | 
			
		||||
	void add_spectrum_pixel(Color color);
 | 
			
		||||
	
 | 
			
		||||
	const RecentEntriesColumns columns { {
 | 
			
		||||
		{ "Frequency", 9 },
 | 
			
		||||
		{ "Time", 8 },
 | 
			
		||||
		{ "Duration", 11 }
 | 
			
		||||
	} };
 | 
			
		||||
	SearchRecentEntries recent { };
 | 
			
		||||
	RecentEntriesView<RecentEntries<SearchRecentEntry>> recent_entries_view { columns, recent };
 | 
			
		||||
	
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 1 * 8, 0 }, "Min:      Max:       LNA VGA", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 4 * 8 }, "Trig:   /255    Mean:   /255", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 6 * 8 }, "Slices:  /32      Rate:   Hz", Color::light_grey() },
 | 
			
		||||
		{ { 6 * 8, 10 * 8 }, "Timer  Status", Color::light_grey() },
 | 
			
		||||
		{ { 1 * 8, 25 * 8 }, "Accuracy +/-4.9kHz", Color::light_grey() },
 | 
			
		||||
		{ { 26 * 8, 25 * 8 }, "MHz", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	 
 | 
			
		||||
	FrequencyField field_frequency_min {
 | 
			
		||||
		{ 1 * 8, 1 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	FrequencyField field_frequency_max {
 | 
			
		||||
		{ 11 * 8, 1 * 16 },
 | 
			
		||||
	};
 | 
			
		||||
	LNAGainField field_lna {
 | 
			
		||||
		{ 22 * 8, 1 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	VGAGainField field_vga {
 | 
			
		||||
		{ 26 * 8, 1 * 16 }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_threshold {
 | 
			
		||||
		{ 6 * 8, 2 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ 5, 255 },
 | 
			
		||||
		5,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
	Text text_mean {
 | 
			
		||||
		{ 22 * 8, 2 * 16, 3 * 8, 16 },
 | 
			
		||||
		"---"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_slices {
 | 
			
		||||
		{ 8 * 8, 3 * 16, 2 * 8, 16 },
 | 
			
		||||
		"--"
 | 
			
		||||
	};
 | 
			
		||||
	Text text_rate {
 | 
			
		||||
		{ 24 * 8, 3 * 16, 3 * 8, 16 },
 | 
			
		||||
		"---"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	VuMeter vu_max {
 | 
			
		||||
		{ 1 * 8, 11 * 8 - 4, 3 * 8, 48 },
 | 
			
		||||
		18,
 | 
			
		||||
		false
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	ProgressBar progress_timers {
 | 
			
		||||
		{ 6 * 8, 12 * 8, 6 * 8, 16 }
 | 
			
		||||
	};
 | 
			
		||||
	Text text_infos {
 | 
			
		||||
		{ 13 * 8, 12 * 8, 15 * 8, 16 },
 | 
			
		||||
		"Listening"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_snap {
 | 
			
		||||
		{ 6 * 8, 15 * 8 },
 | 
			
		||||
		7,
 | 
			
		||||
		"Snap to:",
 | 
			
		||||
		true
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_snap {
 | 
			
		||||
		{ 17 * 8, 15 * 8 }, // Position
 | 
			
		||||
		7,                  // Length
 | 
			
		||||
		{                   // Options
 | 
			
		||||
			{ "25kHz  ", 25000 },
 | 
			
		||||
			{ "12.5kHz", 12500 },
 | 
			
		||||
			{ "8.33kHz", 8333 },
 | 
			
		||||
			{ "2.5kHz", 2500 },
 | 
			
		||||
			{ "500Hz", 500 }
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	BigFrequency big_display {
 | 
			
		||||
		{ 4, 9 * 16, 28 * 8, 52 },
 | 
			
		||||
		0
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	MessageHandlerRegistration message_handler_spectrum_config {
 | 
			
		||||
		Message::ID::ChannelSpectrumConfig,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
 | 
			
		||||
			this->fifo = message.fifo;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	MessageHandlerRegistration message_handler_frame_sync {
 | 
			
		||||
		Message::ID::DisplayFrameSync,
 | 
			
		||||
		[this](const Message* const) {
 | 
			
		||||
			if( this->fifo ) {
 | 
			
		||||
				ChannelSpectrum channel_spectrum;
 | 
			
		||||
				while( fifo->out(channel_spectrum) ) {
 | 
			
		||||
					this->on_channel_spectrum(channel_spectrum);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			this->do_timers();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,390 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_settings.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ui_touch_calibration.hpp"
 | 
			
		||||
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
#include "lpc43xx_cpp.hpp"
 | 
			
		||||
using namespace lpc43xx;
 | 
			
		||||
 | 
			
		||||
#include "audio.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
using portapack::receiver_model;
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
#include "ui_font_fixed_8x16.hpp"
 | 
			
		||||
#include "cpld_update.hpp"
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
SetDateTimeView::SetDateTimeView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) {
 | 
			
		||||
	button_save.on_select = [&nav, this](Button&){
 | 
			
		||||
		const auto model = this->form_collect();
 | 
			
		||||
		const rtc::RTC new_datetime {
 | 
			
		||||
			model.year, model.month, model.day,
 | 
			
		||||
			model.hour, model.minute, model.second
 | 
			
		||||
		};
 | 
			
		||||
		rtcSetTime(&RTCD1, &new_datetime);
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	button_cancel.on_select = [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_year,
 | 
			
		||||
		&field_month,
 | 
			
		||||
		&field_day,
 | 
			
		||||
		&field_hour,
 | 
			
		||||
		&field_minute,
 | 
			
		||||
		&field_second,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	rtc::RTC datetime;
 | 
			
		||||
	rtcGetTime(&RTCD1, &datetime);
 | 
			
		||||
	SetDateTimeModel model {
 | 
			
		||||
		datetime.year(),
 | 
			
		||||
		datetime.month(),
 | 
			
		||||
		datetime.day(),
 | 
			
		||||
		datetime.hour(),
 | 
			
		||||
		datetime.minute(),
 | 
			
		||||
		datetime.second()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	form_init(model);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetDateTimeView::focus() {
 | 
			
		||||
	button_cancel.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetDateTimeView::form_init(const SetDateTimeModel& model) {
 | 
			
		||||
	field_year.set_value(model.year);
 | 
			
		||||
	field_month.set_value(model.month);
 | 
			
		||||
	field_day.set_value(model.day);
 | 
			
		||||
	field_hour.set_value(model.hour);
 | 
			
		||||
	field_minute.set_value(model.minute);
 | 
			
		||||
	field_second.set_value(model.second);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SetDateTimeModel SetDateTimeView::form_collect() {
 | 
			
		||||
	return {
 | 
			
		||||
		.year = static_cast<uint16_t>(field_year.value()),
 | 
			
		||||
		.month = static_cast<uint8_t>(field_month.value()),
 | 
			
		||||
		.day = static_cast<uint8_t>(field_day.value()),
 | 
			
		||||
		.hour = static_cast<uint8_t>(field_hour.value()),
 | 
			
		||||
		.minute = static_cast<uint8_t>(field_minute.value()),
 | 
			
		||||
		.second = static_cast<uint8_t>(field_second.value())
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SetRadioView::SetRadioView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
) {
 | 
			
		||||
	button_cancel.on_select = [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const auto reference = portapack::clock_manager.get_reference();
 | 
			
		||||
	
 | 
			
		||||
	std::string source_name("---");
 | 
			
		||||
	switch(reference.source) {
 | 
			
		||||
	case ClockManager::ReferenceSource::Xtal:      source_name = "HackRF";    break;
 | 
			
		||||
	case ClockManager::ReferenceSource::PortaPack: source_name = "PortaPack"; break;
 | 
			
		||||
	case ClockManager::ReferenceSource::External:  source_name = "External";  break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value_source.set(source_name);
 | 
			
		||||
	value_source_frequency.set(to_string_dec_uint(reference.frequency / 1000000, 2) + "." + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
 | 
			
		||||
 | 
			
		||||
	label_source.set_style(&style_text);
 | 
			
		||||
	value_source.set_style(&style_text);
 | 
			
		||||
	value_source_frequency.set_style(&style_text);
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&label_source,
 | 
			
		||||
		&value_source,
 | 
			
		||||
		&value_source_frequency,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if( reference.source == ClockManager::ReferenceSource::Xtal ) {
 | 
			
		||||
		add_children({
 | 
			
		||||
			&labels_correction,
 | 
			
		||||
			&field_ppm,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	add_children({
 | 
			
		||||
		&check_clkout,
 | 
			
		||||
		&field_clkout_freq,
 | 
			
		||||
		&labels_clkout_khz,
 | 
			
		||||
		&value_freq_step,
 | 
			
		||||
		&labels_bias,
 | 
			
		||||
		&check_bias,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	SetFrequencyCorrectionModel model {
 | 
			
		||||
		static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000) , 0
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	form_init(model);
 | 
			
		||||
 | 
			
		||||
	check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
 | 
			
		||||
	check_clkout.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		clock_manager.enable_clock_output(v);
 | 
			
		||||
		portapack::persistent_memory::set_clkout_enabled(v);
 | 
			
		||||
		StatusRefreshMessage message { };
 | 
			
		||||
		EventDispatcher::send_message(message);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
 | 
			
		||||
	value_freq_step.set_style(&style_text);
 | 
			
		||||
 | 
			
		||||
	field_clkout_freq.on_select = [this](NumberField&) {
 | 
			
		||||
		freq_step_khz++;
 | 
			
		||||
		if(freq_step_khz > 3) {
 | 
			
		||||
			freq_step_khz = 0;
 | 
			
		||||
		}
 | 
			
		||||
		switch(freq_step_khz) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				value_freq_step.set("   |");
 | 
			
		||||
				break;
 | 
			
		||||
			case 1:
 | 
			
		||||
				value_freq_step.set("  | ");
 | 
			
		||||
				break;
 | 
			
		||||
			case 2:
 | 
			
		||||
				value_freq_step.set(" |  ");
 | 
			
		||||
				break;
 | 
			
		||||
			case 3:
 | 
			
		||||
				value_freq_step.set("|   ");
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		field_clkout_freq.set_step(pow(10, freq_step_khz));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	check_bias.set_value(portapack::get_antenna_bias());
 | 
			
		||||
	check_bias.on_select = [this](Checkbox&, bool v) {
 | 
			
		||||
		portapack::set_antenna_bias(v);
 | 
			
		||||
		StatusRefreshMessage message { };
 | 
			
		||||
		EventDispatcher::send_message(message);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_save.on_select = [this, &nav](Button&){
 | 
			
		||||
		const auto model = this->form_collect();
 | 
			
		||||
		portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
 | 
			
		||||
		portapack::persistent_memory::set_clkout_freq(model.freq);
 | 
			
		||||
		clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetRadioView::focus() {
 | 
			
		||||
	button_save.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
 | 
			
		||||
	field_ppm.set_value(model.ppm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SetFrequencyCorrectionModel SetRadioView::form_collect() {
 | 
			
		||||
	return {
 | 
			
		||||
		.ppm = static_cast<int8_t>(field_ppm.value()),
 | 
			
		||||
		.freq = static_cast<uint32_t>(field_clkout_freq.value()),
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SetUIView::SetUIView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&checkbox_disable_touchscreen,
 | 
			
		||||
		&checkbox_speaker,
 | 
			
		||||
		&checkbox_bloff,
 | 
			
		||||
		&options_bloff,
 | 
			
		||||
		&checkbox_showsplash,
 | 
			
		||||
		&checkbox_showclock,
 | 
			
		||||
		&options_clockformat,
 | 
			
		||||
		&checkbox_guireturnflag,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
 | 
			
		||||
	checkbox_speaker.set_value(persistent_memory::config_speaker());
 | 
			
		||||
	checkbox_showsplash.set_value(persistent_memory::config_splash());
 | 
			
		||||
	checkbox_showclock.set_value(!persistent_memory::hide_clock());
 | 
			
		||||
	checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
 | 
			
		||||
	
 | 
			
		||||
	const auto backlight_config = persistent_memory::config_backlight_timer();
 | 
			
		||||
	checkbox_bloff.set_value(backlight_config.timeout_enabled());
 | 
			
		||||
	options_bloff.set_by_value(backlight_config.timeout_enum());
 | 
			
		||||
 | 
			
		||||
	if (persistent_memory::clock_with_date()) {
 | 
			
		||||
		options_clockformat.set_selected_index(1);
 | 
			
		||||
	} else {
 | 
			
		||||
		options_clockformat.set_selected_index(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	button_save.on_select = [&nav, this](Button&) {
 | 
			
		||||
		persistent_memory::set_config_backlight_timer({
 | 
			
		||||
			(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
 | 
			
		||||
			checkbox_bloff.value()
 | 
			
		||||
		});
 | 
			
		||||
				
 | 
			
		||||
		if (checkbox_showclock.value()){
 | 
			
		||||
		    if (options_clockformat.selected_index() == 1)
 | 
			
		||||
			    persistent_memory::set_clock_with_date(true);    
 | 
			
		||||
     		else
 | 
			
		||||
			    persistent_memory::set_clock_with_date(false);		
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (checkbox_speaker.value())  audio::output::speaker_mute();		//Just mute audio if speaker is disabled
 | 
			
		||||
		persistent_memory::set_config_speaker(checkbox_speaker.value());	//Store Speaker status
 | 
			
		||||
        	StatusRefreshMessage message { };					//Refresh status bar with/out speaker
 | 
			
		||||
        	EventDispatcher::send_message(message);
 | 
			
		||||
	
 | 
			
		||||
		persistent_memory::set_config_splash(checkbox_showsplash.value());
 | 
			
		||||
		persistent_memory::set_clock_hidden(!checkbox_showclock.value());
 | 
			
		||||
		persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
 | 
			
		||||
		persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
	button_cancel.on_select = [&nav, this](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetUIView::focus() {
 | 
			
		||||
	button_save.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ---------------------------------------------------------
 | 
			
		||||
// Appl. Settings
 | 
			
		||||
// ---------------------------------------------------------
 | 
			
		||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&checkbox_load_app_settings,
 | 
			
		||||
		&checkbox_save_app_settings,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
 | 
			
		||||
	checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	button_save.on_select = [&nav, this](Button&) {
 | 
			
		||||
		persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
 | 
			
		||||
		persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
	button_cancel.on_select = [&nav, this](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetAppSettingsView::focus() {
 | 
			
		||||
	button_save.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SetAudioView::SetAudioView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&labels,
 | 
			
		||||
		&field_tone_mix,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	field_tone_mix.set_value(persistent_memory::tone_mix());
 | 
			
		||||
	
 | 
			
		||||
	button_save.on_select = [&nav, this](Button&) {
 | 
			
		||||
		persistent_memory::set_tone_mix(field_tone_mix.value());
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_cancel.on_select = [&nav, this](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetAudioView::focus() {
 | 
			
		||||
	button_save.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
 | 
			
		||||
	add_children({
 | 
			
		||||
		&checkbox_bigger_qr,
 | 
			
		||||
		&button_save,
 | 
			
		||||
		&button_cancel
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
 | 
			
		||||
	
 | 
			
		||||
	button_save.on_select = [&nav, this](Button&) {
 | 
			
		||||
		persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	button_cancel.on_select = [&nav, this](Button&) {
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SetQRCodeView::focus() {
 | 
			
		||||
	button_save.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---------------------------------------------------------
 | 
			
		||||
// Settings main menu
 | 
			
		||||
// ---------------------------------------------------------
 | 
			
		||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
 | 
			
		||||
    if( portapack::persistent_memory::show_gui_return_icon() )
 | 
			
		||||
    {
 | 
			
		||||
        add_items( { { "..", 		ui::Color::light_grey(),&bitmap_icon_previous,	[&nav](){ nav.pop(); } } } );
 | 
			
		||||
    }
 | 
			
		||||
	add_items({
 | 
			
		||||
		{ "Audio", 		ui::Color::dark_cyan(), &bitmap_icon_speaker,			[&nav](){ nav.push<SetAudioView>(); } },
 | 
			
		||||
		{ "Radio",		ui::Color::dark_cyan(), &bitmap_icon_options_radio,		[&nav](){ nav.push<SetRadioView>(); } },
 | 
			
		||||
		{ "User Interface", 	ui::Color::dark_cyan(), &bitmap_icon_options_ui,		[&nav](){ nav.push<SetUIView>(); } },
 | 
			
		||||
		{ "Date/Time",		ui::Color::dark_cyan(), &bitmap_icon_options_datetime,		[&nav](){ nav.push<SetDateTimeView>(); } },
 | 
			
		||||
		{ "Calibration",	ui::Color::dark_cyan(), &bitmap_icon_options_touch,		[&nav](){ nav.push<TouchCalibrationView>(); } },
 | 
			
		||||
		{ "App Settings",	ui::Color::dark_cyan(), &bitmap_icon_setup,			[&nav](){ nav.push<SetAppSettingsView>(); } },
 | 
			
		||||
		{ "QR Code",		ui::Color::dark_cyan(), &bitmap_icon_qr_code,			[&nav](){ nav.push<SetQRCodeView>(); } }
 | 
			
		||||
	});
 | 
			
		||||
	set_max_rows(2); // allow wider buttons
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
@@ -0,0 +1,397 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __UI_SETTINGS_H__
 | 
			
		||||
#define __UI_SETTINGS_H__
 | 
			
		||||
 | 
			
		||||
#include "ui_widget.hpp"
 | 
			
		||||
#include "ui_menu.hpp"
 | 
			
		||||
#include "ui_navigation.hpp"
 | 
			
		||||
#include "ff.h"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
struct SetDateTimeModel {
 | 
			
		||||
	uint16_t year;
 | 
			
		||||
	uint8_t month;
 | 
			
		||||
	uint8_t day;
 | 
			
		||||
	uint8_t hour;
 | 
			
		||||
	uint8_t minute;
 | 
			
		||||
	uint8_t second;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SetDateTimeView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetDateTimeView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Date/Time"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 6 * 8, 7 * 16 }, "YYYY-MM-DD HH:MM:SS", Color::grey() },
 | 
			
		||||
		{ { 10 * 8, 9 * 16 }, "-  -     :  :", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_year {
 | 
			
		||||
		{ 6 * 8, 9 * 16 },
 | 
			
		||||
		4,
 | 
			
		||||
		{ 2015, 2099 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_month {
 | 
			
		||||
		{ 11 * 8, 9 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 12 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_day {
 | 
			
		||||
		{ 14 * 8, 9 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 1, 31 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_hour {
 | 
			
		||||
		{ 17 * 8, 9 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 23 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_minute {
 | 
			
		||||
		{ 20 * 8, 9 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 59 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
	NumberField field_second {
 | 
			
		||||
		{ 23 * 8, 9 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 0, 59 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void form_init(const SetDateTimeModel& model);
 | 
			
		||||
	SetDateTimeModel form_collect();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SetFrequencyCorrectionModel {
 | 
			
		||||
	int8_t ppm;
 | 
			
		||||
	uint32_t freq;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SetRadioView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetRadioView(NavigationView& nav);
 | 
			
		||||
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Radio settings"; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const Style style_text {
 | 
			
		||||
		.font = font::fixed_8x16,
 | 
			
		||||
		.background = Color::black(),
 | 
			
		||||
		.foreground = Color::light_grey(),
 | 
			
		||||
	};
 | 
			
		||||
	uint8_t freq_step_khz = 3;
 | 
			
		||||
 | 
			
		||||
	Text label_source {
 | 
			
		||||
		{ 0, 1 * 16, 17 * 8, 16 },
 | 
			
		||||
		"Reference Source:"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text value_source {
 | 
			
		||||
		{ (240 - 11 * 8), 1 * 16, 11 * 8, 16 },
 | 
			
		||||
		"---"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text value_source_frequency {
 | 
			
		||||
		{ (240 - 11 * 8), 2 * 16, 11 * 8, 16 },
 | 
			
		||||
		"---"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Labels labels_correction {
 | 
			
		||||
		{ { 2 * 8, 3 * 16 }, "Frequency correction:", Color::light_grey() },
 | 
			
		||||
		{ { 6 * 8, 4 * 16 }, "PPM", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Checkbox check_clkout {
 | 
			
		||||
		{ 18, (6 * 16 - 4) },
 | 
			
		||||
		13,
 | 
			
		||||
		"Enable CLKOUT"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_clkout_freq {
 | 
			
		||||
		{ 20 * 8, 6 * 16 },
 | 
			
		||||
		5,
 | 
			
		||||
		{ 10, 60000 },
 | 
			
		||||
		1000,
 | 
			
		||||
		' '
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Labels labels_clkout_khz {
 | 
			
		||||
		{ { 26 * 8, 6 * 16 }, "kHz", Color::light_grey() }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Text value_freq_step {
 | 
			
		||||
		{ 21 * 8, (7 * 16 ), 4 * 8, 16 },
 | 
			
		||||
		"|   "
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Labels labels_bias {
 | 
			
		||||
		{ { 24, 8 * 16 }, "CAUTION: Ensure that all", Color::red() },
 | 
			
		||||
		{ { 28, 9 * 16 }, "devices attached to the", Color::red() },
 | 
			
		||||
		{ { 8, 10 * 16 }, "antenna connector can accept", Color::red() },
 | 
			
		||||
		{ { 68, 11 * 16 }, "a DC voltage!", Color::red() }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NumberField field_ppm {
 | 
			
		||||
		{ 2 * 8, 4 * 16 },
 | 
			
		||||
		3,
 | 
			
		||||
		{ -50, 50 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0',
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox check_bias {
 | 
			
		||||
		{ 28, 13 * 16 },
 | 
			
		||||
		5,
 | 
			
		||||
		"Turn on bias voltage"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void form_init(const SetFrequencyCorrectionModel& model);
 | 
			
		||||
	SetFrequencyCorrectionModel form_collect();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using portapack::persistent_memory::backlight_timeout_t;
 | 
			
		||||
 | 
			
		||||
class SetUIView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetUIView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "UI settings"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	Checkbox checkbox_disable_touchscreen {
 | 
			
		||||
		{ 3 * 8, 2 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Disable touchscreen"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_speaker {
 | 
			
		||||
		{ 3 * 8, 4 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Hide H1 Speaker option"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_bloff {
 | 
			
		||||
		{ 3 * 8, 6 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Backlight off after:"
 | 
			
		||||
	};
 | 
			
		||||
	OptionsField options_bloff {
 | 
			
		||||
		{ 52, 7 * 16 + 8 },
 | 
			
		||||
		20,
 | 
			
		||||
		{
 | 
			
		||||
			{ "5 seconds",  backlight_timeout_t::Timeout5Sec    },
 | 
			
		||||
			{ "15 seconds", backlight_timeout_t::Timeout15Sec   },
 | 
			
		||||
			{ "30 seconds", backlight_timeout_t::Timeout30Sec   },
 | 
			
		||||
			{ "1 minute",   backlight_timeout_t::Timeout60Sec   },
 | 
			
		||||
			{ "3 minutes",  backlight_timeout_t::Timeout180Sec  },
 | 
			
		||||
			{ "5 minutes",  backlight_timeout_t::Timeout300Sec  },
 | 
			
		||||
			{ "10 minutes", backlight_timeout_t::Timeout600Sec  },
 | 
			
		||||
			{ "1 hour",     backlight_timeout_t::Timeout3600Sec },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_showsplash {
 | 
			
		||||
		{ 3 * 8, 9 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Show splash"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_showclock {	
 | 
			
		||||
		{ 3 * 8, 11 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Show clock with:"
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	OptionsField options_clockformat {
 | 
			
		||||
		{ 52, 12 * 16 + 8 },
 | 
			
		||||
		20,
 | 
			
		||||
		{
 | 
			
		||||
			{ "time only", 0 },
 | 
			
		||||
			{ "time and date", 1 }
 | 
			
		||||
		}
 | 
			
		||||
	};	
 | 
			
		||||
 | 
			
		||||
    Checkbox checkbox_guireturnflag {	
 | 
			
		||||
		{ 3 * 8, 14 * 16 },
 | 
			
		||||
		25,
 | 
			
		||||
		"add return icon in GUI"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel",
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SetAppSettingsView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetAppSettingsView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "App Settings"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	Checkbox checkbox_load_app_settings {
 | 
			
		||||
		{ 3 * 8, 2 * 16 },
 | 
			
		||||
		25,
 | 
			
		||||
		"Load app settings"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Checkbox checkbox_save_app_settings {
 | 
			
		||||
		{ 3 * 8, 4 * 16 },
 | 
			
		||||
		25,
 | 
			
		||||
		"Save app settings"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel",
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SetAudioView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetAudioView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Audio settings"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Labels labels {
 | 
			
		||||
		{ { 2 * 8, 3 * 16 }, "Tone key mix:   %", Color::light_grey() },
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	NumberField field_tone_mix {
 | 
			
		||||
		{ 16 * 8, 3 * 16 },
 | 
			
		||||
		2,
 | 
			
		||||
		{ 10, 99 },
 | 
			
		||||
		1,
 | 
			
		||||
		'0'
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel",
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SetQRCodeView : public View {
 | 
			
		||||
public:
 | 
			
		||||
	SetQRCodeView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	void focus() override;
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "QR Code"; };
 | 
			
		||||
	
 | 
			
		||||
private:
 | 
			
		||||
	Checkbox checkbox_bigger_qr {
 | 
			
		||||
		{ 3 * 8, 9 * 16 },
 | 
			
		||||
		20,
 | 
			
		||||
		"Show large QR code"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_save {
 | 
			
		||||
		{ 2 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Save"
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Button button_cancel {
 | 
			
		||||
		{ 16 * 8, 16 * 16, 12 * 8, 32 },
 | 
			
		||||
		"Cancel",
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SettingsMenuView : public BtnGridView {
 | 
			
		||||
public:
 | 
			
		||||
	SettingsMenuView(NavigationView& nav);
 | 
			
		||||
	
 | 
			
		||||
	std::string title() const override { return "Settings"; };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
 | 
			
		||||
#endif/*__UI_SETTINGS_H__*/
 | 
			
		||||
@@ -0,0 +1,150 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 | 
			
		||||
 * Copyright (C) 2016 Furrtek
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of PortaPack.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "ui_sigfrx.hpp"
 | 
			
		||||
#include "ui_receiver.hpp"
 | 
			
		||||
 | 
			
		||||
#include "ch.h"
 | 
			
		||||
#include "evtimer.h"
 | 
			
		||||
 | 
			
		||||
#include "event_m0.hpp"
 | 
			
		||||
#include "ff.h"
 | 
			
		||||
#include "hackrf_gpio.hpp"
 | 
			
		||||
#include "portapack.hpp"
 | 
			
		||||
#include "radio.hpp"
 | 
			
		||||
 | 
			
		||||
#include "string_format.hpp"
 | 
			
		||||
 | 
			
		||||
#include "hackrf_hal.hpp"
 | 
			
		||||
#include "portapack_shared_memory.hpp"
 | 
			
		||||
#include "portapack_persistent_memory.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
using namespace portapack;
 | 
			
		||||
 | 
			
		||||
namespace ui {
 | 
			
		||||
 | 
			
		||||
void SIGFRXView::focus() {
 | 
			
		||||
	button_exit.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SIGFRXView::~SIGFRXView() {
 | 
			
		||||
	receiver_model.disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SIGFRXView::paint(Painter& painter) {
 | 
			
		||||
	uint8_t i, xp;
 | 
			
		||||
	
 | 
			
		||||
	//portapack::display.drawBMP({0, 302-160}, fox_bmp);
 | 
			
		||||
	portapack::display.fill_rectangle({0,16,240,160-16}, ui::Color::white());
 | 
			
		||||
	for (i = 0; i < 6; i++) {
 | 
			
		||||
		xp = sigfrx_marks[i*3];
 | 
			
		||||
		painter.draw_string({ (ui::Coord)sigfrx_marks[(i*3)+1], 144-20 }, style_white, to_string_dec_uint(sigfrx_marks[(i*3)+2]) );
 | 
			
		||||
		portapack::display.draw_line({xp, 144-4}, {xp, 144}, ui::Color::black());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
 | 
			
		||||
	portapack::display.fill_rectangle({0, 144, 240, 4},ui::Color::white());
 | 
			
		||||
	
 | 
			
		||||
	uint8_t xmax = 0, imax = 0;
 | 
			
		||||
	size_t i;
 | 
			
		||||
	
 | 
			
		||||
	for (i=0; i<120; i++) {
 | 
			
		||||
		if (spectrum.db[i] > xmax) {
 | 
			
		||||
			xmax = spectrum.db[i];
 | 
			
		||||
			imax = i;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for (i=136; i<256; i++) {
 | 
			
		||||
		if (spectrum.db[i-16] > xmax) {
 | 
			
		||||
			xmax = spectrum.db[i-16];
 | 
			
		||||
			imax = i-16;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if ((imax >= last_channel-2) && (imax <= last_channel+2)) {
 | 
			
		||||
		if (detect_counter >= 5) {
 | 
			
		||||
			// Latched !
 | 
			
		||||
		} else {
 | 
			
		||||
			detect_counter++;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (detect_counter >= 5) text_channel.set("...        ");
 | 
			
		||||
		detect_counter = 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	last_channel = imax;
 | 
			
		||||
	
 | 
			
		||||
	portapack::display.fill_rectangle({(ui::Coord)(imax-2), 144, 4, 4}, ui::Color::red());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SIGFRXView::on_show() {
 | 
			
		||||
	/*EventDispatcher::message_map().register_handler(Message::ID::ChannelSpectrum,
 | 
			
		||||
		[this](const Message* const p) {
 | 
			
		||||
			this->on_channel_spectrum(reinterpret_cast<const ChannelSpectrumMessage*>(p)->spectrum);
 | 
			
		||||
		}
 | 
			
		||||
	);*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SIGFRXView::on_hide() {
 | 
			
		||||
	//EventDispatcher::message_map().unregister_handler(Message::ID::ChannelSpectrum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SIGFRXView::SIGFRXView(
 | 
			
		||||
	NavigationView& nav
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
	receiver_model.set_baseband_configuration({
 | 
			
		||||
		.mode = 255,	// DEBUG
 | 
			
		||||
		.sampling_rate = 3072000,
 | 
			
		||||
		.decimation_factor = 4,
 | 
			
		||||
	});
 | 
			
		||||
	receiver_model.set_baseband_bandwidth(1750000);
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_tuning_frequency(868110000);
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.set_lna(0);
 | 
			
		||||
	receiver_model.set_vga(0);
 | 
			
		||||
	
 | 
			
		||||
	add_children({
 | 
			
		||||
		&text_type,
 | 
			
		||||
		&text_channel,
 | 
			
		||||
		&text_data,
 | 
			
		||||
		&button_exit
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	text_type.set_style(&style_white);
 | 
			
		||||
	text_channel.set_style(&style_white);
 | 
			
		||||
	text_data.set_style(&style_white);
 | 
			
		||||
 | 
			
		||||
	button_exit.on_select = [&nav](Button&){
 | 
			
		||||
		nav.pop();
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	receiver_model.enable();
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace ui */
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user