Compare commits

...

33 Commits

Author SHA1 Message Date
Mark Thompson bc035cff6a ADSB retrieve_record just-in-case check (#1848) 2024-02-05 20:13:06 +01:00
E.T f2c008602d Adsb rx airline display fix (#1847)
* This doesn't do anything, just gives me some sanity :D

* Don’t abuse std

* Only look for airline info if the callsign is received

* Format…

* Let the airline update when callsign is received
2024-02-05 12:06:29 -06:00
Brumi-2021 b8073bca0f Adding_TX_IQ_phase_Calibration_to_Mic_App (#1843)
* Adding_TX_IQ_phase_Calibration_to_Mic_App

* Adding_persistent_CAL_data_and_correct_init_data
2024-02-03 17:57:45 -06:00
Mark Thompson c30a61441b Tetris external app (#1842)
* Tetris external app
* 4 levels
2024-02-03 19:33:36 +01:00
Matias Fernandez f0f279eec5 Add frequency ranges to looking glass presets (#1841)
* Add DECT frequency range. The most common allocated range fits within 1880MHz-1930MHz (different countries use different ranges). To provide extra detail at the edges I'm extending 1MHz the range on each direction. So the final range covers from 1879MHz to 1931MHz

* Add VHF Marine band. Even when Wikipedia says that the Marine Band covers from 156MHz to 174MHz, I couldn't find any channels defined above 163MHz. So, the final range I'm using goes from 155MHz to 163MHz to ensure some detail at the edges.

* Add a narrow range around 433MHz frequency. This is a widely used frequency for all sorts of remote controls, keyfobs, etc. I think it's useful to have a narrower range just focusing on this frequency

* Add FRS/GMRS frequency range

* Alphabetize the list of ranges, keeping the really big ranges of whole ITU bands and the full supported range at the end of the list
2024-02-03 09:57:07 +01:00
Mark Thompson 464cc8449e Use std:rand() in Touchscreen Test (#1840) 2024-02-03 09:54:10 +01:00
Mark Thompson ee2e57d702 2nd-Level Encoder Debouncing for scrappy encoder dials (#1838)
* Experimental encoder debouncing

* Experimental encoder debouncing

* Clang

* Comment changed
2024-02-01 14:13:28 -06:00
Mark Thompson a2a5fb166e Improved Debounce for Encoders (#1837)
* Fix variable type declaration

* Fix typo

* Two-bit encoder debouncing

* Slight optimization

* Comment change
2024-01-31 14:13:21 -06:00
Mark Thompson f59f5dfaa3 Fixed ms_duration function to show actual ms (#1836) 2024-01-31 21:08:17 +01:00
E.T 13f12227bd Remove ccache hack from dockerfiles (#1835)
Now we have a proper solution in cmake
2024-01-30 21:32:01 +01:00
Herman Semenov c9db21063b Speedup compilation using ccache package (#1832) 2024-01-30 20:55:59 +01:00
Mark Thompson 125184f300 Add audio playback ability to WAV Viewer app (#1829) 2024-01-30 20:55:32 +01:00
ʎʞsʌoʞᴉɐɥɔꓕ ɹʇoʎꓒ 8068517808 add warn when enable stealth mode & add nav valid check (#1823)
* add warn when enable stealth

* change FLASH ERROR to FLASH ERR cuz previous one were overlapped with date

* check if nav is valid
2024-01-30 13:58:40 +08:00
Mark Thompson a4f6bbda5e Fix compiler warning (#1827)
* Fix compiler warning

* Fix compiler warning

* Friggin C-lang
2024-01-29 18:07:32 -06:00
Totoo 8b2598fdac Call the flasher app when flashing from serial (#1825) 2024-01-29 20:52:35 +01:00
sommermoregentraum c36fe78bd5 pacman: cheat lifes and cheat skip level (#1821)
cheat lives and cheat skip level and fixed review issue. Thanks @NotherNgineer
2024-01-29 00:54:13 +08:00
Mark Thompson bd8385464e Tuner code readability (no functional change) (#1820) 2024-01-28 10:02:11 -06:00
Totoo 8c996b5bc6 Embedded friendly usb (#1817) 2024-01-27 19:33:08 +01:00
Mark Thompson d1901939e1 Fix tuning of 2170-2300 and 2700-2740 MHz (#1819) 2024-01-27 13:30:32 +01:00
Mark Thompson 41ec11fb66 Check app checksums during untar (#1815) 2024-01-27 13:29:24 +01:00
Mark Thompson df47490d85 Allow 3 boot failures before Config Mode starts (#1816)
* Allow 3 boot attempts before Config Mode starts

* Add files via upload
2024-01-26 21:02:52 +13:00
Totoo a2f6d14b15 ISS for aprs, for the request #1810 (#1814) 2024-01-25 21:10:43 +01:00
Mark Thompson 6a6c6d6502 Add Checksums to Firmware & External App images (#1809)
* Pad image to 1MB and add simple checksum

* Test code to verify firmware checksum

* Comment out unneeded zlib

* Add files via upload

* Print space remaining in ROM

* Append checksum to external apps too

* Check external app checksums when loading

* Is it 2024 already?!

* Validate firmware checksum before flashing

* Add files via upload

* Added flash error warning to nav screen

* Clang

* Replaced some hard-coded values with #defines

* Check FW checksum before USB serial flash too

* Add files via upload
2024-01-24 16:37:21 -06:00
Mark Thompson 2d98c5d311 Resolve Soundboard tone key issue (#1806)
* Resolve Soundboard tone key issue
2024-01-23 20:53:04 +01:00
sommermoregentraum 03b13f8ab0 prevent crash when nothing to broadcast in soundbrd app (#1807)
* prevent crash when nothing to broadcast in soundbrd app

* format
2024-01-22 22:15:37 -06:00
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
97 changed files with 2962 additions and 679 deletions
+8 -8
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
+8 -8
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
+7
View File
@@ -72,5 +72,12 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
find_program(CCACHE "ccache")
if(CCACHE)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
endif(CCACHE)
enable_testing()
add_subdirectory(firmware)
-7
View File
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin \
&& cd ~/bin \
&& for tool in gcc g++ cpp c++; do \
ln -s $(which ccache) arm-none-eabi-$tool \
; done
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
-4
View File
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Configure CCACHE
RUN mkdir ~/bin && cd ~/bin && \
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
CMD cd .. && cd build && \
cmake .. && make ppfw
-7
View File
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin \
&& cd ~/bin \
&& for tool in gcc g++ cpp c++; do \
ln -s $(which ccache) arm-none-eabi-$tool \
; done
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+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
View File
@@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
namespace app_settings {
enum class Mode : uint8_t {
NO_RF = 0x00,
RX = 0x01,
TX = 0x02,
RX_TX = 0x03, // Both TX/RX
+2 -2
View File
@@ -64,9 +64,9 @@ static std::string mmsi(
static std::string mid(
const ais::MMSI& mmsi) {
std::database db;
database db;
std::string mid_code = "";
std::database::MidDBRecord mid_record = {};
database::MidDBRecord mid_record = {};
int return_code = 0;
// Try getting the country name from mids.db using MID code for given MMSI
+23 -7
View File
@@ -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;
@@ -113,12 +119,13 @@ void SoundBoardView::start_tx(const uint32_t id) {
1536000 / 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),
0, // AM
0, // DSB
0, // USB
0 // LSB
8, // shift_bits_s16, default 8 bits, but also unused
8, // bits per sample
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
@@ -126,7 +133,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 +240,7 @@ SoundBoardView::SoundBoardView(
//&text_duration,
//&progressbar,
&field_volume,
&text_volume_disabled,
&page_info,
&check_loop,
&check_random,
@@ -257,6 +266,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},
+32 -21
View File
@@ -127,8 +127,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_icao_address.set(entry.icao_str);
// Try getting the aircraft information from icao24.db
std::database db{};
std::database::AircraftDBRecord aircraft_record;
database db{};
database::AircraftDBRecord aircraft_record;
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
switch (return_code) {
case DATABASE_RECORD_FOUND:
@@ -199,6 +199,10 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
}
break;
case DATABASE_RECORD_NOT_FOUND:
// Defaults should be filled by the constructor
break;
case DATABASE_NOT_FOUND:
text_manufacturer.set("No icao24.db file");
break;
@@ -233,24 +237,6 @@ ADSBRxDetailsView::ADSBRxDetailsView(
&button_aircraft_details,
&button_see_map});
// The following won't change for a given airborne aircraft.
// Try getting the airline's name from airlines.db.
// NB: Only works once callsign has been read and won't be updated.
std::database db;
std::database::AirlinesDBRecord airline_record;
std::string airline_code = entry_.callsign.substr(0, 3);
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_airline.set(airline_record.airline);
text_country.set(airline_record.country);
break;
case DATABASE_NOT_FOUND:
text_airline.set("No airlines.db file");
break;
}
text_icao_address.set(entry_.icao_str);
button_aircraft_details.on_select = [this, &nav](Button&) {
@@ -330,6 +316,31 @@ void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
}
void ADSBRxDetailsView::refresh_ui() {
// The following won't change for a given airborne aircraft.
// Try getting the airline's name from airlines.db.
if (!airline_checked && !entry_.callsign.empty()) {
airline_checked = true;
database db;
database::AirlinesDBRecord airline_record;
std::string airline_code = entry_.callsign.substr(0, 3);
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_airline.set(airline_record.airline);
text_country.set(airline_record.country);
break;
case DATABASE_RECORD_NOT_FOUND:
// text_airline.set("-"); // It's what it is constructed with
// text_country.set("-"); // It's what it is constructed with
break;
case DATABASE_NOT_FOUND:
text_airline.set("No airlines.db file");
break;
}
}
auto age = entry_.age;
if (age < 60)
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
@@ -407,7 +418,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
View File
@@ -280,6 +280,7 @@ class ADSBRxDetailsView : public View {
// NB: Keeping a copy so that it doesn't end up dangling
// if removed from the recent entries list.
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
bool airline_checked{false};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
+3 -1
View File
@@ -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();
+2 -1
View File
@@ -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},
+4 -10
View File
@@ -552,6 +552,7 @@ void DebugPmemView::update() {
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
std::srand(LPC_RTC->CTIME0);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
@@ -561,10 +562,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
nav_.pop();
break;
case KeyEvent::Down:
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
break;
case KeyEvent::Left:
pen_color = semirand();
pen_color = std::rand();
break;
default:
break;
@@ -584,17 +585,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
return true;
}
uint16_t DebugScreenTest::semirand() {
static uint64_t seed{0x0102030405060708};
seed = seed * 137;
seed = (seed >> 1) | ((seed & 0x01) << 63);
return (uint16_t)seed;
}
void DebugScreenTest::paint(Painter& painter) {
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
pen_color = semirand();
pen_color = std::rand();
}
/* DebugLCRView *******************************************************/
-1
View File
@@ -385,7 +385,6 @@ class DebugScreenTest : public View {
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
bool on_touch(TouchEvent event) override;
uint16_t semirand();
void paint(Painter& painter) override;
private:
+37 -12
View File
@@ -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,
@@ -85,9 +110,8 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
}
}
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
//
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
@@ -98,22 +122,22 @@ 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) {
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
ui::Painter painter;
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path).native();
if (path.empty()) return;
path = extract_tar(u'/' + path, painter).native();
}
if (path.empty() || !valid_firmware_file(path.c_str())) {
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 false;
}
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
@@ -126,6 +150,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
m0_halt();
return true; // fixes compiler warning (line should not be reached due to halt)
}
void FlashUtilityView::focus() {
@@ -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);
@@ -41,6 +47,7 @@ class FlashUtilityView : public View {
void focus() override;
std::string title() const override { return "Flash Utility"; };
bool flash_firmware(std::filesystem::path::string_type path);
private:
NavigationView& nav_;
@@ -55,9 +62,9 @@ class FlashUtilityView : public View {
{0, 2 * 8, 240, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
void firmware_selected(std::filesystem::path::string_type path);
void flash_firmware(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
};
+18
View File
@@ -31,6 +31,7 @@
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "wm8731.hpp"
#include "radio.hpp"
#include <cstring>
@@ -105,6 +106,7 @@ void MicTXView::configure_baseband() {
transmitting ? transmitter_model.channel_bandwidth() : 0,
mic_gain_x10 / 10.0,
shift_bits(), // to be used in dsp_modulate
8, // bits per sample
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
(mic_mod_index == MIC_MOD_AM),
(mic_mod_index == MIC_MOD_DSB),
@@ -335,6 +337,7 @@ MicTXView::MicTXView(
&field_rxlna,
&field_rxvga,
&field_rxamp,
hackrf_r9 ? &field_tx_iq_phase_cal_2839 : &field_tx_iq_phase_cal_2837,
&tx_button,
&tx_icon});
@@ -367,6 +370,21 @@ MicTXView::MicTXView(
receiver_model.set_rf_amp(v);
};
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
if (hackrf_r9) { // MAX2839 has 6 bits IQ CAL phasse adjustment.
field_tx_iq_phase_cal_2839.set_value(iq_phase_calibration_value);
field_tx_iq_phase_cal_2839.on_change = [this](int32_t v) {
iq_phase_calibration_value = v;
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
};
} else { // MAX2837 has 5 bits IQ CAL phase adjustment.
field_tx_iq_phase_cal_2837.set_value(iq_phase_calibration_value);
field_tx_iq_phase_cal_2837.on_change = [this](int32_t v) {
iq_phase_calibration_value = v;
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
};
}
options_gain.on_change = [this](size_t, int32_t v) {
mic_gain_x10 = v;
configure_baseband();
+20 -1
View File
@@ -113,6 +113,7 @@ class MicTXView : public View {
uint32_t va_level{40};
uint32_t attack_ms{500};
uint32_t decay_ms{1000};
uint8_t iq_phase_calibration_value{15};
app_settings::SettingsManager settings_{
"tx_mic",
app_settings::Mode::RX_TX,
@@ -132,6 +133,7 @@ class MicTXView : public View {
{"vox"sv, &va_enabled},
{"rogerbeep"sv, &rogerbeep_enabled},
{"tone_key_index"sv, &tone_key_index},
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
}};
rf::Frequency tx_frequency{0};
@@ -160,7 +162,8 @@ class MicTXView : public View {
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()},
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", Color::light_grey()}};
Labels labels_WM8731{
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
Labels labels_AK4951{
@@ -338,6 +341,22 @@ class MicTXView : public View {
' ',
};
NumberField field_tx_iq_phase_cal_2837{
{24 * 8, (33 * 8)},
2,
{0, 31}, // 5 bits IQ CAL phase adjustment.
1,
' ',
};
NumberField field_tx_iq_phase_cal_2839{
{24 * 8, (33 * 8)},
2,
{0, 63}, // 6 bits IQ CAL phasse adjustment.
1,
' ',
};
Button tx_button{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
"PTT TX",
+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();
+52 -8
View File
@@ -61,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();
},
@@ -80,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);
}
@@ -108,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 ******************************************/
+64 -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},
+107 -4
View File
@@ -22,11 +22,12 @@
#include "ui_view_wav.hpp"
#include "ui_fileman.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack;
#include "string_format.hpp"
namespace ui {
void ViewWavView::update_scale(int32_t new_scale) {
@@ -81,6 +82,8 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
int16_t sample;
uint32_t average;
wav_file_path = file_path;
text_filename.set(file_path.filename().string());
auto ms_duration = wav_reader->ms_duration();
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
@@ -117,9 +120,90 @@ void ViewWavView::reset_controls() {
field_cursor_b.set_value(0);
}
bool ViewWavView::is_active() {
return (bool)replay_thread;
}
void ViewWavView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
button_play.set_bitmap(&bitmap_play);
ready_signal = false;
}
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
(void)return_code;
stop();
progressbar.set_value(0);
if (return_code == ReplayThread::READ_ERROR)
file_error();
}
void ViewWavView::set_ready() {
ready_signal = true;
}
void ViewWavView::file_error() {
nav_.display_modal("Error", "File read error.");
}
void ViewWavView::start_playback() {
uint32_t sample_rate;
auto reader = std::make_unique<WAVFileReader>();
stop();
if (!reader->open(wav_file_path)) {
file_error();
return;
}
button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
progressbar.set_max(reader->sample_count());
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
read_size, buffer_count,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
baseband::set_audiotx_config(
1536000 / 20, // Rate of sending progress updates
0, // Transmit BW = 0 = not transmitting
0, // Gain - unused
8, // shift_bits_s16, default 8 bits - unused
16, // bits per sample
0, // tone key disabled
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
audio::output::start();
}
void ViewWavView::on_playback_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
ViewWavView::ViewWavView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
wav_reader = std::make_unique<WAVFileReader>();
add_children({&labels,
@@ -128,21 +212,26 @@ ViewWavView::ViewWavView(
&text_title,
&text_duration,
&button_open,
&button_play,
&waveform,
&progressbar,
&field_pos_seconds,
&field_pos_samples,
&field_scale,
&field_cursor_a,
&field_cursor_b,
&text_delta});
&text_delta,
&field_volume});
reset_controls();
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".WAV");
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
// Can't show new dialogs in an on_changed handler, so use continuation.
nav.set_on_pop([this, &nav, file_path]() {
if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file.");
file_error();
return;
}
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
@@ -155,6 +244,15 @@ ViewWavView::ViewWavView(
};
};
field_volume.set_value(field_volume.value());
button_play.on_select = [this, &nav](ImageButton&) {
if (this->is_active())
stop();
else
start_playback();
};
field_scale.on_change = [this](int32_t value) {
update_scale(value);
};
@@ -179,4 +277,9 @@ void ViewWavView::focus() {
button_open.focus();
}
ViewWavView::~ViewWavView() {
stop();
baseband::shutdown();
}
} /* namespace ui */
+70 -16
View File
@@ -24,12 +24,15 @@
#include "ui_navigation.hpp"
#include "io_wave.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
namespace ui {
class ViewWavView : public View {
public:
ViewWavView(NavigationView& nav);
~ViewWavView();
void focus() override;
void paint(Painter&) override;
@@ -37,6 +40,9 @@ class ViewWavView : public View {
std::string title() const override { return "WAV viewer"; };
private:
app_settings::SettingsManager settings_{
"wav_viewer", app_settings::Mode::NO_RF};
NavigationView& nav_;
static constexpr uint32_t subsampling_factor = 8;
@@ -46,6 +52,20 @@ class ViewWavView : public View {
void on_pos_changed();
void load_wav(std::filesystem::path file_path);
void reset_controls();
bool is_active();
void stop();
void handle_replay_thread_done(const uint32_t return_code);
void set_ready();
void file_error();
void start_playback();
void on_playback_progress(const uint32_t progress);
std::filesystem::path wav_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
const size_t read_size{2048};
const size_t buffer_count{3};
const uint32_t progress_interval_samples{1536000 / 20};
std::unique_ptr<WAVFileReader> wav_reader{};
@@ -60,10 +80,11 @@ class ViewWavView : public View {
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
{{0 * 8, 11 * 16}, "Position: s Scale:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}};
{{0 * 8, 12 * 16}, "Position: s Scale:", Color::light_grey()},
{{0 * 8, 13 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 15 * 16}, "Delta:", Color::light_grey()},
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
Text text_filename{
{5 * 8, 0 * 16, 12 * 8, 16},
@@ -80,6 +101,13 @@ class ViewWavView : public View {
Button button_open{
{24 * 8, 8, 6 * 8, 2 * 16},
"Open"};
ImageButton button_play{
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
AudioVolumeField field_volume{
{28 * 8, 18 * 16}};
Waveform waveform{
{0, 5 * 16, 240, 64},
@@ -89,34 +117,29 @@ class ViewWavView : public View {
false,
Color::white()};
ProgressBar progressbar{
{0 * 8, 11 * 16, 30 * 8, 8}};
NumberField field_pos_seconds{
{9 * 8, 11 * 16},
{9 * 8, 12 * 16},
3,
{0, 999},
1,
' '};
NumberField field_pos_samples{
{14 * 8, 11 * 16},
{14 * 8, 12 * 16},
6,
{0, 999999},
1,
'0'};
NumberField field_scale{
{28 * 8, 11 * 16},
{28 * 8, 12 * 16},
2,
{1, 40},
1,
' '};
NumberField field_cursor_a{
{9 * 8, 12 * 16},
3,
{0, 239},
1,
' ',
true};
NumberField field_cursor_b{
{9 * 8, 13 * 16},
3,
{0, 239},
@@ -124,9 +147,40 @@ class ViewWavView : public View {
' ',
true};
NumberField field_cursor_b{
{9 * 8, 14 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{
{6 * 8, 14 * 16, 30 * 8, 16},
{7 * 8, 15 * 16, 30 * 8, 16},
"-"};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
this->handle_replay_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_playback_progress(message.progress);
}};
};
} /* namespace ui */
+2
View File
@@ -211,6 +211,7 @@ void set_audiotx_config(
const float deviation_hz,
const float audio_gain,
uint8_t audio_shift_bits_s16,
uint8_t bits_per_sample,
const uint32_t tone_key_delta,
const bool am_enabled,
const bool dsb_enabled,
@@ -221,6 +222,7 @@ void set_audiotx_config(
deviation_hz,
audio_gain,
audio_shift_bits_s16,
bits_per_sample,
tone_key_delta,
(float)persistent_memory::tone_mix() / 100.0f,
am_enabled,
+1 -1
View File
@@ -63,7 +63,7 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_fifo_data(const int8_t* data);
void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
+7 -2
View File
@@ -27,14 +27,19 @@
void config_mode_blink_until_dfu();
void config_mode_set() {
portapack::persistent_memory::set_config_mode_storage_direct(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() {
if (portapack::persistent_memory::config_disable_config_mode_direct())
return false;
else
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_GUARD_VALUE;
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
}
void config_mode_clear() {
+7
View File
@@ -28,6 +28,13 @@
#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();
+6 -7
View File
@@ -25,14 +25,12 @@
#include "file.hpp"
#include <cstring>
namespace std {
int database::retrieve_mid_record(MidDBRecord* record, std::string search_term) {
file_path = "AIS/mids.db";
index_item_length = 4;
record_length = 32;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
@@ -42,7 +40,7 @@ int database::retrieve_airline_record(AirlinesDBRecord* record, std::string sear
index_item_length = 4;
record_length = 64;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
@@ -52,12 +50,15 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
index_item_length = 7;
record_length = 146;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
int database::retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
auto result = db_file.open(file_path);
if (!result.is_valid()) {
number_of_records = (db_file.size() / (index_item_length + record_length)); // determine number of records in file
@@ -91,5 +92,3 @@ int database::retrieve_record(std::string file_path, int index_item_length, int
} else
return (DATABASE_NOT_FOUND);
}
} /* namespace std */
+3 -5
View File
@@ -30,7 +30,6 @@
#include "file.hpp"
namespace std {
class database {
public:
#define DATABASE_RECORD_FOUND 0 // record found in database
@@ -62,9 +61,9 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
private:
string file_path = ""; // path inclusing filename
int index_item_length = 0; // length of index item
int record_length = 0; // length of record
std::string file_path = ""; // path inclusing filename
int index_item_length = 0; // length of index item
int record_length = 0; // length of record
File db_file{};
int number_of_records = 0;
@@ -77,6 +76,5 @@ class database {
int retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
} // namespace std
#endif /*__DATABASE_H__*/
+16 -6
View File
@@ -3,7 +3,11 @@ set(EXTCPPSRC
#pacman
external/pacman/main.cpp
external/pacman/ui_pacman.cpp
#tetris
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
@@ -19,7 +23,7 @@ set(EXTCPPSRC
#blespam
external/blespam/main.cpp
external/blespam/ui_blespam.cpp
#analogtv
external/analogtv/main.cpp
external/analogtv/analog_tv_app.cpp
@@ -35,13 +39,12 @@ set(EXTCPPSRC
#lge
external/lge/main.cpp
external/lge/lge_app.cpp
#lcr
external/lcr/main.cpp
external/lcr/ui_lcr.cpp
#lcr
#jammer
external/jammer/main.cpp
external/jammer/ui_jammer.cpp
@@ -54,6 +57,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 +78,6 @@ set(EXTAPPLIST
jammer
gpssim
spainter
keyfob
tetris
)
+20 -6
View File
@@ -30,6 +30,8 @@ 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
ram_external_app_tetris(rwx) : org = 0xEEF70000, len = 32k
}
SECTIONS
@@ -58,7 +60,7 @@ SECTIONS
*(*ui*external_app*font_viewer*);
} > ram_external_app_font_viewer
.external_app_blespam : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_blespam.application_information));
@@ -76,33 +78,33 @@ SECTIONS
KEEP(*(.external_app.app_nrf_rx.application_information));
*(*ui*external_app*nrf_rx*);
} > ram_external_app_nrf_rx
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_coasterp.application_information));
*(*ui*external_app*coasterp*);
} > ram_external_app_coasterp
.external_app_lge : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_lge.application_information));
*(*ui*external_app*lge*);
} > ram_external_app_lge
.external_app_lcr : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_lcr.application_information));
*(*ui*external_app*lcr*);
} > ram_external_app_lcr
.external_app_jammer : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_jammer.application_information));
*(*ui*external_app*jammer*);
} > ram_external_app_jammer
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_gpssim.application_information));
@@ -116,4 +118,16 @@ 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
.external_app_tetris : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tetris.application_information));
*(*ui*external_app*tetris*);
} > ram_external_app_tetris
}
+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 */
+60 -15
View File
@@ -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) {
+29
View File
@@ -0,0 +1,29 @@
/*
* 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.
*/
// dummy include file to avoid changing original source
#ifndef __UI_Arial12x12_H__
#define __UI_Arial12x12_H__
#define Arial12x12 (0)
#endif /*__UI_Arial12x12_H__*/
+102
View File
@@ -0,0 +1,102 @@
/*
* 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.
*/
// "HAL" display layer for Tetris code to run on PortaPack without its original ILI9341 functions
#ifndef __UI_SPI_TFT_ILI9341_H__
#define __UI_SPI_TFT_ILI9341_H__
ui::Painter painter;
static int x_pos{0};
static int y_pos{0};
static int fg_color;
static int bg_color;
enum {
White,
Blue,
Yellow,
Purple,
Green,
Red,
Maroon,
Orange,
Black,
};
// pp_colors must be in same order as enums above
static const Color pp_colors[] = {
Color::white(),
Color::blue(),
Color::yellow(),
Color::purple(),
Color::green(),
Color::red(),
Color::magenta(),
Color::orange(),
Color::black(),
};
class SPI_TFT_ILI9341 {
public:
SPI_TFT_ILI9341(int, int, int, int, int, int, std::string) { (void)0; };
void claim(__FILE* x) { (void)x; };
void cls() {
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
};
void background(int color) { bg_color = color; };
void foreground(int color) { fg_color = color; };
void locate(int x, int y) {
x_pos = x;
y_pos = y;
};
void set_orientation(int x) { (void)x; };
void set_font(unsigned char* x) { (void)x; };
void fillrect(int x1, int y1, int x2, int y2, int color) {
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
void rect(int x1, int y1, int x2, int y2, int color) {
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
private:
};
static void printf(std::string str) {
auto style = (fg_color == White) ? ui::Styles::white : ui::Styles::bg_white;
painter.draw_string({x_pos, y_pos - 1}, style, str);
};
static void printf(std::string str, int v) {
if (str.find_first_of("%") != std::string::npos) {
str.resize(str.find_first_of("%")); // remove %d from end of string
}
printf(str + to_string_dec_uint(v));
};
#endif /*__UI_SPI_TFT_ILI9341_H__*/
+82
View File
@@ -0,0 +1,82 @@
/*
* 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.
*/
#include "ui.hpp"
#include "ui_tetris.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::tetris {
void initialize_app(ui::NavigationView& nav) {
nav.push<TetrisView>();
}
} // namespace ui::external_app::tetris
extern "C" {
__attribute__((section(".external_app.app_tetris.application_information"), used)) application_information_t _application_information_tetris = {
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
/*.externalAppEntry = */ ui::external_app::tetris::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Tetris",
/*.bitmap_data = */ {
0xF8,
0xFF,
0x88,
0x88,
0x88,
0x88,
0x88,
0x88,
0xF8,
0xFF,
0x80,
0x08,
0x80,
0x08,
0x9F,
0x08,
0x91,
0x0F,
0x11,
0x00,
0x11,
0x00,
0xFF,
0xF1,
0x11,
0x91,
0x11,
0x91,
0x11,
0x91,
0xFF,
0xF1,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.m4_app_tag = portapack::spi_flash::image_tag_noop */ {'\0', '\0', '\0', '\0'}, // optional
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+220
View File
@@ -0,0 +1,220 @@
/*
* 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.
*/
// "HAL" layer for Tetris code to run on PortaPack without its original mbed OS
// (the dream here was to avoid modifying the original code)
#ifndef __UI_mbed_H__
#define __UI_mbed_H__
using Callback = void (*)(void);
#define wait_us(x) (void)0
#define wait(x) chThdSleepMilliseconds(x * 1000)
#define PullUp 1
enum {
dp0,
dp1,
dp2,
dp3,
dp4,
dp5,
dp6,
dp7,
dp8,
dp9,
dp10,
dp11,
dp12,
dp13,
dp14,
dp15,
dp16,
dp17,
dp18,
dp19,
dp20,
dp21,
dp22,
dp23,
dp24,
dp25,
};
static bool but_RIGHT;
static bool but_LEFT;
static bool but_UP;
static bool but_DOWN;
static bool but_SELECT;
//
// AnalogIn Class -- DID NOT WORK BECAUSE INITIALIZER CODE WON'T EXECUTE -- hacked original code module instead
//
// dp9 = joystick rotate button --> select button
// dp10 = joystick y --> up & down buttons
// dp11 = joystick x --> left & right buttons
// dp13 = random number generator
//
//
// class AnalogIn {
// public:
// AnalogIn(uint32_t analog_input) {
// // FIXME - THIS CODE NEVER GETS EXECUTED!
// analog_input_ = analog_input;
// };
//
// // Tetris code only uses this function for dp13 - supposed to be a random number
// uint16_t read_u16() {
// return std::rand();
// };
//
// // Tetris code uses read() function for direction buttons only
// float read() {
// float retval = 0.5;
// switch (analog_input_) {
// case dp11:
// if (but_LEFT)
// retval = 0.0;
// else if (but_RIGHT)
// retval = 1.0;
// break;
//
// case dp10:
// if (but_UP)
// retval = 0.0;
// else if (but_DOWN)
// retval = 1.0;
// break;
// }
// return retval;
// };
//
// operator float() {
// return read();
// };
//
// private:
// uint32_t analog_input_{INT_MAX};
// };
//
// Timer Class
// (Timer object was used for unneeded button debouncing, so just returning 1000ms to indicate we've waited long enough)
//
class Timer {
public:
// NOTE: INITIALIZER CODE WON'T RUN
Timer() { (void)0; };
void reset() { (void)0; };
void start() { (void)0; }
uint32_t read_ms() { return 1000; };
private:
};
//
// Ticker Class
// (Ticker is timed callback, used for checking "joystick" directional switches and when time to move piece down a row)
//
// NB: Only one callback is supported per Ticker class instantiation
static Callback fall_timer_callback;
static uint32_t fall_timer_timeout;
static uint32_t fall_timer_counter;
static Callback dir_button_callback;
static void check_fall_timer() {
if (fall_timer_callback) {
if (++fall_timer_counter >= fall_timer_timeout) {
fall_timer_counter = 0;
fall_timer_callback();
}
}
}
class Ticker {
public:
// NOTE: INITIALIZER CODE WON'T RUN
Ticker() { (void)0; };
void attach(Callback func, double delay_sec) {
// 0.3 sec is requested only for button check -- kludge to use on_key callback for this one instead of timer
if (delay_sec == 0.3) {
dir_button_callback = func;
} else {
fall_timer_callback = func;
fall_timer_timeout = delay_sec * 60; // timer interrupts at 60 Hz
}
}
void detach() {
// shouldn't detach both, but don't know how to tell which object is which
dir_button_callback = nullptr;
fall_timer_callback = nullptr;
}
private:
};
//
// InterruptIn Class
// (just used for the Select button)
//
static Callback sel_button_callback;
static bool check_encoder(const EncoderEvent delta) {
(void)delta;
// TODO: consider adding ability to rotate Tetronimo via encoder too
return false;
}
static bool check_key(const KeyEvent key) {
auto switches_debounced = get_switches_state().to_ulong();
but_RIGHT = (switches_debounced & 0x01) != 0;
but_LEFT = (switches_debounced & 0x02) != 0;
but_DOWN = (switches_debounced & 0x04) != 0;
but_UP = (switches_debounced & 0x08) != 0;
but_SELECT = (switches_debounced & 0x10) != 0;
if (key == KeyEvent::Select) {
if (sel_button_callback)
sel_button_callback();
} else {
if (dir_button_callback)
dir_button_callback();
}
return true;
}
class InterruptIn {
public:
InterruptIn(int reg) {
// NOTE: INITIALIZER CODE WON'T RUN
(void)reg;
};
void fall(Callback func) { sel_button_callback = func; };
void mode(int v) { (void)v; };
private:
};
#endif /*__UI_mbed_H__*/
+578
View File
@@ -0,0 +1,578 @@
// Projekat Tetris
// by Vrnjak Lamija & Selimović Denis
// Elektrotehnički fakultet Sarajevo
// clang-format off
//////// PORTAPACK CHANGES HIGHLIGHTED
int main();
void pause_game();
//////// PORTAPACK
#include "mbed.h"
#include "SPI_TFT_ILI9341.h"
#include "Arial12x12.h"
#define dp23 P0_0
//deklaracija display-a
SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT");
//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING:
//analogni ulazi za joystick
// AnalogIn VRx(dp11);
// AnalogIn VRy(dp10);
//////// PORTAPACK
//taster na joysticku za rotaciju
InterruptIn taster(dp9);
//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING:
// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti
//////// PORTAPACK
//ticker za spustanje figure
//timer za debouncing tastera na joysticku
Ticker game, joystick;
Timer debounceTaster;
unsigned char level = 0; //mora biti tipa usigned char jer inače se može desiti da level bude manji od 0, a i da ne trošimo memoriju
const float delays[4] = {1.2, 0.7, 0.4, 0.25}; //svakih koliko se spusti jedan red, ovo provjeriti da li je presporo ili prebrzo, ovisi o levelu
//////// PORTAPACK - UNNEEDED JOYSTICK HYSTERESIS VARIABLES
//char leftBoundary = 1, rightBoundary = 5, downBoundary = 1, upBoundary = 5;// sada je ovo tipa char
//////// PORTAPACK
unsigned int score = 0; //stavio sam ovo unsigned int za veći opseg, mada je jako teško da se i int premaši, ali nmvz
bool firstTime = true; //ako je prvi put, figura se crta u Tickeru
bool gameStarted = false;
unsigned char nextFigure = 1; //ovo je sad globalna varijabla, da bi mogli unaprijed generisati sljedeću figuru radi prikaza
//white - no figure
//I - BLUE
//O - YELLOW
//T - PURPLE
//S - GREEN
//Z - RED
//J - MAROON
//L - ORANGE
const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
const short DIMENSION = 16;
const short DIMENSION_NEXT = 12;
short board[20][10] = {0}; //matrica boja, 0 - 7 indeksi boja
short figuresX[7][4] = {{0,0,0,0}, {0,0,1,1}, {0,1,1,1}, {1,1,0,0}, {0,1,0,1}, {1,1,1,0}, {1,1,1,0}};
short figuresY[7][4] = {{0,1,2,3}, {1,0,0,1}, {1,1,2,0}, {0,1,1,2}, {0,1,1,2}, {2,1,0,0}, {0,1,2,2}};
unsigned int GenerateRandomSeed() {
//////// PORTAPACK - USE RTC FOR SEED
return LPC_RTC->CTIME0;
// unsigned int randomNumber = 0;
// for(int i = 0; i <= 32; i += 2) {
// randomNumber += ((random.read_u16() % 3) << i);
// wait_us(10);
// }
// return randomNumber;
//////// PORTAPACK
}
void Init() {
//ovo su zajedničke osobine za sve prikaze na display-u
//nikad se ne mijenjaju i pozvat ćemo je jednom prije petlje
display.claim(stdout);
display.set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat
display.set_font((unsigned char*) Arial12x12);
}
void ShowScore() {
//pomocna funkcija za prikazivanje score-a
display.fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score
display.locate(200, 35); //valjda je na sredini pravougaonika
printf("%d", score);
}
void ShowNextFigure() {
//prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure
display.fillrect(165, 70, 235, 120, White);
int upperLeftX = 176, upperLeftY = 83;
for(int i = 0; i < 4; i++) {
int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
display.fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
display.rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
}
}
//funkcija za crtanje cursora za odabir levela
void DrawCursor(int color, unsigned char lev) {
display.fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
}
// PORTAPACK - ADDED EXTRA LEVEL:
void ShowLevelMenu() {
//ovdje inicijalizujemo display
display.cls(); // brišemo prethodno
display.background(Black);
display.foreground(White);
display.locate(80, 50);
printf("LEVEL 1");
display.locate(80, 120);
printf("LEVEL 2");
display.locate(80, 190);
printf("LEVEL 3");
display.locate(80, 260);
printf("LEVEL 4");
DrawCursor(White, level);
}
//////// PORTAPACK - KLUDGED FOR BUTTONS VS JOYSTICK:
void ReadJoystickForLevel(){
unsigned char old = level;
if(but_UP){
// upBoundary = 4;
(level == 0) ? level = 3 : level--;
}
else if(but_DOWN){
//ne radi ona prethodna varijanta jer % vraća i negastivni rezultat
//to što ne koristimo unsigned tip ne pomaže jer će doći do overflow-a
// downBoundary = 2;
level = (level + 1) % 4;
}
else {
// downBoundary = 1;
// upBoundary = 5;
}
DrawCursor(Black, old); //na prethodni level popunimo bojom pozadine
DrawCursor(White, level); //na novi level popunimo bijelom bojom - pozadina je crna
//koristio sam fillrect, jer njega svakako moramo koristiti, jer možda budemo morali da brišemo fillcircle iz biblioteke
}
//////// PORTAPACK
void EndPlay() {
joystick.detach();
score = 0;
firstTime = true;
gameStarted = false;
for(int i = 0; i < 20; i++) {
for(int j = 0; j < 10; j++) {
board[i][j] = 0;
}
}
//////// PORTAPACK - FIX TO REINITIALIZE SCREEN
// ShowLevelMenu();
// joystick.attach(&ReadJoystickForLevel, 0.3);
main();
//////// PORTAPACK
}
void StartGame()
{
display.cls(); // brišemo ShowLevelMenu
display.background(White);
display.foreground(Black);
display.fillrect(0, 0, 160, 320, White);
display.fillrect(160, 0, 240, 320, Black); //dio za prikazivanje rezultata će biti crni pravougaonik, a tabla je bijeli
ShowScore();
}
void copyCoordinates(short X[], short Y[], unsigned char index)
{
//umjesto one prošle metode za kopiranje, ova prima index, čisto da nam olakša život
for(int i = 0; i < 4; i++) {
X[i] = figuresX[index][i];
Y[i] = figuresY[index][i];
}
}
bool BottomEdge(int x){
return x > 19;
}
bool LeftEdge(int y){
return y < 0;
}
bool RightEdge(int y){
return y > 9;
}
bool OutOfBounds(int y, int x){
return y < 0 || y > 9 || x > 19;
}
void PutBorders(short x, short y) {
for(int i = x - 1; i <= x + 1; i++) {
for(int j = y - 1; j <= y + 1; j++) {
if(i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
display.rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
}
}
}
class Tetromino{
private:
short X[4];
short Y[4];
short boardX, boardY;
unsigned char colorIndex;//dodao sam colorIndex zasad, jer nema drugog načina da popunimo matricu sa indeksima boja
//ovo je najbezbolnija varijanta što se memorije tiče
public:
Tetromino(){
//////// PORTAPACK - NOTE - DEFAULT INITIALIZER CODE DOESN'T GET EXECUTED FOR SOME REASON:
unsigned char r = rand() % 7 + 1;
Initialize(r);
}
Tetromino(unsigned char colorIndex) {
Initialize(colorIndex);
}
void Initialize(unsigned char colorIndex) {
Tetromino::colorIndex = colorIndex;
boardX = 0;
boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display
copyCoordinates(X, Y, colorIndex - 1);
}
void Rotate(){
short pivotX = X[1], pivotY = Y[1];
//prvi elemnti u matrici su pivoti oko koje rotiramo
short newX[4]; //pozicije blokova nakon rotiranja ako sve bude ok
short newY[4];
for(int i = 0; i < 4; i++){
short tmp = X[i], tmp2 = Y[i];
newX[i] = pivotX + pivotY - tmp2;
newY[i] = tmp + pivotX - pivotY;
if(OutOfBounds(boardY + newY[i], boardX + newX[i]) || board[boardX + newX[i]][boardY + newY[i]] != 0) return;
}
DeleteFigure();
for(int i = 0; i < 4; i++){
X[i] = newX[i];
Y[i] = newY[i];
}
DrawFigure();
}
void DrawFigure() {
for(int i = 0; i < 4; i++) {
//display je deklarisani display logy iz biblioteke one
//računamo gornji lijevi pixel po x i y
//donji desni dobijemo kad dodamo DIMENZIJU
//stavio sam 16 za početak, možemo se opet skontati na labu
//ovo pretpostavlja da nema margina, mogu se lagano dodati uz neku konstantu kao offset
int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
display.fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
//ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa
//////// PORTAPACK - HIDE DEFAULT WHITE BLOCK (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION)
if (colorIndex != White)
//////// PORTAPACK
display.rect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
}
}
void DeleteFigure() {
for(int i = 0; i < 4; i++) {
//ista logika kao u DrawFigure, samo popunjavamo sve blokove sa bijelim pravougaonicima
short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
display.fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, White);
PutBorders(upperLeftY, upperLeftX);
}
}
void OnAttached() {
//metoda se poziva kad figura prestanje da se krece
//popunimo matricu indeksom boje
for(int i = 0; i < 4; i++) {
board[boardX + X[i]][boardY + Y[i]] = colorIndex;
//btw board bi mogao biti niz tipa unsigned char, ali to ćemo vidjet kasnije
}
}
bool MoveDown(char delta = 1){
//////// PORTAPACK - MOVE DEFAULT WHITE BLOCK TO BOTTOM IMMEDIATELY (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION)
if (colorIndex == White) delta = 19;
//////// PORTAPACK
if(!InCollisionDown(delta)){
DeleteFigure();
boardX+=delta;
DrawFigure();
return true;
}
return false;
}
void MoveLeft(){
if(!InCollisionLeft()){
DeleteFigure();
boardY--;
DrawFigure();
}
}
void MoveRight(){
if(!InCollisionRight()){
DeleteFigure();
boardY++;
DrawFigure();
}
}
void SoftDrop() {
//ovo je funkcija za soft drop
//obrisemo figuru, postavimo novu poziciju dva reda ispod, nacrtamo figuru
DeleteFigure();
MoveDown(2);
DrawFigure();
//treba još vidjeti koje izmjene u tickeru trebaju
score += 2 * (level +1); //prema igrici koju smo igrali, dobije se 14 poena kad se uradi hard drop
ShowScore();
}
bool InCollisionDown(char delta = 1){
int newX, newY; //da bi bilo citljivije
for(int i = 0; i < 4; i++){
newX = boardX + X[i] + delta;
newY = boardY + Y[i];
if(BottomEdge(newX) || board[newX][newY] != 0){
return true;
}
//jedna figura je u koliziji ako
//pozicija na koju zelimo da pomjerimo bilo koji blok dotakne dno ili lijevu ili desnu ivicu ekrana
//ili ako je pozicija na koju zelimo da pomjerimo bilo koji od blokova vec zauzeta a da nije dio figure prije pomijeranja
}
return false;
}
bool InCollisionLeft(){
int newX, newY;
for(int i = 0; i < 4; i++){
newX = boardX + X[i];
newY = boardY + Y[i] - 1;
if(LeftEdge(newY) || board[newX][newY] != 0){
return true;
}
}
return false;
}
bool InCollisionRight(){
int newX, newY;
for(int i = 0; i < 4; i++){
newX = boardX + X[i];
newY = boardY + Y[i] + 1;
if(RightEdge(newY) || board[newX][newY] != 0){
return true;
}
}
return false;
}
};
Tetromino currentTetromino;
//////// PORTAPACK - KLUDGED FOR BUTTONS VS JOYSTICK:
void ReadJoystickForFigure() {
if(but_LEFT) {
// leftBoundary = 2;
currentTetromino.MoveLeft();
}
else if(but_RIGHT) {
// rightBoundary = 4;
currentTetromino.MoveRight();
}
else if(but_UP) {
// downBoundary = 2;
pause_game();
}
else if(but_DOWN) {
// upBoundary = 4;
currentTetromino.SoftDrop();
}
else {
// leftBoundary = 1;
// rightBoundary = 5;
// downBoundary = 1;
// upBoundary = 5;
}
}
//////// PORTAPACK
void CheckLines(short &firstLine, short &numberOfLines)
{
//vraća preko reference prvu liniju koja je popunjena do kraja, kao i takvih linija
firstLine = -1; //postavljen na -1 jer ako nema linija koje treba brisati ispod u UpdateBoard neće se ući u petlju
numberOfLines = 0;
for(int i = 19; i >= 0; i--) {
short temp = 0;
for(int j = 0; j < 10; j++) {
if(board[i][j] == 0) {
if(numberOfLines > 0) return;
break;
}//ako je makar jedna bijela, prekida se brojanje
temp++;
}
if(temp == 10) { //ako je temo došao do 10, niti jedna bijela - puna linija
numberOfLines++;
if(firstLine == -1) firstLine = i; //ovo mijenjamo samo prvi put
}
}
}
unsigned int UpdateScore (short numOfLines){
unsigned int newIncrement = 0;
switch(numOfLines) {
case 1 : newIncrement = 40; break;
case 2 : newIncrement = 100; break;
case 3 : newIncrement = 300; break;
case 4 : newIncrement = 1200; break;
default : newIncrement = 0; break; //update funkcije za score, još sam vratio ovo na 0
}
return newIncrement * (level + 1);
}
void UpdateBoard()
{
short firstLine, numberOfLines;
//pozivamo funkciju
do {
CheckLines(firstLine, numberOfLines);
for(int i = firstLine; i >= numberOfLines; i--) {
for(int j = 0; j < 10; j++) {
board[i][j] = board[i - numberOfLines][j];
board[i - numberOfLines][j] = 0;
short tmp = i - numberOfLines;
display.fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok
display.fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White);
if(board[i][j] != 0)
display.rect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , Black);
}
}
score += UpdateScore(numberOfLines);
}
while(numberOfLines != 0); //ovdje se mijenja globalna varijabla score
}
bool IsOver() {
for(int i = 0; i < 10; i++) {
if(board[0][i] != 0) return true;
}
return false;
}
void ShowGameOverScreen() {
//////// PORTAPACK - SKIP CLS
// display.cls();
// display.background(Black);
// display.foreground(White);
display.background(White);
display.foreground(Black);
//////// PORTAPACK
display.locate(60, 120);
printf("GAME OVER");
display.locate(40, 150);
printf("YOUR SCORE IS %d", score);
wait(5); //ovaj prikaz traje 3s (možemo mijenjati) a nakon toga se ponovo prikazuje meni sa levelima
}
void InitGame() {
if(firstTime) {
//////// PORTAPACK - NOTE - ATTEMPTED WORKAROUND FOR SKIPPED INITIALIZER CODE - BUT ANY OF THESE CRASHES FIRMWARE AT POWER-UP EVEN IF THERE'S NO TETRIS APP INSTALLED:
// currentTetromino = Tetromino(rand() % 7 + 1); // TEST #1
// currentTetromino.Initialize(rand() % 7 + 1); // TEST #2
//////// PORTAPACK
currentTetromino.DrawFigure();
nextFigure = rand() % 7 + 1;
ShowNextFigure();
firstTime = false;
}
}
void PlayGame(){
InitGame();
if(!currentTetromino.MoveDown()){
currentTetromino.OnAttached();
UpdateBoard();
ShowScore();
currentTetromino = Tetromino(nextFigure);
currentTetromino.DrawFigure();
nextFigure = rand() % 7 + 1;
ShowNextFigure();
if(IsOver()) {
//ako je igra završena brišemo sve sa displey-a, prikazujemo poruku i score
//takođe moramo dettach-at ticker
game.detach();
ShowGameOverScreen(); //prikaz da je kraj igre, ima wait od 3s
EndPlay();
}
}
}
void OnTasterPressed(){
if(debounceTaster.read_ms() > 200) {
if(gameStarted){
currentTetromino.Rotate();
}
else{
joystick.detach();
gameStarted = true;
StartGame(); //pocinje igra, prikazuje se tabla
joystick.attach(&ReadJoystickForFigure, 0.3);
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
}
debounceTaster.reset();
}
}
void SetTaster() {
taster.mode(PullUp); //mora se aktivirati pull up otpornik na tasteru joystick-a
taster.fall(&OnTasterPressed);
debounceTaster.reset();
debounceTaster.start();
}
int main() {
srand(GenerateRandomSeed());
Init();
ShowLevelMenu();
joystick.attach(&ReadJoystickForLevel, 0.3);
SetTaster();
//////// PORTAPACK: CAN'T HANG HERE IN MAYHEM OR WE WON'T GET ANY CALLBACKS
// while(1);
return 0;
//////// PORTAPACK
}
//////// PORTAPACK - ADDED PAUSE FEATURE:
void pause_game() {
game.detach();
joystick.detach();
display.locate(180, 200);
printf("PAUSED");
while ((get_switches_state().to_ulong() & 0x10) == 0); // wait for SELECT button to resume
printf(" ");
joystick.attach(&ReadJoystickForFigure, 0.3);
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
};
//////// PORTAPACK
+39
View File
@@ -0,0 +1,39 @@
#include "ui_tetris.hpp"
namespace ui::external_app::tetris {
#pragma GCC diagnostic push
// external code, so ignore warnings
#pragma GCC diagnostic ignored "-Weffc++"
#include "tetris.cpp"
#pragma GCC diagnostic pop
TetrisView::TetrisView(NavigationView& nav)
: nav_(nav) {
add_children({&dummy});
}
void TetrisView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
main();
}
}
void TetrisView::frame_sync() {
check_fall_timer();
set_dirty();
}
bool TetrisView::on_encoder(const EncoderEvent delta) {
return check_encoder(delta);
}
bool TetrisView::on_key(const KeyEvent key) {
return check_key(key);
}
} // namespace ui::external_app::tetris
+65
View File
@@ -0,0 +1,65 @@
/*
* 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.
*/
#ifndef __UI_TETRIS_H__
#define __UI_TETRIS_H__
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
#include "ui_styles.hpp"
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "limits.h"
namespace ui::external_app::tetris {
class TetrisView : public View {
public:
TetrisView(NavigationView& nav);
std::string title() const override { return "Tetris"; };
void focus() override { dummy.focus(); };
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
private:
bool initialized = false;
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::tetris
#endif /*__UI_TETRIS_H__*/
+22 -1
View File
@@ -25,7 +25,7 @@
#include "utility.hpp"
uint8_t Debounce::state() {
bool v = state_to_report_;
uint8_t v = state_to_report_;
simulated_pulse_ = false;
return v;
}
@@ -149,3 +149,24 @@ bool Debounce::feed(const uint8_t bit) {
return false;
}
uint8_t EncoderDebounce::state() {
return state_;
}
// Returns TRUE if encoder position phase bits changed (after debouncing)
bool EncoderDebounce::feed(const uint8_t phase_bits) {
history_ = (history_ << 2) | phase_bits;
// Has input been constant for 4 ticks? (phase_bits * 01010101b should be 0x00, 0x55, 0xAA, or 0xFF)
if (history_ == (phase_bits * 0x55)) {
// Has the debounced input value changed?
if (state_ != phase_bits) {
state_ = phase_bits;
return true;
}
}
// Unstable input, or no change
return false;
}
+12
View File
@@ -64,4 +64,16 @@ class Debounce {
bool long_press_occurred_{false}; // TRUE when button is being held down and LONG_PRESS_DELAY has been reached (only when long_press_enabled)
};
class EncoderDebounce {
public:
bool feed(const uint8_t phase_bits); // returns TRUE if state changed after debouncing
uint8_t state(); // returns debounced phase bits from encoder
private:
uint8_t history_{0}; // shift register of previous reads from encoder
uint8_t state_{0}; // actual encoder output state (after debounce logic)
};
#endif /*__DEBOUNCE_H__*/
+44 -75
View File
@@ -26,82 +26,51 @@
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
// Now supporting multiple levels of rotary encoder dial sensitivity
//
// Portapack H2 normally has a 30-step encoder, meaning one step (pulse) every
// 12 degrees of rotation.
//
// For each encoder "pulse" there are 4 state transitions, and we can choose
// between looking at all of them (high sensitivity), half of them (medium/default),
// or one quarter of them (low sensitivity).
static const int8_t transition_map[][16] = {
// Normal (Medium) Sensitivity -- default
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// Low Sensitivity
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
0, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
0, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// High Sensitivity
{
0, // 0000: noop
-1, // 0001: ccw start
1, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
1, // 1101: cw start
-1, // 1110: ccw start
0, // 1111: noop
},
// Transition map for rotary encoder phase bits
// 00 -> 01 -> 11 -> 10 cw
// 00 -> 10 -> 11 -> 01 ccw
// NB: Bits are swapped versus older FW versions
static const int8_t transition_map[16] = {
0, // 00->00: noop
1, // 00->01: cw start
-1, // 00->10: ccw start
0, // 00->11: rate
-1, // 01->00: ccw end
0, // 01->01: noop
0, // 01->10: rate
1, // 01->11: cw end
1, // 10->00: cw end
0, // 10->01: rate
0, // 10->10: noop
-1, // 10->11: ccw end
0, // 11->00: rate
-1, // 11->01: ccw start
1, // 11->10: cw start
0, // 11->11: noop
};
int_fast8_t Encoder::update(
const uint_fast8_t phase_0,
const uint_fast8_t phase_1) {
state <<= 1;
state |= phase_0;
state <<= 1;
state |= phase_1;
// Rotary encoder dial sensitivity (transition ignored if bit is 0)
static const uint16_t sensitivity_map[] = {
0x0990, // DIAL_SENSITIVITY_NORMAL
0x0110, // DIAL_SENSITIVITY_LOW
0x6996, // DIAL_SENSITIVITY_HIGH
};
// dial sensitivity setting is stored in pmem
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
state = ((state << 2) | phase_bits) & 0x0F;
int_fast8_t direction = transition_map[state];
// Require 2 state changes in same direction to register movement -- for additional level of contact switch debouncing
if (direction == prev_direction) {
if ((sensitivity_map[portapack::persistent_memory::config_encoder_dial_sensitivity()] & (1 << state)) == 0)
return 0;
return direction;
}
// It's normal for transition map to return 0 between every +1/-1, so discarding those
if (direction != 0)
prev_direction = direction;
return 0;
}
+2 -3
View File
@@ -26,12 +26,11 @@
class Encoder {
public:
int_fast8_t update(
const uint_fast8_t phase_0,
const uint_fast8_t phase_1);
int_fast8_t update(const uint_fast8_t phase_bits);
private:
uint_fast8_t state{0};
int_fast8_t prev_direction{0};
};
#endif /*__ENCODER_H__*/
+35 -3
View File
@@ -149,7 +149,33 @@ void MAX2837::init() {
set_mode(Mode::Standby);
}
enum class Mask {
void MAX2837::set_tx_LO_iq_phase_calibration(const size_t v) {
/* IQ phase deg CAL adj (+4 ...-4) in 32 steps (5 bits), 00000 = +4deg (Q lags I by 94degs, default), 01111 = +0deg, 11111 = -4deg (Q lags I by 86degs) */
// TX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,0,1 (5dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
gpio_max283x_enable.output();
gpio_max2837_rxenable.output();
gpio_max2837_txenable.output();
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
_map.r.tx_lo_iq.TXLO_IQ_SPI_EN = 1; // reg 30 D5, TX LO I/Q Phase SPI Adjust. Active when Address 30 D5 (TXLO_IQ_SPI_EN) = 1.
_map.r.tx_lo_iq.TXLO_IQ_SPI = v; // reg 30 D4:D0, TX LO I/Q Phase SPI Adjust.
flush_one(Register::TX_LO_IQ);
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
flush_one(Register::SPI_EN);
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
}
enum class Mask { // There are class Mask ,and class mode with same names, but they are not same.
Enable = 0b001,
RxEnable = 0b010,
TxEnable = 0b100,
@@ -157,9 +183,11 @@ enum class Mask {
Standby = Enable,
Receive = Enable | RxEnable,
Transmit = Enable | TxEnable,
Rx_calibration = Enable | RxEnable, // sets the same 3 x logic pins to the Receive operating mode.
Tx_calibration = Enable | TxEnable, // sets the same 3 x logic pins to the Transmit operating mode.
};
Mask mode_mask(const Mode mode) {
Mask mode_mask(const Mode mode) { // based on enum Mode cases, we set up the correct 3 logic PINS .
switch (mode) {
case Mode::Standby:
return Mask::Standby;
@@ -167,12 +195,16 @@ Mask mode_mask(const Mode mode) {
return Mask::Receive;
case Mode::Transmit:
return Mask::Transmit;
case Mode::Rx_Calibration: // Let's add those two CAL logic pin settings- Rx and Tx calibration modes.
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxEnable, (the difference is in Reg add 16 D1:DO)
case Mode::Tx_Calibration: // Let's add this CAL Tx calibration mode = Transmit.
return Mask::Tx_calibration; // same logic pins as Transmit = Enable | TxEnable,(the difference is in Reg add 16 D1:DO)
default:
return Mask::Shutdown;
}
}
void MAX2837::set_mode(const Mode mode) {
void MAX2837::set_mode(const Mode mode) { // We set up the 3 Logic Pins ENABLE, RXENABLE, TXENABLE accordingly to the max2837 mode case, that we want to set up .
Mask mask = mode_mask(mode);
gpio_max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
gpio_max2837_rxenable.write(toUType(mask) & toUType(Mask::RxEnable));
+1
View File
@@ -829,6 +829,7 @@ class MAX2837 : public MAX283x {
bool set_frequency(const rf::Frequency lo_frequency) override;
void set_rx_lo_iq_calibration(const size_t v) override;
void set_tx_LO_iq_phase_calibration(const size_t v) override;
void set_rx_bias_trim(const size_t v);
void set_vco_bias(const size_t v);
void set_rx_buff_vcm(const size_t v) override;
+31
View File
@@ -142,6 +142,31 @@ void MAX2839::init() {
set_mode(Mode::Standby);
}
void MAX2839::set_tx_LO_iq_phase_calibration(const size_t v) {
/* IQ phase deg CAL adj (+4 ...-4) This IC in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
// TX calibration , 2 x Logic pins , ENABLE, RXENABLE = 1,0, (2dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one(Register::SPI_EN);
_map.r.pa_drv.TXLO_IQ_SPI_EN = 1; // reg 27 D6, TX LO I/Q Phase SPI Adjust. Active when Address 27 D6 (TXLO_IQ_SPI_EN) = 1.
_map.r.pa_drv.TXLO_IQ_SPI = v; // reg 27 D5:D0 6 bits, TX LO I/Q Phase SPI Adjust.
flush_one(Register::PA_DRV);
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
flush_one(Register::SPI_EN);
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
}
enum class Mask {
Enable = 0b01,
RxTx = 0b10,
@@ -149,6 +174,8 @@ enum class Mask {
Standby = RxTx,
Receive = Enable | RxTx,
Transmit = Enable,
Rx_calibration = Enable | RxTx, // sets the same 2 x logic pins to the Receive operating mode.
Tx_calibration = Enable, // sets the same 2 x logic pins to the Transmit operating mode.
};
Mask mode_mask(const Mode mode) {
@@ -159,6 +186,10 @@ Mask mode_mask(const Mode mode) {
return Mask::Receive;
case Mode::Transmit:
return Mask::Transmit;
case Mode::Rx_Calibration: // Let's add this not used previously Rx calibration mode.
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxTx, ,(the difference is in Regs )
case Mode::Tx_Calibration: // Let's add this not used previously Tx calibration mode.
return Mask::Tx_calibration; // same logic pins as Transmit = Enable , (the difference is in Reg add 16 D1:DO)
default:
return Mask::Shutdown;
}
+1
View File
@@ -690,6 +690,7 @@ class MAX2839 : public MAX283x {
void set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) override;
bool set_frequency(const rf::Frequency lo_frequency) override;
void set_rx_lo_iq_calibration(const size_t v) override;
void set_tx_LO_iq_phase_calibration(const size_t v) override;
void set_rx_buff_vcm(const size_t v) override;
int8_t temp_sense() override;
+7 -8
View File
@@ -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 */
/*************************************************************************/
@@ -103,11 +98,13 @@ constexpr auto bandwidth_maximum = bandwidths[bandwidths.size() - 1];
/*************************************************************************/
enum Mode {
enum Mode { // MAX283x Operating modes.
Shutdown,
Standby,
Receive,
Transmit,
Rx_Calibration, // just add the sequential enum of those two CAL operating modes .
Tx_Calibration,
};
using reg_t = uint16_t;
@@ -129,6 +126,8 @@ class MAX283x {
virtual bool set_frequency(const rf::Frequency lo_frequency);
virtual void set_rx_lo_iq_calibration(const size_t v);
virtual void set_tx_LO_iq_phase_calibration(const size_t v);
virtual void set_rx_buff_vcm(const size_t v);
virtual int8_t temp_sense();
+3 -8
View File
@@ -43,7 +43,7 @@ static Thread* thread_controls_event = NULL;
// Index with the Switch enum.
static std::array<Debounce, 6> switch_debounce;
static std::array<Debounce, 2> encoder_debounce;
static EncoderDebounce encoder_debounce;
static_assert(std::size(switch_debounce) == toUType(Switch::Dfu) + 1);
@@ -162,16 +162,11 @@ static bool switches_update(const uint8_t raw) {
}
static bool encoder_update(const uint8_t raw) {
bool encoder_changed = false;
encoder_changed |= encoder_debounce[0].feed((raw >> 6) & 0x01);
encoder_changed |= encoder_debounce[1].feed((raw >> 7) & 0x01);
return encoder_changed;
return encoder_debounce.feed(raw >> 6);
}
static bool encoder_read() {
auto delta = encoder.update(encoder_debounce[0].state(), encoder_debounce[1].state());
auto delta = encoder.update(encoder_debounce.state());
if (injected_encoder > 0) {
if (injected_encoder == 1) delta = -1;
+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();
+6
View File
@@ -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);
@@ -246,6 +248,10 @@ void set_antenna_bias(const bool on) {
}
}
void set_tx_max283x_iq_phase_calibration(const size_t v) {
second_if->set_tx_LO_iq_phase_calibration(v);
}
/*void enable(Configuration configuration) {
configure(configuration);
}
+1
View File
@@ -54,6 +54,7 @@ void set_baseband_filter_bandwidth_rx(const uint32_t bandwidth_minimum);
void set_baseband_filter_bandwidth_tx(const uint32_t bandwidth_minimum);
void set_baseband_rate(const uint32_t rate);
void set_antenna_bias(const bool on);
void set_tx_max283x_iq_phase_calibration(const size_t v);
/* Use ReceiverModel or TransmitterModel instead. */
// void enable(Configuration configuration);
+2 -2
View File
@@ -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 {
+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) {
+21 -22
View File
@@ -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)) {
+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";
}
}
@@ -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>
+172 -254
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;
}
@@ -373,8 +315,15 @@ SystemStatusView::SystemStatusView(
refresh();
};
toggle_stealth.on_change = [this](bool v) {
toggle_stealth.on_change = [this, &nav](bool v) {
pmem::set_stealth_mode(v);
if (nav.is_valid() && v) {
nav.display_modal(
"Stealth",
"You just enabled stealth mode.\n"
"When you transmit,\n"
"screen will turn off;\n");
}
refresh();
};
@@ -561,8 +510,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;
@@ -605,16 +554,16 @@ InformationView::InformationView(
&ltime});
#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 ERR");
version.set_style(&Styles::red);
}
ltime.set_style(&style_infobar);
refresh();
set_dirty();
@@ -626,12 +575,27 @@ 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 {
return view_stack.size() == 1;
}
bool NavigationView::is_valid() const {
return view_stack.size() != 0; // work around to check if nav is valid, not elegant i know. so TODO
}
View* NavigationView::push_view(std::unique_ptr<View> new_view) {
free_view();
@@ -727,43 +691,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 +740,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 +752,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 +775,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);
+31 -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;
@@ -75,6 +94,7 @@ class NavigationView : public View {
NavigationView& operator=(NavigationView&&) = delete;
bool is_top() const;
bool is_valid() const;
template <class T, class... Args>
T* push(Args&&... args) {
@@ -106,8 +126,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 +287,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)
+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 =
+2 -2
View File
@@ -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
+39 -36
View File
@@ -41,6 +41,7 @@
#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"
@@ -151,39 +152,15 @@ 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.\r\nPlease wait!\r\nDevice will restart.");
// check file extensions
if (strEndsWith(path.native(), u".ppfw.tar")) {
// extract tar
chprintf(chp, "Extracting TAR file.\r\n");
auto res = UnTar::untar(
path.native(), [chp](const std::string fileName) {
chprintf(chp, fileName.c_str());
chprintf(chp, "\r\n");
});
if (res.empty()) {
chprintf(chp, "error bad TAR file.\r\n");
nav->pop();
return;
}
path = res; // it will contain the last bin file in tar
} else if (strEndsWith(path.native(), u".bin")) {
// nothing to do for this case yet.
} else {
chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n");
nav->pop();
return;
}
nav->home(false);
chprintf(chp, "Flashing: ");
chprintf(chp, path.string().c_str());
chprintf(chp, "\r\n");
chThdSleepMilliseconds(50);
std::memcpy(&shared_memory.bb_data.data[0], path.native().c_str(), (path.native().length() + 1) * 2);
m4_request_shutdown();
chThdSleepMilliseconds(50);
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
m0_halt();
// call nav with flash
auto open_view = nav->push<ui::FlashUtilityView>();
chprintf(chp, "Flashing started\r\n");
chThdSleepMilliseconds(150); // to give display some time to paint the screen
if (!open_view->flash_firmware(path.native())) {
chprintf(chp, "error\r\n");
}
}
static void cmd_screenshot(BaseSequentialStream* chp, int argc, char* argv[]) {
@@ -404,7 +381,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());
}
@@ -420,11 +397,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");
}
@@ -710,6 +688,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;
@@ -720,8 +722,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);
+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__
+6 -3
View File
@@ -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() {
+1 -1
View File
@@ -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();
+36 -16
View File
@@ -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,
+6
View File
@@ -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);
};
+26 -11
View File
@@ -30,24 +30,36 @@
void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return;
int32_t audio_sample_m;
// Zero-order hold (poop)
for (size_t i = 0; i < buffer.count; i++) {
resample_acc += resample_inc;
if (resample_acc >= 0x10000) {
resample_acc -= 0x10000;
if (stream) {
stream->read(&audio_sample, 1);
bytes_read++;
audio_sample = 0;
stream->read(&audio_sample, bytes_per_sample); // assumes little endian when reading 1 byte
samples_read++;
}
}
// 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);
if (bytes_per_sample == 1) {
sample = audio_sample - 0x80;
audio_sample_m = sample * 256;
} else {
audio_sample_m = audio_sample;
}
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] = audio_sample_m;
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
audio_output.write_unprocessed(audio_buffer);
}
sample = tone_gen.process(sample);
// FM
delta = sample * fm_delta;
@@ -65,7 +77,7 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (progress_samples >= progress_interval_samples) {
progress_samples -= progress_interval_samples;
txprogress_message.progress = bytes_read; // Inform UI about progress
txprogress_message.progress = samples_read; // Inform UI about progress
txprogress_message.done = false;
shared_memory.application_queue.push(txprogress_message);
}
@@ -79,7 +91,7 @@ void AudioTXProcessor::on_message(const Message* const message) {
case Message::ID::ReplayConfig:
configured = false;
bytes_read = 0;
samples_read = 0;
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
break;
@@ -101,8 +113,11 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight);
progress_interval_samples = message.divider;
resample_acc = 0;
bytes_per_sample = message.bits_per_sample / 8;
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) {
+7 -4
View File
@@ -48,18 +48,21 @@ class AudioTXProcessor : public BasebandProcessor {
uint32_t resample_inc{}, resample_acc{};
uint32_t fm_delta{0};
uint32_t phase{0}, sphase{0};
uint8_t audio_sample{};
uint32_t audio_sample{};
int32_t sample{0}, delta{};
int8_t re{0}, im{0};
int8_t bytes_per_sample{1};
int16_t audio_sample_s16{};
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};
uint32_t samples_read{0};
bool tone_key_enabled{false};
void sample_rate_config(const SampleRateConfigMessage& message);
void audio_config(const AudioTXConfigMessage& message);
+4 -2
View File
@@ -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 {
+3
View File
@@ -931,6 +931,7 @@ class AudioTXConfigMessage : public Message {
const float deviation_hz,
const float audio_gain,
const uint8_t audio_shift_bits_s16,
const uint8_t bits_per_sample,
const uint32_t tone_key_delta,
const float tone_key_mix_weight,
const bool am_enabled,
@@ -942,6 +943,7 @@ class AudioTXConfigMessage : public Message {
deviation_hz(deviation_hz),
audio_gain(audio_gain),
audio_shift_bits_s16(audio_shift_bits_s16),
bits_per_sample(bits_per_sample),
tone_key_delta(tone_key_delta),
tone_key_mix_weight(tone_key_mix_weight),
am_enabled(am_enabled),
@@ -954,6 +956,7 @@ class AudioTXConfigMessage : public Message {
const float deviation_hz;
const float audio_gain;
const uint8_t audio_shift_bits_s16;
const uint8_t bits_per_sample;
const uint32_t tone_key_delta;
const float tone_key_mix_weight;
const bool am_enabled;
@@ -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>
@@ -235,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),
@@ -287,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() {
}
};
@@ -972,6 +978,22 @@ bool config_disable_config_mode_direct() {
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
bool should_use_sdcard_for_pmem() {
@@ -1030,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]");
@@ -1073,6 +1098,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();
@@ -32,6 +32,7 @@
#include "modems.hpp"
#include "serializer.hpp"
#include "volume.hpp"
#include "config_mode.hpp"
// persistent memory from/to sdcard flag file
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
@@ -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. */
@@ -227,8 +243,6 @@ 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 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();
@@ -241,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();
+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) {
+38 -3
View File
@@ -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:
@@ -86,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 "";
@@ -132,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;
+7
View File
@@ -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;
}
+3 -1
View File
@@ -111,7 +111,7 @@ inline uint32_t ms_duration(
if (sample_rate == 0 || bytes_per_sample == 0)
return 0;
return buffer_size / bytes_per_sample / sample_rate * 1000;
return buffer_size * 1000 / (bytes_per_sample * sample_rate);
}
int fast_int_magnitude(int y, int x);
@@ -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__*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

@@ -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')
+16
View File
@@ -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))
+19 -2
View File
@@ -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, "%)")
+57
View File
@@ -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)
+31 -22
View File
@@ -2,31 +2,20 @@
# (Freq range in MHz, min separation 3MHz)
# (Description up to 20 char)
# (fields comma delimiter)
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
34,36,USA PAGERS 35
43,45,USA PAGERS 44
151,153,USA PAGERS 152
453,454,USA PAGERS 454
929,931,USA PAGERS 930
# KEYFOBS
260,500,315/433 MHz KEYFOBS
# MICS, RADIOSONDES, LEYFOB
140,380,VHF MICS AND MARINE
390,420,RADIOSONDES
420,660,UHF MICS
# LoRa
433,435,LoRa 443EU
863,870,LoRa 868EU
902,928,LoRa 915US
779,787,LoRa 780CN
# BROADCAST RADIO
65,74,FM BROADCAST RUSSIA
76,95,FM BROADCAST JAPAN
76,108,FM BROADCAST BRAZIL
87,108,FM BROADCAST
# 433 MHz BAND
423,443,433 BAND
# AVIATION
108,137,AVIATION 108
960,1215,AVIATION 960
# BROADCAST RADIO
87,108,FM BROADCAST
76,108,FM BROADCAST BRAZIL
76,95,FM BROADCAST JAPAN
65,74,FM BROADCAST RUSSIA
# DECT
1879,1931,DECT
# FRS/GMRS
462,468,FRS/GMRS
# HAM RADIO
28,30,HAM 10-METER
50,54,HAM 6-METER
@@ -35,6 +24,26 @@
420,450,HAM 70-CM
902,928,HAM 33-CM
1240,1300,HAM 23-CM
# KEYFOBS
260,500,315/433 MHz KEYFOBS
# LoRa
433,435,LoRa 443EU
863,870,LoRa 868EU
902,928,LoRa 915US
779,787,LoRa 780CN
# MARINE BAND
155,163,MARINE VHF
# MICS
140,380,MICS (VHF) AND MARINE
420,660,MICS (UHF)
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
34,36,USA PAGERS 35
43,45,USA PAGERS 44
151,153,USA PAGERS 152
453,454,USA PAGERS 454
929,931,USA PAGERS 930
# RADIOSONDES
390,420,RADIOSONDES
# WATER METERS
850,900,Water meters
902,928,ISM 900MHz