Compare commits

...

13 Commits

Author SHA1 Message Date
E.T 6e74ad942f Gh workflow changelog formatting fix (#1803)
* Remove replacement of newlines with hex codes

Assuming that its unnecessary as we are no longer outputting these via stdout but pipe it into some binary that could handle newlines in its stdin therefore don't need and support this transformation

* Attempt 2 after reading gh docs :D

* Fix changelog for stable release too
2024-01-22 10:50:14 +01:00
Mark Thompson 0f85f247b6 Fix rounding bug when displaying frequencies (#1801) 2024-01-22 07:54:29 +01:00
sommermoregentraum ac1d350aaf add a item that warn user to put ext app if nothing to pop in home page (#1798)
add an item that warn user to put ext app if nothing to pop
2024-01-22 03:21:06 +08:00
Mark Thompson 5f8e1ef307 Daylight Savings Time support (#1793)
* Daylight Savings Time support

* Cleanup

* Clean-up

* Revert ADSB change

* Clean-up

* Corrected date in comment, ironically
2024-01-21 12:47:28 -06:00
E.T aa5d4ad078 Update github workflow to remove deprecated set-output usage (#1799) 2024-01-22 07:24:38 +13:00
sommermoregentraum 44d9572f5c add_bitmap_reverse_decode (#1795) 2024-01-21 18:51:08 +08:00
sommermoregentraum 3314001205 bring keyfob app back (#1794)
* bring keyfob app back

* format

* cleanup

* format

* remove committed line from original list
2024-01-21 11:49:17 +01:00
E.T ce1084abc7 Introduce common app list for menu and serial (#1791) 2024-01-20 23:35:39 +01:00
E.T ea8563cb19 Fix workflow to pick up the OCI named ppfw (#1787) 2024-01-18 21:08:02 +01:00
Mark Thompson b3c1c83677 Remove time from PPFW filename and modify Flash app to support .tar (#1783)
* Update timestamp in PPFW filename

* Only include today's DATE in filename

* Remove date from PPFW filename

* Allow any .tar file name in FIRMWARE folder

* Add files via upload

* Removed unnecessary lines per ufoka
2024-01-18 13:59:44 -06:00
Totoo fcbc3b4d75 pmemreset and settingsreset commands (#1786) 2024-01-18 16:12:13 +01:00
Mark Thompson 39e1a0ffe8 Cmake - generate new ppfw when firmware changes (#1782) 2024-01-17 07:01:48 +01:00
Mark Thompson 99bbd8805c Added Settings app to disable Config Mode (#1781) 2024-01-17 06:59:35 +01:00
36 changed files with 1030 additions and 345 deletions
+9 -9
View File
@@ -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 -9
View File
@@ -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
-11
View File
@@ -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)
+2 -1
View File
@@ -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 :)
)
+1
View File
@@ -346,6 +346,7 @@ set(CPPSRC
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
view_factory_base.cpp
${EXTCPPSRC}
)
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -258,7 +258,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();
@@ -50,7 +50,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();
@@ -108,7 +108,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
}
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;
+2 -11
View File
@@ -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();
+80 -10
View File
@@ -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
}
+94 -8
View File
@@ -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);
+6 -3
View File
@@ -27,15 +27,18 @@
void config_mode_blink_until_dfu();
void config_mode_set() {
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_GUARD_VALUE);
}
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_GUARD_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[] = {
+2
View File
@@ -31,6 +31,8 @@
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();
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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
};
}
@@ -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 */
@@ -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 */
+1
View File
@@ -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();
+216
View File
@@ -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 */
+15
View File
@@ -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__*/
+2 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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) {
+2 -1
View File
@@ -498,12 +498,13 @@ void GeoMap::draw_scale(Painter& painter) {
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 + 50) / 100, 1) + "km";
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint(m / 100, 1) + "km";
}
}
+141 -245
View File
@@ -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;
}
@@ -727,43 +669,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 +718,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 +730,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 +753,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);
+29 -4
View File
@@ -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;
+1 -1
View File
@@ -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 =
+87 -5
View File
@@ -47,6 +47,8 @@
#include "ui_navigation.hpp"
#include "usb_serial_shell_filesystem.hpp"
#include "portapack_persistent_memory.hpp"
#include <string>
#include <cstring>
#include <libopencm3/lpc43xx/wwdt.h>
@@ -149,7 +151,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
@@ -402,7 +404,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());
}
@@ -418,11 +420,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");
}
@@ -708,6 +711,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;
@@ -718,8 +745,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);
@@ -962,6 +990,58 @@ static void cmd_radioinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
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},
@@ -989,6 +1069,8 @@ static const ShellCommand commands[] = {
{"gotorientation", cmd_gotorientation},
{"sysinfo", cmd_sysinfo},
{"radioinfo", cmd_radioinfo},
{"pmemreset", cmd_pmemreset},
{"settingsreset", cmd_settingsreset},
{NULL, NULL}};
static const ShellConfig shell_cfg1 = {
+39
View File
@@ -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__
+2 -1
View File
@@ -34,7 +34,8 @@ enum app_location_t : uint32_t {
UTILITIES = 0,
RX,
TX,
DEBUG
DEBUG,
HOME
};
struct application_information_t {
@@ -33,6 +33,7 @@
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "utility.hpp"
#include "rtc_time.hpp"
#include <algorithm>
#include <string>
@@ -145,7 +146,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 +157,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 +236,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 +291,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 +393,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 +424,8 @@ 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.
@@ -434,7 +442,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() {
@@ -598,6 +606,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;
}
@@ -667,6 +679,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 */
@@ -948,12 +964,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
@@ -1056,6 +1094,7 @@ bool debug_dump() {
// 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();
@@ -1088,8 +1127,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]");
@@ -116,6 +116,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 +193,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 +215,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 +242,11 @@ 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);
#define CONFIG_MODE_GUARD_VALUE 0x000007d1
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
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 +256,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();
+3 -1
View File
@@ -32,8 +32,10 @@
#include "irq_controls.hpp"
#include "string_format.hpp"
#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) {
@@ -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')