mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 | |||
| 6a6c6d6502 | |||
| 2d98c5d311 | |||
| 03b13f8ab0 | |||
| 6e74ad942f | |||
| 0f85f247b6 | |||
| ac1d350aaf | |||
| 5f8e1ef307 | |||
| aa5d4ad078 | |||
| 44d9572f5c | |||
| 3314001205 | |||
| ce1084abc7 | |||
| ea8563cb19 | |||
| b3c1c83677 | |||
| fcbc3b4d75 | |||
| 39e1a0ffe8 | |||
| 99bbd8805c | |||
| e7af85ffba | |||
| 75f3277102 | |||
| 216f8b64bf | |||
| bc301c5fdb | |||
| ae9d6de093 | |||
| 7a4c3184a1 | |||
| 58307aee9c |
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: check latest commit is less than a day
|
||||
id: should_run
|
||||
continue-on-error: true
|
||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
|
||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
build:
|
||||
needs: check_date
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||
@@ -28,10 +28,10 @@ jobs:
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Get version date
|
||||
id: version_date
|
||||
run: echo "::set-output name=date::n_$(date +'%y%m%d')"
|
||||
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
@@ -71,11 +71,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "::set-output name=content::$CHANGELOG"
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem.ppfw.tar
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
@@ -26,10 +26,10 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "::set-output name=version::$(cat .github/workflows/version.txt)"
|
||||
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Get past version
|
||||
id: past_version
|
||||
run: echo "::set-output name=past_version::$(cat .github/workflows/past_version.txt)"
|
||||
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Build the Docker image
|
||||
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
|
||||
- name: Make build folder
|
||||
@@ -55,11 +55,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "::set-output name=content::$CHANGELOG"
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem.ppfw.tar
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
|
||||
@@ -46,7 +46,6 @@ if ("${VERSION}" STREQUAL "")
|
||||
set(VERSION_NOHASH "dev")
|
||||
else()
|
||||
set(VERSION_NOHASH "${VERSION}")
|
||||
set(PPFW_FILENAME "portapack-mayhem.ppfw.tar")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
@@ -55,16 +54,6 @@ execute_process(
|
||||
)
|
||||
set(VERSION_MD5 "0x${VERSION_MD5}")
|
||||
|
||||
if (NOT DEFINED PPFW_FILENAME)
|
||||
# Funny business here: trying to generate unique names to prevent renaming of shared test-drive builds by the browser on download to .ppfw-42.tar as it wont be recognized by the flasher
|
||||
execute_process(
|
||||
COMMAND date "+%y%m%d-%H%M"
|
||||
OUTPUT_VARIABLE PPFW_FILENAME
|
||||
)
|
||||
string(STRIP ${PPFW_FILENAME} PPFW_FILENAME)
|
||||
set(PPFW_FILENAME "portapack-mayhem_${PPFW_FILENAME}_OCI.ppfw.tar")
|
||||
endif()
|
||||
|
||||
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
||||
|
||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||
|
||||
@@ -35,6 +35,7 @@ set(LZ4 lz4)
|
||||
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||
|
||||
# In our current build container cmake need a little help to get the version :)
|
||||
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
|
||||
@@ -100,7 +101,7 @@ add_custom_command(
|
||||
COMMAND mkdir -p firmware_tar/APPS
|
||||
COMMAND cp application/*.ppma firmware_tar/APPS
|
||||
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
|
||||
DEPENDS firmware
|
||||
DEPENDS firmware ${FIRMWARE_FILENAME}
|
||||
# Dont use VERBATIM here as it prevents usage of globbing (*)
|
||||
# There shouldnt be any funny business in the filenames above :)
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ set(CSRC
|
||||
usb_serial_cdc.c
|
||||
usb_serial_descriptor.c
|
||||
usb_serial_endpoints.c
|
||||
usb_serial_io.c
|
||||
usb_serial_device_to_host.c
|
||||
${HACKRF_PATH}/firmware/common/usb.c
|
||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
@@ -207,6 +207,7 @@ set(CPPSRC
|
||||
usb_serial_event.cpp
|
||||
usb_serial_thread.cpp
|
||||
usb_serial.cpp
|
||||
usb_serial_host_to_device.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
receiver_model.cpp
|
||||
@@ -345,6 +346,7 @@ set(CPPSRC
|
||||
${CPLD_20170522_DATA_CPP}
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
|
||||
${EXTCPPSRC}
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ using namespace portapack;
|
||||
|
||||
namespace {
|
||||
fs::path get_settings_path(const std::string& app_name) {
|
||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
||||
return fs::path{SETTINGS_DIR} / app_name + u".ini";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -156,7 +156,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
File f;
|
||||
auto path = get_settings_path(std::string{store_name});
|
||||
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
@@ -80,7 +80,13 @@ void SoundBoardView::file_error() {
|
||||
}
|
||||
|
||||
void SoundBoardView::start_tx(const uint32_t id) {
|
||||
if (file_list.empty()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
|
||||
@@ -110,11 +116,11 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
// TODO: Delete all this and use tx model.
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
@@ -126,7 +132,8 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
audio::output::start();
|
||||
if (tone_key_index == 0)
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -232,6 +239,7 @@ SoundBoardView::SoundBoardView(
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&field_volume,
|
||||
&text_volume_disabled,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -257,6 +265,13 @@ SoundBoardView::SoundBoardView(
|
||||
tone_keys_populate(options_tone_key);
|
||||
options_tone_key.set_selected_index(0);
|
||||
|
||||
text_volume_disabled.hidden(true);
|
||||
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
bool tone_key_enabled = (index != 0);
|
||||
text_volume_disabled.hidden(!tone_key_enabled);
|
||||
field_volume.hidden(tone_key_enabled);
|
||||
};
|
||||
|
||||
check_loop.set_value(false);
|
||||
check_random.set_value(false);
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ class SoundBoardView : public View {
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 180}};
|
||||
Text text_volume_disabled{
|
||||
{28 * 8, 180, 3 * 8, 16},
|
||||
"--"};
|
||||
|
||||
Checkbox check_loop{
|
||||
{0, 25 * 8 + 4},
|
||||
|
||||
@@ -407,7 +407,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
status_good_frame.toggle();
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta
|
||||
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
|
||||
|
||||
// NB: Reference to update entry in-place.
|
||||
|
||||
@@ -101,6 +101,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 3) {
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 4) {
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -258,7 +260,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
|
||||
std::string source_formatted = packet.get_source_formatted();
|
||||
std::string info_string = packet.get_stream_text();
|
||||
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
auto& entry = ::on_packet(recent, packet.get_source());
|
||||
entry.reset_age();
|
||||
entry.inc_hit();
|
||||
|
||||
@@ -221,7 +221,8 @@ class APRSRxView : public View {
|
||||
{{"NA ", 0},
|
||||
{"EUR", 1},
|
||||
{"AUS", 2},
|
||||
{"NZ ", 3}}};
|
||||
{"NZ ", 3},
|
||||
{"ISS", 4}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16},
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -30,6 +31,30 @@ static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
File firmware_file;
|
||||
uint32_t read_buffer[128];
|
||||
uint32_t checksum{(uint32_t)~FLASH_EXPECTED_CHECKSUM}; // initializing to invalid checksum in case file can't be read
|
||||
|
||||
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
|
||||
auto result = firmware_file.open(path.c_str());
|
||||
if (!result.is_valid()) {
|
||||
checksum = 0;
|
||||
for (uint32_t i = 0; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
|
||||
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
|
||||
|
||||
// if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum
|
||||
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
|
||||
checksum = FLASH_EXPECTED_CHECKSUM;
|
||||
break;
|
||||
}
|
||||
|
||||
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
|
||||
}
|
||||
}
|
||||
return (checksum == FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
|
||||
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&labels,
|
||||
@@ -50,7 +75,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.ppfw.tar")) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.tar")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
@@ -98,21 +123,23 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
||||
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")) {
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str())) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
|
||||
@@ -32,8 +32,14 @@
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#define FLASH_ROM_SIZE 1048576
|
||||
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path);
|
||||
|
||||
class FlashUtilityView : public View {
|
||||
public:
|
||||
FlashUtilityView(NavigationView& nav);
|
||||
|
||||
@@ -277,18 +277,9 @@ NumbersStationView::NumbersStationView(
|
||||
symfield_code.set_offset(10, 12); // End
|
||||
|
||||
/*
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
|
||||
// Thanks, Sakamoto-sama !
|
||||
y = datetime.year();
|
||||
m = datetime.month();
|
||||
d = datetime.day();
|
||||
y -= m < 3;
|
||||
dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7;
|
||||
|
||||
dayofweek = rtc_time::current_day_of_week();
|
||||
text_title.set(day_of_week[dayofweek]);
|
||||
*/
|
||||
*/
|
||||
|
||||
button_exit.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace fs = std::filesystem;
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
@@ -60,10 +61,9 @@ SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav) {
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
const auto model = this->form_collect();
|
||||
const rtc::RTC new_datetime{
|
||||
model.year, model.month, model.day,
|
||||
model.hour, model.minute, model.second};
|
||||
rtcSetTime(&RTCD1, &new_datetime);
|
||||
rtc::RTC new_datetime{model.year, model.month, model.day, model.hour, model.minute, model.second};
|
||||
pmem::set_config_dst(model.dst);
|
||||
rtc_time::set(new_datetime); // NB: 1 hour will be subtracted if value is stored in RTC during DST
|
||||
nav.pop();
|
||||
},
|
||||
|
||||
@@ -79,20 +79,49 @@ SetDateTimeView::SetDateTimeView(
|
||||
&field_hour,
|
||||
&field_minute,
|
||||
&field_second,
|
||||
&text_weekday,
|
||||
&text_day_of_year,
|
||||
&checkbox_dst_enable,
|
||||
&options_dst_start_which,
|
||||
&options_dst_start_weekday,
|
||||
&options_dst_start_month,
|
||||
&options_dst_end_which,
|
||||
&options_dst_end_weekday,
|
||||
&options_dst_end_month,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
// Populate DST options (same string text for start & end)
|
||||
options_dst_start_which.set_options(which_options);
|
||||
options_dst_end_which.set_options(which_options);
|
||||
options_dst_start_weekday.set_options(weekday_options);
|
||||
options_dst_end_weekday.set_options(weekday_options);
|
||||
options_dst_start_month.set_options(month_options);
|
||||
options_dst_end_month.set_options(month_options);
|
||||
|
||||
const auto date_changed_fn = [this](int32_t) {
|
||||
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
|
||||
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
|
||||
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
|
||||
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
|
||||
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
|
||||
};
|
||||
|
||||
field_year.on_change = date_changed_fn;
|
||||
field_month.on_change = date_changed_fn;
|
||||
field_day.on_change = date_changed_fn;
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
SetDateTimeModel model{
|
||||
datetime.year(),
|
||||
datetime.month(),
|
||||
datetime.day(),
|
||||
datetime.hour(),
|
||||
datetime.minute(),
|
||||
datetime.second()};
|
||||
|
||||
datetime.second(),
|
||||
pmem::config_dst()};
|
||||
form_init(model);
|
||||
}
|
||||
|
||||
@@ -107,16 +136,32 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
|
||||
field_hour.set_value(model.hour);
|
||||
field_minute.set_value(model.minute);
|
||||
field_second.set_value(model.second);
|
||||
checkbox_dst_enable.set_value(model.dst.b.dst_enabled);
|
||||
options_dst_start_which.set_by_value(model.dst.b.start_which);
|
||||
options_dst_start_weekday.set_by_value(model.dst.b.start_weekday);
|
||||
options_dst_start_month.set_by_value(model.dst.b.start_month);
|
||||
options_dst_end_which.set_by_value(model.dst.b.end_which);
|
||||
options_dst_end_weekday.set_by_value(model.dst.b.end_weekday);
|
||||
options_dst_end_month.set_by_value(model.dst.b.end_month);
|
||||
}
|
||||
|
||||
SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
pmem::dst_config_t dst;
|
||||
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
|
||||
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
|
||||
dst.b.start_weekday = static_cast<uint8_t>(options_dst_start_weekday.selected_index_value());
|
||||
dst.b.start_month = static_cast<uint8_t>(options_dst_start_month.selected_index_value());
|
||||
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
|
||||
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
|
||||
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value())};
|
||||
.second = static_cast<uint8_t>(field_second.value()),
|
||||
.dst = dst};
|
||||
}
|
||||
|
||||
/* SetRadioView ******************************************/
|
||||
@@ -657,6 +702,30 @@ void AppSettingsView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
/* SetConfigModeView ************************************/
|
||||
|
||||
SetConfigModeView::SetConfigModeView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&checkbox_config_mode_enabled,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_config_mode_enabled.set_value(!pmem::config_disable_config_mode());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_config_disable_config_mode(!checkbox_config_mode_enabled.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetConfigModeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
@@ -667,15 +736,16 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
|
||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [&nav]() { nav.push<SetConfigModeView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
|
||||
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
|
||||
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
|
||||
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ struct SetDateTimeModel {
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
portapack::persistent_memory::dst_config_t dst;
|
||||
};
|
||||
|
||||
class SetDateTimeView : public View {
|
||||
@@ -55,56 +56,111 @@ class SetDateTimeView : public View {
|
||||
std::string title() const override { return "Date/Time"; };
|
||||
|
||||
private:
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
std::vector<option_t> which_options = {{"1st", 0}, {"2nd", 1}, {"3rd", 2}, {"4th", 3}, {"Last", 4}};
|
||||
std::vector<option_t> weekday_options = {{"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6}};
|
||||
std::vector<option_t> month_options = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}};
|
||||
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjust the RTC clock date &", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "time. If clock resets after", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "reboot, coin batt. is dead. ", Color::light_grey()},
|
||||
{{5 * 8, 8 * 16 - 2}, "YYYY-MM-DD HH:MM:SS", Color::grey()},
|
||||
{{9 * 8, 9 * 16}, "- - : :", Color::light_grey()}};
|
||||
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Color::grey()},
|
||||
{{5 * 8, 6 * 16}, "- - : :", Color::light_grey()},
|
||||
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Color::light_grey()},
|
||||
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Color::light_grey()}};
|
||||
|
||||
NumberField field_year{
|
||||
{5 * 8, 9 * 16},
|
||||
{1 * 8, 6 * 16},
|
||||
4,
|
||||
{2015, 2099},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
NumberField field_month{
|
||||
{10 * 8, 9 * 16},
|
||||
{6 * 8, 6 * 16},
|
||||
2,
|
||||
{1, 12},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
NumberField field_day{
|
||||
{13 * 8, 9 * 16},
|
||||
{9 * 8, 6 * 16},
|
||||
2,
|
||||
{1, 31},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
|
||||
NumberField field_hour{
|
||||
{16 * 8, 9 * 16},
|
||||
{12 * 8, 6 * 16},
|
||||
2,
|
||||
{0, 23},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
NumberField field_minute{
|
||||
{19 * 8, 9 * 16},
|
||||
{15 * 8, 6 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
NumberField field_second{
|
||||
{22 * 8, 9 * 16},
|
||||
{18 * 8, 6 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
'0',
|
||||
true,
|
||||
};
|
||||
Text text_weekday{
|
||||
{22 * 8, 6 * 16, 3 * 8, 16},
|
||||
""};
|
||||
Text text_day_of_year{
|
||||
{26 * 8, 6 * 16, 3 * 8, 16},
|
||||
""};
|
||||
|
||||
Checkbox checkbox_dst_enable{
|
||||
{2 * 8, 9 * 16},
|
||||
23,
|
||||
"Enable Daylight Savings"};
|
||||
|
||||
OptionsField options_dst_start_which{
|
||||
{15 * 8, 12 * 16},
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField options_dst_start_weekday{
|
||||
{20 * 8, 12 * 16},
|
||||
3,
|
||||
{}};
|
||||
|
||||
OptionsField options_dst_start_month{
|
||||
{27 * 8, 12 * 16},
|
||||
3,
|
||||
{}};
|
||||
|
||||
OptionsField options_dst_end_which{
|
||||
{15 * 8, 13 * 16},
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField options_dst_end_weekday{
|
||||
{20 * 8, 13 * 16},
|
||||
3,
|
||||
{}};
|
||||
|
||||
OptionsField options_dst_end_month{
|
||||
{27 * 8, 13 * 16},
|
||||
3,
|
||||
{}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
@@ -581,6 +637,36 @@ class AppSettingsView : public View {
|
||||
true};
|
||||
};
|
||||
|
||||
class SetConfigModeView : public View {
|
||||
public:
|
||||
SetConfigModeView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Config Mode"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Controls whether firmware", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "will enter Config Mode", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "after a boot failure.", Color::light_grey()},
|
||||
};
|
||||
|
||||
Checkbox checkbox_config_mode_enabled{
|
||||
{2 * 8, 6 * 16},
|
||||
16,
|
||||
"Config Mode enable"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -27,15 +27,23 @@
|
||||
void config_mode_blink_until_dfu();
|
||||
|
||||
void config_mode_set() {
|
||||
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
|
||||
uint32_t cms = portapack::persistent_memory::config_mode_storage_direct();
|
||||
if ((cms >= CONFIG_MODE_GUARD_VALUE) && (cms < CONFIG_MODE_LIMIT_VALUE))
|
||||
cms += 1;
|
||||
else
|
||||
cms = CONFIG_MODE_GUARD_VALUE;
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(cms);
|
||||
}
|
||||
|
||||
bool config_mode_should_enter() {
|
||||
return portapack::persistent_memory::config_mode_storage() == CONFIG_MODE_GUARD_VALUE;
|
||||
if (portapack::persistent_memory::config_disable_config_mode_direct())
|
||||
return false;
|
||||
else
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
void config_mode_clear() {
|
||||
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
|
||||
}
|
||||
|
||||
uint32_t blink_patterns[] = {
|
||||
|
||||
@@ -28,9 +28,18 @@
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
// number of boot failures before entering config menu mode
|
||||
#define BOOT_FAILURES_BEFORE_CONFIG_MODE 3
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 0xbadb0000
|
||||
#define CONFIG_MODE_LIMIT_VALUE (CONFIG_MODE_GUARD_VALUE + BOOT_FAILURES_BEFORE_CONFIG_MODE - 1)
|
||||
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
|
||||
|
||||
void config_mode_set();
|
||||
bool config_mode_should_enter();
|
||||
void config_mode_clear();
|
||||
void config_mode_enable(bool v);
|
||||
bool config_mode_disabled();
|
||||
|
||||
void config_mode_run();
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ bool memory_dump(uint32_t* addr_start, uint32_t num_words, bool stack_flag) {
|
||||
int n{0};
|
||||
bool data_found{false};
|
||||
|
||||
make_new_directory(debug_dir);
|
||||
ensure_directory(debug_dir);
|
||||
filename = next_filename_matching_pattern(debug_dir + "/" + (stack_flag ? "STACK" : "MEMORY") + "_DUMP_????.TXT");
|
||||
error = filename.empty();
|
||||
if (!error)
|
||||
|
||||
@@ -164,9 +164,8 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_USB) {
|
||||
handle_usb();
|
||||
}
|
||||
handle_usb_transfer();
|
||||
handle_usb();
|
||||
|
||||
if (events & EVT_MASK_SWITCHES) {
|
||||
handle_switches();
|
||||
@@ -225,6 +224,10 @@ void EventDispatcher::handle_usb() {
|
||||
portapack::usb_serial.dispatch();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_usb_transfer() {
|
||||
portapack::usb_serial.dispatch_transfer();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_shell() {
|
||||
if (waiting_for_shellmode) {
|
||||
waiting_for_shellmode = false;
|
||||
|
||||
@@ -122,6 +122,7 @@ class EventDispatcher {
|
||||
void handle_local_queue();
|
||||
void handle_rtc_tick();
|
||||
void handle_usb();
|
||||
void handle_usb_transfer();
|
||||
void handle_shell();
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
|
||||
|
||||
@@ -54,6 +54,11 @@ set(EXTCPPSRC
|
||||
external/spainter/ui_spectrum_painter.cpp
|
||||
external/spainter/ui_spectrum_painter_text.cpp
|
||||
external/spainter/ui_spectrum_painter_image.cpp
|
||||
|
||||
#keyfob
|
||||
external/keyfob/main.cpp
|
||||
external/keyfob/ui_keyfob.cpp
|
||||
external/keyfob/ui_keyfob.hpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -70,4 +75,5 @@ set(EXTAPPLIST
|
||||
jammer
|
||||
gpssim
|
||||
spainter
|
||||
keyfob
|
||||
)
|
||||
|
||||
+7
@@ -30,6 +30,7 @@ MEMORY
|
||||
ram_external_app_jammer(rwx) : org = 0xEEF30000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xEEF40000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xEEF50000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xEEF60000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -116,4 +117,10 @@ SECTIONS
|
||||
*(*ui*external_app*spainter*);
|
||||
} > ram_external_app_spainter
|
||||
|
||||
.external_app_keyfob : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_keyfob.application_information));
|
||||
*(*ui*external_app*keyfob*);
|
||||
} > ram_external_app_keyfob
|
||||
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_keyfob.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::keyfob {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<KeyfobView>();
|
||||
}
|
||||
} // namespace ui::external_app::keyfob
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_keyfob.application_information"), used)) application_information_t _application_information_keyfob = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::keyfob::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Keyfob",
|
||||
/*.bitmap_data = */ {
|
||||
0x30,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0xFC,
|
||||
0x00,
|
||||
0xCE,
|
||||
0x01,
|
||||
0x86,
|
||||
0x01,
|
||||
0xFE,
|
||||
0x01,
|
||||
0x86,
|
||||
0x31,
|
||||
0x86,
|
||||
0x49,
|
||||
0xCE,
|
||||
0x87,
|
||||
0xFC,
|
||||
0x84,
|
||||
0xFC,
|
||||
0x4B,
|
||||
0x78,
|
||||
0x30,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+2
-2
@@ -27,7 +27,7 @@
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::keyfob {
|
||||
|
||||
uint8_t KeyfobView::subaru_get_checksum() {
|
||||
uint8_t checksum = 0;
|
||||
@@ -239,4 +239,4 @@ KeyfobView::KeyfobView(
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::keyfob */
|
||||
Vendored
+2
-2
@@ -29,7 +29,7 @@
|
||||
|
||||
using namespace encoders;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::keyfob {
|
||||
|
||||
class KeyfobView : public View {
|
||||
public:
|
||||
@@ -126,4 +126,4 @@ class KeyfobView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::keyfob */
|
||||
+60
-15
@@ -13,8 +13,8 @@ typedef uint8_t byte;
|
||||
typedef uint32_t word;
|
||||
|
||||
byte SPEED = 2;
|
||||
byte MAXLIFES = 5;
|
||||
byte LIFES = START_LIFES;
|
||||
byte MAXLIFES = 20;
|
||||
size_t LIFES = START_LIFES;
|
||||
byte GAMEWIN = 0;
|
||||
byte GAMEOVER = 0;
|
||||
byte DEMO = 1;
|
||||
@@ -25,6 +25,9 @@ byte GAMEPAUSED = 0;
|
||||
|
||||
byte PACMANFALLBACK = 0;
|
||||
|
||||
bool cheat_level = false;
|
||||
bool cheat_lifes = false;
|
||||
|
||||
#include "DrawIndexedMap.h"
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -901,14 +904,15 @@ class Playfield {
|
||||
|
||||
void PackmanDied() { // Noooo... PACMAN DIED :(
|
||||
|
||||
if (LIFES <= 0) {
|
||||
if (LIFES <= 0 && !cheat_lifes) {
|
||||
GAMEOVER = 1;
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
DEMO = 1;
|
||||
Init();
|
||||
} else {
|
||||
LIFES--;
|
||||
if (!cheat_lifes)
|
||||
LIFES--;
|
||||
|
||||
_inited = true;
|
||||
_state = ReadyState;
|
||||
@@ -934,8 +938,14 @@ class Playfield {
|
||||
_icons[13 - i] = BONUSICON + i;
|
||||
}
|
||||
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (!cheat_lifes) {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < 14; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1177,8 +1187,13 @@ class Playfield {
|
||||
if (GAMEWIN == 1) {
|
||||
GAMEWIN = 0;
|
||||
} else {
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
if (!cheat_level) {
|
||||
LEVEL = START_LEVEL;
|
||||
}
|
||||
if (!cheat_lifes) {
|
||||
LIFES = START_LIFES;
|
||||
}
|
||||
|
||||
ACTUALBONUS = 0; // actual bonus icon
|
||||
ACTIVEBONUS = 0; // status of bonus
|
||||
|
||||
@@ -1215,8 +1230,14 @@ class Playfield {
|
||||
}
|
||||
|
||||
// SET Lifes icons
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (cheat_lifes) {
|
||||
for (byte i = 0; i < 14; i++) { // cuz 14 lives full fills PP's screen
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1243,21 +1264,45 @@ class Playfield {
|
||||
}
|
||||
|
||||
void Step() {
|
||||
int16_t keys = 0;
|
||||
|
||||
if (GAMEWIN == 1) {
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
LEVEL++;
|
||||
Init();
|
||||
}
|
||||
|
||||
// Start GAME
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) {
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) { // start
|
||||
but_A = false;
|
||||
DEMO = 0;
|
||||
Init();
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // Or PAUSE GAME
|
||||
DEMO = 0;
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // pause
|
||||
but_A = false;
|
||||
GAMEPAUSED = 1;
|
||||
} else if (but_LEFT && DEMO == 1 && GAMEPAUSED == 0) { // -level
|
||||
cheat_level = true;
|
||||
but_LEFT = false;
|
||||
if (LEVEL > 1) {
|
||||
LEVEL--;
|
||||
}
|
||||
Init();
|
||||
} else if (but_RIGHT && DEMO == 1 && GAMEPAUSED == 0) { // +level
|
||||
cheat_level = true;
|
||||
but_RIGHT = false;
|
||||
if (LEVEL < 255) {
|
||||
LEVEL++;
|
||||
}
|
||||
Init();
|
||||
} else if (but_UP && DEMO == 1 && GAMEPAUSED == 0) { // full of lifes
|
||||
cheat_lifes = true;
|
||||
but_UP = false;
|
||||
Init();
|
||||
} else if (but_DOWN && DEMO == 1 && GAMEPAUSED == 0) { // reset
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
but_DOWN = false;
|
||||
LIFES = START_LIFES;
|
||||
Init();
|
||||
}
|
||||
|
||||
if (GAMEPAUSED && but_A && DEMO == 0) {
|
||||
|
||||
@@ -33,10 +33,10 @@ namespace max283x {
|
||||
namespace lo {
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 4> band{{
|
||||
{2300000000, 2400000000},
|
||||
{2170000000, 2400000000},
|
||||
{2400000000, 2500000000},
|
||||
{2500000000, 2600000000},
|
||||
{2600000000, 2700000000},
|
||||
{2600000000, 2740000000},
|
||||
}};
|
||||
|
||||
} /* namespace lo */
|
||||
@@ -48,11 +48,6 @@ namespace lna {
|
||||
constexpr range_t<int8_t> gain_db_range{0, 40};
|
||||
constexpr int8_t gain_db_step = 8;
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 2> band{{
|
||||
{2300000000, 2500000000},
|
||||
{2500000000, 2700000000},
|
||||
}};
|
||||
|
||||
} /* namespace lna */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
@@ -177,7 +177,7 @@ enum class PortaPackModel {
|
||||
static bool save_config(int8_t value) {
|
||||
persistent_memory::set_config_cpld(value);
|
||||
if (sd_card::status() == sd_card::Status::Mounted) {
|
||||
make_new_directory("/hardware");
|
||||
ensure_directory("/hardware");
|
||||
File file;
|
||||
auto sucess = file.create("/hardware/settings.txt");
|
||||
if (!sucess.is_valid()) {
|
||||
@@ -411,6 +411,7 @@ bool init() {
|
||||
|
||||
/* Cache some configuration data from persistent memory. */
|
||||
persistent_memory::cache::init();
|
||||
rtc_time::dst_init();
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
clock_manager.init_clock_generator();
|
||||
|
||||
@@ -184,11 +184,13 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
if (tuning_config.is_valid()) {
|
||||
first_if.disable();
|
||||
|
||||
// Program first local oscillator frequency (if there is one) into RFFC507x
|
||||
if (tuning_config.first_lo_frequency) {
|
||||
first_if.set_frequency(tuning_config.first_lo_frequency);
|
||||
first_if.enable();
|
||||
}
|
||||
|
||||
// Program second local oscillator frequency into MAX283x
|
||||
const auto result_second_if = second_if->set_frequency(tuning_config.second_lo_frequency);
|
||||
|
||||
rf_path.set_band(tuning_config.rf_path_band);
|
||||
|
||||
@@ -39,8 +39,8 @@ enum class Direction {
|
||||
|
||||
namespace path {
|
||||
|
||||
constexpr FrequencyRange band_low{0, 2170000000};
|
||||
constexpr FrequencyRange band_high{2740000000, 7250000000};
|
||||
constexpr FrequencyRange band_low{0, 2170'000'000};
|
||||
constexpr FrequencyRange band_high{2740'000'000, 7250'000'000};
|
||||
constexpr FrequencyRange band_mid{band_low.maximum, band_high.minimum};
|
||||
|
||||
enum class Band {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -20,24 +21,239 @@
|
||||
*/
|
||||
|
||||
#include "rtc_time.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace rtc_time {
|
||||
|
||||
Signal<> signal_tick_second;
|
||||
|
||||
bool dst_enabled{false};
|
||||
bool dst_in_range{false};
|
||||
uint16_t dst_start_doy;
|
||||
uint16_t dst_end_doy;
|
||||
uint16_t dst_current_doy{0xFFFF};
|
||||
uint16_t dst_current_year{0xFFFF};
|
||||
|
||||
static const uint16_t months_with_31 = 0x0AD5; // Bits represents months with 31 days (bit 0 for January, etc)
|
||||
|
||||
void on_tick_second() {
|
||||
signal_tick_second.emit();
|
||||
}
|
||||
|
||||
// Check if DST is configured and active (called at power-up)
|
||||
void dst_init() {
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
dst_update_date_range(datetime.year(), LPC_RTC->DOY);
|
||||
}
|
||||
|
||||
// Return current time & date (adjusted for DST if enabled)
|
||||
rtc::RTC now() {
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
if (dst_enabled) {
|
||||
datetime = dst_adjust_returned_time(datetime);
|
||||
}
|
||||
return datetime;
|
||||
}
|
||||
|
||||
rtc::RTC now(rtc::RTC& out_datetime) {
|
||||
rtcGetTime(&RTCD1, &out_datetime);
|
||||
if (dst_enabled) {
|
||||
out_datetime = dst_adjust_returned_time(out_datetime);
|
||||
}
|
||||
return out_datetime;
|
||||
}
|
||||
|
||||
// Add 1 hour (spring forward) if needed for DST (called whenever RTC is read if DST is enabled)
|
||||
rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime) {
|
||||
// Read day of year from RTC and check for change
|
||||
uint32_t doy = LPC_RTC->DOY;
|
||||
|
||||
// Update DST start/end day-of-year only when year changes, otherwise just check if in range when day changes
|
||||
if (doy != dst_current_doy) {
|
||||
if (datetime.year() != dst_current_year)
|
||||
dst_update_date_range(datetime.year(), doy);
|
||||
else
|
||||
dst_check_date_range(doy);
|
||||
}
|
||||
|
||||
// Not in DST date range
|
||||
if (!dst_in_range)
|
||||
return datetime;
|
||||
|
||||
// Add 1 hour to RTC time
|
||||
uint16_t year = datetime.year();
|
||||
uint8_t month = datetime.month();
|
||||
uint8_t day = datetime.day();
|
||||
uint8_t hour = datetime.hour();
|
||||
|
||||
if (++hour > 23) {
|
||||
hour = 0;
|
||||
if (++day > days_per_month(year, month)) {
|
||||
day = 1;
|
||||
if (++month > 12) {
|
||||
year++;
|
||||
}
|
||||
}
|
||||
}
|
||||
rtc::RTC dst_datetime{year, month, day, hour, datetime.minute(), datetime.second()};
|
||||
return dst_datetime;
|
||||
}
|
||||
|
||||
// Check if current date is within the DST range (called when date changes)
|
||||
void dst_check_date_range(uint16_t doy) {
|
||||
dst_current_doy = doy;
|
||||
|
||||
// Check if date is within DST range
|
||||
// (note that dates are reversed in Southern hemisphere because Summer starts in December)
|
||||
if (dst_start_doy <= dst_end_doy)
|
||||
dst_in_range = ((doy >= dst_start_doy) && (doy < dst_end_doy));
|
||||
else
|
||||
dst_in_range = ((doy >= dst_start_doy) || (doy < dst_end_doy));
|
||||
}
|
||||
|
||||
// Update DST parameters (called at power-up, when year changes, or DST settings are changed)
|
||||
void dst_update_date_range(uint16_t year, uint16_t doy) {
|
||||
dst_enabled = pmem::dst_enabled();
|
||||
if (dst_enabled) {
|
||||
const pmem::dst_config_t dst = pmem::config_dst();
|
||||
dst_current_year = year;
|
||||
dst_start_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.start_month, dst.b.start_which, dst.b.start_weekday);
|
||||
dst_end_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.end_month, dst.b.end_which, dst.b.end_weekday);
|
||||
|
||||
dst_check_date_range(doy);
|
||||
} else {
|
||||
dst_in_range = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set RTC clock.
|
||||
// If the user has enabled DST, the time entered and passed to this function is interpreted as having been adjusted for DST.
|
||||
// When DST functionality is enabled in pmem, the value stored in the RTC hardware is non-DST time, and we only fudge the time when read.
|
||||
// Thus, it's necessary to subtract an hour before storing it in the RTC if we're in the DST date range.
|
||||
// (If RTC is desired to represent UTC, then DST should obviously be disabled in settings.)
|
||||
// NB: Firmware should not change RTC without going through this function.
|
||||
void set(rtc::RTC& new_datetime) {
|
||||
uint16_t year = new_datetime.year();
|
||||
uint8_t month = new_datetime.month();
|
||||
uint8_t day = new_datetime.day();
|
||||
uint8_t hour = new_datetime.hour();
|
||||
|
||||
// NB: DST code relies on the Day of Year (DOY) value to be initialized in the RTC by firmware (RTC hardware only does increment and wrap)
|
||||
uint16_t doy = day_of_year(year, month, day);
|
||||
dst_update_date_range(year, doy);
|
||||
|
||||
// NB: Currently we only support the DST transition at midnight RTC time (not configurable to hour granularity).
|
||||
// Note on handling the the hour before/after DST time change:
|
||||
//
|
||||
// If entered hour==0 on dst_start_day:
|
||||
// Time entered is INVALID due to spring-forward; code below rolls back RTC to last hour of previous day.
|
||||
//
|
||||
// If entered hour==0 on dst_end_doy:
|
||||
// This hour occurs twice due to fall-back; code below treats as if DST has ended (the second occurrence) and doesn't roll back RTC
|
||||
//
|
||||
if (dst_in_range) {
|
||||
// Subtract 1 hour from requested time before storing in RTC
|
||||
if (hour-- == 0) {
|
||||
hour = 23;
|
||||
if (day-- == 0) {
|
||||
if (month-- == 0) {
|
||||
month = 12;
|
||||
year--;
|
||||
}
|
||||
day = days_per_month(year, month);
|
||||
}
|
||||
}
|
||||
|
||||
// Update day-of-year if date was changed above
|
||||
if (day != new_datetime.day()) {
|
||||
doy = day_of_year(year, month, day);
|
||||
dst_update_date_range(year, doy);
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Writing RTC twice takes a second, but ensures that new value can be read back immediately
|
||||
// (if not written twice, the old value will be returned if read back in the following 1-2 seconds)
|
||||
// (you will notice with older Mayhem versions that running the Date/Time Settings app again quickly will show the old time)
|
||||
LPC_RTC->DOY = doy;
|
||||
rtc::RTC adjusted_datetime{year, month, day, hour, new_datetime.minute(), new_datetime.second()};
|
||||
rtcSetTime(&RTCD1, &adjusted_datetime);
|
||||
rtcSetTime(&RTCD1, &adjusted_datetime);
|
||||
}
|
||||
|
||||
// Calculates 1-based day of year for Nth weekday in month (weekday and n are 0-based, month is 1-based)
|
||||
uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday) {
|
||||
uint8_t w = day_of_week(year, month, 1);
|
||||
uint8_t nn = n;
|
||||
|
||||
// special handling for "last" weekday - are there 4 or 5 in the month?
|
||||
if (n == 4) {
|
||||
if (month == 2) {
|
||||
// February
|
||||
// only weekday w occurs 5 times on the 29th on leap years only
|
||||
if ((weekday != w) || !leap_year(year))
|
||||
nn = 3;
|
||||
} else {
|
||||
// Other months have either 30 or 31 days;
|
||||
// weekdays w and w+1 occur 5 times (on the 29th & 30th); weekday w+2 occurs 5 times only if month has 31 days
|
||||
if ((weekday != w) && (weekday != (w + 1) % 7) &&
|
||||
((weekday != (w + 2) % 7) || ((months_with_31 & (1 << (month - 1))) == 0)))
|
||||
nn = 3;
|
||||
}
|
||||
}
|
||||
return day_of_year(year, month, 1) + (nn * 7) + weekday + ((weekday < w) ? 7 : 0) - w;
|
||||
}
|
||||
|
||||
// Calculates 1-based day of year (input month & day are 1-based)
|
||||
uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day) {
|
||||
// 1-based day of year for 1st day or month (index is 1-based month)
|
||||
static uint16_t month_to_day_in_year[1 + 12] = {
|
||||
0, // placeholder for 1-based indexing
|
||||
1,
|
||||
1 + 31,
|
||||
1 + 31 + 28,
|
||||
1 + 31 + 28 + 31,
|
||||
1 + 31 + 28 + 31 + 30,
|
||||
1 + 31 + 28 + 31 + 30 + 31,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
||||
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
||||
};
|
||||
return month_to_day_in_year[month] + day - (((month > 2) && leap_year(year)) ? 0 : 1);
|
||||
}
|
||||
|
||||
bool leap_year(uint16_t year) {
|
||||
// Technically should be: ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)
|
||||
// but year 2100 is a long way off and the LPC43xx RTC doesn't bother handling it either
|
||||
return ((year & 3) == 0);
|
||||
}
|
||||
|
||||
uint8_t days_per_month(uint16_t year, uint8_t month) {
|
||||
if (month == 2)
|
||||
return leap_year(year) ? 29 : 28;
|
||||
else
|
||||
return ((months_with_31 & (1 << (month - 1))) != 0) ? 31 : 30;
|
||||
}
|
||||
|
||||
uint8_t current_day_of_week() {
|
||||
rtc::RTC datetime;
|
||||
now(datetime);
|
||||
return day_of_week(datetime.year(), datetime.month(), datetime.day());
|
||||
}
|
||||
|
||||
// Returns 0-based weekday for date (0=Sunday, 1=Monday, etc) - using Zeller's Congruence formula
|
||||
// (month and day are 1-based)
|
||||
uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day) {
|
||||
int m = (month < 3) ? month + 13 : month + 1;
|
||||
int y = (month < 3) ? year - 1 : year;
|
||||
return (day - 1 + (13 * m / 5) + y + (y / 4) - (y / 100) + (y / 400)) % 7;
|
||||
}
|
||||
|
||||
} /* namespace rtc_time */
|
||||
|
||||
@@ -33,12 +33,27 @@ extern Signal<> signal_tick_second;
|
||||
|
||||
void on_tick_second();
|
||||
|
||||
/* Sets the current RTCTime in the RTC. */
|
||||
void set(rtc::RTC& new_datetime);
|
||||
|
||||
/* Returns the current RTCTime from the RTC. */
|
||||
rtc::RTC now();
|
||||
|
||||
/* Returns the current RTCTime from the RTC. */
|
||||
rtc::RTC now(rtc::RTC& out_datetime);
|
||||
|
||||
/* Daylight Savings Time functions */
|
||||
void dst_init();
|
||||
rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime);
|
||||
void dst_check_date_range(uint16_t doy);
|
||||
void dst_update_date_range(uint16_t year, uint16_t doy);
|
||||
uint8_t days_per_month(uint16_t year, uint8_t month);
|
||||
uint8_t current_day_of_week();
|
||||
uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day);
|
||||
bool leap_year(uint16_t year);
|
||||
uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day);
|
||||
uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday);
|
||||
|
||||
} /* namespace rtc_time */
|
||||
|
||||
#endif /*__RTC_TIME_H__*/
|
||||
|
||||
@@ -185,7 +185,7 @@ std::string to_string_freq(const uint64_t f) {
|
||||
|
||||
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
|
||||
std::string to_string_short_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
auto final_str = to_string_dec_int((f + 50) / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
||||
|
||||
uint32_t divisor = pow10[6 - precision];
|
||||
|
||||
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
final_str = to_string_dec_uint((f + (divisor / 2)) / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
}
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ const tone_key_t tone_keys = {
|
||||
{"Senn. 32.768k", F2Ix100(32768.0)}};
|
||||
|
||||
std::string fx100_string(uint32_t f) {
|
||||
return to_string_dec_uint(f / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
|
||||
return to_string_dec_uint((f + 5) / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
|
||||
}
|
||||
|
||||
float tone_key_frequency(tone_index index) {
|
||||
|
||||
@@ -26,44 +26,43 @@
|
||||
namespace tuning {
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
// Low band <2170 Mhz:
|
||||
constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return 2650000000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_regions_2_and_3(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 5100000000)
|
||||
? (2350000000 + ((target_frequency - 3600000000) / 5))
|
||||
: (2500000000 + ((target_frequency - 5100000000) / 9));
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 3600000000)
|
||||
? (2170000000 + (((target_frequency - 2740000000) * 57) / 86))
|
||||
: high_band_second_lo_regions_2_and_3(target_frequency);
|
||||
return 2650'000'000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
Config low_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency + low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = first_lo_frequency - target_frequency;
|
||||
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency;
|
||||
const bool mixer_invert = true;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
|
||||
}
|
||||
|
||||
// Mid band 2170-2740 Mhz:
|
||||
Config mid_band(const rf::Frequency target_frequency) {
|
||||
return {0, target_frequency, rf::path::Band::Mid, false};
|
||||
const rf::Frequency second_lo_frequency = target_frequency;
|
||||
const rf::Frequency first_lo_frequency = 0;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
|
||||
}
|
||||
|
||||
// High band >2740 Mhz:
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
if (target_frequency < 3600'000'000)
|
||||
return (2170'000'000 + (((target_frequency - 2740'000'000) * 57) / 86));
|
||||
else if (target_frequency < 5100'000'000)
|
||||
return (2350'000'000 + ((target_frequency - 3600'000'000) / 5));
|
||||
else
|
||||
return (2500'000'000 + ((target_frequency - 5100'000'000) / 9));
|
||||
}
|
||||
|
||||
Config high_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency - high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = target_frequency - first_lo_frequency;
|
||||
const rf::Frequency second_lo_frequency = high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency - second_lo_frequency;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
Config create(const rf::Frequency target_frequency) {
|
||||
/* TODO: This is some lame code. */
|
||||
if (rf::path::band_low.contains(target_frequency)) {
|
||||
|
||||
@@ -65,6 +65,7 @@ GeoPos::GeoPos(
|
||||
set_lon(0);
|
||||
|
||||
const auto changed_fn = [this](int32_t) {
|
||||
// Convert degrees/minutes/seconds fields to decimal (floating point) lat/lon degree
|
||||
float lat_value = lat();
|
||||
float lon_value = lon();
|
||||
|
||||
@@ -84,6 +85,27 @@ GeoPos::GeoPos(
|
||||
field_lon_minutes.on_change = changed_fn;
|
||||
field_lon_seconds.on_change = changed_fn;
|
||||
|
||||
const auto wrapped_lat_seconds = [this](int32_t v) {
|
||||
field_lat_minutes.on_encoder(v);
|
||||
};
|
||||
|
||||
const auto wrapped_lat_minutes = [this](int32_t v) {
|
||||
field_lat_degrees.on_encoder((field_lat_degrees.value() >= 0) ? v : -v);
|
||||
};
|
||||
|
||||
const auto wrapped_lon_seconds = [this](int32_t v) {
|
||||
field_lon_minutes.on_encoder(v);
|
||||
};
|
||||
|
||||
const auto wrapped_lon_minutes = [this](int32_t v) {
|
||||
field_lon_degrees.on_encoder((field_lon_degrees.value() >= 0) ? v : -v);
|
||||
};
|
||||
|
||||
field_lat_seconds.on_wrap = wrapped_lat_seconds;
|
||||
field_lat_minutes.on_wrap = wrapped_lat_minutes;
|
||||
field_lon_seconds.on_wrap = wrapped_lon_seconds;
|
||||
field_lon_minutes.on_wrap = wrapped_lon_minutes;
|
||||
|
||||
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
|
||||
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
|
||||
if (speed_unit_ == MPH) text_speed_unit.set("mph");
|
||||
@@ -173,12 +195,8 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if (map_zoom == -2) {
|
||||
map_zoom = 1;
|
||||
} else {
|
||||
map_zoom++;
|
||||
|
||||
// When zooming in, ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
||||
if ((map_zoom > 1) && (geomap_rect_width % map_zoom != 0)) {
|
||||
map_zoom--;
|
||||
}
|
||||
// zoom in faster after exceeding the map resolution limit
|
||||
map_zoom += (map_zoom >= MAP_ZOOM_RESOLUTION_LIMIT) ? map_zoom : 1;
|
||||
}
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
@@ -186,15 +204,21 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if (map_zoom == 1) {
|
||||
map_zoom = -2;
|
||||
} else {
|
||||
map_zoom--;
|
||||
if (map_zoom > MAP_ZOOM_RESOLUTION_LIMIT)
|
||||
map_zoom /= 2;
|
||||
else
|
||||
map_zoom--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
map_visible = map_opened && (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT);
|
||||
zoom_pixel_offset = (map_visible && (map_zoom > 1)) ? (float)map_zoom / 2 : 0.0f;
|
||||
|
||||
// Trigger map redraw
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -208,7 +232,9 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
||||
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
|
||||
// Future TODO: Add dithering to smooth out the pixelation.
|
||||
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6.
|
||||
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
|
||||
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
|
||||
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < map_zoom; j++) {
|
||||
buffer[(i * map_zoom) + j] = buffer[i];
|
||||
@@ -228,91 +254,152 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
||||
}
|
||||
|
||||
void GeoMap::draw_markers(Painter& painter) {
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
draw_marker_item(painter, markerList[i], Color::blue(), Color::blue(), Color::magenta());
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor, const Color backColor) {
|
||||
const auto r = screen_rect();
|
||||
const ui::Point itemPoint = item_rect_pixel(item);
|
||||
|
||||
if ((itemPoint.x() >= 0) && (itemPoint.x() < r.width()) &&
|
||||
(itemPoint.y() > 10) && (itemPoint.y() < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
draw_marker(painter, {itemPoint.x(), itemPoint.y() + r.top()}, item.angle, item.tag, color, fontColor, backColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate screen position of item, adjusted for zoom factor.
|
||||
ui::Point GeoMap::item_rect_pixel(GeoMarker& item) {
|
||||
const auto r = screen_rect();
|
||||
const auto geomap_rect_half_width = r.width() / 2;
|
||||
const auto geomap_rect_half_height = r.height() / 2;
|
||||
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(item.lat, item.lon);
|
||||
float x = mapPoint.x - x_pos;
|
||||
float y = mapPoint.y - y_pos;
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = x * map_zoom + zoom_pixel_offset;
|
||||
y = y * map_zoom + zoom_pixel_offset;
|
||||
} else if (map_zoom < 0) {
|
||||
x = x / (-map_zoom);
|
||||
y = y / (-map_zoom);
|
||||
}
|
||||
|
||||
x += geomap_rect_half_width;
|
||||
y += geomap_rect_half_height;
|
||||
|
||||
return {(int16_t)x, (int16_t)y};
|
||||
}
|
||||
|
||||
// Converts latitude/longitude to pixel coordinates in map file.
|
||||
// (Note that when map_zoom==1, one pixel in map file corresponds to 1 pixel on screen)
|
||||
GeoPoint GeoMap::lat_lon_to_map_pixel(float lat, float lon) {
|
||||
// Using WGS 84/Pseudo-Mercator projection
|
||||
float x = (map_width * (lon + 180) / 360);
|
||||
|
||||
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
||||
double lat_rad = sin(lat * pi / 180);
|
||||
float y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset));
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
// Draw grid in place of map (when zoom-in level is too high).
|
||||
void GeoMap::draw_map_grid() {
|
||||
const auto r = screen_rect();
|
||||
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
GeoMarker& item = markerList[i];
|
||||
double lat_rad = sin(item.lat * pi / 180);
|
||||
int x = (map_width * (item.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
// Grid spacing is just based on zoom at the moment, and centered on screen.
|
||||
// TODO: Maybe align with latitude/longitude seconds instead?
|
||||
int grid_spacing = map_zoom * 2;
|
||||
int x = (r.width() / 2) % grid_spacing;
|
||||
int y = (r.height() / 2) % grid_spacing;
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
} else if (map_zoom < 0) {
|
||||
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
||||
}
|
||||
if (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT)
|
||||
return;
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (y >= 32) { // Dont draw text if it would overlap top
|
||||
// Text and symbol
|
||||
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
|
||||
} else {
|
||||
// Only symbol
|
||||
draw_bearing(itemPoint, item.angle, 10, Color::blue());
|
||||
}
|
||||
}
|
||||
display.fill_rectangle({{0, r.top()}, {r.width(), r.height()}}, Color::black());
|
||||
|
||||
for (uint16_t line = y; line < r.height(); line += grid_spacing) {
|
||||
display.fill_rectangle({{0, r.top() + line}, {r.width(), 1}}, Color::darker_grey());
|
||||
}
|
||||
for (uint16_t column = x; column < r.width(); column += grid_spacing) {
|
||||
display.fill_rectangle({{column, r.top()}, {1, r.height()}}, Color::darker_grey());
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
std::array<ui::Color, 240> map_line_buffer;
|
||||
std::array<ui::Color, geomap_rect_width> map_line_buffer;
|
||||
int16_t zoom_seek_x, zoom_seek_y;
|
||||
|
||||
// Ony redraw map if it moved by at least 1 pixel
|
||||
// or the markers list was updated
|
||||
int x_diff = abs(x_pos - prev_x_pos);
|
||||
int y_diff = abs(y_pos - prev_y_pos);
|
||||
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
|
||||
if (map_zoom <= 1) {
|
||||
// Zooming out, or no zoom
|
||||
const int min_diff = abs(map_zoom);
|
||||
if ((int)abs(x_pos - prev_x_pos) >= min_diff)
|
||||
redraw_map = true;
|
||||
else if ((int)abs(y_pos - prev_y_pos) >= min_diff)
|
||||
redraw_map = true;
|
||||
} else {
|
||||
// Zooming in; magnify position differences (utilizing zoom_pixel_offset)
|
||||
if ((int)(abs(x_pos - prev_x_pos) * map_zoom) >= 1)
|
||||
redraw_map = true;
|
||||
else if ((int)(abs(y_pos - prev_y_pos) * map_zoom) >= 1)
|
||||
redraw_map = true;
|
||||
}
|
||||
|
||||
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
|
||||
int32_t zoom_seek_x = x_pos;
|
||||
int32_t zoom_seek_y = y_pos;
|
||||
|
||||
// Adjust starting corner position of map per zoom setting
|
||||
if (map_zoom > 1) {
|
||||
zoom_seek_x += (r.width() - (r.width() / map_zoom)) / 2;
|
||||
zoom_seek_y += (r.height() - (r.height() / map_zoom)) / 2;
|
||||
} else if (map_zoom < 0) {
|
||||
zoom_seek_x += (r.width() - (r.width() * (-map_zoom))) / 2;
|
||||
zoom_seek_y += (r.height() - (r.height() * (-map_zoom))) / 2;
|
||||
}
|
||||
|
||||
// Read from map file and display to zoomed scale
|
||||
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
|
||||
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
|
||||
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
|
||||
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
|
||||
map_read_line(map_line_buffer.data(), r.width());
|
||||
|
||||
for (uint16_t j = 0; j < duplicate_lines; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
prev_x_pos = x_pos;
|
||||
if (redraw_map) {
|
||||
prev_x_pos = x_pos; // Note x_pos/y_pos pixel position in map file now correspond to screen rect CENTER pixel
|
||||
prev_y_pos = y_pos;
|
||||
redraw_map = false;
|
||||
|
||||
// Adjust starting corner position of map per zoom setting;
|
||||
// When zooming in the map should technically by shifted left & up by another map_zoom/2 pixels but
|
||||
// the map_read_line() function doesn't handle that yet so we're adjusting markers instead (see zoom_pixel_offset).
|
||||
if (map_zoom > 1) {
|
||||
zoom_seek_x = x_pos - (float)r.width() / (2 * map_zoom);
|
||||
zoom_seek_y = y_pos - (float)r.height() / (2 * map_zoom);
|
||||
} else {
|
||||
zoom_seek_x = x_pos - (r.width() * abs(map_zoom)) / 2;
|
||||
zoom_seek_y = y_pos - (r.height() * abs(map_zoom)) / 2;
|
||||
}
|
||||
|
||||
if (map_visible) {
|
||||
// Read from map file and display to zoomed scale
|
||||
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
|
||||
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
|
||||
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
|
||||
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
|
||||
map_read_line(map_line_buffer.data(), r.width());
|
||||
|
||||
for (uint16_t j = 0; j < duplicate_lines; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No map data or excessive zoom; just draw a grid
|
||||
draw_map_grid();
|
||||
}
|
||||
|
||||
// Draw crosshairs in center in manual panning mode
|
||||
if (manual_panning_) {
|
||||
display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(16, 1) + Point(zoom_pixel_offset, zoom_pixel_offset), {32, 2}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(1, 16) + Point(zoom_pixel_offset, zoom_pixel_offset), {2, 32}}, Color::red());
|
||||
}
|
||||
|
||||
// Draw the other markers
|
||||
draw_markers(painter);
|
||||
draw_scale(painter);
|
||||
draw_mypos();
|
||||
markerListUpdated = false;
|
||||
draw_mypos(painter);
|
||||
set_clean();
|
||||
}
|
||||
|
||||
// Draw the marker in the center
|
||||
if (!manual_panning_) {
|
||||
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
if (!manual_panning_ && !hide_center_marker_) {
|
||||
draw_marker(painter, r.center() + Point(zoom_pixel_offset, zoom_pixel_offset), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,35 +423,41 @@ bool GeoMap::on_touch(const TouchEvent event) {
|
||||
}
|
||||
|
||||
void GeoMap::move(const float lon, const float lat) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
lon_ = lon;
|
||||
lat_ = lat;
|
||||
|
||||
// Using WGS 84/Pseudo-Mercator projection
|
||||
x_pos = map_width * (lon_ + 180) / 360 - (geomap_rect_width / 2);
|
||||
|
||||
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
||||
double lat_rad = sin(lat * pi / 180);
|
||||
y_pos = map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset) - 128; // Offset added for the GUI
|
||||
// Calculate x_pos/y_pos in map file corresponding to CENTER pixel of screen rect
|
||||
// (Note there is a 1:1 correspondence between map file pixels and screen pixels when map_zoom=1)
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(lat_, lon_);
|
||||
x_pos = mapPoint.x;
|
||||
y_pos = mapPoint.y;
|
||||
|
||||
// Cap position
|
||||
if (x_pos > (map_width - geomap_rect_width))
|
||||
x_pos = map_width - geomap_rect_width;
|
||||
if (y_pos > (map_height + geomap_rect_height))
|
||||
y_pos = map_height - geomap_rect_height;
|
||||
if (x_pos > (map_width - r.width() / 2))
|
||||
x_pos = map_width - r.width() / 2;
|
||||
if (y_pos > (map_height + r.height() / 2))
|
||||
y_pos = map_height - r.height() / 2;
|
||||
|
||||
// Scale calculation
|
||||
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
|
||||
pixels_per_km = (geomap_rect_width / 2) / km_per_deg_lon;
|
||||
pixels_per_km = (r.width() / 2) / km_per_deg_lon;
|
||||
}
|
||||
|
||||
bool GeoMap::init() {
|
||||
auto result = map_file.open("ADSB/world_map.bin");
|
||||
if (result.is_valid())
|
||||
return false;
|
||||
map_opened = !result.is_valid();
|
||||
|
||||
map_file.read(&map_width, 2);
|
||||
map_file.read(&map_height, 2);
|
||||
if (map_opened) {
|
||||
map_file.read(&map_width, 2);
|
||||
map_file.read(&map_height, 2);
|
||||
} else {
|
||||
map_width = 32768;
|
||||
map_height = 32768;
|
||||
}
|
||||
|
||||
map_visible = map_opened;
|
||||
map_center_x = map_width >> 1;
|
||||
map_center_y = map_height >> 1;
|
||||
|
||||
@@ -375,7 +468,7 @@ bool GeoMap::init() {
|
||||
map_world_lon = map_width / (2 * pi);
|
||||
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
|
||||
|
||||
return true;
|
||||
return map_opened;
|
||||
}
|
||||
|
||||
void GeoMap::set_mode(GeoMapMode mode) {
|
||||
@@ -391,21 +484,35 @@ bool GeoMap::manual_panning() {
|
||||
}
|
||||
|
||||
void GeoMap::draw_scale(Painter& painter) {
|
||||
uint16_t km = 800;
|
||||
uint16_t scale_width = (map_zoom > 0) ? km * pixels_per_km * map_zoom : km * pixels_per_km / (-map_zoom);
|
||||
const auto r = screen_rect();
|
||||
uint32_t m = 800000;
|
||||
uint32_t scale_width = (map_zoom > 0) ? m * map_zoom * pixels_per_km : m * pixels_per_km / (-map_zoom);
|
||||
ui::Color scale_color = (map_visible) ? Color::black() : Color::white();
|
||||
std::string km_string;
|
||||
|
||||
while (scale_width > screen_width / 2) {
|
||||
while (scale_width > (uint32_t)r.width() * (1000 / 2)) {
|
||||
scale_width /= 2;
|
||||
km /= 2;
|
||||
m /= 2;
|
||||
}
|
||||
scale_width /= 1000;
|
||||
if (m < 1000) {
|
||||
km_string = to_string_dec_uint(m) + "m";
|
||||
} else {
|
||||
m += 50; // (add rounding factor for div by 100 below)
|
||||
uint32_t km = m / 1000;
|
||||
m -= km * 1000;
|
||||
if (m == 0) {
|
||||
km_string = to_string_dec_uint(km) + " km";
|
||||
} else {
|
||||
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint(m / 100, 1) + "km";
|
||||
}
|
||||
}
|
||||
|
||||
std::string km_string = to_string_dec_uint(km) + " km";
|
||||
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 4}, {(uint16_t)scale_width, 2}}, scale_color);
|
||||
display.fill_rectangle({{r.right() - 5, r.bottom() - 8}, {2, 6}}, scale_color);
|
||||
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 8}, {2, 6}}, scale_color);
|
||||
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 4}, {scale_width, 2}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5, screen_height - 8}, {2, 6}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 8}, {2, 6}}, Color::black());
|
||||
|
||||
painter.draw_string({(uint16_t)(screen_width - 25 - scale_width - km_string.length() * 5 / 2), screen_height - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
||||
painter.draw_string({(uint16_t)(r.right() - 25 - scale_width - km_string.length() * 5 / 2), r.bottom() - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
||||
}
|
||||
|
||||
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
||||
@@ -422,9 +529,13 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
|
||||
|
||||
size--;
|
||||
}
|
||||
|
||||
display.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
|
||||
}
|
||||
|
||||
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
int tagOffset = 10;
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
@@ -442,45 +553,15 @@ void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint
|
||||
tagOffset = 8;
|
||||
}
|
||||
// center tag above point
|
||||
if (itemTag.find_first_not_of(' ') != itemTag.npos) { // only draw tag if we have something other than spaces
|
||||
if ((itemPoint.y() - r.top() >= 32) && (itemTag.find_first_not_of(' ') != itemTag.npos)) { // only draw tag if doesn't overlap top & not just spaces
|
||||
painter.draw_string(itemPoint - Point(((int)itemTag.length() * 8 / 2), 14 + tagOffset),
|
||||
style().font, fontColor, backColor, itemTag);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_mypos() {
|
||||
const auto r = screen_rect();
|
||||
if (my_lat >= 200 || my_lon >= 200) return; // invalid
|
||||
int x = (map_width * (my_lon + 180) / 360) - x_pos;
|
||||
double lat_rad = sin(my_lat * pi / 180);
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
auto color = Color::yellow();
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
} else if (map_zoom < 0) {
|
||||
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
||||
}
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
display.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
|
||||
} else if (my_angle < 360) {
|
||||
// if we have a valid angle draw bearing
|
||||
draw_bearing(itemPoint, my_angle, 10, color);
|
||||
} else {
|
||||
// draw a small cross
|
||||
display.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
|
||||
}
|
||||
}
|
||||
void GeoMap::draw_mypos(Painter& painter) {
|
||||
if ((my_pos.lat < INVALID_LAT_LON) && (my_pos.lon < INVALID_LAT_LON))
|
||||
draw_marker_item(painter, my_pos, Color::yellow());
|
||||
}
|
||||
|
||||
void GeoMap::clear_markers() {
|
||||
@@ -488,20 +569,22 @@ void GeoMap::clear_markers() {
|
||||
}
|
||||
|
||||
MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
const auto r = screen_rect();
|
||||
MapMarkerStored ret;
|
||||
|
||||
// Check if it could be on screen
|
||||
// Only checking one direction to reduce CPU
|
||||
double lat_rad = sin(marker.lat * pi / 180);
|
||||
int x = (map_width * (marker.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
if (false == ((x >= 0) && (x < geomap_rect_width) && (y > 10) && (y < geomap_rect_height))) // Dont draw within symbol size of top
|
||||
{
|
||||
// (Shows more distant planes when zoomed out)
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(marker.lat, marker.lon);
|
||||
int x_dist = abs((int)mapPoint.x - (int)x_pos);
|
||||
int y_dist = abs((int)mapPoint.y - (int)y_pos);
|
||||
int zoom_out = (map_zoom < 0) ? -map_zoom : 1;
|
||||
|
||||
if ((x_dist >= (zoom_out * r.width() / 2)) || (y_dist >= (zoom_out * r.height() / 2))) {
|
||||
ret = MARKER_NOT_STORED;
|
||||
} else if (markerListLen < NumMarkerListElements) {
|
||||
markerList[markerListLen] = marker;
|
||||
markerListLen++;
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
ret = MARKER_STORED;
|
||||
} else {
|
||||
ret = MARKER_LIST_FULL;
|
||||
@@ -510,16 +593,17 @@ MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
}
|
||||
|
||||
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
|
||||
my_lat = lat;
|
||||
my_lon = lon;
|
||||
my_pos.lat = lat;
|
||||
my_pos.lon = lon;
|
||||
my_altitude = altitude;
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
my_angle = angle;
|
||||
my_pos.angle = angle;
|
||||
if (refresh) {
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
@@ -527,7 +611,7 @@ void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
void GeoMapView::focus() {
|
||||
geopos.focus();
|
||||
|
||||
if (!map_opened)
|
||||
if (!geomap.map_file_opened())
|
||||
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT);
|
||||
}
|
||||
|
||||
@@ -627,8 +711,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
|
||||
@@ -660,8 +743,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
add_child(&button_ok);
|
||||
|
||||
@@ -29,16 +29,30 @@
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
#define MAX_MAP_ZOOM_IN 5
|
||||
#define MAX_MAP_ZOOM_OUT 10
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define MAX_MAP_ZOOM_IN 4000
|
||||
#define MAX_MAP_ZOOM_OUT 10
|
||||
#define MAP_ZOOM_RESOLUTION_LIMIT 5 // Max zoom-in to show map; rect height & width must divide into this evenly
|
||||
|
||||
#define INVALID_LAT_LON 200
|
||||
#define INVALID_ANGLE 400
|
||||
|
||||
#define GEOMAP_BANNER_HEIGHT (3 * 16)
|
||||
#define GEOMAP_RECT_WIDTH 240
|
||||
#define GEOMAP_RECT_HEIGHT (320 - 16 - GEOMAP_BANNER_HEIGHT)
|
||||
|
||||
enum GeoMapMode {
|
||||
DISPLAY,
|
||||
PROMPT
|
||||
};
|
||||
|
||||
struct GeoPoint {
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
};
|
||||
|
||||
struct GeoMarker {
|
||||
public:
|
||||
float lat{0};
|
||||
@@ -133,13 +147,15 @@ class GeoPos : public View {
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
NumberField field_lat_seconds{
|
||||
{13 * 8, 1 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
Text text_lat_decimal{
|
||||
{17 * 8, 1 * 16, 13 * 8, 1 * 16},
|
||||
""};
|
||||
@@ -155,13 +171,15 @@ class GeoPos : public View {
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
NumberField field_lon_seconds{
|
||||
{13 * 8, 2 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
Text text_lon_decimal{
|
||||
{17 * 8, 2 * 16, 13 * 8, 1 * 16},
|
||||
""};
|
||||
@@ -201,26 +219,40 @@ class GeoMap : public Widget {
|
||||
angle_ = new_angle;
|
||||
}
|
||||
|
||||
bool map_file_opened() { return map_opened; }
|
||||
|
||||
void set_hide_center_marker(bool hide) {
|
||||
hide_center_marker_ = hide;
|
||||
}
|
||||
bool hide_center_marker() { return hide_center_marker_; }
|
||||
|
||||
static const int NumMarkerListElements = 30;
|
||||
|
||||
void clear_markers();
|
||||
MapMarkerStored store_marker(GeoMarker& marker);
|
||||
|
||||
static const Dim banner_height = 3 * 16;
|
||||
static const Dim geomap_rect_width = 240;
|
||||
static const Dim geomap_rect_height = 320 - 16 - banner_height;
|
||||
static const Dim banner_height = GEOMAP_BANNER_HEIGHT;
|
||||
static const Dim geomap_rect_width = GEOMAP_RECT_WIDTH;
|
||||
static const Dim geomap_rect_height = GEOMAP_RECT_HEIGHT;
|
||||
|
||||
private:
|
||||
void draw_scale(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
ui::Point item_rect_pixel(GeoMarker& item);
|
||||
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
|
||||
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
void draw_mypos();
|
||||
void draw_mypos(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
void draw_map_grid();
|
||||
void map_read_line(ui::Color* buffer, uint16_t pixels);
|
||||
|
||||
bool manual_panning_{false};
|
||||
bool hide_center_marker_{false};
|
||||
GeoMapMode mode_{};
|
||||
File map_file{};
|
||||
bool map_opened{};
|
||||
bool map_visible{};
|
||||
uint16_t map_width{}, map_height{};
|
||||
int32_t map_center_x{}, map_center_y{};
|
||||
int16_t map_zoom{1};
|
||||
@@ -229,23 +261,22 @@ class GeoMap : public Widget {
|
||||
double map_world_lon{};
|
||||
double map_offset{};
|
||||
|
||||
int32_t x_pos{}, y_pos{};
|
||||
int32_t prev_x_pos{0xFFFF}, prev_y_pos{0xFFFF};
|
||||
float x_pos{}, y_pos{};
|
||||
float prev_x_pos{32767.0f}, prev_y_pos{32767.0f};
|
||||
float lat_{};
|
||||
float lon_{};
|
||||
float zoom_pixel_offset{0.0f};
|
||||
float pixels_per_km{};
|
||||
uint16_t angle_{};
|
||||
std::string tag_{};
|
||||
|
||||
// the portapack's position data ( for example injected from serial )
|
||||
float my_lat{200};
|
||||
float my_lon{200};
|
||||
GeoMarker my_pos{INVALID_LAT_LON, INVALID_LAT_LON, INVALID_ANGLE, ""}; // lat, lon, angle, tag
|
||||
int32_t my_altitude{0};
|
||||
uint16_t my_angle{400};
|
||||
|
||||
int markerListLen{0};
|
||||
GeoMarker markerList[NumMarkerListElements];
|
||||
bool markerListUpdated{false};
|
||||
bool redraw_map{false};
|
||||
};
|
||||
|
||||
class GeoMapView : public View {
|
||||
@@ -303,8 +334,6 @@ class GeoMapView : public View {
|
||||
uint16_t angle_{};
|
||||
std::function<void(void)> on_close_{nullptr};
|
||||
|
||||
bool map_opened{};
|
||||
|
||||
GeoPos geopos{
|
||||
{0, 0},
|
||||
altitude_unit_,
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace ui {
|
||||
|
||||
/* static */ bool ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
File app;
|
||||
uint32_t checksum{0};
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
@@ -115,7 +116,6 @@ namespace ui {
|
||||
application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(application_information_t));
|
||||
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
@@ -135,6 +135,8 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
}
|
||||
@@ -156,6 +158,8 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)target_memory, readResult.value());
|
||||
|
||||
if (readResult.value() != bytes_to_read)
|
||||
break;
|
||||
}
|
||||
@@ -168,11 +172,16 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
|
||||
return false;
|
||||
|
||||
application_information.externalAppEntry(nav);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <size_t Width, size_t Height>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
#include "ui_fsk_rx.hpp"
|
||||
#include "ui_iq_trim.hpp"
|
||||
// #include "ui_jammer.hpp" //moved to ext
|
||||
// #include "ui_keyfob.hpp"
|
||||
// #include "ui_keyfob.hpp" //moved to ext
|
||||
// #include "ui_lcr.hpp"
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_looking_glass_app.hpp"
|
||||
@@ -111,166 +111,108 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
// When adding or removing apps from the Menu, please update it here:
|
||||
std::vector<AppInfoConsole> NavigationView::fixedAppListFC = {
|
||||
{"adsbrx", "ADS-B", RX},
|
||||
{"ais", "AIS Boats", RX},
|
||||
{"aprsrx", "APRS", RX},
|
||||
{"audio", "Audio", RX},
|
||||
{"blerx", "BLE Rx", RX},
|
||||
{"ert", "ERT Meter", RX},
|
||||
{"level", "Level", RX},
|
||||
{"pocsagrx", "POCSAG", RX},
|
||||
{"radiosnde", "Radiosnde", RX},
|
||||
{"recon", "Recon", RX},
|
||||
{"search", "Search", RX},
|
||||
{"tpms", "TPMS Cars", RX},
|
||||
{"weather", "Weather", RX},
|
||||
{"subghzd", "SubGhzD", RX},
|
||||
{"adsbtx", "ADS-B", TX},
|
||||
{"aprstx", "APRS TX", TX},
|
||||
{"bht", "BHT Xy/EP", TX},
|
||||
{"bletx", "BLE Tx", TX},
|
||||
{"morsetx", "Morse", TX},
|
||||
{"ooktx", "OOK", TX},
|
||||
{"pocsatx", "POCSAG TX", TX},
|
||||
{"rdstx", "RDS TX", TX},
|
||||
{"soundbrd", "Soundbrd", TX},
|
||||
{"sstvtx", "SSTV", TX},
|
||||
{"touchtune", "TouchTune", TX},
|
||||
{"capture", "Capture", RX},
|
||||
{"replay", "Replay", TX},
|
||||
{"remote", "Remote", TX},
|
||||
{"scanner", "Scanner", RX},
|
||||
{"microphone", "Microphone", TX},
|
||||
{"lookingglass", "Looking Glass", RX}};
|
||||
bool CstrCmp::operator()(const char* a, const char* b) const {
|
||||
return strcmp(a, b) < 0;
|
||||
}
|
||||
|
||||
static NavigationView::AppMap generate_app_map(const NavigationView::AppList& appList) {
|
||||
NavigationView::AppMap out;
|
||||
|
||||
for (auto& app : appList) {
|
||||
if (app.id == nullptr) {
|
||||
// Skip items with no id
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = out.emplace(app.id, app);
|
||||
if (!res.second) {
|
||||
chDbgPanic("Application cannot be added, ID not unique!");
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
|
||||
const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
//{"playdead", "Play dead", HOME, Color::red(), &bitmap_icon_playdead, new ViewFactory<PlayDeadView>()},
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"remote", "Remote", HOME, ui::Color::green(), &bitmap_icon_remote, new ViewFactory<RemoteView>()},
|
||||
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"about", "About", HOME, Color::cyan(), nullptr, new ViewFactory<AboutView>()},
|
||||
/* RX ********************************************************************/
|
||||
//{"acars", "ACARS", RX, Color::yellow(), &bitmap_icon_adsb, new ViewFactory<ACARSAppView>()},
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
|
||||
//{"btle", "BTLE", RX, Color::yellow(), &bitmap_icon_btle, new ViewFactory<BTLERxView>()},
|
||||
//{"blecomm", "BLE Comm", RX, ui::Color::orange(), &bitmap_icon_btle, new ViewFactory<BLECommView>()},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
|
||||
{"ert", "ERT Meter", RX, Color::green(), &bitmap_icon_ert, new ViewFactory<ERTAppView>()},
|
||||
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()},
|
||||
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
|
||||
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
|
||||
//{"lora", "LoRa", RX, Color::dark_grey(), &bitmap_icon_lora, new ViewFactory<NotImplementedView>()},
|
||||
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
|
||||
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
|
||||
/* TX ********************************************************************/
|
||||
{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
{"morse", "Morse", TX, ui::Color::green(), &bitmap_icon_morse, new ViewFactory<MorseView>()},
|
||||
//{"nuoptixdtmf", "Nuoptix DTMF", TX, ui::Color::green(), &bitmap_icon_nuoptix, new ViewFactory<NuoptixView>()},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
|
||||
{"pocsagtx", "POCSAG TX", TX, ui::Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGTXView>()},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
{"sstvtx", "SSTV", TX, ui::Color::green(), &bitmap_icon_sstv, new ViewFactory<SSTVTXView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
//{"tonesearch", "Tone Search", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<ToneSearchView>()},
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_temperature, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
|
||||
};
|
||||
|
||||
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
home(false);
|
||||
if (strcmp(name, "adsbrx") == 0) {
|
||||
push<ADSBRxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ais") == 0) {
|
||||
push<AISAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "aprsrx") == 0) {
|
||||
push<APRSRXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "audio") == 0) {
|
||||
push<AnalogAudioView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "blerx") == 0) {
|
||||
push<BLERxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ert") == 0) {
|
||||
push<ERTAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "level") == 0) {
|
||||
push<LevelView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pocsagrx") == 0) {
|
||||
push<POCSAGAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "radiosnode") == 0) {
|
||||
push<SondeView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "recon") == 0) {
|
||||
push<ReconView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "search") == 0) {
|
||||
push<SearchView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "tpms") == 0) {
|
||||
push<TPMSAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "weather") == 0) {
|
||||
push<WeatherView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "subghzd") == 0) {
|
||||
push<SubGhzDView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "adsbtx") == 0) {
|
||||
push<ADSBTxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "aprstx") == 0) {
|
||||
push<APRSTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bht") == 0) {
|
||||
push<BHTView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bletx") == 0) {
|
||||
push<BLETxView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "morsetx") == 0) {
|
||||
push<MorseView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "ooktx") == 0) {
|
||||
push<EncodersView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pocsatx") == 0) {
|
||||
push<POCSAGTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "rdstx") == 0) {
|
||||
push<RDSView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "soundbrd") == 0) {
|
||||
push<SoundBoardView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sstvtx") == 0) {
|
||||
push<SSTVTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "touchtune") == 0) {
|
||||
push<TouchTunesView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "capture") == 0) {
|
||||
push<CaptureAppView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "replay") == 0) {
|
||||
push<PlaylistView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "remote") == 0) {
|
||||
push<RemoteView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "scanner") == 0) {
|
||||
push<ScannerView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "microphone") == 0) {
|
||||
push<MicTXView>();
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "lookingglass") == 0) {
|
||||
push<GlassView>();
|
||||
|
||||
auto it = appMap.find(name);
|
||||
if (it != appMap.end()) {
|
||||
push_view(std::unique_ptr<View>(it->second.viewFactory->produce(*this)));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -561,8 +503,8 @@ void SystemStatusView::rtc_battery_workaround() {
|
||||
month = (timestamp.FAT_date >> 5) & 0xF;
|
||||
day = timestamp.FAT_date & 0x1F;
|
||||
|
||||
// bump to next month at 28 days for simplicity
|
||||
if (++day > 28) {
|
||||
// bump to next month
|
||||
if (++day > rtc_time::days_per_month(year, month)) {
|
||||
day = 1;
|
||||
if (++month > 12) {
|
||||
month = 1;
|
||||
@@ -570,7 +512,7 @@ void SystemStatusView::rtc_battery_workaround() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
make_new_file(DATE_FILEFLAG);
|
||||
|
||||
year = 1980;
|
||||
@@ -605,16 +547,16 @@ InformationView::InformationView(
|
||||
<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);
|
||||
version.set_style(&Styles::yellow);
|
||||
#else
|
||||
version.set_style(&style_infobar);
|
||||
#endif
|
||||
|
||||
if (firmware_checksum_error()) {
|
||||
version.set("FLASH ERROR");
|
||||
version.set_style(&Styles::red);
|
||||
}
|
||||
|
||||
ltime.set_style(&style_infobar);
|
||||
refresh();
|
||||
set_dirty();
|
||||
@@ -626,6 +568,17 @@ void InformationView::refresh() {
|
||||
ltime.set_date_enabled(pmem::clock_with_date());
|
||||
}
|
||||
|
||||
bool InformationView::firmware_checksum_error() {
|
||||
static bool fw_checksum_checked{false};
|
||||
static bool fw_checksum_error{false};
|
||||
|
||||
// only checking firmware checksum once per boot
|
||||
if (!fw_checksum_checked) {
|
||||
fw_checksum_error = (simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE) != FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
return fw_checksum_error;
|
||||
}
|
||||
|
||||
/* Navigation ************************************************************/
|
||||
|
||||
bool NavigationView::is_top() const {
|
||||
@@ -727,43 +680,47 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Helpers **************************************************************/
|
||||
|
||||
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, &app]() { nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView& grid) {
|
||||
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
|
||||
if (externalItems.empty()) {
|
||||
grid.add_item({"Notice",
|
||||
Color::red(),
|
||||
&bitmap_icon_debug,
|
||||
[&nav]() {
|
||||
nav.display_modal(
|
||||
"Notice",
|
||||
"External app directory empty;\n"
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to APPS folder of SD card.");
|
||||
}});
|
||||
} else {
|
||||
for (auto const& gridItem : externalItems) {
|
||||
grid.add_item(gridItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
add_item({"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }});
|
||||
}
|
||||
add_items({
|
||||
// {"ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }},
|
||||
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
|
||||
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
|
||||
//{"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }}, //moved to ext
|
||||
{"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }},
|
||||
{"Audio", Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push<AnalogAudioView>(); }},
|
||||
//{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
|
||||
//{"BLE Comm", ui::Color::orange(), &bitmap_icon_btle, [&nav]() { nav.push<BLECommView>(); }},
|
||||
{"BLE Rx", Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLERxView>(); }},
|
||||
{"ERT Meter", Color::green(), &bitmap_icon_ert, [&nav]() { nav.push<ERTAppView>(); }},
|
||||
{"Level", Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push<LevelView>(); }},
|
||||
//{"NRF", Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push<NRFRxView>(); }}, //moved to ext
|
||||
{"POCSAG", Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGAppView>(); }},
|
||||
{"Radiosnde", Color::green(), &bitmap_icon_sonde, [&nav]() { nav.push<SondeView>(); }},
|
||||
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
|
||||
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
|
||||
{"Weather", Color::green(), &bitmap_icon_thermometer, [&nav]() { nav.push<WeatherView>(); }},
|
||||
{"SubGhzD", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<SubGhzDView>(); }},
|
||||
// {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<FskxRxMainView>(); }},
|
||||
// {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"LoRa", Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"SSTV", Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"TETRA", Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
});
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::RX, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
add_apps(nav, *this, RX);
|
||||
|
||||
addExternalItems(nav, app_location_t::RX, *this);
|
||||
}
|
||||
|
||||
/* TransmittersMenuView **************************************************/
|
||||
@@ -772,31 +729,10 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"ADS-B TX", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
|
||||
{"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }},
|
||||
{"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }},
|
||||
{"BLE Tx", ui::Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLETxView>(); }},
|
||||
// {"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push<CoasterPagerView>(); }}, //moved to ext
|
||||
//{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }}, //moved to ext
|
||||
//{"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }}, //moved to ext
|
||||
// { "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); }},
|
||||
// {"LGE", ui::Color::yellow(), &bitmap_icon_lge, [&nav]() { nav.push<LGEView>(); }}, //moved to ext
|
||||
{"Morse", ui::Color::green(), &bitmap_icon_morse, [&nav]() { nav.push<MorseView>(); }},
|
||||
// { "Nuoptix DTMF", ui::Color::green(), &bitmap_icon_nuoptix, [&nav](){ nav.push<NuoptixView>(); }},
|
||||
{"OOK", ui::Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<EncodersView>(); }},
|
||||
{"POCSAG TX", ui::Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGTXView>(); }},
|
||||
{"RDS", ui::Color::green(), &bitmap_icon_rds, [&nav]() { nav.push<RDSView>(); }},
|
||||
{"Soundbrd", ui::Color::green(), &bitmap_icon_soundboard, [&nav]() { nav.push<SoundBoardView>(); }},
|
||||
//{"S.Painter", ui::Color::orange(), &bitmap_icon_paint, [&nav]() { nav.push<SpectrumPainterView>(); }},
|
||||
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
|
||||
// {"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }}, //moved to ext
|
||||
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
|
||||
});
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::TX, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
add_apps(nav, *this, TX);
|
||||
|
||||
addExternalItems(nav, app_location_t::TX, *this);
|
||||
}
|
||||
|
||||
/* UtilitiesMenuView *****************************************************/
|
||||
@@ -805,26 +741,10 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"Antenna Length", Color::green(), &bitmap_icon_tools_antenna, [&nav]() { nav.push<WhipCalcView>(); }},
|
||||
{"File Manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
|
||||
{"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
|
||||
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
|
||||
{"IQ Trim", Color::orange(), &bitmap_icon_trim, [&nav]() { nav.push<IQTrimView>(); }},
|
||||
{"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
{"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
|
||||
// {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); }},
|
||||
// {"Tone Search", Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); }},
|
||||
{"Wav View", Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
|
||||
|
||||
// Dangerous apps.
|
||||
{"Flash Utility", Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
|
||||
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
|
||||
});
|
||||
add_apps(nav, *this, UTILITIES);
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::UTILITIES, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
addExternalItems(nav, app_location_t::UTILITIES, *this);
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -844,22 +764,9 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) {
|
||||
}
|
||||
|
||||
SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
add_items({
|
||||
// {"Play dead", Color::red(), &bitmap_icon_playdead, [&nav]() { nav.push<PlayDeadView>(); }},
|
||||
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
|
||||
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
|
||||
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
|
||||
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"Remote", ui::Color::green(), &bitmap_icon_remote, [&nav]() { nav.push<RemoteView>(); }},
|
||||
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
|
||||
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
|
||||
{"Looking Glass", Color::green(), &bitmap_icon_looking, [&nav]() { nav.push<GlassView>(); }},
|
||||
{"Utilities", Color::cyan(), &bitmap_icon_utilities, [&nav]() { nav.push<UtilitiesMenuView>(); }},
|
||||
{"Settings", Color::cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SettingsMenuView>(); }},
|
||||
{"Debug", Color::light_grey(), &bitmap_icon_debug, [&nav]() { nav.push<DebugMenuView>(); }},
|
||||
{"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }},
|
||||
// {"About", Color::cyan(), nullptr, [&nav]() { nav.push<AboutView>(); }},
|
||||
});
|
||||
add_apps(nav, *this, HOME);
|
||||
|
||||
add_item({"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }});
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
set_arrow_enabled(false);
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
#ifndef __UI_NAVIGATION_H__
|
||||
#define __UI_NAVIGATION_H__
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_focus.hpp"
|
||||
@@ -41,8 +45,7 @@
|
||||
#include "lfsr_random.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include "external_app.hpp"
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "view_factory.hpp"
|
||||
|
||||
// for incrementing fake date when RTC battery is dead
|
||||
#define DATE_FILEFLAG u"/SETTINGS/DATE_FILEFLAG"
|
||||
@@ -57,6 +60,22 @@ enum modal_t {
|
||||
ABORT
|
||||
};
|
||||
|
||||
class CstrCmp {
|
||||
public:
|
||||
bool operator()(const char* a, const char* b) const;
|
||||
};
|
||||
|
||||
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
|
||||
class AppInfo {
|
||||
public:
|
||||
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
|
||||
const char* displayName;
|
||||
app_location_t menuLocation;
|
||||
Color iconColor;
|
||||
const Bitmap* icon;
|
||||
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
|
||||
};
|
||||
|
||||
struct AppInfoConsole {
|
||||
const char* appCallName;
|
||||
const char* appFriendlyName;
|
||||
@@ -106,8 +125,14 @@ class NavigationView : public View {
|
||||
* Returns true if the handler was bound successfully. */
|
||||
bool set_on_pop(std::function<void()> on_pop);
|
||||
|
||||
static std::vector<AppInfoConsole> fixedAppListFC; // fixed app list for console. vector, so can be incomplete and still iterateable
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in fixedAppListFC. This is to start apps from console
|
||||
// App list is used to preserve order, so the menu items in the menu grid can stay in place
|
||||
// App map is used to look up apps by id used by serial app start
|
||||
using AppMap = std::map<const char*, const AppInfo&, CstrCmp>;
|
||||
using AppList = std::vector<AppInfo>;
|
||||
static const AppMap appMap;
|
||||
static const AppList appList;
|
||||
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
@@ -261,6 +286,7 @@ class InformationView : public View {
|
||||
public:
|
||||
InformationView(NavigationView& nav);
|
||||
void refresh();
|
||||
bool firmware_checksum_error();
|
||||
|
||||
private:
|
||||
// static constexpr auto version_string = "v1.4.4"; // This is commented out as we are now setting the version via ENV (VERSION_STRING=v1.0.0)
|
||||
|
||||
@@ -181,7 +181,7 @@ void RecordView::start() {
|
||||
|
||||
std::filesystem::path base_path;
|
||||
if (filename_date_frequency) {
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
|
||||
// ISO 8601
|
||||
std::string date_time =
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
extern "C" {
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "usb_serial_cdc.h"
|
||||
}
|
||||
|
||||
#include "usb_serial_shell.hpp"
|
||||
#include "usb_serial.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "usb_serial_host_to_device.hpp"
|
||||
|
||||
#include <libopencm3/cm3/common.h>
|
||||
#include <libopencm3/lpc43xx/usb.h>
|
||||
@@ -24,6 +25,7 @@ void USBSerial::initialize() {
|
||||
|
||||
init_serial_usb_driver(&SUSBD1);
|
||||
shellInit();
|
||||
init_host_to_device();
|
||||
}
|
||||
|
||||
void USBSerial::dispatch() {
|
||||
@@ -35,7 +37,11 @@ void USBSerial::dispatch() {
|
||||
create_shell(_eventDispatcher);
|
||||
}
|
||||
|
||||
bulk_out_receive();
|
||||
schedule_host_to_device_transfer();
|
||||
}
|
||||
|
||||
void USBSerial::dispatch_transfer() {
|
||||
complete_host_to_device_transfer();
|
||||
}
|
||||
|
||||
void USBSerial::on_channel_opened() {
|
||||
|
||||
@@ -32,6 +32,7 @@ class USBSerial {
|
||||
public:
|
||||
void initialize();
|
||||
void dispatch();
|
||||
void dispatch_transfer();
|
||||
void on_channel_opened();
|
||||
void on_channel_closed();
|
||||
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
|
||||
|
||||
@@ -31,11 +31,16 @@ static Thread* thread_usb_event = NULL;
|
||||
|
||||
CH_IRQ_HANDLER(USB0_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
const uint32_t status = USB0_USBSTS_D & USB0_USBINTR_D;
|
||||
|
||||
usb0_isr();
|
||||
|
||||
chSysLockFromIsr();
|
||||
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
|
||||
chSysUnlockFromIsr();
|
||||
if (status & USB0_USBSTS_D_UI) {
|
||||
chSysLockFromIsr();
|
||||
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ uint8_t usb_descriptor_configuration_full_speed[] = {
|
||||
USB_INT_IN_EP_ADDR, // bEndpointAddress
|
||||
0x03, // bmAttributes: BULK
|
||||
USB_WORD(16), // wMaxPacketSize
|
||||
0xFF, // bInterval: no NAK
|
||||
0x20, // bInterval: no NAK
|
||||
|
||||
9, // bLength
|
||||
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
|
||||
@@ -203,7 +203,7 @@ uint8_t usb_descriptor_configuration_high_speed[] = {
|
||||
USB_INT_IN_EP_ADDR, // bEndpointAddress
|
||||
0x03, // bmAttributes: BULK
|
||||
USB_WORD(16), // wMaxPacketSize
|
||||
0xFF, // bInterval: no NAK
|
||||
0x20, // bInterval: no NAK
|
||||
|
||||
9, // bLength
|
||||
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
|
||||
|
||||
+1
-38
@@ -19,7 +19,7 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include "usb_serial_endpoints.h"
|
||||
|
||||
@@ -40,43 +40,6 @@
|
||||
|
||||
SerialUSBDriver SUSBD1;
|
||||
|
||||
uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE];
|
||||
|
||||
void bulk_out_receive(void) {
|
||||
int ret;
|
||||
|
||||
while (chIQGetEmptyI(&SUSBD1.iqueue) < USB_BULK_BUFFER_SIZE)
|
||||
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
|
||||
|
||||
do {
|
||||
ret = usb_transfer_schedule(
|
||||
&usb_endpoint_bulk_out,
|
||||
&usb_bulk_buffer[0],
|
||||
USB_BULK_BUFFER_SIZE,
|
||||
serial_bulk_transfer_complete,
|
||||
NULL);
|
||||
|
||||
} while (ret != -1);
|
||||
}
|
||||
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
|
||||
(void)user_data;
|
||||
|
||||
chSysLockFromIsr();
|
||||
for (unsigned int i = 0; i < bytes_transferred; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&SUSBD1.iqueue, usb_bulk_buffer[i]);
|
||||
|
||||
if (ret == Q_FULL) {
|
||||
chDbgPanic("USB iqueue buffer full");
|
||||
}
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
static void onotify(GenericQueue* qp) {
|
||||
SerialUSBDriver* sdp = chQGetLink(qp);
|
||||
uint8_t buff[USBSERIAL_BUFFERS_SIZE];
|
||||
-2
@@ -48,8 +48,6 @@ typedef struct SerialUSBDriver SerialUSBDriver;
|
||||
extern SerialUSBDriver SUSBD1;
|
||||
|
||||
void init_serial_usb_driver(SerialUSBDriver* sdp);
|
||||
void bulk_out_receive(void);
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_host_to_device.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
extern "C" {
|
||||
#include <common/usb.h>
|
||||
#include <hackrf_usb/usb_device.h>
|
||||
#include <hackrf_usb/usb_endpoint.h>
|
||||
}
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
static Thread* thread_usb_event = NULL;
|
||||
|
||||
struct usb_bulk_buffer_t {
|
||||
uint8_t* data;
|
||||
size_t length;
|
||||
bool completed;
|
||||
};
|
||||
|
||||
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_queue;
|
||||
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_spare;
|
||||
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
|
||||
usb_bulk_buffer_t* transfer_data = (usb_bulk_buffer_t*)user_data;
|
||||
|
||||
transfer_data->length = bytes_transferred;
|
||||
transfer_data->completed = true;
|
||||
}
|
||||
|
||||
void init_host_to_device() {
|
||||
thread_usb_event = chThdSelf();
|
||||
}
|
||||
|
||||
void schedule_host_to_device_transfer() {
|
||||
if (usb_bulk_buffer_queue.size() >= 8)
|
||||
return;
|
||||
|
||||
static usb_bulk_buffer_t* transfer_data = nullptr;
|
||||
|
||||
int ret;
|
||||
|
||||
do {
|
||||
if (transfer_data == nullptr) {
|
||||
if (usb_bulk_buffer_spare.empty() == false) {
|
||||
transfer_data = usb_bulk_buffer_spare.front();
|
||||
transfer_data->length = 0;
|
||||
transfer_data->completed = false;
|
||||
usb_bulk_buffer_spare.pop();
|
||||
} else {
|
||||
transfer_data = new usb_bulk_buffer_t{
|
||||
.data = new uint8_t[USB_BULK_BUFFER_SIZE],
|
||||
.length = 0,
|
||||
.completed = false};
|
||||
}
|
||||
}
|
||||
|
||||
ret = usb_transfer_schedule(
|
||||
&usb_endpoint_bulk_out,
|
||||
transfer_data->data,
|
||||
USB_BULK_BUFFER_SIZE,
|
||||
serial_bulk_transfer_complete,
|
||||
transfer_data);
|
||||
|
||||
if (ret != -1) {
|
||||
usb_bulk_buffer_queue.push(transfer_data);
|
||||
transfer_data = nullptr;
|
||||
|
||||
if (usb_bulk_buffer_queue.size() >= 8)
|
||||
return;
|
||||
}
|
||||
} while (ret != -1);
|
||||
}
|
||||
|
||||
void complete_host_to_device_transfer() {
|
||||
for (; !usb_bulk_buffer_queue.empty(); usb_bulk_buffer_queue.pop()) {
|
||||
usb_bulk_buffer_t* transfer_data = usb_bulk_buffer_queue.front();
|
||||
|
||||
while (transfer_data->completed == false)
|
||||
return;
|
||||
|
||||
chSysLock();
|
||||
for (unsigned int i = 0; i < transfer_data->length; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&SUSBD1.iqueue, transfer_data->data[i]);
|
||||
|
||||
if (ret == Q_FULL) {
|
||||
chSysUnlock();
|
||||
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
|
||||
chSysLock();
|
||||
}
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
chSysUnlock();
|
||||
|
||||
usb_bulk_buffer_spare.push(transfer_data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#define USB_BULK_BUFFER_SIZE 64
|
||||
|
||||
void init_host_to_device();
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
void schedule_host_to_device_transfer();
|
||||
void complete_host_to_device_transfer();
|
||||
@@ -35,17 +35,21 @@
|
||||
#include "portapack_cpld_data.hpp"
|
||||
#include "crc.hpp"
|
||||
#include "hackrf_cpld_data.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "chprintf.h"
|
||||
#include "chqueues.h"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "untar.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
@@ -148,7 +152,7 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->display_modal("Flashing", "Flashing from serial.\rPlease wait!\nDevice will restart.");
|
||||
nav->display_modal("Flashing", "Flashing from serial.\r\nPlease wait!\r\nDevice will restart.");
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
@@ -167,7 +171,13 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
} 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");
|
||||
chprintf(chp, "error only .bin or .ppfw.tar files can be flashed.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ui::valid_firmware_file(path.native().c_str())) {
|
||||
chprintf(chp, "error corrupt firmware file.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
@@ -401,7 +411,7 @@ static void cmd_rtcget(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
|
||||
chprintf(chp, "Current time: %04d-%02d-%02d %02d:%02d:%02d\r\n", datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second());
|
||||
}
|
||||
@@ -417,11 +427,12 @@ static void cmd_rtcset(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: additional commands/parameters for DST?
|
||||
rtc::RTC new_datetime{
|
||||
(uint16_t)strtol(argv[0], NULL, 10), (uint8_t)strtol(argv[1], NULL, 10),
|
||||
(uint8_t)strtol(argv[2], NULL, 10), (uint32_t)strtol(argv[3], NULL, 10),
|
||||
(uint32_t)strtol(argv[4], NULL, 10), (uint32_t)strtol(argv[5], NULL, 10)};
|
||||
rtcSetTime(&RTCD1, &new_datetime);
|
||||
rtc_time::set(new_datetime);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
@@ -707,6 +718,30 @@ static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element)
|
||||
}
|
||||
}
|
||||
|
||||
static void printAppInfo(BaseSequentialStream* chp, const ui::AppInfo& element) {
|
||||
if (strlen(element.id) == 0) return;
|
||||
chprintf(chp, element.id);
|
||||
chprintf(chp, " ");
|
||||
chprintf(chp, element.displayName);
|
||||
chprintf(chp, " ");
|
||||
switch (element.menuLocation) {
|
||||
case RX:
|
||||
chprintf(chp, "[RX]\r\n");
|
||||
break;
|
||||
case TX:
|
||||
chprintf(chp, "[TX]\r\n");
|
||||
break;
|
||||
case UTILITIES:
|
||||
chprintf(chp, "[UTIL]\r\n");
|
||||
break;
|
||||
case DEBUG:
|
||||
chprintf(chp, "[DEBUG]\r\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// returns the installed apps, those can be called by appstart APPNAME
|
||||
static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
@@ -717,8 +752,9 @@ static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
for (auto element : ui::NavigationView::fixedAppListFC) {
|
||||
printAppInfo(chp, element);
|
||||
// TODO(u-foka): Somehow order static and dynamic app lists together
|
||||
for (auto& element : ui::NavigationView::appMap) { // Use the map as its ordered by id
|
||||
printAppInfo(chp, element.second);
|
||||
}
|
||||
ui::ExternalItemsMenuLoader::load_all_external_items_callback([chp](ui::AppInfoConsole& info) {
|
||||
printAppInfo(chp, info);
|
||||
@@ -916,6 +952,103 @@ static void cmd_gotorientation(BaseSequentialStream* chp, int argc, char* argv[]
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sysinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: sysinfo\r\n";
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
std::string info =
|
||||
"M0 heap: " + to_string_dec_uint(chCoreStatus()) + "\r\n" +
|
||||
"M0 stack: " + to_string_dec_uint((uint32_t)get_free_stack_space()) + "\r\n" +
|
||||
"M0 cpu%: " + to_string_dec_uint(utilisation) + "\r\n" +
|
||||
"M4 heap: " + to_string_dec_uint(shared_memory.m4_heap_usage) + "\r\n" +
|
||||
"M4 stack: " + to_string_dec_uint(shared_memory.m4_stack_usage) + "\r\n" +
|
||||
"M0 cpu%: " + to_string_dec_uint(shared_memory.m4_performance_counter) + "\r\n" +
|
||||
"M4 miss: " + to_string_dec_uint(shared_memory.m4_buffer_missed) + "\r\n" +
|
||||
"uptime: " + to_string_dec_uint(chTimeNow() / 1000) + "\r\n";
|
||||
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
|
||||
return;
|
||||
}
|
||||
|
||||
static void cmd_radioinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: radioinfo\r\n";
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
std::string info =
|
||||
"receiver_model.target_frequency: " + to_string_dec_uint(portapack::receiver_model.target_frequency()) + "\r\n" +
|
||||
"receiver_model.baseband_bandwidth: " + to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()) + "\r\n" +
|
||||
"receiver_model.sampling_rate: " + to_string_dec_uint(portapack::receiver_model.sampling_rate()) + "\r\n" +
|
||||
"receiver_model.modulation: " + to_string_dec_uint((uint32_t)portapack::receiver_model.modulation()) + "\r\n" +
|
||||
"receiver_model.am_configuration: " + to_string_dec_uint(portapack::receiver_model.am_configuration()) + "\r\n" +
|
||||
"receiver_model.nbfm_configuration: " + to_string_dec_uint(portapack::receiver_model.nbfm_configuration()) + "\r\n" +
|
||||
"receiver_model.wfm_configuration: " + to_string_dec_uint(portapack::receiver_model.wfm_configuration()) + "\r\n" +
|
||||
"transmitter_model.target_frequency: " + to_string_dec_uint(portapack::transmitter_model.target_frequency()) + "\r\n" +
|
||||
"transmitter_model.baseband_bandwidth: " + to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth()) + "\r\n" +
|
||||
"transmitter_model.sampling_rate: " + to_string_dec_uint(portapack::transmitter_model.sampling_rate()) + "\r\n";
|
||||
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
|
||||
return;
|
||||
}
|
||||
|
||||
static void cmd_pmemreset(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: pmemreset yes\r\nThis will reset pmem to defaults!\r\n";
|
||||
(void)argv;
|
||||
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->home(true);
|
||||
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
// system refresh
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_settingsreset(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: settingsreset yes\r\nThis will reset all app settings to defaults!\r\n";
|
||||
(void)argv;
|
||||
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->home(true); // to exit all running apps
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
std::filesystem::path pth = SETTINGS_DIR;
|
||||
pth += u"/" + entry.path();
|
||||
chprintf(chp, pth.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
f_unlink(pth.tchar());
|
||||
}
|
||||
}
|
||||
// system refresh
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -941,6 +1074,10 @@ static const ShellCommand commands[] = {
|
||||
{"appstart", cmd_appstart},
|
||||
{"gotgps", cmd_gotgps},
|
||||
{"gotorientation", cmd_gotorientation},
|
||||
{"sysinfo", cmd_sysinfo},
|
||||
{"radioinfo", cmd_radioinfo},
|
||||
{"pmemreset", cmd_pmemreset},
|
||||
{"settingsreset", cmd_settingsreset},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include "chprintf.h"
|
||||
#include "string_format.hpp"
|
||||
@@ -172,6 +172,9 @@ void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = shell_file->sync();
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
delete shell_file;
|
||||
shell_file = nullptr;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "optional.hpp"
|
||||
|
||||
#include "chprintf.h"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __VIEW_FACTORY_HPP__
|
||||
#define __VIEW_FACTORY_HPP__
|
||||
|
||||
#include "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <typename T>
|
||||
class ViewFactory : public ViewFactoryBase {
|
||||
public:
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const override {
|
||||
return std::unique_ptr<View>(new T(nav));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_HPP__
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "view_factory_base.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
ViewFactoryBase::~ViewFactoryBase() {}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __VIEW_FACTORY_BASE_HPP__
|
||||
#define __VIEW_FACTORY_BASE_HPP__
|
||||
|
||||
#include <memory>
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NavigationView;
|
||||
|
||||
class ViewFactoryBase {
|
||||
public:
|
||||
virtual ~ViewFactoryBase();
|
||||
virtual std::unique_ptr<View> produce(NavigationView& nav) const = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif //__VIEW_FACTORY_BASE_HPP__
|
||||
@@ -215,9 +215,12 @@ void disable() {
|
||||
gpdma_channel_i2s0_rx.disable();
|
||||
}
|
||||
|
||||
void shrink_tx_buffer() {
|
||||
single_tx_buffer = true;
|
||||
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[0]);
|
||||
void shrink_tx_buffer(bool shrink) {
|
||||
single_tx_buffer = shrink;
|
||||
if (single_tx_buffer)
|
||||
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[0]);
|
||||
else
|
||||
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[1]);
|
||||
}
|
||||
|
||||
buffer_t tx_empty_buffer() {
|
||||
|
||||
@@ -47,7 +47,7 @@ void init();
|
||||
void configure();
|
||||
void enable();
|
||||
void disable();
|
||||
void shrink_tx_buffer();
|
||||
void shrink_tx_buffer(bool shrink);
|
||||
|
||||
audio::buffer_t tx_empty_buffer();
|
||||
audio::buffer_t rx_empty_buffer();
|
||||
|
||||
@@ -32,34 +32,34 @@
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
void AudioOutput::configure(
|
||||
const bool do_proc) {
|
||||
void AudioOutput::configure(const bool do_proc) {
|
||||
do_processing = do_proc;
|
||||
}
|
||||
|
||||
void AudioOutput::configure(
|
||||
const iir_biquad_config_t& hpf_config,
|
||||
const iir_biquad_config_t& deemph_config,
|
||||
const float squelch_threshold) {
|
||||
void AudioOutput::configure(const iir_biquad_config_t& hpf_config, const iir_biquad_config_t& deemph_config, const float squelch_threshold) {
|
||||
hpf.configure(hpf_config);
|
||||
deemph.configure(deemph_config);
|
||||
squelch.set_threshold(squelch_threshold);
|
||||
}
|
||||
|
||||
void AudioOutput::write(
|
||||
const buffer_s16_t& audio) {
|
||||
void AudioOutput::write_unprocessed(const buffer_s16_t& audio) {
|
||||
block_buffer_s16.feed(
|
||||
audio,
|
||||
[this](const buffer_s16_t& buffer) {
|
||||
audio_present = true;
|
||||
fill_audio_buffer(buffer, audio_present);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::write(const buffer_s16_t& audio) {
|
||||
std::array<float, 32> audio_f;
|
||||
for (size_t i = 0; i < audio.count; i++) {
|
||||
audio_f[i] = audio.p[i] * ki;
|
||||
}
|
||||
write(buffer_f32_t{
|
||||
audio_f.data(),
|
||||
audio.count,
|
||||
audio.sampling_rate});
|
||||
write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate});
|
||||
}
|
||||
|
||||
void AudioOutput::write(
|
||||
const buffer_f32_t& audio) {
|
||||
void AudioOutput::write(const buffer_f32_t& audio) {
|
||||
block_buffer.feed(
|
||||
audio,
|
||||
[this](const buffer_f32_t& buffer) {
|
||||
@@ -67,8 +67,7 @@ void AudioOutput::write(
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::on_block(
|
||||
const buffer_f32_t& audio) {
|
||||
void AudioOutput::on_block(const buffer_f32_t& audio) {
|
||||
if (do_processing) {
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
|
||||
@@ -93,6 +92,18 @@ bool AudioOutput::is_squelched() {
|
||||
return !audio_present;
|
||||
}
|
||||
|
||||
void AudioOutput::fill_audio_buffer(const buffer_s16_t& audio, const bool send_to_fifo) {
|
||||
auto audio_buffer = audio::dma::tx_empty_buffer();
|
||||
for (size_t i = 0; i < audio_buffer.count; i++) {
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = audio.p[i];
|
||||
}
|
||||
if (stream && send_to_fifo) {
|
||||
stream->write(audio.p, audio_buffer.count * sizeof(int16_t));
|
||||
}
|
||||
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo) {
|
||||
std::array<int16_t, 32> audio_int;
|
||||
|
||||
@@ -110,6 +121,15 @@ void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_t
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::feed_audio_stats(const buffer_s16_t& audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
[](const AudioStatistics& statistics) {
|
||||
const AudioStatisticsMessage audio_stats_message{statistics};
|
||||
shared_memory.application_queue.push(audio_stats_message);
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::feed_audio_stats(const buffer_f32_t& audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
|
||||
@@ -44,6 +44,7 @@ class AudioOutput {
|
||||
const iir_biquad_config_t& deemph_config = iir_config_passthrough,
|
||||
const float squelch_threshold = 0.0f);
|
||||
|
||||
void write_unprocessed(const buffer_s16_t& audio);
|
||||
void write(const buffer_s16_t& audio);
|
||||
void write(const buffer_f32_t& audio);
|
||||
|
||||
@@ -57,6 +58,7 @@ class AudioOutput {
|
||||
static constexpr float k = 32768.0f;
|
||||
static constexpr float ki = 1.0f / k;
|
||||
|
||||
BlockDecimator<int16_t, 32> block_buffer_s16{1};
|
||||
BlockDecimator<float, 32> block_buffer{1};
|
||||
|
||||
IIRBiquadFilter hpf{};
|
||||
@@ -73,7 +75,11 @@ class AudioOutput {
|
||||
bool do_processing = true;
|
||||
|
||||
void on_block(const buffer_f32_t& audio);
|
||||
|
||||
void fill_audio_buffer(const buffer_s16_t& audio, const bool send_to_fifo);
|
||||
void fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo);
|
||||
|
||||
void feed_audio_stats(const buffer_s16_t& audio);
|
||||
void feed_audio_stats(const buffer_f32_t& audio);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,19 @@
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
void AudioStatsCollector::consume_audio_buffer(const buffer_s16_t& src) {
|
||||
auto src_p = src.p;
|
||||
const auto src_end = &src.p[src.count];
|
||||
while (src_p < src_end) {
|
||||
const auto sample = *(src_p++);
|
||||
const auto sample_squared = sample * sample;
|
||||
squared_sum += sample_squared;
|
||||
if (sample_squared > max_squared) {
|
||||
max_squared = sample_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStatsCollector::consume_audio_buffer(const buffer_f32_t& src) {
|
||||
auto src_p = src.p;
|
||||
const auto src_end = &src.p[src.count];
|
||||
@@ -56,6 +69,12 @@ bool AudioStatsCollector::update_stats(const size_t sample_count, const size_t s
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioStatsCollector::feed(const buffer_s16_t& src) {
|
||||
consume_audio_buffer(src);
|
||||
|
||||
return update_stats(src.count, src.sampling_rate);
|
||||
}
|
||||
|
||||
bool AudioStatsCollector::feed(const buffer_f32_t& src) {
|
||||
consume_audio_buffer(src);
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
|
||||
class AudioStatsCollector {
|
||||
public:
|
||||
template <typename Callback>
|
||||
void feed(const buffer_s16_t& src, Callback callback) {
|
||||
if (feed(src)) {
|
||||
callback(statistics);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void feed(const buffer_f32_t& src, Callback callback) {
|
||||
if (feed(src)) {
|
||||
@@ -52,10 +59,12 @@ class AudioStatsCollector {
|
||||
|
||||
AudioStatistics statistics{};
|
||||
|
||||
void consume_audio_buffer(const buffer_s16_t& src);
|
||||
void consume_audio_buffer(const buffer_f32_t& src);
|
||||
|
||||
bool update_stats(const size_t sample_count, const size_t sampling_rate);
|
||||
|
||||
bool feed(const buffer_s16_t& src);
|
||||
bool feed(const buffer_f32_t& src);
|
||||
bool mute(const size_t sample_count, const size_t sampling_rate);
|
||||
};
|
||||
|
||||
@@ -41,13 +41,17 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
// Output to speaker too
|
||||
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
|
||||
audio_data[imod32] = ((int16_t)audio_sample - 0x80) * (1.0f / 128.0f);
|
||||
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
|
||||
audio_output.write(audio_buffer);
|
||||
sample = audio_sample - 0x80;
|
||||
|
||||
sample = tone_gen.process(audio_sample - 0x80);
|
||||
// Output to speaker too
|
||||
if (!tone_key_enabled) {
|
||||
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
|
||||
audio_data[imod32] = sample * 256;
|
||||
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
|
||||
audio_output.write_unprocessed(audio_buffer);
|
||||
}
|
||||
|
||||
sample = tone_gen.process(sample);
|
||||
|
||||
// FM
|
||||
delta = sample * fm_delta;
|
||||
@@ -102,7 +106,9 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
|
||||
progress_interval_samples = message.divider;
|
||||
resample_acc = 0;
|
||||
audio_output.configure(false);
|
||||
audio::dma::shrink_tx_buffer();
|
||||
|
||||
tone_key_enabled = (message.tone_key_delta != 0);
|
||||
audio::dma::shrink_tx_buffer(!tone_key_enabled);
|
||||
}
|
||||
|
||||
void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
|
||||
@@ -52,14 +52,15 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
int32_t sample{0}, delta{};
|
||||
int8_t re{0}, im{0};
|
||||
|
||||
float audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
|
||||
buffer_f32_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
|
||||
int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
|
||||
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
|
||||
AudioOutput audio_output{};
|
||||
|
||||
size_t progress_interval_samples = 0, progress_samples = 0;
|
||||
|
||||
bool configured{false};
|
||||
uint32_t bytes_read{0};
|
||||
bool tone_key_enabled{false};
|
||||
|
||||
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||
void audio_config(const AudioTXConfigMessage& message);
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "spi_image.hpp"
|
||||
|
||||
#define CURRENT_HEADER_VERSION 0x00000001
|
||||
#define CURRENT_HEADER_VERSION 0x00000002
|
||||
#define MIN_HEADER_VERSION_FOR_CHECKSUM 0x00000002
|
||||
|
||||
typedef void (*externalAppEntry_t)(ui::NavigationView& nav);
|
||||
|
||||
@@ -34,7 +35,8 @@ enum app_location_t : uint32_t {
|
||||
UTILITIES = 0,
|
||||
RX,
|
||||
TX,
|
||||
DEBUG
|
||||
DEBUG,
|
||||
HOME
|
||||
};
|
||||
|
||||
struct application_information_t {
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
@@ -145,7 +147,7 @@ struct misc_config_t {
|
||||
bool disable_speaker : 1;
|
||||
bool config_disable_external_tcxo : 1;
|
||||
bool config_sdcard_high_speed_io : 1;
|
||||
bool UNUSED_4 : 1;
|
||||
bool config_disable_config_mode : 1;
|
||||
bool UNUSED_5 : 1;
|
||||
bool UNUSED_6 : 1;
|
||||
bool UNUSED_7 : 1;
|
||||
@@ -156,6 +158,8 @@ struct misc_config_t {
|
||||
};
|
||||
static_assert(sizeof(misc_config_t) == sizeof(uint32_t));
|
||||
|
||||
#define MC_CONFIG_DISABLE_CONFIG_MODE 0x00000010 // config_disable_config_mode bit in struct above
|
||||
|
||||
/* IMPORTANT: Update dump_persistent_memory (below) when changing data_t. */
|
||||
|
||||
/* Struct must pack the same way on M4 and M0 cores.
|
||||
@@ -233,6 +237,9 @@ struct data_t {
|
||||
// recovery mode magic value storage
|
||||
uint32_t config_mode_storage;
|
||||
|
||||
// Daylight savings time
|
||||
dst_config_t dst_config;
|
||||
|
||||
constexpr data_t()
|
||||
: structure_version(data_structure_version_enum::VERSION_CURRENT),
|
||||
target_frequency(target_frequency_reset_value),
|
||||
@@ -285,7 +292,8 @@ struct data_t {
|
||||
headphone_volume_cb(-600),
|
||||
misc_config(),
|
||||
ui_config2(),
|
||||
config_mode_storage(CONFIG_MODE_NORMAL_VALUE) {
|
||||
config_mode_storage(CONFIG_MODE_NORMAL_VALUE),
|
||||
dst_config() {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -386,6 +394,7 @@ namespace cache {
|
||||
void defaults() {
|
||||
cached_backup_ram = backup_ram_t();
|
||||
|
||||
// If the desired default is 0/false, then no need to set it here (buffer is initialized to 0)
|
||||
set_config_backlight_timer(backlight_config_t{});
|
||||
set_config_splash(true);
|
||||
set_config_disable_external_tcxo(false);
|
||||
@@ -416,8 +425,9 @@ void init() {
|
||||
const auto switches_state = get_switches_state();
|
||||
|
||||
// ignore for valid check
|
||||
auto config_mode_backup = config_mode_storage();
|
||||
set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
|
||||
auto config_mode_backup = config_mode_storage_direct();
|
||||
set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
|
||||
|
||||
if (!(switches_state[(size_t)ui::KeyEvent::Left] && switches_state[(size_t)ui::KeyEvent::Right]) && backup_ram->is_valid()) {
|
||||
// Copy valid persistent data into cache.
|
||||
cached_backup_ram = *backup_ram;
|
||||
@@ -433,7 +443,7 @@ void init() {
|
||||
// Copy defaults into cache.
|
||||
defaults();
|
||||
}
|
||||
set_config_mode_storage(config_mode_backup);
|
||||
set_config_mode_storage_direct(config_mode_backup);
|
||||
}
|
||||
|
||||
void persist() {
|
||||
@@ -597,6 +607,10 @@ bool config_disable_external_tcxo() {
|
||||
return data->misc_config.config_disable_external_tcxo;
|
||||
}
|
||||
|
||||
bool config_disable_config_mode() {
|
||||
return data->misc_config.config_disable_config_mode;
|
||||
}
|
||||
|
||||
bool config_sdcard_high_speed_io() {
|
||||
return data->misc_config.config_sdcard_high_speed_io;
|
||||
}
|
||||
@@ -666,6 +680,10 @@ void set_config_disable_external_tcxo(bool v) {
|
||||
data->misc_config.config_disable_external_tcxo = v;
|
||||
}
|
||||
|
||||
void set_config_disable_config_mode(bool v) {
|
||||
data->misc_config.config_disable_config_mode = v;
|
||||
}
|
||||
|
||||
void set_config_sdcard_high_speed_io(bool v, bool save) {
|
||||
if (v) {
|
||||
/* 200MHz / (2 * 2) = 50MHz */
|
||||
@@ -947,12 +965,34 @@ void set_encoder_dial_sensitivity(uint8_t v) {
|
||||
// Recovery mode magic value storage
|
||||
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
|
||||
|
||||
uint32_t config_mode_storage() {
|
||||
uint32_t config_mode_storage_direct() {
|
||||
return data_direct_access->config_mode_storage;
|
||||
}
|
||||
void set_config_mode_storage(uint32_t v) {
|
||||
void set_config_mode_storage_direct(uint32_t v) {
|
||||
data_direct_access->config_mode_storage = v;
|
||||
}
|
||||
bool config_disable_config_mode_direct() {
|
||||
// "return data_direct_access->misc_config.config_disable_config_mode"
|
||||
// Casting as U32 as workaround for misaligned memory access
|
||||
uint32_t misc_config_u32 = *(uint32_t*)&data_direct_access->misc_config;
|
||||
return ((misc_config_u32 & MC_CONFIG_DISABLE_CONFIG_MODE) != 0);
|
||||
}
|
||||
|
||||
// Daylight savings time
|
||||
bool dst_enabled() {
|
||||
return data->dst_config.b.dst_enabled;
|
||||
}
|
||||
void set_dst_enabled(bool v) {
|
||||
data->dst_config.b.dst_enabled = v;
|
||||
rtc_time::dst_init();
|
||||
}
|
||||
dst_config_t config_dst() {
|
||||
return data->dst_config;
|
||||
}
|
||||
void set_config_dst(dst_config_t v) {
|
||||
data->dst_config = v;
|
||||
rtc_time::dst_init();
|
||||
}
|
||||
|
||||
// PMem to sdcard settings
|
||||
|
||||
@@ -963,7 +1003,7 @@ bool should_use_sdcard_for_pmem() {
|
||||
int save_persistent_settings_to_file() {
|
||||
File outfile;
|
||||
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
auto error = outfile.create(PMEM_SETTING_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
@@ -996,7 +1036,7 @@ bool debug_dump() {
|
||||
std::filesystem::path filename{};
|
||||
File pmem_dump_file{};
|
||||
// create new dump file name and DEBUG directory
|
||||
make_new_directory(debug_dir);
|
||||
ensure_directory(debug_dir);
|
||||
filename = next_filename_matching_pattern(debug_dir + "/DEBUG_DUMP_????.TXT");
|
||||
if (filename.empty()) {
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::red, "COULD NOT GET DUMP NAME !");
|
||||
@@ -1012,6 +1052,9 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("Ext APPS version req'd: 0x" + to_string_hex(VERSION_MD5));
|
||||
pmem_dump_file.write_line("GCC version: " + to_string_dec_int(__GNUC__) + "." + to_string_dec_int(__GNUC_MINOR__) + "." + to_string_dec_int(__GNUC_PATCHLEVEL__));
|
||||
|
||||
// firmware checksum
|
||||
pmem_dump_file.write_line("Firmware calculated checksum: 0x" + to_string_hex(simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE), 8));
|
||||
|
||||
// write persistent memory
|
||||
pmem_dump_file.write_line("\n[Persistent Memory]");
|
||||
|
||||
@@ -1037,9 +1080,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("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb));
|
||||
pmem_dump_file.write_line("recon_repeat_gain: " + to_string_dec_int(data->recon_repeat_gain));
|
||||
pmem_dump_file.write_line("recon_repeat_delay: " + to_string_dec_int(data->recon_repeat_delay));
|
||||
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));
|
||||
@@ -1054,6 +1097,8 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
|
||||
// pmem_dump_file.write_line("UNUSED_8: " + to_string_dec_uint(data->UNUSED_8));
|
||||
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
|
||||
pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8));
|
||||
pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)data->dst_config.v, 8));
|
||||
|
||||
// ui_config bits
|
||||
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
|
||||
@@ -1086,8 +1131,9 @@ bool debug_dump() {
|
||||
// misc_config bits
|
||||
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
|
||||
pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
|
||||
pmem_dump_file.write_line("ui_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
|
||||
pmem_dump_file.write_line("ui_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
|
||||
pmem_dump_file.write_line("misc_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
|
||||
pmem_dump_file.write_line("misc_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
|
||||
pmem_dump_file.write_line("misc_config config_disable_config_mode: " + to_string_dec_uint(config_disable_config_mode()));
|
||||
|
||||
// receiver_model
|
||||
pmem_dump_file.write_line("\n[Receiver Model]");
|
||||
|
||||
@@ -32,11 +32,12 @@
|
||||
#include "modems.hpp"
|
||||
#include "serializer.hpp"
|
||||
#include "volume.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
// persistant memory from/to sdcard flag file
|
||||
// persistent memory from/to sdcard flag file
|
||||
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
|
||||
|
||||
// persistant memory from/to sdcard flag file
|
||||
// persistent 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
|
||||
@@ -116,6 +117,21 @@ enum encoder_dial_sensitivity {
|
||||
NUM_DIAL_SENSITIVITY
|
||||
};
|
||||
|
||||
typedef union {
|
||||
uint32_t v;
|
||||
struct {
|
||||
uint8_t start_which : 4;
|
||||
uint8_t start_weekday : 4;
|
||||
uint8_t start_month : 4;
|
||||
uint8_t end_which : 4;
|
||||
uint8_t end_weekday : 4;
|
||||
uint8_t end_month : 4;
|
||||
uint8_t UNUSED : 7;
|
||||
uint8_t dst_enabled : 1;
|
||||
} b;
|
||||
} dst_config_t;
|
||||
static_assert(sizeof(dst_config_t) == sizeof(uint32_t));
|
||||
|
||||
namespace cache {
|
||||
|
||||
/* Set values in cache to sensible defaults. */
|
||||
@@ -178,6 +194,8 @@ void set_config_cpld(uint8_t i);
|
||||
|
||||
bool config_disable_external_tcxo();
|
||||
bool config_sdcard_high_speed_io();
|
||||
bool config_disable_config_mode();
|
||||
|
||||
bool config_splash();
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
@@ -198,6 +216,8 @@ void set_save_app_settings(bool v);
|
||||
void set_show_bigger_qr_code(bool v);
|
||||
void set_config_disable_external_tcxo(bool v);
|
||||
void set_config_sdcard_high_speed_io(bool v, bool save);
|
||||
void set_config_disable_config_mode(bool v);
|
||||
|
||||
void set_config_splash(bool v);
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
@@ -223,10 +243,9 @@ void set_disable_touchscreen(bool v);
|
||||
uint8_t config_encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 2001
|
||||
#define CONFIG_MODE_NORMAL_VALUE 1999
|
||||
uint32_t config_mode_storage();
|
||||
void set_config_mode_storage(uint32_t v);
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
bool config_disable_config_mode_direct();
|
||||
|
||||
uint32_t pocsag_last_address();
|
||||
void set_pocsag_last_address(uint32_t address);
|
||||
@@ -236,9 +255,14 @@ void set_pocsag_ignore_address(uint32_t address);
|
||||
|
||||
bool clkout_enabled();
|
||||
void set_clkout_enabled(bool v);
|
||||
uint16_t clkout_freq();
|
||||
void set_clkout_freq(uint16_t freq);
|
||||
|
||||
bool dst_enabled();
|
||||
void set_dst_enabled(bool v);
|
||||
uint16_t clkout_freq();
|
||||
dst_config_t config_dst();
|
||||
void set_config_dst(dst_config_t v);
|
||||
|
||||
/* Recon app */
|
||||
bool recon_autosave_freqs();
|
||||
bool recon_autostart_recon();
|
||||
|
||||
@@ -31,9 +31,11 @@
|
||||
#include "chprintf.h"
|
||||
#include "irq_controls.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "rtc_time.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace rtc_time;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -433,7 +435,7 @@ void Labels::getWidgetName(std::string& result) {
|
||||
/* LiveDateTime **********************************************************/
|
||||
|
||||
void LiveDateTime::on_tick_second() {
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
text = "";
|
||||
if (!hide_clock) {
|
||||
if (date_enabled) {
|
||||
@@ -2118,7 +2120,15 @@ bool NumberField::on_key(const KeyEvent key) {
|
||||
}
|
||||
|
||||
bool NumberField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t old_value = value();
|
||||
set_value(value() + (delta * step));
|
||||
|
||||
if (on_wrap) {
|
||||
if ((delta > 0) && (value() < old_value))
|
||||
on_wrap(1);
|
||||
else if ((delta < 0) && (value() > old_value))
|
||||
on_wrap(-1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -783,6 +783,7 @@ class NumberField : public Widget {
|
||||
public:
|
||||
std::function<void(NumberField&)> on_select{};
|
||||
std::function<void(int32_t)> on_change{};
|
||||
std::function<void(int32_t)> on_wrap{};
|
||||
|
||||
using range_t = std::pair<int32_t, int32_t>;
|
||||
|
||||
|
||||
+39
-16
@@ -6,6 +6,8 @@
|
||||
#include <string.h>
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
class UnTar {
|
||||
public:
|
||||
@@ -53,7 +55,6 @@ class UnTar {
|
||||
}
|
||||
|
||||
static bool create_dir(char* pathname) {
|
||||
char* p;
|
||||
std::filesystem::filesystem_error r;
|
||||
|
||||
if (!isValidName(pathname)) return false;
|
||||
@@ -65,18 +66,7 @@ class UnTar {
|
||||
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);
|
||||
}
|
||||
}
|
||||
r = ensure_directory(dirname);
|
||||
return (r.ok());
|
||||
}
|
||||
|
||||
@@ -98,6 +88,10 @@ class UnTar {
|
||||
std::string binfile = "";
|
||||
std::string fn = "";
|
||||
int filesize;
|
||||
uint32_t app_checksum;
|
||||
bool app_file;
|
||||
bool first_read;
|
||||
bool corrupt_file{false};
|
||||
for (;;) {
|
||||
auto readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
@@ -144,20 +138,49 @@ class UnTar {
|
||||
delete_file(fn);
|
||||
auto fres = f.open(fn, false, true);
|
||||
if (!fres.value().ok()) return "";
|
||||
app_file = (fn.substr(fn.size() - 5) == ".ppma");
|
||||
first_read = true;
|
||||
app_checksum = 0;
|
||||
corrupt_file = false;
|
||||
}
|
||||
while (filesize > 0) {
|
||||
readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
if (!readres.is_ok()) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
bytes_read = readres.value();
|
||||
if (bytes_read < 512) {
|
||||
return "";
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
if (filesize < 512)
|
||||
bytes_read = filesize;
|
||||
if (app_file && first_read) {
|
||||
// Check app file header for min version for checksum support
|
||||
struct application_information_t* app_info = (struct application_information_t*)buff;
|
||||
if (app_info->header_version < MIN_HEADER_VERSION_FOR_CHECKSUM)
|
||||
app_file = false;
|
||||
first_read = false;
|
||||
}
|
||||
if (app_file) {
|
||||
app_checksum += simple_checksum((uint32_t)buff, bytes_read);
|
||||
}
|
||||
auto fwres = f.write(buff, bytes_read);
|
||||
if (!fwres.is_ok()) return "";
|
||||
if (!fwres.is_ok()) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
filesize -= bytes_read;
|
||||
f.sync();
|
||||
if ((filesize == 0) && app_file && (app_checksum != EXT_APP_EXPECTED_CHECKSUM)) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (corrupt_file) {
|
||||
delete_file(fn);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return binfile;
|
||||
|
||||
@@ -235,3 +235,10 @@ std::string join(char c, std::initializer_list<std::string_view> strings) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length) {
|
||||
uint32_t checksum = 0;
|
||||
for (uint32_t i = 0; i < length; i += 4)
|
||||
checksum += *(uint32_t*)(buffer_address + i);
|
||||
return checksum;
|
||||
}
|
||||
@@ -217,4 +217,6 @@ struct range_t {
|
||||
|
||||
std::string join(char c, std::initializer_list<std::string_view> strings);
|
||||
|
||||
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length);
|
||||
|
||||
#endif /*__UTILITY_H__*/
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
bitmap_data = [
|
||||
0x30, 0x00,
|
||||
0x30, 0x00,
|
||||
0x30, 0x00,
|
||||
0x30, 0x00,
|
||||
0x30, 0x00,
|
||||
0x30, 0x00,
|
||||
0xFC, 0x00,
|
||||
0xCE, 0x01,
|
||||
0x86, 0x01,
|
||||
0xFE, 0x01,
|
||||
0x86, 0x31,
|
||||
0x86, 0x49,
|
||||
0xCE, 0x87,
|
||||
0xFC, 0x84,
|
||||
0xFC, 0x4B,
|
||||
0x78, 0x30,
|
||||
]
|
||||
|
||||
bitmap_size = (16, 16)
|
||||
|
||||
image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)
|
||||
|
||||
for y in range(bitmap_size[1]):
|
||||
for x in range(bitmap_size[0]):
|
||||
byte_index = (y * bitmap_size[0] + x) // 8
|
||||
bit_index = x % 8
|
||||
# bit_index = 7 - (x % 8)
|
||||
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
|
||||
image_data[y, x] = pixel_value * 255
|
||||
|
||||
image = Image.fromarray(image_data, 'L')
|
||||
image.save('decoded.png')
|
||||
@@ -104,6 +104,14 @@ for external_image_prefix in sys.argv[4:]:
|
||||
external_application_image = patch_image(external_application_image, search_address, replace_address)
|
||||
external_application_image[memory_location_header_position:memory_location_header_position+4] = replace_address.to_bytes(4, byteorder='little')
|
||||
|
||||
checksum = 0
|
||||
for i in range(0, len(external_application_image), 4):
|
||||
checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24)
|
||||
|
||||
final_checksum = 0
|
||||
checksum = (final_checksum - checksum) & 0xFFFFFFFF
|
||||
external_application_image += checksum.to_bytes(4, 'little')
|
||||
|
||||
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
|
||||
continue
|
||||
|
||||
@@ -127,5 +135,13 @@ for external_image_prefix in sys.argv[4:]:
|
||||
print("application {} can not exceed 32kb: {} bytes used".format(external_image_prefix, len(external_application_image)))
|
||||
sys.exit(-1)
|
||||
|
||||
checksum = 0
|
||||
for i in range(0, len(external_application_image), 4):
|
||||
checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24)
|
||||
|
||||
final_checksum = 0
|
||||
checksum = (final_checksum - checksum) & 0xFFFFFFFF
|
||||
external_application_image += checksum.to_bytes(4, 'little')
|
||||
|
||||
# write .ppma (portapack mayhem application)
|
||||
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
|
||||
|
||||
@@ -74,7 +74,24 @@ for image in images:
|
||||
padded_data = image['data'] + (spi_image_default_byte * pad_size)
|
||||
spi_image += padded_data
|
||||
|
||||
if len(spi_image) > spi_size:
|
||||
raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image), spi_size))
|
||||
if len(spi_image) > spi_size - 4:
|
||||
raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image) + 4, spi_size))
|
||||
|
||||
pad_size = spi_size - 4 - len(spi_image)
|
||||
for i in range(pad_size):
|
||||
spi_image += spi_image_default_byte
|
||||
|
||||
# quick "add up the words" checksum:
|
||||
checksum = 0
|
||||
for i in range(0, len(spi_image), 4):
|
||||
checksum += (spi_image[i] + (spi_image[i + 1] << 8) + (spi_image[i + 2] << 16) + (spi_image[i + 3] << 24))
|
||||
|
||||
final_checksum = 0
|
||||
checksum = (final_checksum - checksum) & 0xFFFFFFFF
|
||||
|
||||
spi_image += checksum.to_bytes(4, 'little')
|
||||
|
||||
write_image(spi_image, output_path)
|
||||
|
||||
percent_remaining = round(1000 * pad_size / spi_size) / 10;
|
||||
print ("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)")
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 Mark Thompson
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import sys
|
||||
import struct
|
||||
|
||||
usage_message = """
|
||||
PortaPack ROM image checksum checker
|
||||
|
||||
Usage: <command> <input-file>
|
||||
"""
|
||||
|
||||
def read_image(path):
|
||||
f = open(path, 'rb')
|
||||
data = f.read()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
def write_image(data, path):
|
||||
f = open(path, 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print(usage_message)
|
||||
sys.exit(-1)
|
||||
|
||||
image = read_image(sys.argv[1])
|
||||
image = bytearray(image)
|
||||
|
||||
# simple "add up the words" checksum:
|
||||
checksum = 0
|
||||
for i in range(0, len(image), 4):
|
||||
checksum += (image[i] + (image[i + 1] << 8) + (image[i + 2] << 16) + (image[i + 3] << 24))
|
||||
|
||||
checksum &= 0xFFFFFFFF
|
||||
print ("Simple checksum =", checksum)
|
||||
Reference in New Issue
Block a user