mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-01 12:29:04 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d93c914dd3 | |||
| 715a2dd448 | |||
| 58bf60695d | |||
| fbe7954f2e | |||
| d122a8fc3b | |||
| 3998dc124a | |||
| 3d2da9c0db | |||
| 3189d3af4c | |||
| 1b3a99cc5e | |||
| 935c43e88f | |||
| 2893c031ab | |||
| 94cdb16ca9 | |||
| 84d4066986 | |||
| 2153c2dd10 | |||
| a7393c3492 | |||
| 794fece8cc | |||
| 1bf95e85a0 | |||
| a85357a8af | |||
| 7bf3e02f6c | |||
| 459e8d0b24 | |||
| 93585d846a | |||
| 4eb5c4603e | |||
| f42ad5ffc6 | |||
| d0bd8e50be | |||
| 337e3f4449 |
+1
-1
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
||||
custom: # https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
||||
|
||||
@@ -56,6 +56,9 @@ jobs:
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Create Firmware TAR
|
||||
run: |
|
||||
mkdir -p firmware_tar/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin firmware_tar/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p firmware_tar/APPS && cp build/firmware/application/*.ppma firmware_tar/APPS && cd firmware_tar && tar -cvaf ../portapack-mayhem.ppfw.tar *
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
@@ -64,7 +67,7 @@ jobs:
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
@@ -88,6 +91,16 @@ jobs:
|
||||
${{ steps.changelog.outputs.content }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./portapack-mayhem.ppfw.tar
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
@@ -40,6 +40,9 @@ jobs:
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Create Firmware TAR
|
||||
run: |
|
||||
mkdir -p firmware_tar/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin firmware_tar/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p firmware_tar/APPS && cp build/firmware/application/*.ppma firmware_tar/APPS && cd firmware_tar && tar -cvaf ../portapack-mayhem.ppfw.tar *
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
@@ -48,7 +51,7 @@ jobs:
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
@@ -74,13 +77,24 @@ jobs:
|
||||
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
|
||||
## Installation
|
||||
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
|
||||
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
|
||||
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
|
||||
|
||||
### MicroSD card files
|
||||
|
||||
For certain functionality, like the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
For certain functionality, like external apps, the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./portapack-mayhem.ppfw.tar
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.8.0
|
||||
v1.9.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.9.0
|
||||
v1.9.1
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
cache: apt
|
||||
dist: xenial
|
||||
|
||||
env:
|
||||
global:
|
||||
- PROJECT_NAME=PortaPack-HAVOC
|
||||
- SHORT_COMMIT_HASH=`git rev-parse --short HEAD`
|
||||
- VERSION_STRING=nightly-$SHORT_COMMIT_HASH
|
||||
- BUILD_DATE="`date +%Y-%m-%d`"
|
||||
- BUILD_NAME="$PROJECT_NAME-$BUILD_DATE-$SHORT_COMMIT_HASH"
|
||||
- ARTEFACT_BASE=$TRAVIS_BUILD_DIR/artefacts/
|
||||
- ARTEFACT_PATH=$ARTEFACT_BASE/$BUILD_NAME
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa -y
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install gcc-arm-embedded -y
|
||||
|
||||
script:
|
||||
# TODO: Introduce top-level Makefile, this is lame.
|
||||
- sed -e "s/\#set(VERSION.*/set(VERSION \"$VERSION_STRING\")/" -i".bak" CMakeLists.txt
|
||||
- mkdir build/
|
||||
- pushd build/
|
||||
- cmake ..
|
||||
- make firmware
|
||||
- popd
|
||||
|
||||
after_success:
|
||||
- mkdir -p $ARTEFACT_PATH
|
||||
# Copy firmware to firmware-bin directory
|
||||
- cd $TRAVIS_BUILD_DIR/build
|
||||
- cp firmware/portapack-h1-havoc.bin $ARTEFACT_PATH/
|
||||
- cp hackrf/firmware/hackrf_usb/hackrf_usb.dfu $ARTEFACT_PATH/
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- cp LICENSE $ARTEFACT_PATH/
|
||||
# Build the archive
|
||||
- cd $ARTEFACT_BASE
|
||||
- tar -cJvf $ARTEFACT_BASE/$BUILD_NAME.tar.xz $BUILD_NAME
|
||||
- md5sum --binary $BUILD_NAME.tar.xz >MD5SUMS
|
||||
- sha256sum --binary $BUILD_NAME.tar.xz >SHA256SUMS
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- coreutils
|
||||
- tar
|
||||
- sed
|
||||
- cmake
|
||||
- dfu-util
|
||||
@@ -25,6 +25,8 @@ set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex
|
||||
|
||||
project(portapack-h1)
|
||||
|
||||
set(EXPECTED_GCC_VERSION "9.2.1")
|
||||
|
||||
set(VERSION "$ENV{VERSION_STRING}")
|
||||
if ("$ENV{VERSION_STRING}" STREQUAL "")
|
||||
execute_process(
|
||||
|
||||
@@ -34,6 +34,17 @@ set(LZ4 lz4)
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
|
||||
# In our current build container cmake need a little help to get the version :)
|
||||
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
|
||||
execute_process(COMMAND bash "-c" "arm-none-eabi-g++ -v 2>&1 | grep 'gcc version' | awk '{print $3}'" OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION)
|
||||
string(STRIP ${CMAKE_CXX_COMPILER_VERSION} CMAKE_CXX_COMPILER_VERSION)
|
||||
endif()
|
||||
|
||||
set(GCC_VERSION_MISMATCH 0)
|
||||
if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
|
||||
set(GCC_VERSION_MISMATCH 1)
|
||||
endif()
|
||||
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(baseband)
|
||||
add_subdirectory(test)
|
||||
@@ -52,6 +63,15 @@ add_custom_target(
|
||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
||||
)
|
||||
|
||||
if(${GCC_VERSION_MISMATCH})
|
||||
set(COMPILER_MISMATCH_MESSAGE "WARNING: Compiler version mismatch, please use the official compiler version ${EXPECTED_GCC_VERSION} when sharing builds! Current compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
message(${COMPILER_MISMATCH_MESSAGE})
|
||||
add_custom_command(
|
||||
TARGET firmware POST_BUILD
|
||||
COMMAND echo ${COMPILER_MISMATCH_MESSAGE}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
program
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
|
||||
|
||||
@@ -122,7 +122,6 @@ set(CSRC
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
${HACKRF_PATH}/firmware/common/usb_request.c
|
||||
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
||||
${CHIBIOS}/os/various/shell.c
|
||||
${CHIBIOS}/os/various/chprintf.c
|
||||
)
|
||||
|
||||
@@ -130,6 +129,7 @@ set(CSRC
|
||||
# setting.
|
||||
set(CPPSRC
|
||||
main.cpp
|
||||
shell.cpp
|
||||
${COMMON}/acars_packet.cpp
|
||||
${COMMON}/adsb.cpp
|
||||
${COMMON}/adsb_frame.cpp
|
||||
@@ -424,7 +424,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
|
||||
# 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}\"' -DVERSION_MD5=${VERSION_MD5}")
|
||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
|
||||
|
||||
# List all default ASM defines here, like -D_DEBUG=1
|
||||
set(DADEFS)
|
||||
|
||||
@@ -426,6 +426,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
&options_sort,
|
||||
&label_found,
|
||||
&text_found_count,
|
||||
&check_serial_log,
|
||||
&button_filter,
|
||||
&button_save_list,
|
||||
&button_clear_list,
|
||||
@@ -436,7 +437,15 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
nav_.push<BleRecentEntryDetailView>(entry);
|
||||
};
|
||||
|
||||
usb_serial_thread = std::make_unique<UsbSerialThread>();
|
||||
check_serial_log.on_select = [this](Checkbox&, bool v) {
|
||||
serial_logging = v;
|
||||
if (v) {
|
||||
usb_serial_thread = std::make_unique<UsbSerialThread>();
|
||||
} else {
|
||||
usb_serial_thread.reset();
|
||||
}
|
||||
};
|
||||
check_serial_log.set_value(serial_logging);
|
||||
|
||||
ensure_directory(find_packet_path);
|
||||
ensure_directory(log_packets_path);
|
||||
@@ -456,8 +465,6 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
check_log.set_value(logging);
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
str_log = "";
|
||||
logging = v;
|
||||
@@ -468,6 +475,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
"/BLELOG_" +
|
||||
to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||
};
|
||||
check_log.set_value(logging);
|
||||
|
||||
button_save_list.on_select = [this, &nav](const ui::Button&) {
|
||||
listFileBuffer = "";
|
||||
@@ -723,8 +731,10 @@ void BLERxView::on_data(BlePacketData* packet) {
|
||||
logger->log_raw_data(str_console + "\r\n");
|
||||
}
|
||||
|
||||
usb_serial_thread->serial_str = str_console + "\r\n";
|
||||
usb_serial_thread->str_ready = true;
|
||||
if (serial_logging) {
|
||||
usb_serial_thread->serial_str = str_console + "\r\n";
|
||||
usb_serial_thread->str_ready = true;
|
||||
}
|
||||
str_console = "";
|
||||
|
||||
if (!searchList.empty()) {
|
||||
|
||||
@@ -215,6 +215,7 @@ class BLERxView : public View {
|
||||
uint8_t sort_index{0};
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool serial_logging{false};
|
||||
|
||||
bool name_enable{true};
|
||||
app_settings::SettingsManager settings_{
|
||||
@@ -225,6 +226,8 @@ class BLERxView : public View {
|
||||
{"sort_index"sv, &sort_index},
|
||||
{"filter"sv, &filter},
|
||||
{"log"sv, &logging},
|
||||
// disabled to always start without USB serial activated until we can make it non blocking if not connected
|
||||
// {"serial_log"sv, &serial_logging},
|
||||
{"name"sv, &name_enable},
|
||||
}};
|
||||
|
||||
@@ -320,6 +323,12 @@ class BLERxView : public View {
|
||||
{11 * 8, 3 * 16, 20 * 8, 16},
|
||||
"0/0"};
|
||||
|
||||
Checkbox check_serial_log{
|
||||
{17 * 8, 3 * 16 - 2},
|
||||
7,
|
||||
"USB Log",
|
||||
true};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
|
||||
|
||||
@@ -54,24 +54,31 @@ std::string id(ID value) {
|
||||
}
|
||||
|
||||
std::string consumption(Consumption value) {
|
||||
return to_string_dec_uint(value, 10);
|
||||
return to_string_dec_uint(value, 8);
|
||||
}
|
||||
|
||||
std::string commodity_type(CommodityType value) {
|
||||
return to_string_dec_uint(value, 2);
|
||||
}
|
||||
|
||||
std::string tamper_flags(TamperFlags value) {
|
||||
return to_string_hex(value & 0xFFFF, 4); // Note: ignoring bits 32-47 of tamper flags in IDM type due to screen width
|
||||
}
|
||||
|
||||
std::string tamper_flags_scm(TamperFlags value) {
|
||||
return " " + to_string_hex(value & 0x0F, 1) + "/" + to_string_hex(value >> 4, 1); // Physical/Encoder flags
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace ert */
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||
|
||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors + " ID:" + to_string_dec_uint(packet.id(), 1);
|
||||
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
|
||||
@@ -81,6 +88,8 @@ void ERTRecentEntry::update(const ert::Packet& packet) {
|
||||
received_count++;
|
||||
|
||||
last_consumption = packet.consumption();
|
||||
last_tamper_flags = packet.tamper_flags();
|
||||
packet_type = packet.type();
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
@@ -91,13 +100,10 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
|
||||
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);
|
||||
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 += (entry.packet_type == ert::Packet::Type::SCM) ? ert::format::tamper_flags_scm(entry.last_tamper_flags) : ert::format::tamper_flags(entry.last_tamper_flags);
|
||||
line += (entry.received_count > 99) ? " ++" : to_string_dec_uint(entry.received_count, 3);
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
@@ -72,11 +72,12 @@ struct ERTRecentEntry {
|
||||
|
||||
ert::ID id{ert::invalid_id};
|
||||
ert::CommodityType commodity_type{ert::invalid_commodity_type};
|
||||
ert::Consumption last_consumption{};
|
||||
ert::TamperFlags last_tamper_flags{};
|
||||
ert::Packet::Type packet_type{};
|
||||
|
||||
size_t received_count{0};
|
||||
|
||||
ert::Consumption last_consumption{};
|
||||
|
||||
ERTRecentEntry(
|
||||
const Key& key)
|
||||
: id{key.id},
|
||||
@@ -137,16 +138,17 @@ class ERTAppView : public View {
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"ID", 10},
|
||||
{"Tp", 2},
|
||||
{"Consumpt", 10},
|
||||
{"Cnt", 3},
|
||||
{"Ty", 2},
|
||||
{"Consumpt", 8},
|
||||
{"Tamp", 4},
|
||||
{"Ct", 2},
|
||||
}};
|
||||
ERTRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
static constexpr auto header_height = 1 * 16;
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
|
||||
@@ -176,12 +176,15 @@ void RegistersWidget::paint(Painter& painter) {
|
||||
|
||||
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
const std::string spaces(config.legend_length(), ' ');
|
||||
|
||||
for (uint32_t i = 0; i < config.registers_per_page; i += config.registers_per_row()) {
|
||||
uint32_t r = page_number * config.registers_per_page + i;
|
||||
|
||||
for (uint32_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());
|
||||
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(r, config.legend_length());
|
||||
painter.draw_string(
|
||||
pos + offset,
|
||||
style().invert(),
|
||||
@@ -193,15 +196,16 @@ void RegistersWidget::draw_values(
|
||||
const Coord left,
|
||||
Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
const std::string spaces(config.value_length(), ' ');
|
||||
|
||||
for (uint32_t i = 0; i < config.registers_per_page; i++) {
|
||||
uint32_t r = page_number * config.registers_per_page + i;
|
||||
|
||||
for (uint32_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 = reg_read(i);
|
||||
|
||||
const auto text = to_string_hex(value, config.value_length());
|
||||
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
|
||||
painter.draw_string(
|
||||
pos + offset,
|
||||
style(),
|
||||
@@ -213,7 +217,7 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
|
||||
if (register_number < config.registers_count) {
|
||||
switch (config.chip_type) {
|
||||
case CT_PMEM:
|
||||
return portapack::persistent_memory::pmem_data_word((page_number * config.registers_count + register_number) / 4) >> (register_number % 4 * 8);
|
||||
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
|
||||
case CT_RFFC5072:
|
||||
return radio::debug::first_if::register_read(register_number);
|
||||
case CT_MAX283X:
|
||||
@@ -272,6 +276,7 @@ RegistersView::RegistersView(
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
|
||||
registers_widget.set_parent_rect({0, 48, 240, 192});
|
||||
registers_widget.set_page(0);
|
||||
|
||||
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
|
||||
static_cast<int>(title.size()) * 8, 16});
|
||||
@@ -296,6 +301,13 @@ void RegistersView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
bool RegistersView::on_encoder(const EncoderEvent delta) {
|
||||
registers_widget.set_page(std::max(0ul, std::min(registers_widget.page_count() - 1, registers_widget.page() + delta)));
|
||||
registers_widget.update();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ControlsSwitchesWidget ************************************************/
|
||||
|
||||
void ControlsSwitchesWidget::on_show() {
|
||||
@@ -429,10 +441,10 @@ DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
|
||||
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
|
||||
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
|
||||
add_items({
|
||||
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 16}); }},
|
||||
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 10}); }},
|
||||
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 96, 8}); }},
|
||||
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
|
||||
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
|
||||
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
||||
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -504,8 +516,8 @@ void DebugMemoryDumpView::focus() {
|
||||
/* DebugPmemView *********************************************************/
|
||||
|
||||
DebugPmemView::DebugPmemView(NavigationView& nav)
|
||||
: registers_widget(RegistersWidgetConfig{CT_PMEM, page_size, 8}) {
|
||||
add_children({&text_page, ®isters_widget, &text_checksum, &text_checksum2, &button_ok});
|
||||
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
|
||||
add_children({®isters_widget, &text_checksum, &text_checksum2, &button_ok});
|
||||
|
||||
registers_widget.set_parent_rect({0, 32, 240, 192});
|
||||
|
||||
@@ -532,7 +544,6 @@ void DebugPmemView::focus() {
|
||||
}
|
||||
|
||||
void DebugPmemView::update() {
|
||||
text_page.set(to_string_hex(registers_widget.page() * page_size, 2) + "+");
|
||||
registers_widget.update();
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ typedef enum {
|
||||
struct RegistersWidgetConfig {
|
||||
chip_type_t chip_type;
|
||||
uint32_t registers_count;
|
||||
uint32_t registers_per_page;
|
||||
uint32_t register_bits;
|
||||
|
||||
constexpr size_t legend_length() const {
|
||||
@@ -194,6 +195,7 @@ class RegistersWidget : public Widget {
|
||||
|
||||
void set_page(int32_t value) { page_number = value; }
|
||||
uint32_t page(void) { return page_number; }
|
||||
uint32_t page_count(void) { return (config.registers_count + config.registers_per_page - 1) / config.registers_per_page; }
|
||||
|
||||
private:
|
||||
const RegistersWidgetConfig config;
|
||||
@@ -208,8 +210,8 @@ class RegistersWidget : public Widget {
|
||||
class RegistersView : public View {
|
||||
public:
|
||||
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
|
||||
|
||||
void focus();
|
||||
void focus() override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
private:
|
||||
Text text_title{};
|
||||
@@ -364,8 +366,6 @@ class DebugPmemView : public View {
|
||||
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
|
||||
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
|
||||
|
||||
Text text_page{{16, 16, 208, 16}};
|
||||
|
||||
RegistersWidget registers_widget;
|
||||
|
||||
Text text_checksum{{16, 232, 208, 16}};
|
||||
|
||||
@@ -50,6 +50,17 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.ppfw.tar")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
||||
ui::Color::purple(),
|
||||
&bitmap_icon_temperature,
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
|
||||
@@ -65,7 +76,43 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
|
||||
});
|
||||
}
|
||||
|
||||
bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||
if (str.length() >= suffix.length()) {
|
||||
std::u16string endOfString = str.substr(str.length() - suffix.length());
|
||||
return endOfString == suffix;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
|
||||
//
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "Unpacking TAR file...");
|
||||
|
||||
auto res = UnTar::untar(path, [this](const std::string fileName) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), fileName);
|
||||
});
|
||||
if (res.string().empty()) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
if (endsWith(path, u".ppfw.tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
}
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "ff.h"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
@@ -55,8 +55,10 @@ class FlashUtilityView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -412,7 +412,8 @@ void FrequencyEditView::refresh_ui() {
|
||||
|
||||
auto is_range = entry_.type == freqman_type::Range;
|
||||
auto is_ham = entry_.type == freqman_type::HamRadio;
|
||||
auto has_freq_b = is_range || is_ham;
|
||||
auto is_repeater = entry_.type == freqman_type::Repeater;
|
||||
auto has_freq_b = is_range || is_ham || is_repeater;
|
||||
|
||||
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
|
||||
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
|
||||
|
||||
@@ -220,7 +220,8 @@ class FrequencyEditView : public View {
|
||||
{"Single", 0},
|
||||
{"Range", 1},
|
||||
{"HamRadio", 2},
|
||||
{"Raw", 3},
|
||||
{"Repeater", 3},
|
||||
{"Raw", 4},
|
||||
}};
|
||||
|
||||
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
|
||||
|
||||
@@ -22,13 +22,24 @@
|
||||
*/
|
||||
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "replay_app.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "io_convert.hpp"
|
||||
#include "oversample.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
@@ -37,6 +48,30 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ReconView::reload_restart_recon() {
|
||||
frequency_file_load();
|
||||
if (frequency_list.size() > 0) {
|
||||
if (fwd) {
|
||||
button_dir.set_text("FW>");
|
||||
} else {
|
||||
button_dir.set_text("<RW");
|
||||
}
|
||||
recon_resume();
|
||||
}
|
||||
if (scanner_mode) {
|
||||
file_name.set_style(&Styles::red);
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
} else {
|
||||
file_name.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||
file_name.set_style(&Styles::yellow);
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::check_update_ranges_from_current() {
|
||||
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
||||
if (update_ranges && !manual_mode) {
|
||||
@@ -67,7 +102,7 @@ void ReconView::set_loop_config(bool v) {
|
||||
persistent_memory::set_recon_continuous(continuous);
|
||||
}
|
||||
|
||||
void ReconView::recon_stop_recording() {
|
||||
void ReconView::recon_stop_recording(bool exiting) {
|
||||
if (is_recording) {
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
button_audio_app.set_text("RAW");
|
||||
@@ -75,13 +110,17 @@ void ReconView::recon_stop_recording() {
|
||||
button_audio_app.set_text("AUDIO");
|
||||
button_audio_app.set_style(&Styles::white);
|
||||
record_view->stop();
|
||||
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
|
||||
button_config.set_style(&Styles::white);
|
||||
is_recording = false;
|
||||
// repeater mode
|
||||
if (!exiting && persistent_memory::recon_repeat_recorded()) {
|
||||
start_repeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::clear_freqlist_for_ui_action() {
|
||||
recon_stop_recording();
|
||||
recon_stop_recording(false);
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
// flag to detect and reload frequency_list
|
||||
@@ -111,6 +150,9 @@ void ReconView::update_description() {
|
||||
case freqman_type::HamRadio:
|
||||
description = "H: ";
|
||||
break;
|
||||
case freqman_type::Repeater:
|
||||
description = "L: ";
|
||||
break;
|
||||
default:
|
||||
description = "S: ";
|
||||
}
|
||||
@@ -182,6 +224,7 @@ void ReconView::load_persisted_settings() {
|
||||
load_freqs = persistent_memory::recon_load_freqs();
|
||||
load_ranges = persistent_memory::recon_load_ranges();
|
||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||
load_repeaters = persistent_memory::recon_load_repeaters();
|
||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||
}
|
||||
@@ -265,9 +308,16 @@ void ReconView::focus() {
|
||||
}
|
||||
|
||||
ReconView::~ReconView() {
|
||||
recon_stop_recording();
|
||||
if (recon_tx) {
|
||||
replay_thread.reset();
|
||||
}
|
||||
|
||||
recon_stop_recording(true);
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
transmitter_model.disable();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -275,9 +325,20 @@ ReconView::~ReconView() {
|
||||
ReconView::ReconView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
chrono_start = chTimeNow();
|
||||
|
||||
tx_view.hidden(true);
|
||||
|
||||
// set record View
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
u"AUTO_AUDIO", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->set_auto_trim(false);
|
||||
record_view->hidden(true);
|
||||
record_view->on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
add_children({&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -314,13 +375,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
&button_restart,
|
||||
&button_mic_app,
|
||||
&button_remove,
|
||||
record_view.get()});
|
||||
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
record_view.get(),
|
||||
&progressbar,
|
||||
&tx_view});
|
||||
|
||||
def_step = 0;
|
||||
load_persisted_settings();
|
||||
@@ -492,7 +549,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_recon.on_select = [this](Button&) {
|
||||
button_remove.set_text("DELETE");
|
||||
button_remove.set_text("<DELETE>");
|
||||
button_add.hidden(false);
|
||||
scanner_mode = false;
|
||||
manual_mode = true;
|
||||
@@ -566,27 +623,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_restart.on_select = [this](Button&) {
|
||||
frequency_file_load();
|
||||
if (frequency_list.size() > 0) {
|
||||
if (fwd) {
|
||||
button_dir.set_text("FW>");
|
||||
} else {
|
||||
button_dir.set_text("<RW");
|
||||
}
|
||||
recon_resume();
|
||||
}
|
||||
if (scanner_mode) {
|
||||
file_name.set_style(&Styles::red);
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
} else {
|
||||
file_name.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||
file_name.set_style(&Styles::yellow);
|
||||
}
|
||||
reload_restart_recon();
|
||||
};
|
||||
|
||||
button_add.on_select = [this](ButtonWithEncoder&) {
|
||||
@@ -606,12 +643,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
scanner_mode = false;
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
button_remove.set_text("REMOVE");
|
||||
button_remove.set_text("<REMOVE>");
|
||||
} else {
|
||||
scanner_mode = true;
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
button_remove.set_text("DELETE");
|
||||
button_remove.set_text("<DELETE>");
|
||||
}
|
||||
frequency_file_load();
|
||||
if (autostart) {
|
||||
@@ -704,6 +741,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
change_mode(AM_MODULATION); // start on AM.
|
||||
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
|
||||
|
||||
// tx progress bar
|
||||
progressbar.hidden(true);
|
||||
|
||||
if (filedelete) {
|
||||
delete_file(freq_file_path);
|
||||
}
|
||||
@@ -741,7 +781,8 @@ void ReconView::frequency_file_load() {
|
||||
freqman_load_options options{
|
||||
.load_freqs = load_freqs,
|
||||
.load_ranges = load_ranges,
|
||||
.load_hamradios = load_hamradios};
|
||||
.load_hamradios = load_hamradios,
|
||||
.load_repeaters = load_repeaters};
|
||||
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
|
||||
file_name.set_style(&Styles::red);
|
||||
desc_cycle.set("...empty file...");
|
||||
@@ -776,6 +817,12 @@ void ReconView::frequency_file_load() {
|
||||
}
|
||||
|
||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (recon_tx)
|
||||
return;
|
||||
|
||||
if (is_repeat_active())
|
||||
return;
|
||||
|
||||
chrono_end = chTimeNow();
|
||||
systime_t time_interval = chrono_end - chrono_start;
|
||||
chrono_start = chrono_end;
|
||||
@@ -804,7 +851,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 1) {
|
||||
status = 1;
|
||||
if (wait != 0) {
|
||||
recon_stop_recording();
|
||||
recon_stop_recording(false);
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
}
|
||||
@@ -835,9 +882,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
// contents of a possible recon_start_recording(), but not yet since it's only called once
|
||||
if (auto_record_locked && !is_recording) {
|
||||
button_audio_app.set_style(&Styles::red);
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
button_audio_app.set_text("RAW REC");
|
||||
else
|
||||
} else
|
||||
button_audio_app.set_text("WAV REC");
|
||||
record_view->start();
|
||||
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
|
||||
@@ -956,6 +1003,26 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (current_entry().type == freqman_type::Repeater) {
|
||||
// repeater is like single, we only listen on frequency_a and then jump to next entry
|
||||
if ((fwd && stepper == 0) || stepper > 0) { // forward
|
||||
current_index++;
|
||||
entry_has_changed = true;
|
||||
// looping
|
||||
if ((uint32_t)current_index >= frequency_list.size()) {
|
||||
has_looped = true;
|
||||
current_index = 0;
|
||||
}
|
||||
} else if ((!fwd && stepper == 0) || stepper < 0) {
|
||||
// reverse
|
||||
current_index--;
|
||||
entry_has_changed = true;
|
||||
// if previous if under the list => go back from end
|
||||
if (current_index < 0) {
|
||||
has_looped = true;
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set index to boundary if !continuous
|
||||
if (has_looped && !continuous) {
|
||||
@@ -984,6 +1051,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (entry_has_changed) {
|
||||
timer = 0;
|
||||
switch (current_entry().type) {
|
||||
case freqman_type::Repeater:
|
||||
case freqman_type::Single:
|
||||
freq = current_entry().frequency_a;
|
||||
break;
|
||||
@@ -1017,7 +1085,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (stepper > 0) stepper--;
|
||||
} // if( recon || stepper != 0 || index_stepper != 0 )
|
||||
} // if (frequency_list.size() > 0 )
|
||||
} /* on_statistic_updates */
|
||||
} /* on_statistics_updates */
|
||||
}
|
||||
handle_retune();
|
||||
recon_redraw();
|
||||
@@ -1080,28 +1148,39 @@ void ReconView::on_stepper_delta(int32_t v) {
|
||||
}
|
||||
|
||||
size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
if (recon_tx || is_repeat_active())
|
||||
return 0;
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
recon_stop_recording();
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
recon_stop_recording(false);
|
||||
if (record_view != nullptr) {
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
}
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"RECON_REPEAT.C16", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_as_is(true);
|
||||
} else if (new_mod == SPEC_MODULATION) {
|
||||
audio::output::stop();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW_", u"CAPTURES",
|
||||
u"AUTO_RAW", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
u"AUTO_AUDIO", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
}
|
||||
record_view->set_auto_trim(false);
|
||||
add_child(record_view.get());
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [this](std::string message) {
|
||||
nav_.display_modal("Error", message);
|
||||
};
|
||||
receiver_model.disable();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
size_t recording_sampling_rate = 0;
|
||||
switch (new_mod) {
|
||||
@@ -1144,7 +1223,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||
auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
|
||||
|
||||
// The radio needs to know the effective sampling rate.
|
||||
receiver_model.set_sampling_rate(actual_sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
|
||||
@@ -1222,4 +1300,153 @@ void ReconView::handle_remove_current_item() {
|
||||
update_description();
|
||||
}
|
||||
|
||||
void ReconView::on_repeat_tx_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void ReconView::repeat_file_error(const std::filesystem::path& path, const std::string& message) {
|
||||
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
|
||||
}
|
||||
|
||||
bool ReconView::is_repeat_active() const {
|
||||
return replay_thread != nullptr;
|
||||
}
|
||||
|
||||
void ReconView::start_repeat() {
|
||||
// Prepare to send a file.
|
||||
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
|
||||
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
|
||||
|
||||
if (recon_tx == false) {
|
||||
recon_tx = true;
|
||||
|
||||
if (record_view != nullptr) {
|
||||
record_view->stop();
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
}
|
||||
|
||||
receiver_model.disable();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
size_t rawsize = 0;
|
||||
{
|
||||
File capture_file;
|
||||
auto error = capture_file.open(rawfile);
|
||||
if (error) {
|
||||
repeat_file_error(rawfile, "Can't open file to send for size");
|
||||
return;
|
||||
}
|
||||
rawsize = capture_file.size();
|
||||
}
|
||||
|
||||
// Reset the transmit progress bar.
|
||||
uint8_t sample_size = std::filesystem::capture_file_sample_size(rawfile);
|
||||
progressbar.set_value(0);
|
||||
progressbar.set_max(rawsize * sizeof(complex16_t) / sample_size);
|
||||
progressbar.hidden(false);
|
||||
|
||||
auto metadata = read_metadata_file(rawmeta);
|
||||
// If no metadata found, fallback to the TX frequency.
|
||||
if (!metadata) {
|
||||
metadata = {freq, 500'000};
|
||||
repeat_file_error(rawmeta, "Can't open file to read meta, using default rx_freq,500'000");
|
||||
}
|
||||
|
||||
// Update the sample rate in proc_replay baseband.
|
||||
baseband::set_sample_rate(metadata->sample_rate,
|
||||
get_oversample_rate(metadata->sample_rate));
|
||||
|
||||
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
|
||||
|
||||
// set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
|
||||
if (current_entry().type == freqman_type::Repeater && current_entry().frequency_b != 0) {
|
||||
transmitter_model.set_target_frequency(current_entry().frequency_b);
|
||||
} else {
|
||||
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||
}
|
||||
|
||||
// set TX powers and enable transmitter
|
||||
transmitter_model.set_tx_gain(persistent_memory::recon_repeat_gain());
|
||||
transmitter_model.set_rf_amp(persistent_memory::recon_repeat_amp());
|
||||
transmitter_model.enable();
|
||||
}
|
||||
|
||||
// clear replay thread and set reader
|
||||
replay_thread.reset();
|
||||
auto reader = std::make_unique<FileConvertReader>();
|
||||
auto error = reader->open(rawfile);
|
||||
if (error) {
|
||||
repeat_file_error(rawfile, "Can't open file to send to thread");
|
||||
return;
|
||||
}
|
||||
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
|
||||
if (persistent_memory::recon_repeat_delay() > 0) {
|
||||
uint8_t delay = persistent_memory::recon_repeat_delay();
|
||||
Painter p;
|
||||
while (delay > 0) {
|
||||
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
|
||||
|
||||
// update display information
|
||||
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Color::light_grey());
|
||||
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::red, delay_message);
|
||||
|
||||
// sleep 1 second
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
// decre delay
|
||||
if (delay > 0)
|
||||
delay = delay - 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ReplayThread starts immediately on construction; must be set before creating.
|
||||
repeat_ready_signal = true;
|
||||
repeat_cur_rep++;
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
/* read_size */ repeat_read_size,
|
||||
/* buffer_count */ repeat_buffer_count,
|
||||
&repeat_ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
void ReconView::stop_repeat(const bool do_loop) {
|
||||
repeat_ready_signal = false;
|
||||
|
||||
if (is_repeat_active()) {
|
||||
replay_thread.reset();
|
||||
transmitter_model.disable();
|
||||
}
|
||||
|
||||
// repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
|
||||
if (do_loop && repeat_cur_rep < persistent_memory::recon_repeat_nb()) {
|
||||
start_repeat();
|
||||
} else {
|
||||
repeat_cur_rep = 0;
|
||||
recon_tx = false;
|
||||
reload_restart_recon();
|
||||
progressbar.hidden(true);
|
||||
set_dirty(); // fix progressbar no hiding
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::handle_repeat_thread_done(const uint32_t return_code) {
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
stop_repeat(true);
|
||||
} else if (return_code == ReplayThread::READ_ERROR) {
|
||||
stop_repeat(false);
|
||||
repeat_file_error(u"/" + repeat_rec_path + u"/" + repeat_rec_file, "Can't open file to send.");
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -41,10 +41,20 @@
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_recon_settings.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
|
||||
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
@@ -66,10 +76,17 @@ class ReconView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState radio_state_{};
|
||||
RxRadioState rx_radio_state_{};
|
||||
TxRadioState tx_radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
500000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_recon"sv, app_settings::Mode::RX};
|
||||
"rx_tx_recon"sv, app_settings::Mode::RX_TX};
|
||||
;
|
||||
|
||||
void reload_restart_recon();
|
||||
void check_update_ranges_from_current();
|
||||
void set_loop_config(bool v);
|
||||
void clear_freqlist_for_ui_action();
|
||||
@@ -93,7 +110,7 @@ class ReconView : public View {
|
||||
void load_persisted_settings();
|
||||
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
void recon_stop_recording(bool exiting);
|
||||
|
||||
// Returns true if 'current_index' is in bounds of frequency_list.
|
||||
bool current_is_valid();
|
||||
@@ -125,6 +142,7 @@ class ReconView : public View {
|
||||
bool load_freqs{true};
|
||||
bool load_ranges{true};
|
||||
bool load_hamradios{true};
|
||||
bool load_repeaters{true};
|
||||
bool update_ranges{true};
|
||||
bool fwd{true};
|
||||
bool recon{true};
|
||||
@@ -163,6 +181,25 @@ class ReconView : public View {
|
||||
systime_t chrono_start{};
|
||||
systime_t chrono_end{};
|
||||
|
||||
const std::filesystem::path repeat_rec_file = u"RECON_REPEAT.C16";
|
||||
const std::filesystem::path repeat_rec_meta = u"RECON_REPEAT.TXT";
|
||||
const std::filesystem::path repeat_rec_path = u"CAPTURES";
|
||||
const size_t repeat_read_size{16384};
|
||||
const size_t repeat_buffer_count{3};
|
||||
int8_t repeat_cur_rep = 0;
|
||||
int64_t repeat_sample_rate = 0;
|
||||
static constexpr uint32_t repeat_bandwidth = 2500000;
|
||||
void on_repeat_tx_progress(const uint32_t progress);
|
||||
void start_repeat();
|
||||
void stop_repeat(const bool do_loop);
|
||||
bool is_repeat_active() const;
|
||||
void handle_repeat_thread_done(const uint32_t return_code);
|
||||
void repeat_file_error(const std::filesystem::path& path, const std::string& message);
|
||||
std::filesystem::path repeat_file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool repeat_ready_signal{false};
|
||||
bool recon_tx{false};
|
||||
|
||||
// Persisted settings.
|
||||
SettingsStore ui_settings{
|
||||
"recon"sv,
|
||||
@@ -354,17 +391,47 @@ class ReconView : public View {
|
||||
{168, (35 * 8) - 4, 72, 28},
|
||||
"<REMOVE>"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 2 * 16},
|
||||
/*short_ui*/ true};
|
||||
|
||||
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);
|
||||
handle_coded_squelch(message.value);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats{
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* p) {
|
||||
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
handle_repeat_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* p) {
|
||||
auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
repeat_ready_signal = true;
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* p) {
|
||||
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
on_repeat_tx_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
@@ -99,8 +99,14 @@ void ReconSetupViewMore::save() {
|
||||
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
|
||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||
persistent_memory::set_recon_load_repeaters(checkbox_load_repeaters.value());
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
||||
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
|
||||
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
|
||||
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
|
||||
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
|
||||
persistent_memory::set_recon_repeat_delay(field_repeat_delay.value());
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::focus() {
|
||||
@@ -113,16 +119,55 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
hidden(true);
|
||||
|
||||
add_children({&checkbox_load_freqs,
|
||||
&checkbox_load_repeaters,
|
||||
&checkbox_load_ranges,
|
||||
&checkbox_load_hamradios,
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&checkbox_auto_record_locked});
|
||||
&checkbox_auto_record_locked,
|
||||
&checkbox_repeat_recorded,
|
||||
&text_repeat_nb,
|
||||
&field_repeat_nb,
|
||||
&checkbox_repeat_amp,
|
||||
&text_repeat_gain,
|
||||
&field_repeat_gain,
|
||||
&text_repeat_delay,
|
||||
&field_repeat_delay});
|
||||
|
||||
// tx options have to be in yellow to inform the users that activating them will make the device transmit
|
||||
checkbox_repeat_recorded.set_style(&Styles::yellow);
|
||||
text_repeat_nb.set_style(&Styles::yellow);
|
||||
field_repeat_nb.set_style(&Styles::yellow);
|
||||
checkbox_repeat_amp.set_style(&Styles::yellow);
|
||||
text_repeat_gain.set_style(&Styles::yellow);
|
||||
field_repeat_gain.set_style(&Styles::yellow);
|
||||
text_repeat_delay.set_style(&Styles::yellow);
|
||||
field_repeat_delay.set_style(&Styles::yellow);
|
||||
|
||||
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
||||
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
|
||||
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
|
||||
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
|
||||
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
||||
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
||||
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
|
||||
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
|
||||
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
|
||||
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
|
||||
field_repeat_delay.set_value(persistent_memory::recon_repeat_delay());
|
||||
|
||||
// tx warning modal
|
||||
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
|
||||
if (v) {
|
||||
nav.display_modal(
|
||||
"TX WARNING",
|
||||
"This activate TX ability\nin Recon !",
|
||||
YESNO,
|
||||
[this, &nav](bool choice) {
|
||||
if (!choice)
|
||||
checkbox_repeat_recorded.set_value(choice);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void ReconSetupViewMore::focus() {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "ui_tabview.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
// 1Mhz helper
|
||||
#ifdef OneMHz
|
||||
@@ -116,26 +117,82 @@ class ReconSetupViewMore : public View {
|
||||
Checkbox checkbox_load_freqs{
|
||||
{1 * 8, 12},
|
||||
3,
|
||||
"input: load freqs"};
|
||||
"load freq",
|
||||
true};
|
||||
|
||||
Checkbox checkbox_load_repeaters{
|
||||
{14 * 8, 12},
|
||||
3,
|
||||
"load repeater",
|
||||
true};
|
||||
|
||||
Checkbox checkbox_load_ranges{
|
||||
{1 * 8, 42},
|
||||
3,
|
||||
"input: load ranges"};
|
||||
"load range",
|
||||
true};
|
||||
|
||||
Checkbox checkbox_load_hamradios{
|
||||
{1 * 8, 72},
|
||||
3,
|
||||
"input: load hamradios"};
|
||||
"load hamradio",
|
||||
true};
|
||||
|
||||
Checkbox checkbox_update_ranges_when_recon{
|
||||
{1 * 8, 102},
|
||||
3,
|
||||
"auto update m-ranges"};
|
||||
|
||||
Checkbox checkbox_auto_record_locked{
|
||||
{1 * 8, 132},
|
||||
3,
|
||||
"record locked periods"};
|
||||
|
||||
Checkbox checkbox_repeat_recorded{
|
||||
{1 * 8, 162},
|
||||
3,
|
||||
"repeater,"};
|
||||
|
||||
Text text_repeat_nb{
|
||||
{14 * 8, 165, 3 * 8, 22},
|
||||
"nb:"};
|
||||
|
||||
NumberField field_repeat_nb{
|
||||
{17 * 8, 165},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Checkbox checkbox_repeat_amp{
|
||||
{1 * 8, 192},
|
||||
3,
|
||||
"AMP,"};
|
||||
|
||||
Text text_repeat_gain{
|
||||
{9 * 8, 196, 5 * 8, 22},
|
||||
"GAIN:"};
|
||||
|
||||
NumberField field_repeat_gain{
|
||||
{14 * 8, 196},
|
||||
2,
|
||||
{0, 47},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Text text_repeat_delay{
|
||||
{16 * 8, 196, 8 * 8, 22},
|
||||
", delay:"};
|
||||
|
||||
NumberField field_repeat_delay{
|
||||
{24 * 8, 196},
|
||||
3,
|
||||
{0, 254},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
};
|
||||
|
||||
class ReconSetupView : public View {
|
||||
|
||||
@@ -564,6 +564,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
|
||||
def_step_index = entry.step;
|
||||
|
||||
switch (entry.type) {
|
||||
case freqman_type::Repeater:
|
||||
case freqman_type::Single:
|
||||
entries.push_back({entry.frequency_a, entry.description});
|
||||
break;
|
||||
|
||||
@@ -133,7 +133,6 @@ SetRadioView::SetRadioView(
|
||||
&check_clkout,
|
||||
&field_clkout_freq,
|
||||
&labels_clkout_khz,
|
||||
&value_freq_step,
|
||||
&labels_bias,
|
||||
&check_bias,
|
||||
&disable_external_tcxo, // TODO: always show?
|
||||
@@ -150,23 +149,10 @@ SetRadioView::SetRadioView(
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
std::string source_name = clock_manager.get_source();
|
||||
|
||||
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");
|
||||
value_source_frequency.set(clock_manager.get_freq());
|
||||
|
||||
// Make these Text controls look like Labels.
|
||||
label_source.set_style(&Styles::light_grey);
|
||||
@@ -185,29 +171,20 @@ SetRadioView::SetRadioView(
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
field_clkout_freq.set_value(pmem::clkout_freq());
|
||||
value_freq_step.set_style(&Styles::light_grey);
|
||||
// Disallow CLKOUT freq change on hackrf_r9 due to dependencies on GP_CLKIN (same Si5351A clock);
|
||||
// see comments in ClockManager::enable_clock_output()
|
||||
if (hackrf_r9) {
|
||||
if (pmem::clkout_freq() != 10000)
|
||||
pmem::set_clkout_freq(10000);
|
||||
field_clkout_freq.set_focusable(false);
|
||||
}
|
||||
|
||||
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));
|
||||
field_clkout_freq.set_value(pmem::clkout_freq());
|
||||
field_clkout_freq.on_change = [this](SymField&) {
|
||||
if (field_clkout_freq.to_integer() < 4) // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
|
||||
field_clkout_freq.set_value(4);
|
||||
if (field_clkout_freq.to_integer() > 60000)
|
||||
field_clkout_freq.set_value(60000);
|
||||
};
|
||||
|
||||
check_bias.set_value(get_antenna_bias());
|
||||
@@ -248,7 +225,7 @@ void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
|
||||
SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
return {
|
||||
.ppm = static_cast<int8_t>(field_ppm.value()),
|
||||
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
|
||||
.freq = static_cast<uint32_t>(field_clkout_freq.to_integer()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -163,20 +163,14 @@ class SetRadioView : public View {
|
||||
13,
|
||||
"Enable CLKOUT"};
|
||||
|
||||
NumberField field_clkout_freq{
|
||||
SymField field_clkout_freq{
|
||||
{20 * 8, 6 * 16},
|
||||
5,
|
||||
{10, 60000},
|
||||
1000,
|
||||
' '};
|
||||
SymField::Type::Dec};
|
||||
|
||||
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{
|
||||
{{4 * 8 + 4, 8 * 16}, "CAUTION: Ensure that all", Color::red()},
|
||||
{{5 * 8 + 0, 9 * 16}, "devices attached to the", Color::red()},
|
||||
|
||||
@@ -217,6 +217,27 @@ ClockManager::Reference ClockManager::get_reference() const {
|
||||
return reference;
|
||||
}
|
||||
|
||||
std::string ClockManager::get_source() {
|
||||
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;
|
||||
}
|
||||
return source_name;
|
||||
}
|
||||
|
||||
std::string ClockManager::get_freq() {
|
||||
return to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
|
||||
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
|
||||
}
|
||||
|
||||
static void portapack_tcxo_enable() {
|
||||
portapack::io.reference_oscillator(true);
|
||||
|
||||
@@ -561,26 +582,32 @@ void ClockManager::stop_audio_pll() {
|
||||
}
|
||||
|
||||
void ClockManager::enable_clock_output(bool enable) {
|
||||
if (enable) {
|
||||
clock_generator.enable_output(clock_generator_output_og_clkout);
|
||||
if (portapack::persistent_memory::clkout_freq() < 1000) {
|
||||
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
|
||||
} else {
|
||||
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
|
||||
}
|
||||
} else {
|
||||
clock_generator.disable_output(clock_generator_output_og_clkout);
|
||||
if (hackrf_r9) {
|
||||
gpio_r9_clkout_en.output();
|
||||
gpio_r9_clkout_en.write(enable);
|
||||
|
||||
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
|
||||
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
|
||||
// FUTURE TBD: REMOVE OTHER LP43XX CLOCK DEPENDENCIES ON GP_CLKIN, THEN DELETE THE return LINE BELOW TO ALLOW
|
||||
// CLKOUT FREQ CHANGES ON R9 BOARDS.
|
||||
return;
|
||||
}
|
||||
|
||||
auto si5351_clock_control_common = hackrf_r9
|
||||
? si5351a_clock_control_common
|
||||
: si5351c_clock_control_common;
|
||||
const auto ref_pll = hackrf_r9
|
||||
? ClockControl::MultiSynthSource::PLLA
|
||||
: get_si5351c_reference_clock_generator_pll(reference.source);
|
||||
auto clkout_select = hackrf_r9 ? clock_generator_output_r9_clkout : clock_generator_output_og_clkout;
|
||||
|
||||
if (enable)
|
||||
clock_generator.set_clock_control(clock_generator_output_og_clkout, si5351_clock_control_common[clock_generator_output_og_clkout].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
||||
else
|
||||
clock_generator.set_clock_control(clock_generator_output_og_clkout, ClockControl::power_off());
|
||||
if (enable) {
|
||||
clock_generator.enable_output(clkout_select);
|
||||
if (portapack::persistent_memory::clkout_freq() < 1000) {
|
||||
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
|
||||
} else {
|
||||
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
|
||||
}
|
||||
|
||||
auto si5351_clock_control_common = hackrf_r9 ? si5351a_clock_control_common : si5351c_clock_control_common;
|
||||
const auto ref_pll = hackrf_r9 ? ClockControl::MultiSynthSource::PLLA : get_si5351c_reference_clock_generator_pll(reference.source);
|
||||
clock_generator.set_clock_control(clkout_select, si5351_clock_control_common[clkout_select].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
|
||||
} else {
|
||||
clock_generator.disable_output(clkout_select);
|
||||
clock_generator.set_clock_control(clkout_select, ClockControl::power_off());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#ifndef __CLOCK_MANAGER_H__
|
||||
#define __CLOCK_MANAGER_H__
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
@@ -74,6 +75,8 @@ class ClockManager {
|
||||
uint32_t get_frequency_monitor_measurement_in_hertz();
|
||||
|
||||
Reference get_reference() const;
|
||||
std::string get_source();
|
||||
std::string get_freq();
|
||||
|
||||
void enable_clock_output(bool enable);
|
||||
|
||||
|
||||
@@ -238,6 +238,10 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EventDispatcher::emulateTouch(ui::TouchEvent event) {
|
||||
on_touch_event(event);
|
||||
}
|
||||
|
||||
void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
/* TODO: Capture widget receiving the Start event, send Move and
|
||||
* End events to the same widget.
|
||||
|
||||
@@ -86,6 +86,8 @@ class EventDispatcher {
|
||||
events_flag(EVT_MASK_LOCAL);
|
||||
}
|
||||
|
||||
void emulateTouch(ui::TouchEvent event);
|
||||
|
||||
private:
|
||||
static Thread* thread_event_loop;
|
||||
|
||||
|
||||
+528
-530
File diff suppressed because it is too large
Load Diff
+8
-3
@@ -27,6 +27,8 @@
|
||||
#ifndef __UI_BLESPAM_H__
|
||||
#define __UI_BLESPAM_H__
|
||||
|
||||
#define BLESPMUSECONSOLE 1
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -111,9 +113,10 @@ class BLESpamView : public View {
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
Checkbox chk_randdev{{100, 16}, 10, "Rnd device", true};
|
||||
|
||||
#ifdef BLESPMUSECONSOLE
|
||||
Console console{
|
||||
{0, 70, 240, 220}};
|
||||
|
||||
#endif
|
||||
OptionsField options_atkmode{
|
||||
{0 * 8, 2 * 8},
|
||||
10,
|
||||
@@ -134,7 +137,7 @@ class BLESpamView : public View {
|
||||
bool randomDev{true};
|
||||
|
||||
uint8_t channel_number = 37;
|
||||
char mac[13] = "010203040507";
|
||||
char mac[13] = "010203040407";
|
||||
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
@@ -161,6 +164,8 @@ class BLESpamView : public View {
|
||||
this->on_tx_progress(message.done);
|
||||
}};
|
||||
|
||||
uint8_t packet[80];
|
||||
|
||||
// continuity
|
||||
|
||||
typedef enum {
|
||||
@@ -227,7 +232,7 @@ class BLESpamView : public View {
|
||||
static const uint16_t fastpairModels_count;
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
const char* name; // could be moved too
|
||||
// const char* name; // could be moved too
|
||||
} fpUi32;
|
||||
static const fpUi32 fastpairModels[];
|
||||
|
||||
|
||||
+4
-3
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -56,8 +57,8 @@ class RangeView : public View {
|
||||
const Style& style_info = Styles::grey;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 8 * 8 + 4}, "Start", Color::light_grey()},
|
||||
{{23 * 8, 8 * 8 + 4}, "Stop", Color::light_grey()},
|
||||
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Color::light_grey()},
|
||||
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Color::light_grey()},
|
||||
{{12 * 8, 5 * 8 - 4}, "Center", Color::light_grey()},
|
||||
{{12 * 8 + 4, 13 * 8}, "Width", Color::light_grey()}};
|
||||
|
||||
@@ -226,7 +227,7 @@ class JammerView : public View {
|
||||
|
||||
Button button_transmit{
|
||||
{148, 216, 80, 80},
|
||||
"START"};
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
|
||||
MessageHandlerRegistration message_handler_retune{
|
||||
Message::ID::Retune,
|
||||
|
||||
@@ -185,6 +185,8 @@ bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
|
||||
} else if (lhs.type == freqman_type::HamRadio) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b &&
|
||||
lhs.tone == rhs.tone;
|
||||
} else if (lhs.type == freqman_type::Repeater) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b;
|
||||
}
|
||||
|
||||
return equal;
|
||||
@@ -248,6 +250,10 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
|
||||
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Repeater:
|
||||
str = "L:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
str = entry.description;
|
||||
break;
|
||||
@@ -292,6 +298,10 @@ std::string to_freqman_string(const freqman_entry& entry) {
|
||||
if (is_valid(entry.tone))
|
||||
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
||||
break;
|
||||
case freqman_type::Repeater:
|
||||
append_field("l", to_string_dec_uint(entry.frequency_a));
|
||||
append_field("t", to_string_dec_uint(entry.frequency_b));
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
return entry.description;
|
||||
default:
|
||||
@@ -372,16 +382,18 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "m") {
|
||||
entry.modulation = find_by_name(freqman_modulations, value);
|
||||
} else if (key == "r") {
|
||||
} else if (key == "r") { // HamRadio relay receive freq
|
||||
entry.type = freqman_type::HamRadio;
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "l") { // Portapack Repeater mode listen freq. Used as a single freq if Repeater mode isn't active
|
||||
entry.type = freqman_type::Repeater;
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "s") {
|
||||
entry.step = find_by_name(freqman_steps_short, value);
|
||||
} else if (key == "t") {
|
||||
} else if (key == "t") { // Tx freq: scanned as a single freq in HamRadio mode, used as TX freq in Repeater mode and ignored by the scanner
|
||||
parse_int(value, entry.frequency_b);
|
||||
}
|
||||
}
|
||||
|
||||
return is_valid(entry);
|
||||
}
|
||||
|
||||
@@ -400,7 +412,8 @@ bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_optio
|
||||
if (entry.type == freqman_type::Unknown ||
|
||||
(entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
(entry.type == freqman_type::Range && !options.load_ranges) ||
|
||||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
|
||||
(entry.type == freqman_type::HamRadio && !options.load_hamradios) ||
|
||||
(entry.type == freqman_type::Repeater && !options.load_repeaters)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -433,8 +446,8 @@ bool is_valid(const freqman_entry& entry) {
|
||||
if (entry.frequency_a == 0)
|
||||
return false;
|
||||
|
||||
// Frequency B must be set for type Range or Ham Radio
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
// Frequency B must be set for type Range or HamRadio or Repeater
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio || entry.type == freqman_type::Repeater) {
|
||||
if (entry.frequency_b == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ enum class freqman_type : uint8_t {
|
||||
Single, // f=
|
||||
Range, // a=,b=
|
||||
HamRadio, // r=,t=
|
||||
Repeater, // l=,t=
|
||||
Raw, // line content in description
|
||||
Unknown,
|
||||
};
|
||||
@@ -167,6 +168,7 @@ struct freqman_load_options {
|
||||
bool load_freqs{true};
|
||||
bool load_ranges{true};
|
||||
bool load_hamradios{true};
|
||||
bool load_repeaters{true};
|
||||
};
|
||||
|
||||
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
|
||||
@@ -277,4 +279,4 @@ class FreqmanDB {
|
||||
bool read_raw_{true};
|
||||
};
|
||||
|
||||
#endif /* __FREQMAN_DB_H__ */
|
||||
#endif /* __FREQMAN_DB_H__ */
|
||||
|
||||
@@ -157,7 +157,7 @@ static void event_loop() {
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}};
|
||||
|
||||
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
|
||||
event_dispatcher.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -542,4 +542,8 @@ void shutdown(const bool leave_screen_on) {
|
||||
shutdown_base();
|
||||
}
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt) {
|
||||
usb_serial.setEventDispatcher(evt);
|
||||
}
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -68,6 +68,8 @@ bool get_antenna_bias();
|
||||
bool init();
|
||||
void shutdown(const bool leave_screen_on = false);
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt);
|
||||
|
||||
Backlight* backlight();
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file shell.c
|
||||
* @brief Simple CLI shell code.
|
||||
*
|
||||
* @addtogroup SHELL
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
#include "shell.hpp"
|
||||
#include "chprintf.h"
|
||||
#include "portapack.hpp"
|
||||
|
||||
/**
|
||||
* @brief Shell termination event source.
|
||||
*/
|
||||
EventSource shell_terminated;
|
||||
|
||||
static char* _strtok(char* str, const char* delim, char** saveptr) {
|
||||
char* token;
|
||||
if (str)
|
||||
*saveptr = str;
|
||||
token = *saveptr;
|
||||
|
||||
if (!token)
|
||||
return NULL;
|
||||
|
||||
token += strspn(token, delim);
|
||||
*saveptr = strpbrk(token, delim);
|
||||
if (*saveptr)
|
||||
*(*saveptr)++ = '\0';
|
||||
|
||||
return *token ? token : NULL;
|
||||
}
|
||||
|
||||
static void usage(BaseSequentialStream* chp, char* p) {
|
||||
chprintf(chp, "Usage: %s\r\n", p);
|
||||
}
|
||||
|
||||
static void list_commands(BaseSequentialStream* chp, const ShellCommand* scp) {
|
||||
while (scp->sc_name != NULL) {
|
||||
chprintf(chp, "%s ", scp->sc_name);
|
||||
scp++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_info(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
usage(chp, const_cast<char*>("info"));
|
||||
return;
|
||||
}
|
||||
|
||||
chprintf(chp, "Kernel: %s\r\n", CH_KERNEL_VERSION);
|
||||
#ifdef CH_COMPILER_NAME
|
||||
chprintf(chp, "Compiler: %s\r\n", CH_COMPILER_NAME);
|
||||
#endif
|
||||
chprintf(chp, "Architecture: %s\r\n", CH_ARCHITECTURE_NAME);
|
||||
#ifdef CH_CORE_VARIANT_NAME
|
||||
chprintf(chp, "Core Variant: %s\r\n", CH_CORE_VARIANT_NAME);
|
||||
#endif
|
||||
#ifdef CH_PORT_INFO
|
||||
chprintf(chp, "Port Info: %s\r\n", CH_PORT_INFO);
|
||||
#endif
|
||||
#ifdef PLATFORM_NAME
|
||||
chprintf(chp, "Platform: %s\r\n", PLATFORM_NAME);
|
||||
#endif
|
||||
#ifdef BOARD_NAME
|
||||
chprintf(chp, "Board: %s\r\n", BOARD_NAME);
|
||||
#endif
|
||||
#ifdef VERSION_STRING
|
||||
chprintf(chp, "Mayhem Version: %s\r\n", VERSION_STRING);
|
||||
#endif
|
||||
chprintf(chp, "HackRF Board Rev: %s\r\n", hackrf_r9 ? "R9" : "R1-R8");
|
||||
chprintf(chp, "Reference Source: %s\r\n", portapack::clock_manager.get_source().c_str());
|
||||
chprintf(chp, "Reference Freq: %s\r\n", portapack::clock_manager.get_freq().c_str());
|
||||
#ifdef __DATE__
|
||||
#ifdef __TIME__
|
||||
chprintf(chp, "Build time: %s%s%s\r\n", __DATE__, " - ", __TIME__);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cmd_systime(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
usage(chp, const_cast<char*>("systime"));
|
||||
return;
|
||||
}
|
||||
chprintf(chp, "%lu\r\n", (unsigned long)chTimeNow());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Array of the default commands.
|
||||
*/
|
||||
static ShellCommand local_commands[] = {
|
||||
{"info", cmd_info},
|
||||
{"systime", cmd_systime},
|
||||
{NULL, NULL}};
|
||||
|
||||
static bool_t cmdexec(const ShellCommand* scp, BaseSequentialStream* chp, char* name, int argc, char* argv[]) {
|
||||
while (scp->sc_name != NULL) {
|
||||
if (strcmp(scp->sc_name, name) == 0) {
|
||||
scp->sc_function(chp, argc, argv);
|
||||
return FALSE;
|
||||
}
|
||||
scp++;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shell thread function.
|
||||
*
|
||||
* @param[in] p pointer to a @p BaseSequentialStream object
|
||||
* @return Termination reason.
|
||||
* @retval RDY_OK terminated by command.
|
||||
* @retval RDY_RESET terminated by reset condition on the I/O channel.
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
static msg_t shell_thread(void* p) {
|
||||
int n;
|
||||
BaseSequentialStream* chp = ((ShellConfig*)p)->sc_channel;
|
||||
const ShellCommand* scp = ((ShellConfig*)p)->sc_commands;
|
||||
char *lp, *cmd, *tokp, line[SHELL_MAX_LINE_LENGTH];
|
||||
char* args[SHELL_MAX_ARGUMENTS + 1];
|
||||
|
||||
chRegSetThreadName("shell");
|
||||
chprintf(chp, "\r\nChibiOS/RT Shell\r\n");
|
||||
while (TRUE) {
|
||||
chprintf(chp, "ch> ");
|
||||
if (shellGetLine(chp, line, sizeof(line))) {
|
||||
chprintf(chp, "\r\nlogout");
|
||||
break;
|
||||
}
|
||||
lp = _strtok(line, " \t", &tokp);
|
||||
cmd = lp;
|
||||
n = 0;
|
||||
while ((lp = _strtok(NULL, " \t", &tokp)) != NULL) {
|
||||
if (n >= SHELL_MAX_ARGUMENTS) {
|
||||
chprintf(chp, "too many arguments\r\n");
|
||||
cmd = NULL;
|
||||
break;
|
||||
}
|
||||
args[n++] = lp;
|
||||
}
|
||||
args[n] = NULL;
|
||||
if (cmd != NULL) {
|
||||
if (strcmp(cmd, "exit") == 0) {
|
||||
if (n > 0) {
|
||||
usage(chp, const_cast<char*>("exit"));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} else if (strcmp(cmd, "help") == 0) {
|
||||
if (n > 0) {
|
||||
usage(chp, const_cast<char*>("help"));
|
||||
continue;
|
||||
}
|
||||
chprintf(chp, "Commands: help exit ");
|
||||
list_commands(chp, local_commands);
|
||||
if (scp != NULL)
|
||||
list_commands(chp, scp);
|
||||
chprintf(chp, "\r\n");
|
||||
} else if (cmdexec(local_commands, chp, cmd, n, args) &&
|
||||
((scp == NULL) || cmdexec(scp, chp, cmd, n, args))) {
|
||||
chprintf(chp, "%s", cmd);
|
||||
chprintf(chp, " ?\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
shellExit(RDY_OK);
|
||||
/* Never executed, silencing a warning.*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shell manager initialization.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void shellInit(void) {
|
||||
chEvtInit(&shell_terminated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Terminates the shell.
|
||||
* @note Must be invoked from the command handlers.
|
||||
* @note Does not return.
|
||||
*
|
||||
* @param[in] msg shell exit code
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void shellExit(msg_t msg) {
|
||||
/* Atomically broadcasting the event source and terminating the thread,
|
||||
there is not a chSysUnlock() because the thread terminates upon return.*/
|
||||
chSysLock();
|
||||
chEvtBroadcastI(&shell_terminated);
|
||||
chThdExitS(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Spawns a new shell.
|
||||
* @pre @p CH_USE_HEAP and @p CH_USE_DYNAMIC must be enabled.
|
||||
*
|
||||
* @param[in] scp pointer to a @p ShellConfig object
|
||||
* @param[in] size size of the shell working area to be allocated
|
||||
* @param[in] prio priority level for the new shell
|
||||
* @return A pointer to the shell thread.
|
||||
* @retval NULL thread creation failed because memory allocation.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#if CH_USE_HEAP && CH_USE_DYNAMIC
|
||||
Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio) {
|
||||
return chThdCreateFromHeap(NULL, size, prio, shell_thread, (void*)scp);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create statically allocated shell thread.
|
||||
*
|
||||
* @param[in] scp pointer to a @p ShellConfig object
|
||||
* @param[in] wsp pointer to a working area dedicated to the shell thread stack
|
||||
* @param[in] size size of the shell working area
|
||||
* @param[in] prio priority level for the new shell
|
||||
* @return A pointer to the shell thread.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio) {
|
||||
return chThdCreateStatic(wsp, size, prio, shell_thread, (void*)scp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a whole line from the input channel.
|
||||
*
|
||||
* @param[in] chp pointer to a @p BaseSequentialStream object
|
||||
* @param[in] line pointer to the line buffer
|
||||
* @param[in] size buffer maximum length
|
||||
* @return The operation status.
|
||||
* @retval TRUE the channel was reset or CTRL-D pressed.
|
||||
* @retval FALSE operation successful.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size) {
|
||||
char* p = line;
|
||||
|
||||
while (TRUE) {
|
||||
char c;
|
||||
|
||||
if (chSequentialStreamRead(chp, (uint8_t*)&c, 1) == 0)
|
||||
return TRUE;
|
||||
if (c == 4) {
|
||||
chprintf(chp, "^D");
|
||||
return TRUE;
|
||||
}
|
||||
if ((c == 8) || (c == 127)) {
|
||||
if (p != line) {
|
||||
chSequentialStreamPut(chp, c);
|
||||
chSequentialStreamPut(chp, 0x20);
|
||||
chSequentialStreamPut(chp, c);
|
||||
p--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '\r') {
|
||||
chprintf(chp, "\r\n");
|
||||
*p = 0;
|
||||
return FALSE;
|
||||
}
|
||||
if (c < 0x20)
|
||||
continue;
|
||||
if (p < line + size - 1) {
|
||||
chSequentialStreamPut(chp, c);
|
||||
*p++ = (char)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file shell.h
|
||||
* @brief Simple CLI shell header.
|
||||
*
|
||||
* @addtogroup SHELL
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef _SHELL_H_
|
||||
#define _SHELL_H_
|
||||
|
||||
/**
|
||||
* @brief Shell maximum input line length.
|
||||
*/
|
||||
#if !defined(SHELL_MAX_LINE_LENGTH) || defined(__DOXYGEN__)
|
||||
#define SHELL_MAX_LINE_LENGTH 64
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Shell maximum arguments per command.
|
||||
*/
|
||||
#if !defined(SHELL_MAX_ARGUMENTS) || defined(__DOXYGEN__)
|
||||
#define SHELL_MAX_ARGUMENTS 4
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Command handler function type.
|
||||
*/
|
||||
typedef void (*shellcmd_t)(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
|
||||
/**
|
||||
* @brief Custom command entry type.
|
||||
*/
|
||||
typedef struct {
|
||||
const char* sc_name; /**< @brief Command name. */
|
||||
shellcmd_t sc_function; /**< @brief Command function. */
|
||||
} ShellCommand;
|
||||
|
||||
/**
|
||||
* @brief Shell descriptor type.
|
||||
*/
|
||||
typedef struct {
|
||||
BaseSequentialStream* sc_channel; /**< @brief I/O channel associated
|
||||
to the shell. */
|
||||
const ShellCommand* sc_commands; /**< @brief Shell extra commands
|
||||
table. */
|
||||
} ShellConfig;
|
||||
|
||||
#if !defined(__DOXYGEN__)
|
||||
extern EventSource shell_terminated;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void shellInit(void);
|
||||
void shellExit(msg_t msg);
|
||||
Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio);
|
||||
Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio);
|
||||
bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _SHELL_H_ */
|
||||
|
||||
/** @} */
|
||||
@@ -173,6 +173,9 @@ SystemStatusView::SystemStatusView(
|
||||
pmem::load_persistent_settings_from_file();
|
||||
}
|
||||
|
||||
// configure CLKOUT per pmem setting
|
||||
portapack::clock_manager.enable_clock_output(pmem::clkout_enabled());
|
||||
|
||||
// force apply of selected sdcard speed override at UI startup
|
||||
pmem::set_config_sdcard_high_speed_io(pmem::config_sdcard_high_speed_io(), false);
|
||||
|
||||
@@ -438,7 +441,17 @@ InformationView::InformationView(
|
||||
&version,
|
||||
<ime});
|
||||
|
||||
#if GCC_VERSION_MISMATCH
|
||||
static constexpr Style style_gcc_warning{
|
||||
.font = font::fixed_8x16,
|
||||
.background = {33, 33, 33},
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
version.set_style(&style_gcc_warning);
|
||||
#else
|
||||
version.set_style(&style_infobar);
|
||||
#endif
|
||||
|
||||
ltime.set_style(&style_infobar);
|
||||
refresh();
|
||||
set_dirty();
|
||||
|
||||
@@ -145,6 +145,15 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
|
||||
// Setter for datetime and frequency filename
|
||||
void RecordView::set_filename_date_frequency(bool set) {
|
||||
filename_date_frequency = set;
|
||||
if (set)
|
||||
filename_as_is = false;
|
||||
}
|
||||
|
||||
// Setter for leaving the filename untouched
|
||||
void RecordView::set_filename_as_is(bool set) {
|
||||
filename_as_is = set;
|
||||
if (set)
|
||||
filename_date_frequency = false;
|
||||
}
|
||||
|
||||
bool RecordView::is_active() const {
|
||||
@@ -186,9 +195,11 @@ void RecordView::start() {
|
||||
base_path = filename_stem_pattern.string() + "_" + date_time + "_" +
|
||||
trim(to_string_freq(receiver_model.target_frequency())) + "Hz";
|
||||
base_path = folder / base_path;
|
||||
} else {
|
||||
} else if (filename_as_is) {
|
||||
base_path = filename_stem_pattern.string();
|
||||
base_path = folder / base_path;
|
||||
} else
|
||||
base_path = next_filename_matching_pattern(folder / filename_stem_pattern);
|
||||
}
|
||||
|
||||
if (base_path.empty()) {
|
||||
return;
|
||||
|
||||
@@ -73,6 +73,7 @@ class RecordView : public View {
|
||||
bool is_active() const;
|
||||
|
||||
void set_filename_date_frequency(bool set);
|
||||
void set_filename_as_is(bool set);
|
||||
|
||||
private:
|
||||
void toggle();
|
||||
@@ -91,6 +92,7 @@ class RecordView : public View {
|
||||
|
||||
// Time Stamp
|
||||
bool filename_date_frequency = false;
|
||||
bool filename_as_is = false;
|
||||
rtc::RTC datetime{};
|
||||
|
||||
const std::filesystem::path filename_stem_pattern;
|
||||
|
||||
@@ -32,7 +32,7 @@ void USBSerial::dispatch() {
|
||||
|
||||
if (shell_created == false) {
|
||||
shell_created = true;
|
||||
create_shell();
|
||||
create_shell(_eventDispatcher);
|
||||
}
|
||||
|
||||
bulk_out_receive();
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
class EventDispatcher;
|
||||
|
||||
namespace portapack {
|
||||
|
||||
class USBSerial {
|
||||
@@ -32,6 +34,7 @@ class USBSerial {
|
||||
void dispatch();
|
||||
void on_channel_opened();
|
||||
void on_channel_closed();
|
||||
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
|
||||
|
||||
private:
|
||||
void enable_xtal();
|
||||
@@ -43,6 +46,8 @@ class USBSerial {
|
||||
|
||||
bool connected{false};
|
||||
bool shell_created{false};
|
||||
|
||||
EventDispatcher* _eventDispatcher = NULL;
|
||||
};
|
||||
|
||||
} // namespace portapack
|
||||
@@ -27,19 +27,35 @@
|
||||
#include "png_writer.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
#include "hackrf_gpio.hpp"
|
||||
#include "jtag_target_gpio.hpp"
|
||||
#include "cpld_max5.hpp"
|
||||
#include "portapack_cpld_data.hpp"
|
||||
#include "crc.hpp"
|
||||
#include "hackrf_cpld_data.hpp"
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "ff.h"
|
||||
#include "chprintf.h"
|
||||
#include "chqueues.h"
|
||||
#include "untar.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
|
||||
#define SHELL_WA_SIZE THD_WA_SIZE(1024 * 3)
|
||||
#define palOutputPad(port, pad) (LPC_GPIO->DIR[(port)] |= 1 << (pad))
|
||||
|
||||
static EventDispatcher* _eventDispatcherInstance = NULL;
|
||||
static EventDispatcher* getEventDispatcherInstance() {
|
||||
return _eventDispatcherInstance;
|
||||
}
|
||||
|
||||
// queue handler from ch
|
||||
static msg_t qwait(GenericQueue* qp, systime_t time) {
|
||||
if (TIME_IMMEDIATE == time)
|
||||
@@ -146,6 +162,15 @@ std::filesystem::path path_from_string8(char* path) {
|
||||
return conv.from_bytes(path);
|
||||
}
|
||||
|
||||
bool strEndsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||
if (str.length() >= suffix.length()) {
|
||||
std::u16string endOfString = str.substr(str.length() - suffix.length());
|
||||
return endOfString == suffix;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "Usage: flash /FIRMWARE/portapack-h1_h2-mayhem.bin\r\n");
|
||||
@@ -153,15 +178,37 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
auto path = path_from_string8(argv[0]);
|
||||
size_t filename_length = strlen(argv[0]);
|
||||
|
||||
if (!std::filesystem::file_exists(path)) {
|
||||
chprintf(chp, "file not found.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (filename_length + 1) * 2);
|
||||
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
chprintf(chp, "Extracting TAR file.\r\n");
|
||||
auto res = UnTar::untar(
|
||||
path.native(), [chp](const std::string fileName) {
|
||||
chprintf(chp, fileName.c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
});
|
||||
if (res.empty()) {
|
||||
chprintf(chp, "error bad TAR file.\r\n");
|
||||
return;
|
||||
}
|
||||
path = res; // it will contain the last bin file in tar
|
||||
} else if (strEndsWith(path.native(), u".bin")) {
|
||||
// nothing to do for this case yet.
|
||||
} else {
|
||||
chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n");
|
||||
return;
|
||||
}
|
||||
chprintf(chp, "Flashing: ");
|
||||
chprintf(chp, path.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
chThdSleepMilliseconds(50);
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.native().c_str(), (path.native().length() + 1) * 2);
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(50);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
@@ -296,6 +343,28 @@ static void cmd_button(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_touch(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
chprintf(chp, "usage: touch x y\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int x = (int)strtol(argv[0], NULL, 10);
|
||||
int y = (int)strtol(argv[1], NULL, 10);
|
||||
if (x < 0 || x > ui::screen_width || y < 0 || y > ui::screen_height) {
|
||||
chprintf(chp, "usage: touch x y\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (evtd == NULL) {
|
||||
chprintf(chp, "error\r\n");
|
||||
}
|
||||
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::Start});
|
||||
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::End});
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (argc != 1) {
|
||||
chprintf(chp, "usage: ls /\r\n");
|
||||
@@ -418,21 +487,18 @@ static void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
|
||||
int size = (int)strtol(argv[0], NULL, 10);
|
||||
|
||||
uint8_t buffer[16];
|
||||
uint8_t buffer[62];
|
||||
|
||||
do {
|
||||
File::Size bytes_to_read = size > 16 ? 16 : size;
|
||||
File::Size bytes_to_read = size > 62 ? 62 : size;
|
||||
auto bytes_read = shell_file->read(buffer, bytes_to_read);
|
||||
if (bytes_read.is_error()) {
|
||||
chprintf(chp, "error %d\r\n", bytes_read.error());
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bytes_read.value(); i++)
|
||||
chprintf(chp, "%02X", buffer[i]);
|
||||
|
||||
chprintf(chp, "\r\n");
|
||||
|
||||
std::string res = to_string_hex_array(buffer, bytes_read.value());
|
||||
res += "\r\n";
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
|
||||
if (bytes_to_read != bytes_read.value())
|
||||
return;
|
||||
|
||||
@@ -479,6 +545,314 @@ static void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cpld_info(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage =
|
||||
"usage: cpld_info <device>\r\n"
|
||||
" supported modes:\r\n"
|
||||
" cpld_info hackrf\r\n"
|
||||
" cpld_info portapack\r\n";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(argv[0], "hackrf", 5) == 0) {
|
||||
jtag::GPIOTarget jtag_target_hackrf_cpld{
|
||||
hackrf::one::gpio_cpld_tck,
|
||||
hackrf::one::gpio_cpld_tms,
|
||||
hackrf::one::gpio_cpld_tdi,
|
||||
hackrf::one::gpio_cpld_tdo,
|
||||
};
|
||||
|
||||
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
|
||||
{
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
|
||||
hackrf_cpld.prepare_read_eeprom();
|
||||
|
||||
for (const auto& block : hackrf::one::cpld::verify_blocks) {
|
||||
auto from_device = hackrf_cpld.read_block_eeprom(block.id);
|
||||
|
||||
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
|
||||
auto bit = *i;
|
||||
crc.process_bit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
hackrf_cpld.finalize_read_eeprom();
|
||||
chprintf(chp, "CPLD eeprom firmware checksum: 0x%08X\r\n", crc.checksum());
|
||||
}
|
||||
|
||||
{
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
|
||||
hackrf_cpld.prepare_read_sram();
|
||||
|
||||
for (const auto& block : hackrf::one::cpld::verify_blocks) {
|
||||
auto from_device = hackrf_cpld.read_block_sram(block);
|
||||
|
||||
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
|
||||
auto bit = *i;
|
||||
crc.process_bit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
hackrf_cpld.finalize_read_sram(hackrf::one::cpld::verify_blocks[0].id);
|
||||
chprintf(chp, "CPLD sram firmware checksum: 0x%08X\r\n", crc.checksum());
|
||||
}
|
||||
|
||||
} else if (strncmp(argv[0], "portapack", 5) == 0) {
|
||||
jtag::GPIOTarget target{
|
||||
portapack::gpio_cpld_tck,
|
||||
portapack::gpio_cpld_tms,
|
||||
portapack::gpio_cpld_tdi,
|
||||
portapack::gpio_cpld_tdo};
|
||||
jtag::JTAG jtag{target};
|
||||
portapack::cpld::CPLD cpld{jtag};
|
||||
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
uint32_t idcode = cpld.get_idcode();
|
||||
|
||||
chprintf(chp, "CPLD IDCODE: 0x%08X\r\n", idcode);
|
||||
|
||||
if (idcode == 0x20A50DD) {
|
||||
chprintf(chp, "CPLD Model: Altera MAX V 5M40Z\r\n");
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
cpld.sample();
|
||||
cpld.bypass();
|
||||
cpld.enable();
|
||||
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
cpld.prepare_read(0x0000);
|
||||
|
||||
for (size_t i = 0; i < 3328; i++) {
|
||||
uint16_t data = cpld.read();
|
||||
crc.process_byte((data >> 0) & 0xff);
|
||||
crc.process_byte((data >> 8) & 0xff);
|
||||
crc.process_byte((data >> 16) & 0xff);
|
||||
crc.process_byte((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
cpld.prepare_read(0x0001);
|
||||
|
||||
for (size_t i = 0; i < 512; i++) {
|
||||
uint16_t data = cpld.read();
|
||||
crc.process_byte((data >> 0) & 0xff);
|
||||
crc.process_byte((data >> 8) & 0xff);
|
||||
crc.process_byte((data >> 16) & 0xff);
|
||||
crc.process_byte((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
chprintf(chp, "CPLD firmware checksum: 0x%08X\r\n", crc.checksum());
|
||||
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
|
||||
WWDT_TC = 100000 & 0xFFFFFF;
|
||||
WWDT_FEED_SEQUENCE;
|
||||
|
||||
} else if (idcode == 0x00025610) {
|
||||
chprintf(chp, "CPLD Model: AGM AG256SL100\r\n");
|
||||
|
||||
if (cpld.AGM_enter_maintenance_mode() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpld.AGM_enter_read_mode();
|
||||
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
for (size_t i = 0; i < 2048; i++) {
|
||||
uint32_t data = cpld.AGM_read(i);
|
||||
crc.process_byte((data >> 0) & 0xff);
|
||||
crc.process_byte((data >> 8) & 0xff);
|
||||
crc.process_byte((data >> 16) & 0xff);
|
||||
crc.process_byte((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
|
||||
chprintf(chp, "CPLD firmware checksum: 0x%08X\r\n", crc.checksum());
|
||||
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
|
||||
WWDT_TC = 100000 & 0xFFFFFF;
|
||||
WWDT_FEED_SEQUENCE;
|
||||
|
||||
} else {
|
||||
chprintf(chp, "CPLD Model: unknown\r\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
chprintf(chp, usage);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage =
|
||||
"usage: cpld_read <device> <target>\r\n"
|
||||
" device can be: hackrf, portapack\r\n"
|
||||
" target can be: sram (hackrf only), eeprom\r\n";
|
||||
|
||||
if (argc != 2) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(argv[0], "hackrf", 5) == 0) {
|
||||
if (strncmp(argv[1], "eeprom", 5) == 0) {
|
||||
jtag::GPIOTarget jtag_target_hackrf_cpld{
|
||||
hackrf::one::gpio_cpld_tck,
|
||||
hackrf::one::gpio_cpld_tms,
|
||||
hackrf::one::gpio_cpld_tdi,
|
||||
hackrf::one::gpio_cpld_tdo,
|
||||
};
|
||||
|
||||
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
|
||||
|
||||
hackrf_cpld.prepare_read_eeprom();
|
||||
|
||||
for (const auto& block : hackrf::one::cpld::verify_blocks) {
|
||||
auto from_device = hackrf_cpld.read_block_eeprom(block.id);
|
||||
|
||||
chprintf(chp, "bank %04X: ", block.id);
|
||||
uint32_t n = 6;
|
||||
uint8_t byte = 0;
|
||||
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
|
||||
auto bit = *i;
|
||||
byte |= bit << (7 - (n % 8));
|
||||
if (n % 8 == 7) {
|
||||
chprintf(chp, "%02X ", byte);
|
||||
byte = 0;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
chprintf(chp, "\r\n");
|
||||
}
|
||||
|
||||
hackrf_cpld.finalize_read_eeprom();
|
||||
}
|
||||
|
||||
else if (strncmp(argv[1], "sram", 5) == 0) {
|
||||
jtag::GPIOTarget jtag_target_hackrf_cpld{
|
||||
hackrf::one::gpio_cpld_tck,
|
||||
hackrf::one::gpio_cpld_tms,
|
||||
hackrf::one::gpio_cpld_tdi,
|
||||
hackrf::one::gpio_cpld_tdo,
|
||||
};
|
||||
|
||||
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
|
||||
|
||||
hackrf_cpld.prepare_read_sram();
|
||||
|
||||
for (const auto& block : hackrf::one::cpld::verify_blocks) {
|
||||
auto from_device = hackrf_cpld.read_block_sram(block);
|
||||
|
||||
chprintf(chp, "bank %04X: ", block.id);
|
||||
uint32_t n = 6;
|
||||
uint8_t byte = 0;
|
||||
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
|
||||
auto bit = *i;
|
||||
byte |= bit << (7 - (n % 8));
|
||||
if (n % 8 == 7) {
|
||||
chprintf(chp, "%02X ", byte);
|
||||
byte = 0;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
chprintf(chp, "\r\n");
|
||||
}
|
||||
|
||||
hackrf_cpld.finalize_read_sram(hackrf::one::cpld::verify_blocks[0].id);
|
||||
}
|
||||
} else if (strncmp(argv[0], "portapack", 5) == 0) {
|
||||
jtag::GPIOTarget target{
|
||||
portapack::gpio_cpld_tck,
|
||||
portapack::gpio_cpld_tms,
|
||||
portapack::gpio_cpld_tdi,
|
||||
portapack::gpio_cpld_tdo};
|
||||
jtag::JTAG jtag{target};
|
||||
portapack::cpld::CPLD cpld{jtag};
|
||||
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
uint32_t idcode = cpld.get_idcode();
|
||||
|
||||
chprintf(chp, "CPLD IDCODE: 0x%08X\r\n", idcode);
|
||||
|
||||
if (idcode == 0x20A50DD) {
|
||||
chprintf(chp, "CPLD Model: Altera MAX V 5M40Z\r\n");
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
cpld.sample();
|
||||
cpld.bypass();
|
||||
cpld.enable();
|
||||
|
||||
cpld.prepare_read(0x0000);
|
||||
|
||||
for (size_t i = 0; i < 3328; i++) {
|
||||
uint16_t data = cpld.read();
|
||||
chprintf(chp, "%d: 0x%04X\r\n", i, data);
|
||||
}
|
||||
|
||||
cpld.prepare_read(0x0001);
|
||||
|
||||
for (size_t i = 0; i < 512; i++) {
|
||||
uint16_t data = cpld.read();
|
||||
chprintf(chp, "%d: 0x%04X\r\n", i, data);
|
||||
}
|
||||
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
|
||||
WWDT_TC = 100000 & 0xFFFFFF;
|
||||
WWDT_FEED_SEQUENCE;
|
||||
|
||||
} else if (idcode == 0x00025610) {
|
||||
chprintf(chp, "CPLD Model: AGM AG256SL100\r\n");
|
||||
|
||||
if (cpld.AGM_enter_maintenance_mode() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpld.AGM_enter_read_mode();
|
||||
|
||||
for (size_t i = 0; i < 2048; i++) {
|
||||
uint32_t data = cpld.AGM_read(i);
|
||||
if (i % 4 == 0)
|
||||
chprintf(chp, "%5d: ", i * 4);
|
||||
|
||||
chprintf(chp, "0x%08X", data);
|
||||
|
||||
if (i % 4 == 3)
|
||||
chprintf(chp, "\r\n");
|
||||
else
|
||||
chprintf(chp, " ");
|
||||
}
|
||||
|
||||
cpld.AGM_exit_maintenance_mode();
|
||||
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
|
||||
WWDT_TC = 100000 & 0xFFFFFF;
|
||||
WWDT_FEED_SEQUENCE;
|
||||
|
||||
} else {
|
||||
chprintf(chp, "CPLD Model: unknown\r\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
chprintf(chp, usage);
|
||||
}
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -491,6 +865,7 @@ static const ShellCommand commands[] = {
|
||||
{"write_memory", cmd_write_memory},
|
||||
{"read_memory", cmd_read_memory},
|
||||
{"button", cmd_button},
|
||||
{"touch", cmd_touch},
|
||||
{"ls", cmd_sd_list_dir},
|
||||
{"rm", cmd_sd_delete},
|
||||
{"open", cmd_sd_open},
|
||||
@@ -499,12 +874,15 @@ static const ShellCommand commands[] = {
|
||||
{"read", cmd_sd_read},
|
||||
{"write", cmd_sd_write},
|
||||
{"filesize", cmd_sd_filesize},
|
||||
{"cpld_info", cpld_info},
|
||||
{"cpld_read", cmd_cpld_read},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
(BaseSequentialStream*)&SUSBD1,
|
||||
commands};
|
||||
|
||||
void create_shell() {
|
||||
void create_shell(EventDispatcher* evtd) {
|
||||
_eventDispatcherInstance = evtd;
|
||||
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO);
|
||||
}
|
||||
|
||||
@@ -26,4 +26,6 @@
|
||||
|
||||
#include "shell.h"
|
||||
|
||||
void create_shell(void);
|
||||
class EventDispatcher;
|
||||
|
||||
void create_shell(EventDispatcher* evtd);
|
||||
|
||||
@@ -147,9 +147,12 @@ void CPLD::sector_select(const uint16_t id) {
|
||||
}
|
||||
|
||||
bool CPLD::idcode_ok() {
|
||||
return (get_idcode() == idcode);
|
||||
}
|
||||
|
||||
uint32_t CPLD::get_idcode() {
|
||||
shift_ir(instruction_t::IDCODE);
|
||||
const auto idcode_read = jtag.shift_dr(idcode_length, 0);
|
||||
return (idcode_read == idcode);
|
||||
return jtag.shift_dr(idcode_length, 0);
|
||||
}
|
||||
|
||||
std::array<uint16_t, 5> CPLD::read_silicon_id() {
|
||||
@@ -208,6 +211,16 @@ void CPLD::program_block(
|
||||
}
|
||||
}
|
||||
|
||||
void CPLD::prepare_read(uint16_t block) {
|
||||
sector_select(block);
|
||||
shift_ir(instruction_t::ISC_READ);
|
||||
jtag.runtest_tck(93); // 5us
|
||||
}
|
||||
|
||||
uint32_t CPLD::read() {
|
||||
return jtag.shift_dr(16, 0xffff) & 0xfbff;
|
||||
}
|
||||
|
||||
bool CPLD::verify_block(
|
||||
const uint16_t id,
|
||||
const uint16_t* const data,
|
||||
@@ -265,5 +278,59 @@ bool CPLD::is_blank() {
|
||||
return block_0_blank && block_1_blank;
|
||||
}
|
||||
|
||||
bool CPLD::AGM_enter_maintenance_mode() {
|
||||
shift_ir(instruction_t::AGM_STAGE_1);
|
||||
jtag.runtest_tck(100);
|
||||
shift_ir(instruction_t::AGM_STAGE_2);
|
||||
jtag.runtest_tck(100);
|
||||
shift_ir(instruction_t::AGM_STAGE_1);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
shift_ir(instruction_t::AGM_SET_REGISTER);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.shift_dr(8, 0x0);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
shift_ir(instruction_t::AGM_PROGRAM);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.shift_dr(32, 0x203f0044uL, 0x80000000);
|
||||
|
||||
shift_ir(instruction_t::IDCODE);
|
||||
jtag.runtest_tck(100);
|
||||
auto idcode = jtag.shift_dr(idcode_length, 0);
|
||||
|
||||
return idcode == 0x00025610;
|
||||
}
|
||||
|
||||
void CPLD::AGM_exit_maintenance_mode() {
|
||||
shift_ir(instruction_t::AGM_RESET);
|
||||
jtag.runtest_tck(100);
|
||||
}
|
||||
|
||||
void CPLD::AGM_enter_read_mode() {
|
||||
shift_ir(instruction_t::AGM_SET_REGISTER);
|
||||
jtag.runtest_tck(100);
|
||||
jtag.shift_dr(8, 0xf0);
|
||||
jtag.runtest_tck(100);
|
||||
|
||||
shift_ir(instruction_t::AGM_READ);
|
||||
jtag.runtest_tck(100);
|
||||
}
|
||||
|
||||
uint32_t CPLD::AGM_encode_address(uint32_t address, uint32_t trailer) {
|
||||
uint32_t p = trailer;
|
||||
for (size_t i = 0; i < 18; i++) {
|
||||
auto address_bit = (address >> i) & 0x01;
|
||||
p |= address_bit << (31 - i);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
uint32_t CPLD::AGM_read(uint32_t address) {
|
||||
auto encoded_address = AGM_encode_address(address * 4, 0xC0);
|
||||
return jtag.shift_dr(32, encoded_address, 0x0);
|
||||
}
|
||||
|
||||
} /* namespace max5 */
|
||||
} /* namespace cpld */
|
||||
|
||||
@@ -60,6 +60,7 @@ class CPLD {
|
||||
}
|
||||
|
||||
bool idcode_ok();
|
||||
uint32_t get_idcode();
|
||||
|
||||
void enable();
|
||||
|
||||
@@ -90,6 +91,15 @@ class CPLD {
|
||||
|
||||
std::pair<bool, uint8_t> boundary_scan();
|
||||
|
||||
void prepare_read(uint16_t block);
|
||||
uint32_t read();
|
||||
|
||||
bool AGM_enter_maintenance_mode();
|
||||
void AGM_exit_maintenance_mode();
|
||||
void AGM_enter_read_mode();
|
||||
uint32_t AGM_encode_address(uint32_t address, uint32_t trailer);
|
||||
uint32_t AGM_read(uint32_t address);
|
||||
|
||||
private:
|
||||
using idcode_t = uint32_t;
|
||||
static constexpr size_t idcode_length = 32;
|
||||
@@ -115,6 +125,13 @@ class CPLD {
|
||||
ISC_ADDRESS_SHIFT = 0b1000000011, // 0x203
|
||||
ISC_READ = 0b1000000101, // 0x205
|
||||
ISC_NOOP = 0b1000010000, // 0x210
|
||||
AGM_RESET = 0x3f7,
|
||||
AGM_STAGE_1 = 0x3f8,
|
||||
AGM_STAGE_2 = 0x3f9,
|
||||
AGM_PROGRAM = 0x3fa,
|
||||
AGM_SET_REGISTER = 0x3fc,
|
||||
AGM_READ = 0x3fd,
|
||||
AGM_ERASE = 0x3fe,
|
||||
};
|
||||
|
||||
void shift_ir(const instruction_t instruction) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
#include "cpld_xilinx.hpp"
|
||||
|
||||
namespace cpld {
|
||||
@@ -47,6 +48,25 @@ void XC2C64A::write_sram(const verify_blocks_t& blocks) {
|
||||
}
|
||||
|
||||
bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
|
||||
prepare_read_sram();
|
||||
|
||||
const jtag::tap::bits_t empty_row{block_length};
|
||||
|
||||
auto error = false;
|
||||
for (const auto& block : blocks) {
|
||||
tap.shift({&block.id, block_id_length}, true);
|
||||
tap.state(state_t::run_test_idle);
|
||||
|
||||
tap.state(state_t::shift_dr);
|
||||
error |= tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false, nullptr);
|
||||
}
|
||||
|
||||
finalize_read_sram(blocks[0].id);
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
void XC2C64A::prepare_read_sram() {
|
||||
tap.set_repeat(0);
|
||||
tap.set_end_ir(state_t::run_test_idle);
|
||||
tap.set_end_dr(state_t::run_test_idle);
|
||||
@@ -61,17 +81,25 @@ bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
|
||||
|
||||
tap.state(state_t::shift_dr);
|
||||
tap.shift(empty_row, false);
|
||||
}
|
||||
|
||||
auto error = false;
|
||||
for (const auto& block : blocks) {
|
||||
tap.shift({&block.id, block_id_length}, true);
|
||||
tap.state(state_t::run_test_idle);
|
||||
std::array<bool, 274> XC2C64A::read_block_sram(verify_block_t block) {
|
||||
tap.shift({&block.id, block_id_length}, true);
|
||||
tap.state(state_t::run_test_idle);
|
||||
|
||||
tap.state(state_t::shift_dr);
|
||||
error |= tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false);
|
||||
}
|
||||
// Redundant operation to finish the row.
|
||||
tap.shift({&blocks[0].id, block_id_length}, true);
|
||||
tap.state(state_t::shift_dr);
|
||||
const jtag::tap::bits_t empty_row{block_length};
|
||||
std::vector<bool> from_device;
|
||||
|
||||
tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false, &from_device);
|
||||
|
||||
std::array<bool, block_length> ret;
|
||||
std::copy_n(std::make_move_iterator(from_device.begin()), block_length, ret.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void XC2C64A::finalize_read_sram(block_id_t id) {
|
||||
tap.shift({&id, block_id_length}, true);
|
||||
tap.state(state_t::run_test_idle);
|
||||
tap.set_end_dr(state_t::run_test_idle);
|
||||
|
||||
@@ -79,8 +107,6 @@ bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
|
||||
bypass();
|
||||
|
||||
tap.state(state_t::test_logic_reset);
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool XC2C64A::verify_eeprom(const verify_blocks_t& blocks) {
|
||||
@@ -133,6 +159,44 @@ void XC2C64A::init_from_eeprom() {
|
||||
tap.state(state_t::test_logic_reset);
|
||||
}
|
||||
|
||||
void XC2C64A::prepare_read_eeprom() {
|
||||
tap.set_repeat(0);
|
||||
tap.set_end_ir(state_t::run_test_idle);
|
||||
tap.set_end_dr(state_t::run_test_idle);
|
||||
|
||||
reset();
|
||||
bypass();
|
||||
enable();
|
||||
|
||||
shift_ir(instruction_t::ISC_READ);
|
||||
}
|
||||
|
||||
std::array<bool, 274> XC2C64A::read_block_eeprom(block_id_t id) {
|
||||
const jtag::tap::bits_t empty_row{block_length};
|
||||
|
||||
tap.set_end_dr(state_t::pause_dr);
|
||||
tap.shift_dr({&id, block_id_length});
|
||||
tap.set_end_ir(state_t::run_test_idle);
|
||||
tap.wait(state_t::pause_dr, state_t::pause_dr, 20);
|
||||
tap.set_end_ir(state_t::run_test_idle);
|
||||
tap.wait(state_t::run_test_idle, state_t::run_test_idle, 100);
|
||||
|
||||
std::vector<bool> from_device = tap.shift_dr_read(empty_row);
|
||||
|
||||
tap.wait(state_t::run_test_idle, state_t::run_test_idle, 100);
|
||||
|
||||
std::array<bool, block_length> ret;
|
||||
std::copy_n(std::make_move_iterator(from_device.begin()), block_length, ret.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void XC2C64A::finalize_read_eeprom() {
|
||||
disable();
|
||||
bypass();
|
||||
|
||||
tap.state(state_t::test_logic_reset);
|
||||
}
|
||||
|
||||
bool XC2C64A::shift_ir(const instruction_t instruction) {
|
||||
const ir_t ir_buffer = toUType(instruction);
|
||||
const jtag::tap::bits_t bits{&ir_buffer, ir_length};
|
||||
|
||||
@@ -68,6 +68,13 @@ class XC2C64A {
|
||||
bool verify_eeprom(const verify_blocks_t& blocks);
|
||||
void init_from_eeprom();
|
||||
|
||||
void prepare_read_eeprom();
|
||||
void prepare_read_sram();
|
||||
std::array<bool, block_length> read_block_eeprom(block_id_t id);
|
||||
std::array<bool, block_length> read_block_sram(verify_block_t block);
|
||||
void finalize_read_eeprom();
|
||||
void finalize_read_sram(block_id_t id);
|
||||
|
||||
private:
|
||||
static constexpr size_t idcode_length = 32;
|
||||
using idcode_t = uint32_t;
|
||||
|
||||
@@ -82,6 +82,19 @@ CommodityType Packet::commodity_type() const {
|
||||
return invalid_commodity_type;
|
||||
}
|
||||
|
||||
TamperFlags Packet::tamper_flags() const {
|
||||
if (type() == Type::SCM) {
|
||||
return (reader_.read(9, 2) << 4) | reader_.read(3, 2); // Physical/Encoder tamper flags in lower/upper nibbles
|
||||
}
|
||||
if (type() == Type::SCMPLUS) {
|
||||
return reader_.read(10 * 8, 16);
|
||||
}
|
||||
if (type() == Type::IDM) {
|
||||
return reader_.read(11 * 8, 48);
|
||||
}
|
||||
return invalid_tamper_flags;
|
||||
}
|
||||
|
||||
FormattedSymbols Packet::symbols_formatted() const {
|
||||
return format_symbols(decoder_);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,12 @@ namespace ert {
|
||||
using ID = uint32_t;
|
||||
using Consumption = uint32_t;
|
||||
using CommodityType = uint32_t;
|
||||
using TamperFlags = uint32_t;
|
||||
|
||||
constexpr ID invalid_id = 0;
|
||||
constexpr CommodityType invalid_commodity_type = -1;
|
||||
constexpr Consumption invalid_consumption = 0;
|
||||
constexpr TamperFlags invalid_tamper_flags = 0;
|
||||
|
||||
class Packet {
|
||||
public:
|
||||
@@ -67,6 +69,7 @@ class Packet {
|
||||
ID id() const;
|
||||
CommodityType commodity_type() const;
|
||||
Consumption consumption() const;
|
||||
TamperFlags tamper_flags() const;
|
||||
|
||||
FormattedSymbols symbols_formatted() const;
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ constexpr size_t clock_generator_output_og_mcu_clkin = 7;
|
||||
|
||||
constexpr size_t clock_generator_output_r9_if = 0;
|
||||
constexpr size_t clock_generator_output_r9_sgpio = 1;
|
||||
constexpr size_t clock_generator_output_r9_clkout = 2;
|
||||
constexpr size_t clock_generator_output_r9_mcu_clkin = 2;
|
||||
|
||||
/* ADC0 */
|
||||
|
||||
@@ -37,4 +37,15 @@ uint32_t JTAG::shift(const size_t count, uint32_t value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t JTAG::shift_header(const size_t count, uint32_t value) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const auto tdo = target.clock(
|
||||
0,
|
||||
value & 1);
|
||||
value >>= 1;
|
||||
value |= tdo << (count - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} /* namespace jtag */
|
||||
|
||||
@@ -89,10 +89,29 @@ class JTAG {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t shift_dr(const size_t count, const uint32_t address, const uint32_t value) {
|
||||
/* Run-Test/Idle -> Select-DR-Scan */
|
||||
target.clock(1, 0);
|
||||
/* Scan -> Capture -> Shift */
|
||||
target.clock(0, 0);
|
||||
target.clock(0, 0);
|
||||
|
||||
shift_header(count, address);
|
||||
const auto result = shift(count, value);
|
||||
|
||||
/* Exit1 -> Update */
|
||||
target.clock(1, 0);
|
||||
/* Update -> Run-Test/Idle */
|
||||
target.clock(0, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
Target& target;
|
||||
|
||||
uint32_t shift(const size_t count, uint32_t value);
|
||||
uint32_t shift_header(const size_t count, uint32_t value);
|
||||
};
|
||||
|
||||
} /* namespace jtag */
|
||||
|
||||
@@ -191,6 +191,10 @@ bool TAPMachine::shift_dr(const bits_t& tdi_value, const bits_t& tdo_expected, c
|
||||
return shift_data(tdi_value, tdo_expected, tdo_mask, state_t::shift_dr, _end_dr, _run_test);
|
||||
}
|
||||
|
||||
std::vector<bool> TAPMachine::shift_dr_read(const bits_t& tdi_value) {
|
||||
return shift_data_read(tdi_value, state_t::shift_dr, _end_dr, _run_test);
|
||||
}
|
||||
|
||||
void TAPMachine::state(const state_t state) {
|
||||
if (state == state_t::test_logic_reset) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
@@ -227,7 +231,7 @@ void TAPMachine::shift_start(const state_t state) {
|
||||
advance_to_state(state);
|
||||
}
|
||||
|
||||
bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms) {
|
||||
bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms, std::vector<bool>* from_device) {
|
||||
if (tdo_expected.length() != tdo_mask.length()) {
|
||||
return false;
|
||||
}
|
||||
@@ -239,6 +243,10 @@ bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits
|
||||
for (uint32_t i = 0; i < tdi.length(); i++) {
|
||||
const auto tms = end_tms & (i == (tdi.length() - 1));
|
||||
const auto tdo = clock(tms, tdi[i]);
|
||||
|
||||
if (from_device != nullptr)
|
||||
from_device->push_back(tdo);
|
||||
|
||||
if (tdo_expected && tdo_mask) {
|
||||
tdo_error |= (tdo & tdo_mask[i]) != (tdo_expected[i] & tdo_mask[i]);
|
||||
}
|
||||
@@ -258,7 +266,15 @@ void TAPMachine::shift_end(const state_t end_state, const uint32_t end_delay) {
|
||||
|
||||
bool TAPMachine::shift_data(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const state_t state, const state_t end_state, const uint32_t end_delay) {
|
||||
shift_start(state);
|
||||
const auto result = shift(tdi, tdo_expected, tdo_mask, true);
|
||||
const auto result = shift(tdi, tdo_expected, tdo_mask, true, nullptr);
|
||||
shift_end(end_state, end_delay);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> TAPMachine::shift_data_read(const bits_t& tdi, const state_t state, const state_t end_state, const uint32_t end_delay) {
|
||||
shift_start(state);
|
||||
std::vector<bool> result;
|
||||
shift(tdi, {}, {}, true, &result);
|
||||
shift_end(end_state, end_delay);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace jtag {
|
||||
namespace tap {
|
||||
@@ -113,13 +114,14 @@ class TAPMachine {
|
||||
void set_end_dr(const state_t state);
|
||||
|
||||
bool shift(const bits_t& tdi, const bool end_tms) {
|
||||
return shift(tdi, {}, {}, end_tms);
|
||||
return shift(tdi, {}, {}, end_tms, nullptr);
|
||||
}
|
||||
|
||||
bool shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms);
|
||||
bool shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms, std::vector<bool>* from_device);
|
||||
|
||||
bool shift_ir(const bits_t& tdi_value, const bits_t& tdo_expected = {}, const bits_t& tdo_mask = {});
|
||||
bool shift_dr(const bits_t& tdi_value, const bits_t& tdo_expected = {}, const bits_t& tdo_mask = {});
|
||||
std::vector<bool> shift_dr_read(const bits_t& tdi_value);
|
||||
|
||||
void state(const state_t state);
|
||||
|
||||
@@ -142,6 +144,7 @@ class TAPMachine {
|
||||
void shift_end(const state_t end_state, const uint32_t end_delay);
|
||||
|
||||
bool shift_data(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const state_t state, const state_t end_state, const uint32_t end_delay);
|
||||
std::vector<bool> shift_data_read(const bits_t& tdi, const state_t state, const state_t end_state, const uint32_t end_delay);
|
||||
};
|
||||
|
||||
} /* namespace tap */
|
||||
|
||||
@@ -77,7 +77,7 @@ constexpr modem_repeat_range_t modem_repeat_range{1, 99};
|
||||
constexpr int32_t modem_repeat_reset_value{5};
|
||||
|
||||
using clkout_freq_range_t = range_t<uint32_t>;
|
||||
constexpr clkout_freq_range_t clkout_freq_range{10, 60000};
|
||||
constexpr clkout_freq_range_t clkout_freq_range{4, 60000}; // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
|
||||
constexpr uint16_t clkout_freq_reset_value{10000};
|
||||
|
||||
enum data_structure_version_enum : uint32_t {
|
||||
@@ -195,6 +195,9 @@ struct data_t {
|
||||
|
||||
// Recon App
|
||||
uint64_t recon_config;
|
||||
int8_t recon_repeat_nb;
|
||||
int8_t recon_repeat_gain;
|
||||
uint8_t recon_repeat_delay;
|
||||
|
||||
// enable or disable converter
|
||||
bool converter;
|
||||
@@ -257,7 +260,11 @@ struct data_t {
|
||||
tone_mix(tone_mix_reset_value),
|
||||
|
||||
hardware_config(0),
|
||||
|
||||
recon_config(0),
|
||||
recon_repeat_nb(0),
|
||||
recon_repeat_gain(0),
|
||||
recon_repeat_delay(0),
|
||||
|
||||
converter(false),
|
||||
updown_converter(false),
|
||||
@@ -284,11 +291,11 @@ struct data_t {
|
||||
|
||||
struct backup_ram_t {
|
||||
private:
|
||||
volatile uint32_t regfile[63];
|
||||
volatile uint32_t regfile[PMEM_SIZE_WORDS - 1];
|
||||
volatile uint32_t check_value;
|
||||
|
||||
static void copy(const backup_ram_t& src, backup_ram_t& dst) {
|
||||
for (size_t i = 0; i < 63; i++) {
|
||||
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
|
||||
dst.regfile[i] = src.regfile[i];
|
||||
}
|
||||
dst.check_value = src.check_value;
|
||||
@@ -297,7 +304,7 @@ struct backup_ram_t {
|
||||
static void copy_from_data_t(const data_t& src, backup_ram_t& dst) {
|
||||
const uint32_t* const src_words = (uint32_t*)&src;
|
||||
const size_t word_count = (sizeof(data_t) + 3) / 4;
|
||||
for (size_t i = 0; i < 63; i++) {
|
||||
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
|
||||
if (i < word_count) {
|
||||
dst.regfile[i] = src_words[i];
|
||||
} else {
|
||||
@@ -308,7 +315,7 @@ struct backup_ram_t {
|
||||
|
||||
uint32_t compute_check_value() {
|
||||
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
|
||||
for (size_t i = 0; i < 63; i++) {
|
||||
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
|
||||
const auto word = regfile[i];
|
||||
crc.process_byte((word >> 0) & 0xff);
|
||||
crc.process_byte((word >> 8) & 0xff);
|
||||
@@ -391,10 +398,16 @@ void defaults() {
|
||||
set_recon_continuous(true);
|
||||
set_recon_clear_output(false);
|
||||
set_recon_load_freqs(true);
|
||||
set_recon_load_repeaters(true);
|
||||
set_recon_load_ranges(true);
|
||||
set_recon_update_ranges_when_recon(true);
|
||||
set_recon_load_hamradios(true);
|
||||
set_recon_match_mode(0);
|
||||
set_recon_repeat_recorded(false);
|
||||
set_recon_repeat_amp(false);
|
||||
set_recon_repeat_gain(35);
|
||||
set_recon_repeat_nb(3);
|
||||
set_recon_repeat_delay(1);
|
||||
|
||||
set_config_sdcard_high_speed_io(false, true);
|
||||
}
|
||||
@@ -747,6 +760,24 @@ bool recon_match_mode() {
|
||||
bool recon_auto_record_locked() {
|
||||
return (data->recon_config & 0x00400000UL) ? true : false;
|
||||
}
|
||||
bool recon_repeat_recorded() {
|
||||
return (data->recon_config & 0x00200000UL) ? true : false;
|
||||
}
|
||||
int8_t recon_repeat_nb() {
|
||||
return data->recon_repeat_nb;
|
||||
}
|
||||
int8_t recon_repeat_gain() {
|
||||
return data->recon_repeat_gain;
|
||||
}
|
||||
uint8_t recon_repeat_delay() {
|
||||
return data->recon_repeat_delay;
|
||||
}
|
||||
bool recon_repeat_amp() {
|
||||
return (data->recon_config & 0x00100000UL) ? true : false;
|
||||
}
|
||||
bool recon_load_repeaters() {
|
||||
return (data->recon_config & 0x00080000UL) ? true : false;
|
||||
}
|
||||
|
||||
void set_recon_autosave_freqs(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31);
|
||||
@@ -778,6 +809,24 @@ void set_recon_match_mode(const bool v) {
|
||||
void set_recon_auto_record_locked(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00400000UL) | (v << 22);
|
||||
}
|
||||
void set_recon_repeat_recorded(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00200000UL) | (v << 21);
|
||||
}
|
||||
void set_recon_repeat_nb(const int8_t v) {
|
||||
data->recon_repeat_nb = v;
|
||||
}
|
||||
void set_recon_repeat_gain(const int8_t v) {
|
||||
data->recon_repeat_gain = v;
|
||||
}
|
||||
void set_recon_repeat_delay(const uint8_t v) {
|
||||
data->recon_repeat_delay = v;
|
||||
}
|
||||
void set_recon_repeat_amp(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00100000UL) | (v << 20);
|
||||
}
|
||||
void set_recon_load_repeaters(const bool v) {
|
||||
data->recon_config = (data->recon_config & ~0x00080000UL) | (v << 19);
|
||||
}
|
||||
|
||||
/* UI Config 2 */
|
||||
bool ui_hide_speaker() {
|
||||
@@ -988,6 +1037,9 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
|
||||
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
|
||||
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_gain:" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_delay:" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
|
||||
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
// persistant memory from/to sdcard flag file
|
||||
#define PMEM_SETTING_FILE u"/SETTINGS/pmem_settings"
|
||||
|
||||
#define PMEM_SIZE_BYTES 256 // total amount of pmem space in bytes, including checksum
|
||||
#define PMEM_SIZE_WORDS (PMEM_SIZE_BYTES / 4)
|
||||
|
||||
using namespace modems;
|
||||
using namespace serializer;
|
||||
|
||||
@@ -242,11 +245,17 @@ bool recon_autostart_recon();
|
||||
bool recon_continuous();
|
||||
bool recon_clear_output();
|
||||
bool recon_load_freqs();
|
||||
bool recon_load_repeaters();
|
||||
bool recon_load_ranges();
|
||||
bool recon_update_ranges_when_recon();
|
||||
bool recon_auto_record_locked();
|
||||
bool recon_repeat_recorded();
|
||||
int8_t recon_repeat_nb();
|
||||
int8_t recon_repeat_gain();
|
||||
bool recon_repeat_amp();
|
||||
bool recon_load_hamradios();
|
||||
bool recon_match_mode();
|
||||
uint8_t recon_repeat_delay();
|
||||
void set_recon_autosave_freqs(const bool v);
|
||||
void set_recon_autostart_recon(const bool v);
|
||||
void set_recon_continuous(const bool v);
|
||||
@@ -255,8 +264,14 @@ void set_recon_load_freqs(const bool v);
|
||||
void set_recon_load_ranges(const bool v);
|
||||
void set_recon_update_ranges_when_recon(const bool v);
|
||||
void set_recon_auto_record_locked(const bool v);
|
||||
void set_recon_repeat_recorded(const bool v);
|
||||
void set_recon_repeat_nb(const int8_t v);
|
||||
void set_recon_repeat_gain(const int8_t v);
|
||||
void set_recon_repeat_amp(const bool v);
|
||||
void set_recon_load_hamradios(const bool v);
|
||||
void set_recon_load_repeaters(const bool v);
|
||||
void set_recon_match_mode(const bool v);
|
||||
void set_recon_repeat_delay(const uint8_t v);
|
||||
|
||||
/* UI Config 2 */
|
||||
bool ui_hide_speaker();
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#ifndef __UNTAR
|
||||
#define __UNTAR
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
class UnTar {
|
||||
public:
|
||||
static std::filesystem::path untar(std::u16string tar, std::function<void(const std::string)> cb = NULL) {
|
||||
File tf;
|
||||
auto result = tf.open(tar, true, false);
|
||||
if (!result.value().ok()) return "";
|
||||
return untar_int(&tf, cb);
|
||||
}
|
||||
|
||||
private:
|
||||
static int parseoct(const char* p, size_t n) {
|
||||
int i = 0;
|
||||
while (*p < '0' || *p > '7') {
|
||||
++p;
|
||||
--n;
|
||||
}
|
||||
while (*p >= '0' && *p <= '7' && n > 0) {
|
||||
i *= 8;
|
||||
i += *p - '0';
|
||||
++p;
|
||||
--n;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool is_end_of_archive(const char* p) {
|
||||
for (int n = 511; n >= 0; --n)
|
||||
if (p[n] != '\0') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t strnlen(const char* s, size_t maxlen) {
|
||||
for (size_t i = 0; i < maxlen; i++) {
|
||||
if (s[i] == '\0')
|
||||
return i;
|
||||
}
|
||||
return maxlen;
|
||||
}
|
||||
|
||||
static bool isValidName(char* name) {
|
||||
size_t pathStrLen = strnlen(name, 100);
|
||||
if (pathStrLen == 0 || pathStrLen >= 100) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool create_dir(char* pathname) {
|
||||
char* p;
|
||||
std::filesystem::filesystem_error r;
|
||||
|
||||
if (!isValidName(pathname)) return false;
|
||||
/* Strip trailing '/' */
|
||||
if (pathname[strlen(pathname) - 1] == '/')
|
||||
pathname[strlen(pathname) - 1] = '\0';
|
||||
|
||||
/* Try creating the directory. */
|
||||
std::string dirnameStr = u'/' + pathname;
|
||||
std::filesystem::path dirname = dirnameStr;
|
||||
|
||||
r = make_new_directory(dirname);
|
||||
|
||||
if (!r.ok()) {
|
||||
/* On failure, try creating parent directory. */
|
||||
p = strrchr(pathname, '/');
|
||||
if (p != NULL) {
|
||||
*p = '\0';
|
||||
create_dir(pathname);
|
||||
*p = '/';
|
||||
r = make_new_directory(dirname);
|
||||
}
|
||||
}
|
||||
return (r.ok());
|
||||
}
|
||||
|
||||
static bool verify_checksum(const char* p) {
|
||||
int n, u = 0;
|
||||
for (n = 0; n < 512; ++n) {
|
||||
if (n < 148 || n > 155)
|
||||
/* Standard tar checksum adds unsigned bytes. */
|
||||
u += ((unsigned char*)p)[n];
|
||||
else
|
||||
u += 0x20;
|
||||
}
|
||||
return (u == parseoct(p + 148, 8));
|
||||
}
|
||||
|
||||
static std::filesystem::path untar_int(File* a, std::function<void(const std::string)> cb = NULL) {
|
||||
char buff[512];
|
||||
UINT bytes_read;
|
||||
std::string binfile = "";
|
||||
std::string fn = "";
|
||||
int filesize;
|
||||
for (;;) {
|
||||
auto readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
bytes_read = readres.value();
|
||||
if (bytes_read < 512) {
|
||||
return "";
|
||||
}
|
||||
if (is_end_of_archive(buff)) {
|
||||
return binfile;
|
||||
}
|
||||
if (!verify_checksum(buff)) {
|
||||
return "";
|
||||
}
|
||||
filesize = parseoct(buff + 124, 12);
|
||||
switch (buff[156]) {
|
||||
case '1':
|
||||
break;
|
||||
case '2':
|
||||
break;
|
||||
case '3':
|
||||
break;
|
||||
case '4':
|
||||
break;
|
||||
case '5':
|
||||
create_dir(buff);
|
||||
filesize = 0;
|
||||
break;
|
||||
case '6':
|
||||
break;
|
||||
default:
|
||||
if (isValidName(buff)) {
|
||||
fn = buff;
|
||||
if (fn.length() > 5 && fn.substr(fn.length() - 4) == ".bin") {
|
||||
binfile = fn;
|
||||
}
|
||||
if (cb != NULL) cb(fn);
|
||||
} else {
|
||||
return ""; // bad tar
|
||||
}
|
||||
break;
|
||||
}
|
||||
File f;
|
||||
if (filesize > 0) {
|
||||
delete_file(fn);
|
||||
auto fres = f.open(fn, false, true);
|
||||
if (!fres.value().ok()) return "";
|
||||
}
|
||||
while (filesize > 0) {
|
||||
readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
bytes_read = readres.value();
|
||||
if (bytes_read < 512) {
|
||||
return "";
|
||||
}
|
||||
if (filesize < 512)
|
||||
bytes_read = filesize;
|
||||
auto fwres = f.write(buff, bytes_read);
|
||||
if (!fwres.is_ok()) return "";
|
||||
filesize -= bytes_read;
|
||||
f.sync();
|
||||
}
|
||||
}
|
||||
return binfile;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -59,6 +59,17 @@ TEST_CASE("It can parse basic ham radio freq entry.") {
|
||||
CHECK_EQ(e.type, freqman_type::HamRadio);
|
||||
}
|
||||
|
||||
TEST_CASE("It can parse repeater entry.") {
|
||||
freqman_entry e;
|
||||
REQUIRE(
|
||||
parse_freqman_entry(
|
||||
"l=123000000,t=123000500,d=This is the description.", e));
|
||||
CHECK_EQ(e.frequency_a, 123'000'000);
|
||||
CHECK_EQ(e.frequency_b, 123'000'500);
|
||||
CHECK_EQ(e.description, "This is the description.");
|
||||
CHECK_EQ(e.type, freqman_type::Repeater);
|
||||
}
|
||||
|
||||
TEST_CASE("It can parse modulation") {
|
||||
freqman_entry e;
|
||||
REQUIRE(
|
||||
@@ -195,6 +206,16 @@ TEST_CASE("It can serialize basic HamRadio entry") {
|
||||
CHECK(str == "r=123456000,t=423456000,d=Foobar");
|
||||
}
|
||||
|
||||
TEST_CASE("It can serialize basic Repeater entry") {
|
||||
auto str = to_freqman_string(freqman_entry{
|
||||
.frequency_a = 123'456'000,
|
||||
.frequency_b = 423'456'000,
|
||||
.description = "Foobar",
|
||||
.type = freqman_type::Repeater,
|
||||
});
|
||||
CHECK(str == "l=123456000,t=423456000,d=Foobar");
|
||||
}
|
||||
|
||||
// New tables for a future PR.
|
||||
/*
|
||||
TEST_CASE("It can parse modulation") {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
f=132475000,m=AM,bw=DSB 6k,d=ATIS - Civil
|
||||
f=130550000,m=AM,bw=DSB 6k,d=AbelagUnicom - Civil
|
||||
f=118250000,m=AM,bw=DSB 6k,d=Approach- Civil
|
||||
f=120100000,m=AM,bw=DSB 6k,d=Approach - Civil
|
||||
f=122500000,m=AM,bw=DSB 6k,d=Approach Military Crossing
|
||||
f=121950000,m=AM,bw=DSB 6k,d=Clearance Delivery - Civil
|
||||
f=126625000,m=AM,bw=DSB 6k,d=Departure - Civil
|
||||
f=127575000,m=AM,bw=DSB 6k,d=Final 25L - Civil
|
||||
f=129725000,m=AM,bw=DSB 6k,d=Final 25R - Civil
|
||||
f=118050000,m=AM,bw=DSB 6k,d=Ground - Civik
|
||||
f=121700000,m=AM,bw=DSB 6k,d=Ground - Civil
|
||||
f=121875000,m=AM,bw=DSB 6k,d=Ground - Civil
|
||||
f=118600000,m=AM,bw=DSB 6k,d=Tower VHF - Civil
|
||||
f=120775000,m=AM,bw=DSB 6k,d=Tower - Civil
|
||||
f=127150000,m=AM,bw=DSB 6k,d=Tower Spare - Civil
|
||||
f=240400000,m=AM,bw=DSB 6k,d=Air to Air - Military
|
||||
f=241000000,m=AM,bw=DSB 6k,d=Air to Air - Military
|
||||
f=289400000,m=AM,bw=DSB 6k,d=Military Approach
|
||||
f=362300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
|
||||
f=389300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
|
||||
f=138250000,m=AM,bw=DSB 6k,d=Inflight Safety - Military
|
||||
f=140575000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
|
||||
f=367950000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
|
||||
f=257800000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=388500000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=388525000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=121725000,m=AM,bw=DSB 6k,d=Wing Ops - Military
|
||||
f=336500000,m=AM,bw=DSB 6k,d=Wing Ops UHF - Military
|
||||
Reference in New Issue
Block a user