Compare commits

...

20 Commits

Author SHA1 Message Date
Totoo caac5e1041 Sonde + map (#2879)
* Update geomap view position on GPS data reception

* Refactor timestamp and temperature/humidity display formatting in SondeView

* Implement battery voltage reading for Meteomodem M20

* Enhance GPS data validation and update OSM zoom handling

- Introduced a new method to validate GPS data in the Packet structure.
- Updated the SondeView to use the new GPS validation method.
- Modified GeoMap to improve handling of OSM zoom levels and ensure consistent usage of real zoom values.
- Adjusted battery voltage calculation for Meteomodem M20 to ensure correct scaling.

* Add serial number extraction for Meteomodem M20 support

* Add support for Meteomodem M20 temperature and humidity readings

* Update log file naming to include timestamp in SondeView

* Add pressure reading support for Meteomodem M20 and update UI

* Update SondeView UI layout and enhance Meteomodem M20 packet handling

* Add vertical speed calculation and display to SondeView

* Fix set_fsk function parameter type for samplesPerSymbol
2025-11-26 19:13:21 +01:00
Totoo 6b02ba6e5d Faster osm (#2874)
Much faster OSM map handler
2025-11-21 09:30:01 +01:00
Totoo c01597baf2 Pocsag manual baud option (#2870)
* baseband part of manual baud control of pocsag

* added manual pocsag baud to ui, to fix #2546

* fix adult toys settings filename
2025-11-18 14:05:27 +01:00
Totoo 43a5163b77 prevent sonde pos to change to 0 when the frame is invalid. fixes #2862 (#2869) 2025-11-18 19:07:40 +08:00
jLynx c46cc431c9 Revert "resolve conflicts merge" (#2860)
This reverts commit 344aa0c741.
2025-11-08 10:21:33 +13:00
jLynx a4d23768c1 Update version.txt (#2858) 2025-11-08 10:13:35 +13:00
gullradriel 344aa0c741 resolve conflicts merge 2025-11-07 21:36:41 +01:00
gullradriel fd7637187a updated version number (#2855)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-11-07 20:52:28 +01:00
Totoo 674b865ec0 Refactor OnTouchEvent functions to return boolean values for better event handling (#2854) 2025-11-05 20:53:23 +01:00
Totoo 00c9c6b40e Fix shift key position (#2850) 2025-10-31 18:25:23 +01:00
RocketGod 113cbc42c9 Add sound effects (#2849)
* Add sound effects

Adding sound effects and volume adjuster in start menu. Sounds are a little clicky but I tinkered a long time and couldn't solve that. Maybe someone else wants to tinker a bit with me.

* Format code
2025-10-30 07:38:45 +01:00
RocketGod 454b8776b6 Refactor gfxEQView UI layout for consistency (#2848)
* Refactor gfxEQView UI layout for consistency

Updated control and graph positioning in gfxEQView to use a standard layout approach, aligning with other applications. Screen dimensions are now used for dynamic placement, and hardcoded values have been replaced for improved maintainability and consistency.

Basically, centered for smaller and larger screens with other visual improvements.

* Fixed screen to Macros - looks good now

Better methods for dynamic screen size
2025-10-30 07:33:38 +01:00
Matej Sochan efdf043d62 fix typo: wps -> wpm in morse_tx (#2847) 2025-10-28 20:55:21 +01:00
gullradriel 0f9d9e589d Revert "Additional debounce control parameters for rotary encoder settings (f…" (#2846)
This reverts commit bf18851b6b.
2025-10-28 22:34:14 +13:00
Totoo 3b57672dda sht4x support,, typo fix (#2843) 2025-10-28 10:14:11 +08:00
Totoo b739dd883c Free up some fw space (#2844) 2025-10-28 08:10:09 +08:00
rmckay bf18851b6b Additional debounce control parameters for rotary encoder settings (for bad/noisy encoders) (#2841)
* Added debounce control options for rotary encoder settings

* ran format-code.sh to adjust whitespace

---------

Co-authored-by: Robert McKay <robert.mckay@ubermorgen.land>
2025-10-27 23:56:27 +01:00
Pezsma 81afec9ea4 Added Adult toy controller external app (#2840) 2025-10-23 13:09:48 +02:00
Totoo 60a5222058 Pacman update (#2837) 2025-10-21 07:37:26 +02:00
Totoo 7041d507ca gracefully handle i2c error (#2838) 2025-10-21 07:37:14 +02:00
68 changed files with 2090 additions and 388 deletions
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.2.0
+1 -1
View File
@@ -1 +1 @@
v2.2.0
v2.3.1
-2
View File
@@ -192,7 +192,6 @@ set(CPPSRC
core_control.cpp
database.cpp
gradient.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
file.cpp
@@ -261,7 +260,6 @@ set(CPPSRC
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
+5 -2
View File
@@ -56,6 +56,7 @@ POCSAGSettingsView::POCSAGSettingsView(
: settings_{settings} {
add_children(
{&labels,
&opt_baud_rate,
&check_log,
&check_log_raw,
&check_small_font,
@@ -65,6 +66,7 @@ POCSAGSettingsView::POCSAGSettingsView(
&field_filter_address,
&button_save});
opt_baud_rate.set_by_value(settings_.baud_rate);
check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font);
@@ -81,7 +83,7 @@ POCSAGSettingsView::POCSAGSettingsView(
settings_.hide_addr_only = check_hide_addr_only.value();
settings_.filter_mode = opt_filter_mode.selected_index_value();
settings_.filter_address = field_filter_address.to_integer();
settings_.baud_rate = opt_baud_rate.selected_index_value();
nav.pop();
};
}
@@ -142,7 +144,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
audio::output::start();
receiver_model.enable();
baseband::set_pocsag();
baseband::set_pocsag((int8_t)settings_.baud_rate);
}
void POCSAGAppView::focus() {
@@ -182,6 +184,7 @@ void POCSAGAppView::refresh_ui() {
btn_text = "Filter Last";
break;
}
baseband::set_pocsag((int8_t)settings_.baud_rate);
button_filter_last.set_text(btn_text);
}
+11
View File
@@ -126,6 +126,7 @@ struct POCSAGSettings {
bool hide_bad_data = false;
bool hide_addr_only = false;
uint8_t filter_mode = false;
int32_t baud_rate = -1;
uint32_t filter_address = 0;
};
@@ -139,7 +140,16 @@ class POCSAGSettingsView : public View {
private:
POCSAGSettings& settings_;
OptionsField opt_baud_rate{
{8 * 8, 0 * 16},
4,
{{"Auto", -1},
{" 512", 0},
{"1200", 1},
{"2400", 2}}};
Labels labels{
{{2 * 8, 0 * 16}, "Baud:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 12 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 13 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
};
@@ -221,6 +231,7 @@ class POCSAGAppView : public View {
{"filter_address"sv, &settings_.filter_address},
{"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only},
{"baud_rate"sv, &settings_.baud_rate},
}};
void refresh_ui();
+1 -1
View File
@@ -171,7 +171,7 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
}
void IQTrimView::profile_capture() {
power_buckets_ = {};
power_buckets_.clear();
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
+34 -12
View File
@@ -30,11 +30,10 @@
#include "portapack.hpp"
#include <cstring>
#include <stdio.h>
#include "rtc_time.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
#include "string_format.hpp"
#include "complex.hpp"
void SondeLogger::on_packet(const sonde::Packet& packet) {
@@ -65,6 +64,8 @@ SondeView::SondeView(NavigationView& nav)
&text_frame,
&text_temp,
&text_humid,
&text_press,
&text_vspeed,
&geopos,
&button_see_qr,
&button_see_map});
@@ -107,7 +108,7 @@ SondeView::SondeView(NavigationView& nav)
logger = std::make_unique<SondeLogger>();
if (logger)
logger->append(logs_dir / u"SONDE.TXT");
logger->append(logs_dir / u"SONDE_" + to_string_timestamp(rtc_time::now()) + u".TXT");
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
@@ -155,7 +156,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
sonde_id = packet.serial_number(); // used also as tag on the geomap
text_serial.set(sonde_id);
text_timestamp.set(to_string_timestamp(packet.received_at()));
text_timestamp.set(to_string_datetime(packet.received_at(), TimeFormat::YMDHMS));
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 2) + "V");
@@ -163,21 +164,42 @@ void SondeView::on_packet(const sonde::Packet& packet) {
temp_humid_info = packet.get_temp_humid();
if (temp_humid_info.humid != 0) {
double decimals = abs(get_decimals(temp_humid_info.humid, 10, true));
text_humid.set(to_string_dec_int((int)temp_humid_info.humid) + "." + to_string_dec_uint(decimals, 1) + "%");
text_humid.set(to_string_decimal(temp_humid_info.humid, 1) + "%");
}
if (temp_humid_info.temp != 0) {
double decimals = abs(get_decimals(temp_humid_info.temp, 10, true));
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + STR_DEGREES_C);
text_temp.set(to_string_decimal(temp_humid_info.temp, 1) + STR_DEGREES_C);
}
if (packet.get_pressure() != 0) {
text_press.set(to_string_decimal(packet.get_pressure(), 1) + " hPa");
}
gps_info = packet.get_GPS_data();
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (last_timestamp_update_ != 0 && last_altitude_ != 0) {
// calculate speeds
float vspeed = 0;
time_t currpackettime = rtc_time::rtcToUnixUTC(packet.received_at());
int32_t time_diff = (currpackettime - last_timestamp_update_);
if (time_diff >= 10) { // update only every 10 seconds
vspeed = (static_cast<int>(gps_info.alt) - static_cast<int>(last_altitude_)) / (float)time_diff;
last_timestamp_update_ = currpackettime;
last_altitude_ = gps_info.alt;
text_vspeed.set(to_string_decimal(vspeed, 1) + " m/s");
}
} else { // save first valid packet time + altitude
last_timestamp_update_ = rtc_time::rtcToUnixUTC(packet.received_at());
last_altitude_ = geopos.altitude();
}
if (gps_info.is_valid()) { // only update when valid, to prevent flashing
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (geomap_view_) {
geomap_view_->update_position(gps_info.lat, gps_info.lon, 400, gps_info.alt, 0);
}
}
if (logger && logging) {
logger->on_packet(packet);
}
+36 -24
View File
@@ -30,6 +30,7 @@
#include "ui_rssi.hpp"
#include "ui_qrcode.hpp"
#include "ui_geomap.hpp"
#include "string_format.hpp"
#include "event_m0.hpp"
@@ -94,32 +95,33 @@ class SondeView : public View {
// AudioOutput audio_output { };
Labels labels{
{{4 * 8, 2 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 3 * 16}, "ID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(2)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(6), UI_POS_Y(3)}, "ID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(4)}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 5 * 16}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 6 * 16}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 7 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(3), UI_POS_Y(5)}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(3), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(7)}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(8)}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(9)}, "Pressure:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(10)}, "VSpeed:", Theme::getInstance()->fg_light->foreground}};
RxFrequencyField field_frequency{
{UI_POS_X(0), 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{13 * 8, UI_POS_Y(0)}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, UI_POS_Y(0)}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, UI_POS_Y(0)}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
{UI_POS_X(21), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
@@ -136,47 +138,57 @@ class SondeView : public View {
"CRC"};
Text text_signature{
{9 * 8, 2 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(2), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_serial{
{9 * 8, 3 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_timestamp{
{9 * 8, 4 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(9), UI_POS_HEIGHT(1)},
"..."};
Text text_voltage{
{9 * 8, 5 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_frame{
{9 * 8, 6 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(6), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_temp{
{9 * 8, 7 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(7), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_humid{
{9 * 8, 8 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_press{
{UI_POS_X(9), UI_POS_Y(9), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_vspeed{
{UI_POS_X(9), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
GeoPos geopos{
{0, 12 * 16},
{UI_POS_X(0), UI_POS_Y(12)},
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
Button button_see_qr{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See QR"};
Button button_see_map{
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See on map"};
GeoMapView* geomap_view_{nullptr};
time_t last_timestamp_update_{0};
uint32_t last_altitude_{0};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
@@ -285,9 +285,9 @@ bool StandaloneView::on_encoder(const EncoderEvent event) {
bool StandaloneView::on_touch(const TouchEvent event) {
if (get_application_information()->header_version > 1) {
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
return get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
}
return true;
return false;
}
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
+1 -1
View File
@@ -339,7 +339,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
if (entry_.sensorType == FPS_CAMEATOMO) {
entry_.data ^= 0xFFFFFFFFFFFFFFFF;
entry_.data <<= 4;
uint8_t pack[8] = {};
uint8_t pack[8];
pack[0] = (entry_.data >> 56);
pack[1] = ((entry_.data >> 48) & 0xFF);
pack[2] = ((entry_.data >> 40) & 0xFF);
+1 -1
View File
@@ -612,7 +612,7 @@ void TextEditorView::open_file(const fs::path& path) {
viewer.clear_file();
delete_temp_file(path_);
path_ = {};
path_ = "";
file_dirty_ = false;
has_temp_file_ = false;
auto result = FileWrapper::open(
+2 -2
View File
@@ -288,8 +288,8 @@ void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit,
send_message(&message);
}
void set_pocsag() {
const POCSAGConfigureMessage message{};
void set_pocsag(int8_t baud_config) {
const POCSAGConfigureMessage message{baud_config};
send_message(&message);
}
+2 -2
View File
@@ -80,7 +80,7 @@ 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);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_aprs(const uint32_t baudrate);
void set_btlerx(uint8_t channel_number);
@@ -91,7 +91,7 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat, const uint32_t pause_symbols, const uint8_t de_bruijn_length = 0);
void kill_ook();
void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift, const uint32_t progress_notice);
void set_pocsag();
void set_pocsag(int8_t baud_config = -1);
void set_adsb();
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
void set_rds_data(const uint16_t message_length);
+38
View File
@@ -415,6 +415,44 @@ static constexpr Bitmap bitmap_icon_adsb{
{16, 16},
bitmap_icon_adsb_data};
static constexpr uint8_t bitmap_icon_adulttoys_data[] = {
0x00,
0x00,
0x30,
0x0C,
0x78,
0x1E,
0x7C,
0x3E,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFC,
0x3F,
0xFC,
0x3F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_adulttoys{
{16, 16},
bitmap_icon_adulttoys_data};
static constexpr uint8_t bitmap_icon_ais_data[] = {
0x00,
0x01,
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2025 Pezsma
*
* 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_adult_toys_controller.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::adult_toys_controller {
void initialize_app(::ui::NavigationView& nav) {
nav.push<AdultToysView>();
}
} // namespace ui::external_app::adult_toys_controller
extern "C" {
__attribute__((section(".external_app.app_adult_toys_controller.application_information"), used)) application_information_t _application_information_adult_toys_controller = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::adult_toys_controller::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Adult Toys",
/*.bitmap_data = */ {
0x00,
0x00,
0x30,
0x0C,
0x78,
0x1E,
0x7C,
0x3E,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFC,
0x3F,
0xFC,
0x3F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,373 @@
#include "ui_adult_toys_controller.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_navigation.hpp"
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::adult_toys_controller {
void AdultToysView::focus() {
button_on.focus();
}
AdultToysView::AdultToysView(NavigationView& nav)
: nav_{nav} {
add_children({
&button_on,
&btn_adult,
&btn_child,
&btn_mode_decrease,
&btn_mode_increase,
&btn_pcknum_inc,
&btn_pcknum_dec,
&chk_mode_rnd,
&chk_mode_play,
&chk_mode_stop,
&chk_mode_ncnt,
&chk_mode_infinite,
&field_frequency,
&txt_selected_mode,
&options_target,
&labels,
&message_num,
&tx_view,
&screen_message_1,
&screen_message_2,
&screen_message_3,
&screen_message_4,
&screen_message_5,
&screen_message_6,
&screen_message_7,
&screen_message_8,
&screen_message_9,
&screen_message_10,
});
btn_adult.on_select = [this](Button&) {
start_screen = true;
hidden_program(!start_screen);
};
btn_child.on_select = [this](Button&) {
nav_.pop();
};
button_on.on_select = [this](Button&) {
if (play_running || stop_running) {
if (button_onstate) {
button_onstate = false;
stop();
button_on.set_text(LanguageHelper::currentMessages[LANG_START]);
} else {
button_onstate = true;
printCurrentModes();
start();
button_on.set_text(LanguageHelper::currentMessages[LANG_STOP]);
}
} // if play_running || stop_running
};
btn_mode_decrease.on_select = [this](Button&) {
if (toy_packet > 0) {
toy_packet--;
printCurrentModes();
}
};
btn_mode_increase.on_select = [this](Button&) {
if (stop_running || play_running) {
if (play_running && (toy_packet < max_plays - 1)) {
toy_packet++;
} else {
if (stop_running && toy_packet < max_stops - 1) {
toy_packet++;
}
}
printCurrentModes();
}
};
btn_pcknum_inc.on_select = [this](Button&) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
if (n_message_set < n_message_max) {
n_message_set++;
} else {
n_message_set = 0;
}
message_num.set_value(n_message_set);
n_message_current = n_message_set;
}
};
btn_pcknum_dec.on_select = [this](Button&) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
if (n_message_set > 0) {
n_message_set--;
} else {
n_message_set = n_message_max;
}
message_num.set_value(n_message_set);
n_message_current = n_message_set;
}
};
message_num.on_change = [this](int32_t v) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
n_message_set = static_cast<uint16_t>(v);
n_message_current = n_message_set;
} else {
if (!button_onstate) message_num.set_value(n_message_current);
}
};
chk_mode_rnd.on_select = [this](Checkbox&, bool v) {
random_running = v;
};
chk_mode_play.on_select = [this](Checkbox&, bool v) {
play_running = v;
if (play_running && stop_running) {
toy_packet = 0;
stop_running = false;
chk_mode_stop.set_value(stop_running);
}
if (play_running) printCurrentModes();
};
chk_mode_stop.on_select = [this](Checkbox&, bool v) {
stop_running = v;
if (play_running && stop_running) {
toy_packet = 0;
play_running = false;
chk_mode_play.set_value(play_running);
}
if (stop_running) printCurrentModes();
};
chk_mode_ncnt.on_select = [this](Checkbox&, bool v) {
n_message_running = v;
if (n_message_running && inf_message_running) {
inf_message_running = false;
chk_mode_infinite.set_value(inf_message_running);
}
};
chk_mode_infinite.on_select = [this](Checkbox&, bool v) {
inf_message_running = v;
if (n_message_running && inf_message_running) {
n_message_running = false;
chk_mode_ncnt.set_value(n_message_running);
}
};
options_target.on_change = [this](size_t, int32_t i) {
target = (uint8_t)i;
};
chk_mode_infinite.set_value(true);
chk_mode_play.set_value(true);
}
void AdultToysView::hidden_program(bool hide) {
options_target.hidden(hide);
labels.hidden(hide);
button_on.hidden(hide);
btn_mode_decrease.hidden(hide);
btn_mode_increase.hidden(hide);
btn_pcknum_inc.hidden(hide);
btn_pcknum_dec.hidden(hide);
chk_mode_rnd.hidden(hide);
chk_mode_play.hidden(hide);
chk_mode_stop.hidden(hide);
chk_mode_ncnt.hidden(hide);
chk_mode_infinite.hidden(hide);
field_frequency.hidden(hide);
txt_selected_mode.hidden(hide);
message_num.hidden(hide);
tx_view.hidden(hide);
btn_adult.hidden(!hide);
btn_child.hidden(!hide);
screen_message_1.hidden(!hide);
screen_message_2.hidden(!hide);
screen_message_3.hidden(!hide);
screen_message_4.hidden(!hide);
screen_message_5.hidden(!hide);
screen_message_6.hidden(!hide);
screen_message_7.hidden(!hide);
screen_message_8.hidden(!hide);
screen_message_9.hidden(!hide);
screen_message_10.hidden(!hide);
set_dirty();
if (hide)
btn_child.focus();
else
button_on.focus();
}
void AdultToysView::on_show() {
hidden_program(!start_screen);
}
void AdultToysView::createPacket(bool regenerate) {
if (regenerate) randomizeMac();
randomChn();
if (target == 1 || 1) { // now only lovespouse supported, in the future, need to separee them
uint32_t mode = play_running ? plays[toy_packet].value : stops[toy_packet].value;
const uint8_t size = 22;
uint8_t packet[size];
uint8_t i = 0;
// 1. AD Flags
packet[i++] = 2;
packet[i++] = 0x01;
packet[i++] = 0x1A;
// 2. Manufacturer Specific
packet[i++] = 14;
packet[i++] = 0xFF;
packet[i++] = 0xFF;
packet[i++] = 0x00;
packet[i++] = 0x6D;
packet[i++] = 0xB6;
packet[i++] = 0x43;
packet[i++] = 0xCE;
packet[i++] = 0x97;
packet[i++] = 0xFE;
packet[i++] = 0x42;
packet[i++] = 0x7C;
packet[i++] = (mode >> 0x10) & 0xFF;
packet[i++] = (mode >> 0x8) & 0xFF;
packet[i++] = (mode >> 0x0) & 0xFF;
// 3. Service UUID List
packet[i++] = 3;
packet[i++] = 0x03;
packet[i++] = 0x8F;
packet[i++] = 0xAE;
std::string res = to_string_hex_array(packet, i);
memset(advertisementData, 0, sizeof(advertisementData));
std::copy(res.begin(), res.end(), advertisementData);
}
}
AdultToysView::~AdultToysView() {
stop();
}
void AdultToysView::stop() {
transmitter_model.disable();
baseband::shutdown();
}
void AdultToysView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
on_tx_progress(true);
}
void AdultToysView::reset() {
stop();
start();
}
uint8_t AdultToysView::randomize(uint8_t max) {
return static_cast<uint8_t>(rand() % max);
}
void AdultToysView::printCurrentModes() {
uint8_t max_val = play_running ? max_plays : max_stops;
auto name = play_running ? plays[toy_packet].name : stops[toy_packet].name;
std::string current = to_string_dec_uint(toy_packet + 1);
std::string max_val_str = to_string_dec_uint(max_val);
std::string result_str = current;
result_str += "/";
result_str += max_val_str;
result_str += " ";
result_str += name;
txt_selected_mode.set(result_str.c_str());
}
uint64_t AdultToysView::get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void AdultToysView::randomizeMac() {
const char hexDigits[] = "0123456789ABCDEF";
// Generate 12 random hexadecimal characters
for (int i = 0; i < 12; ++i) {
int randomIndex = rand() % 16;
mac[i] = hexDigits[randomIndex];
}
mac[12] = '\0'; // Null-terminate the string
}
void AdultToysView::randomChn() {
channel_number = 37 + std::rand() % (39 - 37 + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
}
void AdultToysView::on_tx_progress(const bool done) {
if (!done) return;
if (!button_onstate) return;
bool regenerate = (counter++ % 600) == 0;
if (random_running && regenerate) {
toy_packet = randomize(play_running ? max_plays : max_stops);
}
createPacket(regenerate);
printCurrentModes();
if (n_message_running) {
if (n_message_current > 0) {
n_message_current--;
message_num.set_value(n_message_current);
} else {
stop();
message_num.set_value(n_message_set);
button_on.set_text(LanguageHelper::currentMessages[LANG_START]);
button_onstate = false;
n_message_current = n_message_set;
return;
}
} // if n_message
baseband::set_btletx(channel_number, mac, advertisementData, pduType);
}
} // namespace ui::external_app::adult_toys_controller
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2025 Pezsma
*
* 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.
*/
// Code from https://github.com/Next-Flip/Momentum-Apps/blob/89d493daa1ea0cb86906fa0042c41dd4edc22a6a/ble_spam/protocols/lovespouse.c#L93
// Thanks for the work of the original creators!
#ifndef __UI_ADULT_TOYS_H__
#define __UI_ADULT_TOYS_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
using namespace ui;
namespace ui::external_app::adult_toys_controller {
enum PKT_TYPE {
PKT_TYPE_INVALID_TYPE,
PKT_TYPE_RAW,
PKT_TYPE_DISCOVERY,
PKT_TYPE_IBEACON,
PKT_TYPE_ADV_IND,
PKT_TYPE_ADV_DIRECT_IND,
PKT_TYPE_ADV_NONCONN_IND,
PKT_TYPE_ADV_SCAN_IND,
PKT_TYPE_SCAN_REQ,
PKT_TYPE_SCAN_RSP,
PKT_TYPE_CONNECT_REQ,
PKT_TYPE_LL_DATA,
PKT_TYPE_LL_CONNECTION_UPDATE_REQ,
PKT_TYPE_LL_CHANNEL_MAP_REQ,
PKT_TYPE_LL_TERMINATE_IND,
PKT_TYPE_LL_ENC_REQ,
PKT_TYPE_LL_ENC_RSP,
PKT_TYPE_LL_START_ENC_REQ,
PKT_TYPE_LL_START_ENC_RSP,
PKT_TYPE_LL_UNKNOWN_RSP,
PKT_TYPE_LL_FEATURE_REQ,
PKT_TYPE_LL_FEATURE_RSP,
PKT_TYPE_LL_PAUSE_ENC_REQ,
PKT_TYPE_LL_PAUSE_ENC_RSP,
PKT_TYPE_LL_VERSION_IND,
PKT_TYPE_LL_REJECT_IND,
PKT_TYPE_NUM_PKT_TYPE
};
class AdultToysView : public ui::View {
public:
AdultToysView(NavigationView& nav);
~AdultToysView();
void focus() override;
void on_show() override;
std::string title() const override {
return "Adult Toys";
};
struct LovespouseMode {
uint32_t value;
std::string name;
};
private:
NavigationView& nav_;
TxRadioState radio_state_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
TxFrequencyField field_frequency{
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
TransmitterView2 tx_view{
{UI_POS_X(11), UI_POS_Y(0)},
/*short_ui*/ true};
app_settings::SettingsManager settings_{
"tx_adult_toys", app_settings::Mode::TX};
OptionsField options_target{
{UI_POS_X(6), UI_POS_Y(1)},
11,
{{"LoveSpouse", 1}}};
Button btn_adult{{UI_POS_X(0), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "adult"};
Button btn_child{{UI_POS_X_RIGHT(10), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "child"};
Button button_on{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
LanguageHelper::currentMessages[LANG_START]};
Button btn_mode_decrease{
{UI_POS_X(0), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)},
"<-"};
Button btn_mode_increase{
{UI_POS_X_RIGHT(3), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)},
"->"};
Text txt_selected_mode{{UI_POS_X_CENTER(22), UI_POS_Y(13.5), UI_POS_WIDTH(22), UI_POS_HEIGHT(1)}, ""};
Button btn_pcknum_inc{
{UI_POS_X(7), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)},
"+"};
Button btn_pcknum_dec{
{UI_POS_X(0), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)},
"-"};
NumberField message_num{{UI_POS_X(3), UI_POS_Y(9.2)}, 3, {0, 999}, 1, ' ', true};
Checkbox chk_mode_rnd{{UI_POS_X_RIGHT(15), UI_POS_Y(3)}, 12, "Random Msg.", false};
Checkbox chk_mode_play{{UI_POS_X_RIGHT(15), UI_POS_Y(5)}, 10, "Play Mode", false};
Checkbox chk_mode_stop{{UI_POS_X_RIGHT(15), UI_POS_Y(7)}, 10, "Stop Mode", false};
Checkbox chk_mode_ncnt{{UI_POS_X_RIGHT(15), UI_POS_Y(9)}, 7, "N Msg.", false};
Checkbox chk_mode_infinite{{UI_POS_X_RIGHT(15), UI_POS_Y(11)}, 10, "Inf. Msg.", false};
Text screen_message_1{{0, 0, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "The application is intended"};
Text screen_message_2{{0, 25, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for controlling Love Spouse"};
Text screen_message_3{{0, 50, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "auxiliary tools/devices."};
Text screen_message_4{{0, 75, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "It is only for use by adults."};
Text screen_message_5{{0, 100, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "Are you an adult?"};
Text screen_message_6{{0, 140, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "To use the application, "};
Text screen_message_7{{0, 165, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "I assume full responsibility"};
Text screen_message_8{{0, 190, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for the \"I do not assume"};
Text screen_message_9{{0, 215, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "responsibility\"."};
Text screen_message_10{{UI_POS_X_RIGHT(16), 230, UI_POS_WIDTH(16), UI_POS_HEIGHT(1)}, "(The developer)"};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(1)}, "Type:", Theme::getInstance()->fg_light->foreground}};
bool button_onstate{false};
bool play_running{false};
bool stop_running{false};
bool random_running{false};
bool n_message_running{false};
bool inf_message_running{false};
bool start_screen{false};
uint8_t target = 1; // lovespouse
char mac[13] = "010203040506";
uint8_t channel_number = 37;
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
uint8_t toy_packet = 0;
uint16_t n_message_max = 999;
uint16_t n_message_set = 0;
uint16_t n_message_current = 0;
static const size_t max_plays = 27;
static const size_t max_stops = 3;
uint16_t counter = 599;
void createPacket(bool regenerate);
void start();
void stop();
void reset();
void printCurrentModes();
void on_tx_progress(const bool done);
void hidden_program(bool hide);
void randomizeMac();
void randomChn();
uint8_t randomize(uint8_t max);
uint64_t get_freq_by_channel_number(uint8_t channel_number);
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
LovespouseMode plays[max_plays] = {
{0xE49C6C, "Classic 1"},
{0xE7075E, "Classic 2"},
{0xE68E4F, "Classic 3"},
{0xE1313B, "Classic 4"},
{0xE0B82A, "Classic 5"},
{0xE32318, "Classic 6"},
{0xE2AA09, "Classic 7"},
{0xED5DF1, "Classic 8"},
{0xECD4E0, "Classic 9"},
{0xD41F5D, "Independent 1-1"},
{0xD7846F, "Independent 1-2"},
{0xD60D7E, "Independent 1-3"},
{0xD1B20A, "Independent 1-4"},
{0xD0B31B, "Independent 1-5"},
{0xD3A029, "Independent 1-6"},
{0xD22938, "Independent 1-7"},
{0xDDDEC0, "Independent 1-8"},
{0xDC57D1, "Independent 1-9"},
{0xA4982E, "Independent 2-1"},
{0xA7031C, "Independent 2-2"},
{0xA68A0D, "Independent 2-3"},
{0xA13579, "Independent 2-4"},
{0xA0BC68, "Independent 2-5"},
{0xA3275A, "Independent 2-6"},
{0xA2AE4B, "Independent 2-7"},
{0xAD59B3, "Independent 2-8"},
{0xACD0A2, "Independent 2-9"},
};
LovespouseMode stops[max_stops] = {
{0xE5157D, "Classic Stop"},
{0xD5964C, "Independent 1 Stop"},
{0xA5113F, "Independent 2 Stop"},
};
};
}; // namespace ui::external_app::adult_toys_controller
#endif // ui_adult_toys_controller_hpp
@@ -34,7 +34,7 @@ using namespace portapack;
#include <cmath>
#include <array>
namespace ui {
namespace ui::external_app::analogtv {
namespace tv {
/* TimeScopeView******************************************************/
@@ -235,4 +235,4 @@ void TVWidget::on_audio_spectrum() {
}
} /* namespace tv */
} /* namespace ui */
} // namespace ui::external_app::analogtv
@@ -33,7 +33,7 @@
#include <cstdint>
#include <cstddef>
namespace ui {
namespace ui::external_app::analogtv {
namespace tv {
class TimeScopeView : public View {
@@ -167,6 +167,6 @@ class TVWidget : public View {
};
} /* namespace tv */
} /* namespace ui */
} // namespace ui::external_app::analogtv
#endif /*__UI_TV_H__*/
-1
View File
@@ -21,7 +21,6 @@
*/
#include "bht.hpp"
#include "portapack_persistent_memory.hpp"
namespace ui::external_app::bht_tx {
+1 -1
View File
@@ -207,7 +207,7 @@ class BHTView : public View {
"Scan"};
Checkbox checkbox_flashing{
{16 * 8, 25 * 8},
{UI_POS_X(16), 25 * 8},
8,
"Flashing"};
NumberField field_speed{
+12 -12
View File
@@ -20,13 +20,13 @@ void initialize_app(ui::NavigationView& nav) {
extern "C" {
__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
(uint8_t*)0x00000000,
ui::external_app::breakout::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::breakout::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
"Breakout",
{
/*.app_name = */ "Breakout",
/*.bitmap_data = */ {
0x00,
0x00,
0x7F,
@@ -60,11 +60,11 @@ __attribute__((section(".external_app.app_breakout.application_information"), us
0xF7,
0xF7,
},
ui::Color::green().v,
app_location_t::GAMES,
-1,
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
{0, 0, 0, 0},
0x00000000,
/*.m4_app_tag = */ {'P', 'A', 'B', 'P'}, // This is the audio beep baseband tag!
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
} // namespace ui::external_app::breakout
}
+279 -10
View File
@@ -7,6 +7,12 @@
*/
#include "ui_breakout.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
namespace ui::external_app::breakout {
@@ -92,6 +98,37 @@ void BreakoutView::init_game() {
game_state = STATE_MENU;
menu_blink_state = true;
menu_blink_counter = 0;
// Play startup sound
if (sound_enabled) {
baseband::request_audio_beep(262, 24000, 150); // C4 -
chThdSleepMilliseconds(200);
baseband::request_audio_beep(262, 24000, 150); // C4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(392, 24000, 150); // G4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(392, 24000, 150); // G4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(440, 24000, 150); // A4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(440, 24000, 150); // A4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(392, 24000, 300); // G4
chThdSleepMilliseconds(350);
baseband::request_audio_beep(349, 24000, 150); // F4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(349, 24000, 150); // F4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(330, 24000, 150); // E4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(330, 24000, 150); // E4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(294, 24000, 150); // D4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(294, 24000, 150); // D4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(262, 24000, 400); // C4
}
}
void BreakoutView::init_level() {
@@ -126,6 +163,34 @@ void BreakoutView::draw_screen() {
draw_level();
}
void BreakoutView::draw_menu_volume_bar() {
// Clear the entire volume area first to handle bar shrinking
painter.fill_rectangle({40, 259, 220, 40}, Color::black());
// Draw volume label
auto style = *ui::Theme::getInstance()->fg_light;
painter.draw_string({40, 260}, style, "Volume:");
// Draw volume bar background
painter.draw_rectangle({100, 259, 102, 10}, Color::grey());
// Draw volume level
int bar_width = (volume_level * 100) / 99;
if (bar_width > 0) {
painter.fill_rectangle({101, 260, bar_width, 8}, Color::green());
}
// Draw percentage
painter.draw_string({210, 260}, style, std::to_string(volume_level) + "%");
// Draw sound status
if (sound_enabled) {
painter.draw_string({UI_POS_X_CENTER(11), 280}, *ui::Theme::getInstance()->fg_green, "Sound: ON");
} else {
painter.draw_string({UI_POS_X_CENTER(11), 280}, *ui::Theme::getInstance()->fg_red, "Sound: OFF");
}
}
void BreakoutView::draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
}
@@ -166,6 +231,22 @@ void BreakoutView::draw_level() {
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
}
void BreakoutView::adjust_volume(int delta) {
volume_level += delta;
if (volume_level < 0) volume_level = 0;
if (volume_level > 99) volume_level = 99;
// Update the actual audio volume using decibel conversion
// Map 0-99 to roughly -60dB to 0dB range
int db = (volume_level - 99) * 60 / 99; // Maps 0->-60dB, 99->0dB
audio::headphone::set_volume(volume_t::decibel(db));
// If we're in the menu, redraw the volume bar
if (game_state == STATE_MENU) {
draw_menu_volume_bar();
}
}
void BreakoutView::move_paddle_left() {
if (paddle_x > 0) {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
@@ -231,19 +312,23 @@ void BreakoutView::update_game() {
if (ball_x < 0) {
ball_x = 0;
ball_dx = -ball_dx;
play_wall_hit();
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
ball_x = SCREEN_WIDTH - BALL_SIZE;
ball_dx = -ball_dx;
play_wall_hit();
}
if (ball_y < GAME_AREA_TOP) {
ball_y = GAME_AREA_TOP;
ball_dy = -ball_dy;
play_wall_hit();
}
// Bottom edge - lose life
if (ball_y > GAME_AREA_BOTTOM) {
lives--;
play_death_sound(); // Play death sound
draw_lives();
if (lives <= 0) {
handle_game_over();
@@ -262,6 +347,8 @@ void BreakoutView::update_game() {
ball_y = PADDLE_Y - BALL_SIZE;
ball_dy = -ball_dy;
play_paddle_hit();
// Add some angle based on hit position
float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
@@ -301,6 +388,8 @@ bool BreakoutView::check_brick_collision(int row, int col) {
score += (5 - row) * 10;
draw_score();
play_brick_hit();
ball_dy = -ball_dy;
return true;
}
@@ -314,6 +403,8 @@ bool BreakoutView::check_level_complete() {
void BreakoutView::next_level() {
level++;
play_level_complete();
chThdSleepMilliseconds(500); // Give time for sound to play
init_level();
draw_screen();
}
@@ -322,9 +413,11 @@ void BreakoutView::handle_game_over() {
game_state = STATE_GAME_OVER;
gameover_blink_state = true;
gameover_blink_counter = 0;
play_game_over();
}
void BreakoutView::show_menu() {
// Only draw the static parts once
if (menu_blink_counter == 0) {
cls();
background(COLOR_BACKGROUND);
@@ -338,23 +431,30 @@ void BreakoutView::show_menu() {
painter.draw_string({UI_POS_X_CENTER(20), 130}, style_cyan, "ROTARY: MOVE PADDLE");
painter.draw_string({UI_POS_X_CENTER(21), 160}, style_cyan, "SELECT: START/LAUNCH");
painter.draw_string({UI_POS_X_CENTER(25), 190}, style_blue, "========================");
// Volume adjustment instructions
painter.draw_string({UI_POS_X_CENTER(24), 220}, style_cyan, "Press UP/DOWN for Volume");
// Draw volume control at bottom
draw_menu_volume_bar();
}
auto style_red = *ui::Theme::getInstance()->fg_red;
// Only flash the "PRESS SELECT" text
if (++menu_blink_counter >= 30) {
menu_blink_counter = 0;
menu_blink_counter = 1; // Keep it at 1 so we don't redraw everything
menu_blink_state = !menu_blink_state;
painter.fill_rectangle({UI_POS_X_CENTER(17), 228, 128, 20}, Color::black());
auto style_red = *ui::Theme::getInstance()->fg_red;
painter.fill_rectangle({UI_POS_X_CENTER(17), 238, 128, 20}, Color::black());
if (menu_blink_state) {
painter.draw_string({UI_POS_X_CENTER(17), 230}, style_red, "* PRESS SELECT *");
painter.draw_string({UI_POS_X_CENTER(17), 240}, style_red, "* PRESS SELECT *");
}
}
}
void BreakoutView::show_game_over() {
// Only draw the static parts once
if (gameover_blink_counter == 0) {
cls();
background(COLOR_BACKGROUND);
@@ -366,12 +466,12 @@ void BreakoutView::show_game_over() {
painter.draw_string({UI_POS_X_CENTER(11), 150}, style_yellow, "SCORE: " + std::to_string(score));
}
auto style_green = *ui::Theme::getInstance()->fg_green;
// Only flash the "PRESS SELECT" text
if (++gameover_blink_counter >= 30) {
gameover_blink_counter = 0;
gameover_blink_counter = 1; // Keep it at 1 so we don't redraw everything
gameover_blink_state = !gameover_blink_state;
auto style_green = *ui::Theme::getInstance()->fg_green;
painter.fill_rectangle({UI_POS_X_CENTER(13), 198, 96, 20}, Color::black());
if (gameover_blink_state) {
@@ -385,17 +485,169 @@ void BreakoutView::reset_game() {
score = 0;
lives = 3;
game_state = STATE_PLAYING;
dummy.focus();
init_level();
draw_screen();
}
// Sound effect implementations with timing control
void BreakoutView::play_paddle_hit() {
if (sound_enabled) {
uint32_t current_time = chTimeNow();
if (current_time - last_sound_time >= min_sound_interval) {
baseband::request_beep_stop(); // Stop any previous sound
chThdSleepMilliseconds(5); // Small delay
baseband::request_audio_beep(440, 24000, 40); // 440Hz A4, 40ms
last_sound_time = current_time;
}
}
}
void BreakoutView::play_brick_hit() {
if (sound_enabled) {
uint32_t current_time = chTimeNow();
if (current_time - last_sound_time >= min_sound_interval) {
baseband::request_beep_stop(); // Stop any previous sound
chThdSleepMilliseconds(5); // Small delay
// Higher pitch that varies with remaining bricks
uint32_t freq = 880 + ((25 - brick_count) * 20); // Gets higher as bricks are destroyed
baseband::request_audio_beep(freq, 24000, 30);
last_sound_time = current_time;
}
}
}
void BreakoutView::play_wall_hit() {
if (sound_enabled) {
uint32_t current_time = chTimeNow();
if (current_time - last_sound_time >= min_sound_interval) {
baseband::request_beep_stop(); // Stop any previous sound
chThdSleepMilliseconds(5); // Small delay
baseband::request_audio_beep(220, 24000, 120); // 220Hz A3, 120ms
last_sound_time = current_time;
}
}
}
void BreakoutView::play_death_sound() {
if (sound_enabled) {
baseband::request_audio_beep(493, 24000, 150); // B4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(466, 24000, 150); // Bb4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(440, 24000, 150); // A4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(415, 24000, 150); // Ab4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(392, 24000, 150); // G4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(369, 24000, 150); // Gb4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(349, 24000, 150); // F4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(329, 24000, 150); // E4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(293, 24000, 150); // D4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(261, 24000, 150); // C4
chThdSleepMilliseconds(180);
baseband::request_audio_beep(196, 24000, 200); // G3
chThdSleepMilliseconds(230);
baseband::request_audio_beep(130, 24000, 300); // C3
}
}
void BreakoutView::play_game_over() {
if (sound_enabled) {
// First phrase
baseband::request_audio_beep(392, 24000, 300); // G4
chThdSleepMilliseconds(350);
baseband::request_audio_beep(392, 24000, 150); // G4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(392, 24000, 150); // G4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(311, 24000, 500); // Eb4 - long
chThdSleepMilliseconds(600);
// Second phrase
baseband::request_audio_beep(349, 24000, 300); // F4
chThdSleepMilliseconds(350);
baseband::request_audio_beep(349, 24000, 150); // F4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(349, 24000, 150); // F4
chThdSleepMilliseconds(200);
baseband::request_audio_beep(294, 24000, 500); // D4 - long
chThdSleepMilliseconds(600);
// Final phrase - slower and more dramatic
baseband::request_audio_beep(262, 24000, 200); // C4
chThdSleepMilliseconds(250);
baseband::request_audio_beep(247, 24000, 200); // B3
chThdSleepMilliseconds(250);
baseband::request_audio_beep(233, 24000, 200); // Bb3
chThdSleepMilliseconds(250);
baseband::request_audio_beep(220, 24000, 200); // A3
chThdSleepMilliseconds(250);
baseband::request_audio_beep(196, 24000, 200); // G3
chThdSleepMilliseconds(250);
baseband::request_audio_beep(175, 24000, 200); // F3
chThdSleepMilliseconds(250);
baseband::request_audio_beep(131, 24000, 600); // C3
}
}
void BreakoutView::play_level_complete() {
if (sound_enabled) {
baseband::request_beep_stop(); // Stop any previous sound
chThdSleepMilliseconds(10);
baseband::request_audio_beep(1047, 24000, 300); // 1047Hz C6, 300ms
}
}
BreakoutView::BreakoutView(NavigationView& nav)
: nav_{nav}, bricks{} { // Add bricks{} here
: nav_{nav}, bricks{} {
// Initialize audio system for beeps FIRST
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // Load the audio beep baseband
add_children({&dummy});
// Initialize audio
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
// Set initial volume to 80% and ensure sound is ON
volume_level = 80;
sound_enabled = true; // Make sure this is explicitly set to ON
// Set the actual volume
int db = (volume_level - 99) * 60 / 99; // Maps to roughly -12dB for 80%
audio::headphone::set_volume(volume_t::decibel(db));
attach(1.0 / 60.0);
}
BreakoutView::~BreakoutView() {
// Clean shutdown
baseband::request_beep_stop();
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
void BreakoutView::on_show() {
// Nothing needed here, initialization done in constructor
}
void BreakoutView::on_hide() {
// Additional cleanup
baseband::request_beep_stop();
audio::output::stop();
}
void BreakoutView::focus() {
dummy.focus();
}
void BreakoutView::paint(Painter& painter) {
@@ -433,16 +685,33 @@ bool BreakoutView::on_key(const KeyEvent key) {
} else if (game_state == STATE_PLAYING && ball_attached) {
launch_ball();
} else if (game_state == STATE_GAME_OVER) {
reset_game();
// Return to menu after game over
game_state = STATE_MENU;
menu_blink_counter = 0;
init_game();
}
} else if (key == KeyEvent::Left && game_state == STATE_PLAYING) {
move_paddle_left();
} else if (key == KeyEvent::Right && game_state == STATE_PLAYING) {
move_paddle_right();
} else if (key == KeyEvent::Up && game_state == STATE_MENU) {
// Only adjust volume in menu
adjust_volume(5);
if (!sound_enabled) sound_enabled = true;
set_dirty();
} else if (key == KeyEvent::Down && game_state == STATE_MENU) {
// Only adjust volume in menu
adjust_volume(-5);
set_dirty();
}
set_dirty();
return true;
}
bool BreakoutView::on_touch(const TouchEvent event) {
(void)event; // Intentionally unused - touch disabled to avoid nav bar issues
return true;
}
} // namespace ui::external_app::breakout
+21 -1
View File
@@ -11,6 +11,7 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
@@ -56,15 +57,18 @@ enum {
class BreakoutView : public View {
public:
BreakoutView(NavigationView& nav);
~BreakoutView();
void on_show() override;
void on_hide() override;
std::string title() const override { return "Breakout"; }
void focus() override { dummy.focus(); }
void focus() override;
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
void cls();
void background(int color);
@@ -81,6 +85,7 @@ class BreakoutView : public View {
void draw_lives();
void draw_level();
void draw_borders();
void draw_menu_volume_bar();
void move_paddle_left();
void move_paddle_right();
void launch_ball();
@@ -96,6 +101,15 @@ class BreakoutView : public View {
void check_game_timer();
void attach(double delay_sec);
void detach();
void adjust_volume(int delta);
// Sound effect methods
void play_paddle_hit();
void play_brick_hit();
void play_wall_hit();
void play_death_sound();
void play_game_over();
void play_level_complete();
private:
const Color pp_colors[9] = {
@@ -147,6 +161,12 @@ class BreakoutView : public View {
double game_update_timeout = 0;
uint32_t game_update_counter = 0;
// Audio control
bool sound_enabled = true;
int volume_level = 80; // 0-99
uint32_t last_sound_time = 0;
uint32_t min_sound_interval = 20; // Minimum ms between sounds
Button dummy{
{screen_width, 0, 0, 0},
""};
+79 -72
View File
@@ -1,263 +1,269 @@
set(EXTCPPSRC
#afsk_rx
#afsk_rx 16 byte
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
#calculator
#calculator 632 bytes
external/calculator/main.cpp
external/calculator/ui_calculator.cpp
#font_viewer
#font_viewer 8 byte?!
external/font_viewer/main.cpp
external/font_viewer/ui_font_viewer.cpp
#blespam
#blespam 336 bytes - array initializers?
external/blespam/main.cpp
external/blespam/ui_blespam.cpp
#analogtv
#analogtv 552 bytes
external/analogtv/main.cpp
external/analogtv/analog_tv_app.cpp
external/analogtv/ui_tv.cpp
#nrf_rx
#nrf_rx 40 byte
external/nrf_rx/main.cpp
external/nrf_rx/ui_nrf_rx.cpp
#coasterp
#coasterp 0 byte
external/coasterp/main.cpp
external/coasterp/ui_coasterp.cpp
#lge
#lge 120 byte
external/lge/main.cpp
external/lge/lge_app.cpp
external/lge/rfm69.cpp
#lcr
#lcr - 460 byte flash
external/lcr/main.cpp
external/lcr/ui_lcr.cpp
#jammer
#jammer 144 byte
external/jammer/main.cpp
external/jammer/ui_jammer.cpp
#gpssim
#gpssim 160 byte
external/gpssim/main.cpp
external/gpssim/gps_sim_app.cpp
#spainter
#spainter 464 byte
external/spainter/main.cpp
external/spainter/ui_spectrum_painter.cpp
external/spainter/ui_spectrum_painter_text.cpp
external/spainter/ui_spectrum_painter_image.cpp
#keyfob
#keyfob 216 byte
external/keyfob/main.cpp
external/keyfob/ui_keyfob.cpp
external/keyfob/ui_keyfob.hpp
#tetris
#tetris 88 byte
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#extsensors
#extsensors 192 byte
external/extsensors/main.cpp
external/extsensors/ui_extsensors.cpp
external/extsensors/ui_extsensors.hpp
#foxhunt
#foxhunt 0
external/foxhunt/main.cpp
external/foxhunt/ui_foxhunt_rx.cpp
external/foxhunt/ui_foxhunt_rx.hpp
#audio_test
#audio_test 192 byte
external/audio_test/main.cpp
external/audio_test/ui_audio_test.cpp
#wardrivemap
#wardrivemap 64 byte
external/wardrivemap/main.cpp
external/wardrivemap/ui_wardrivemap.cpp
#tpmsrx
#tpmsrx 920 byte- possible shared part with baseband
external/tpmsrx/main.cpp
external/tpmsrx/tpms_app.cpp
#protoview
#protoview 8 byte
external/protoview/main.cpp
external/protoview/ui_protoview.cpp
#adsbtx
#adsbtx 3544 byte - adsb shared part
external/adsbtx/main.cpp
external/adsbtx/ui_adsb_tx.cpp
#morse_tx
#morse_tx 768 bytes
external/morse_tx/main.cpp
external/morse_tx/ui_morse.cpp
#sstvtx
#sstvtx 456 bytes
external/sstvtx/main.cpp
external/sstvtx/ui_sstvtx.cpp
#random
#random 464 bytes.
external/random_password/main.cpp
external/random_password/ui_random_password.cpp
external/random_password/sha512.cpp
external/random_password/sha512.h
#acars
external/acars_rx/main.cpp
external/acars_rx/acars_app.cpp
#external/acars_rx/main.cpp
#external/acars_rx/acars_app.cpp
#wefax_rx
#wefax_rx 192 bytes
external/wefax_rx/main.cpp
external/wefax_rx/ui_wefax_rx.cpp
#noaaapt_rx
#noaaapt_rx 72 bytes
external/noaaapt_rx/main.cpp
external/noaaapt_rx/ui_noaaapt_rx.cpp
#shoppingcart_lock
#shoppingcart_lock 272 bytes
external/shoppingcart_lock/main.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
#ookbrute
#ookbrute 80 byte
external/ookbrute/main.cpp
external/ookbrute/ui_ookbrute.cpp
#ook_editor
#ook_editor 1808 bytes
external/ook_editor/main.cpp
external/ook_editor/ui_ook_editor.cpp
#cvs_spam
#cvs_spam 24 byte
external/cvs_spam/main.cpp
external/cvs_spam/cvs_spam.cpp
#flippertx
#flippertx 712 bytes
external/flippertx/main.cpp
external/flippertx/ui_flippertx.cpp
#remote
#remote 1664 bytes
external/remote/main.cpp
external/remote/ui_remote.cpp
#mcu_temperature
#mcu_temperature 112
external/mcu_temperature/main.cpp
external/mcu_temperature/mcu_temperature.cpp
#fmradio
#fmradio 640
external/fmradio/main.cpp
external/fmradio/ui_fmradio.cpp
#tuner
#tuner 384
external/tuner/main.cpp
external/tuner/ui_tuner.cpp
#metronome
#metronome 696 bytes
external/metronome/main.cpp
external/metronome/ui_metronome.cpp
#app_manager
#app_manager 40 bytes
external/app_manager/main.cpp
external/app_manager/ui_app_manager.cpp
#hopper
#hopper 472 bytes
external/hopper/main.cpp
external/hopper/ui_hopper.cpp
# whip calculator
# whip calculator 48 bytes
external/antenna_length/main.cpp
external/antenna_length/ui_whipcalc.cpp
# wav viewer
# wav viewer 1232 bytes
external/wav_view/main.cpp
external/wav_view/ui_view_wav.cpp
# wipe sdcard
# wipe sdcard 16 byte
external/sd_wipe/main.cpp
external/sd_wipe/ui_sd_wipe.cpp
# playlist editor
# playlist editor 232 bytes
external/playlist_editor/main.cpp
external/playlist_editor/ui_playlist_editor.cpp
#snake
#snake 240 bytes
external/snake/main.cpp
external/snake/ui_snake.cpp
#stopwatch
#stopwatch 0
external/stopwatch/main.cpp
external/stopwatch/ui_stopwatch.cpp
#breakout
#breakout 1144 bytes
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#dinogame
#dinogame 0
external/dinogame/main.cpp
external/dinogame/ui_dinogame.cpp
#doom
#doom 224
external/doom/main.cpp
external/doom/ui_doom.cpp
#debug_pmem
#debug_pmem 944 byte
external/debug_pmem/main.cpp
external/debug_pmem/ui_debug_pmem.cpp
#scanner
#scanner 520 byte
external/scanner/main.cpp
external/scanner/ui_scanner.cpp
#level
#level 24 byte
external/level/main.cpp
external/level/ui_level.cpp
#gfxEQ
#gfxEQ 80 byte
external/gfxeq/main.cpp
external/gfxeq/ui_gfxeq.cpp
#detector_rx
#detector_rx 168 byte
external/detector_rx/main.cpp
external/detector_rx/ui_detector_rx.cpp
#space_invaders
#space_invaders 0 byte
external/spaceinv/main.cpp
external/spaceinv/ui_spaceinv.cpp
#blackjack
#blackjack 24 byte
external/blackjack/main.cpp
external/blackjack/ui_blackjack.cpp
#battleship
#battleship 256 byte
external/battleship/main.cpp
external/battleship/ui_battleship.cpp
#ert
#ert 3040 bytes - has common with baseband, could be renamed the namespace, so both could have it, but not kept in fw.
external/ert/main.cpp
external/ert/ert_app.cpp
#epirb_rx
#epirb_rx 168 byte flash
external/epirb_rx/main.cpp
external/epirb_rx/ui_epirb_rx.cpp
#soundboard
#soundboard 272byte - 1236 bytes
external/soundboard/main.cpp
external/soundboard/soundboard_app.cpp
#game2048
#game2048 - 168 byte flash
external/game2048/main.cpp
external/game2048/ui_game2048.cpp
#bht_tx
#bht_tx - 3920 byte flash, unknown
external/bht_tx/main.cpp
external/bht_tx/ui_bht_tx.cpp
external/bht_tx/bht.cpp
#morse_practice
#morse_practice - 80 byte flash - bc of array initializers
external/morse_practice/main.cpp
external/morse_practice/ui_morse_practice.cpp
#adult_toys_controller 144 bytes
external/adult_toys_controller/main.cpp
external/adult_toys_controller/ui_adult_toys_controller.cpp
)
set(EXTAPPLIST
@@ -265,8 +271,8 @@ set(EXTAPPLIST
calculator
font_viewer
blespam
nrf_rx
analogtv
nrf_rx
coasterp
lge
lcr
@@ -279,19 +285,19 @@ set(EXTAPPLIST
foxhunt_rx
audio_test
wardrivemap
cvs_spam
tpmsrx
protoview
adsbtx
morse_tx
sstvtx
random_password
# acars_rx
ookbrute
ook_editor
# acars_rx --not working
wefax_rx
noaaapt_rx
shoppingcart_lock
ookbrute
ook_editor
cvs_spam
flippertx
remote
mcu_temperature
@@ -323,4 +329,5 @@ set(EXTAPPLIST
game2048
bht_tx
morse_practice
adult_toys_controller
)
+6 -1
View File
@@ -85,7 +85,7 @@ MEMORY
ram_external_app_game2048 (rwx) : org = 0xADEC0000, len = 32k
ram_external_app_bht_tx (rwx) : org = 0xADED0000, len = 32k
ram_external_app_morse_practice (rwx) : org = 0xADEE0000, len = 32k
ram_external_app_adult_toys_controller (rwx) : org = 0xADEF0000, len = 32k
}
@@ -465,6 +465,11 @@ SECTIONS
*(*ui*external_app*morse_practice*);
} > ram_external_app_morse_practice
.external_app_adult_toys_controller : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_adult_toys_controller.application_information));
*(*ui*external_app*adult_toys_controller*);
} > ram_external_app_adult_toys_controller
}
+1 -1
View File
@@ -79,7 +79,7 @@ class FlipperTxView : public View {
void on_tx_progress(const bool done);
bool on_file_changed(std::filesystem::path new_file_path);
std::filesystem::path filename = {};
std::filesystem::path filename = "";
FlipperProto proto = FLIPPER_PROTO_UNSUPPORTED;
FlipperPreset preset = FLIPPER_PRESET_UNK;
uint16_t te = 0; // for binraw
-1
View File
@@ -41,7 +41,6 @@ gfxEQView::gfxEQView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_48000);
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
//
receiver_model.enable();
receiver_model.set_target_frequency(frequency_value); // Retune to actual freq
+9 -7
View File
@@ -46,6 +46,7 @@ class gfxEQView : public View {
NavigationView& nav_;
uint32_t current_theme{0};
const std::array<ColorTheme, 20> themes{
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
@@ -68,13 +69,14 @@ class gfxEQView : public View {
ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
ButtonWithEncoder button_frequency{{UI_POS_X(0), UI_POS_Y(0) + 4, 11 * 8, 1 * 8}, ""};
RFAmpField field_rf_amp{{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{{18 * 8, UI_POS_Y(0)}};
Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
AudioVolumeField field_volume{{screen_width - 2 * 8, UI_POS_Y(0)}};
GraphEq gr{{2, UI_POS_DEFAULT_HEIGHT, UI_POS_MAXWIDTH - 4, UI_POS_HEIGHT_REMAINING(2)}, false};
// Widgets with positions using UI_POS macros
ButtonWithEncoder button_frequency{{UI_POS_X(0), UI_POS_Y(0) + 4, UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, ""};
RFAmpField field_rf_amp{{UI_POS_X(12), UI_POS_Y(0) + 4}};
LNAGainField field_lna{{UI_POS_X(14), UI_POS_Y(0) + 4}};
VGAGainField field_vga{{UI_POS_X(17), UI_POS_Y(0) + 4}};
Button button_mood{{UI_POS_X(21), UI_POS_Y(0) + 4, UI_POS_WIDTH(4), UI_POS_HEIGHT(1)}, "MOOD"};
AudioVolumeField field_volume{{UI_POS_X_RIGHT(2), UI_POS_Y(0) + 4}};
GraphEq gr{{2, UI_POS_Y(1) + 8, UI_POS_MAXWIDTH, UI_POS_Y_BOTTOM(1) - 28}, false};
rf::Frequency frequency_value{93100000};
@@ -24,6 +24,8 @@
#include "crc.hpp"
#include "portapack_shared_memory.hpp"
namespace ui::external_app::lge {
uint32_t RFM69::gen_frame(std::vector<uint8_t>& payload) {
CRC<16> crc{0x1021, 0x1D0F, 0xFFFF};
std::vector<uint8_t> frame{};
@@ -74,3 +76,5 @@ uint32_t RFM69::gen_frame(std::vector<uint8_t>& payload) {
// Copy for baseband
return frame.size();
}
} /* namespace ui::external_app::lge */
@@ -28,6 +28,8 @@
#include "utility.hpp"
namespace ui::external_app::lge {
class RFM69 {
public:
RFM69(const uint8_t num_preamble, const uint16_t sync_word, const bool CRC, const bool manchester)
@@ -54,4 +56,6 @@ class RFM69 {
bool manchester_{false};
};
} /* namespace ui::external_app::lge */
#endif /*__RFM69_H__*/
-1
View File
@@ -34,7 +34,6 @@ void initialize_app(NavigationView& nav) {
extern "C" {
// Az alkalmazás információ C-linkage-ként, hogy a firmware hívhassa
__attribute__((section(".external_app.app_morse_practice.application_information"), used))
application_information_t _application_information_morse_practice = {
/*.memory_location = */ (uint8_t*)0x00000000,
+1 -1
View File
@@ -110,7 +110,7 @@ class MorseView : public View {
bool run{false};
Labels labels{
{{4 * 8, 6 * 8}, "Speed: wps", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 6 * 8}, "Speed: wpm", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 8 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 10 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 12 * 8}, "Loop:", Theme::getInstance()->fg_light->foreground},
@@ -231,7 +231,7 @@ void RandomPasswordView::on_data(uint32_t value, bool is_data) {
}
void RandomPasswordView::clean_buffer() {
seeds_deque = {};
seeds_deque.clear();
}
void RandomPasswordView::on_freqchg(int64_t freq) {
@@ -247,6 +247,18 @@ void RandomPasswordView::set_random_freq() {
field_frequency.set_value(random_freq);
}
bool RandomPasswordView::islower(char c) {
return (c >= 'a' && c <= 'z');
}
bool RandomPasswordView::isupper(char c) {
return (c >= 'A' && c <= 'Z');
}
bool RandomPasswordView::isdigit(char c) {
return (c >= '0' && c <= '9');
}
void RandomPasswordView::new_password() {
if (seeds_deque.size() < MAX_DIGITS * 2) {
seeds_buffer_not_full = true;
@@ -261,21 +273,21 @@ void RandomPasswordView::new_password() {
int password_length = field_digits.value();
/// charset worker
if (check_digits.value())
charset += "0123456789";
if (check_latin_lower.value())
charset += "abcdefghijklmnopqrstuvwxyz";
if (check_latin_upper.value())
charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (check_digits.value()) {
charset += "23456789";
if (check_allow_confusable_chars.value()) charset += "01";
}
if (check_latin_lower.value()) {
charset += "abcdefghijkmnpqrstuvwxyz";
if (check_allow_confusable_chars.value()) charset += "ol";
}
if (check_latin_upper.value()) {
charset += "ABCDEFGHIJKLMNPQRSTUVWXYZ";
if (check_allow_confusable_chars.value()) charset += "O";
}
if (check_punctuation.value())
charset += ".,-!?";
if (!check_allow_confusable_chars.value()) {
charset.erase(std::remove_if(charset.begin(), charset.end(),
[](char c) { return c == '0' || c == 'O' || c == 'o' || c == '1' || c == 'l'; }),
charset.end());
}
if (charset.empty()) {
text_generated_passwd.set("generate failed,");
text_char_type_hints.set("select at least 1 type");
@@ -306,11 +318,11 @@ void RandomPasswordView::new_password() {
/// hint text worker
for (char c : password_chars) {
password += c;
if (std::isdigit(c)) {
if (isdigit(c)) {
char_type_hints += "1";
} else if (std::islower(c)) {
} else if (islower(c)) {
char_type_hints += "a";
} else if (std::isupper(c)) {
} else if (isupper(c)) {
char_type_hints += "A";
} else {
char_type_hints += ",";
@@ -360,11 +372,11 @@ void RandomPasswordView::paint_password_hints() {
for (size_t i = 0; i < password.length(); i++) {
char c = password[i];
Color color;
if (std::isdigit(c)) {
if (isdigit(c)) {
color = Color::red();
} else if (std::islower(c)) {
} else if (islower(c)) {
color = Color::green();
} else if (std::isupper(c)) {
} else if (isupper(c)) {
color = Color::blue();
} else {
color = Color::white();
@@ -379,8 +391,8 @@ void RandomPasswordView::paint_password_hints() {
std::string RandomPasswordView::generate_log_line() {
std::string seeds_set = "";
for (auto seed : seeds_deque) {
seeds_set += std::to_string(seed);
for (unsigned int seed : seeds_deque) {
seeds_set += to_string_dec_uint(seed);
seeds_set += " ";
}
std::string line = "\npassword=" + password +
@@ -92,6 +92,10 @@ class RandomPasswordView : public View {
std::string generate_log_line();
void paint_password_hints();
bool islower(char c);
bool isupper(char c);
bool isdigit(char c);
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
+1 -1
View File
@@ -114,7 +114,7 @@ Optional<capture_metadata> read_metadata_file(const fs::path& path) {
}
bool parse_float_meta(std::string_view str, float& out_val) {
out_val = {};
out_val = 0.0f;
if (str.size() > max_parse_int_length)
return false;
+27
View File
@@ -265,4 +265,31 @@ uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day) {
return (day - 1 + (13 * m / 5) + y + (y / 4) - (y / 100) + (y / 400)) % 7;
}
bool isLeap(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
time_t rtcToUnixUTC(const rtc::RTC& rtc) {
const uint8_t daysOfMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint16_t y = rtc.year();
uint8_t m = rtc.month();
uint8_t d = rtc.day();
uint32_t totalDays = 0;
for (int i = 1970; i < y; i++) {
totalDays += isLeap(i) ? 366 : 365;
}
for (int i = 1; i < m; i++) {
totalDays += daysOfMonth[i];
if (i == 2 && isLeap(y)) {
totalDays++;
}
}
totalDays += (d - 1);
time_t totalSeconds = totalDays * 86400; // 24 * 60 * 60
totalSeconds += rtc.hour() * 3600;
totalSeconds += rtc.minute() * 60;
totalSeconds += rtc.second();
return totalSeconds;
}
} /* namespace rtc_time */
+3
View File
@@ -56,6 +56,9 @@ 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);
bool isLeap(int year);
time_t rtcToUnixUTC(const rtc::RTC& rtc);
} /* namespace rtc_time */
#endif /*__RTC_TIME_H__*/
+1 -1
View File
@@ -81,7 +81,7 @@ class AlphanumView : public TextEntryView {
std::array<Button, 29> buttons{};
NewButton button_shift{
{192, 214, screen_width / 5, 38},
{screen_width - screen_width / 5, 214, screen_width / 5, 38},
{},
&bitmap_icon_shift,
Theme::getInstance()->bg_dark->background,
+28 -17
View File
@@ -199,7 +199,7 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
}
}
map_osm_zoom++;
if (has_osm) set_osm_max_zoom();
if (has_osm) set_osm_max_zoom(true);
} else if (delta < 0) {
if (map_zoom > -MAX_MAP_ZOOM_OUT) {
if (map_zoom == 1) {
@@ -212,6 +212,7 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
}
}
if (map_osm_zoom > 0) map_osm_zoom--;
if (has_osm) set_osm_max_zoom(true);
} else {
return false;
}
@@ -299,8 +300,8 @@ ui::Point GeoMap::item_rect_pixel(GeoMarker& item) {
return {(int16_t)x, (int16_t)y};
}
// osm calculation
double y = lat_to_pixel_y_tile(item.lat, map_osm_zoom) - viewport_top_left_py;
double x = lon_to_pixel_x_tile(item.lon, map_osm_zoom) - viewport_top_left_px;
double y = lat_to_pixel_y_tile(item.lat, map_osm_real_zoom) - viewport_top_left_py;
double x = lon_to_pixel_x_tile(item.lon, map_osm_real_zoom) - viewport_top_left_px;
return {(int16_t)x, (int16_t)y};
}
@@ -327,7 +328,7 @@ int GeoMap::lat2tile(double lat, int zoom) {
return (int)floor((1.0 - log(tan(lat_rad) + 1.0 / cos(lat_rad)) / M_PI) / 2.0 * pow(2.0, zoom));
}
void GeoMap::set_osm_max_zoom() {
void GeoMap::set_osm_max_zoom(bool changeboth) {
if (map_osm_zoom > 20) map_osm_zoom = 20;
for (uint8_t i = map_osm_zoom; i > 0; i--) {
int tile_x = lon2tile(lon_, i);
@@ -335,11 +336,13 @@ void GeoMap::set_osm_max_zoom() {
std::string filename = "/OSM/" + to_string_dec_int(i) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp";
std::filesystem::path file_path(filename);
if (file_exists(file_path)) {
map_osm_zoom = i;
map_osm_real_zoom = i;
if (changeboth) map_osm_zoom = i;
return;
}
}
map_osm_zoom = 0; // should not happen
if (changeboth) map_osm_zoom = 0; // should not happen
map_osm_real_zoom = 0; // should not happen
}
// checks if the tile file presents or not. to determine if we got osm or not
@@ -454,14 +457,22 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
return false;
}
std::vector<ui::Color> line(clip_w);
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
for (int x = 0; x < clip_w; ++x) {
bmp.read_next_px(line[x], true);
if (bmp.is_bottomup()) {
for (int y = clip_h - 1; y >= 0; --y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
} else {
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
return true;
}
@@ -522,8 +533,8 @@ void GeoMap::paint(Painter& painter) {
} else {
// display osm tiles
// Convert center GPS to a global pixel coordinate
double global_center_px = lon_to_pixel_x_tile(lon_, map_osm_zoom);
double global_center_py = lat_to_pixel_y_tile(lat_, map_osm_zoom);
double global_center_px = lon_to_pixel_x_tile(lon_, map_osm_real_zoom);
double global_center_py = lat_to_pixel_y_tile(lat_, map_osm_real_zoom);
// Find the top-left corner of the screen (viewport) in global pixel coordinates
viewport_top_left_px = global_center_px - (r.width() / 2.0);
@@ -552,7 +563,7 @@ void GeoMap::paint(Painter& painter) {
// For the first tile (x=0, y=0), this will be the negative offset.
int draw_pos_x = round(render_offset_x + x * TILE_SIZE);
int draw_pos_y = round(render_offset_y + y * TILE_SIZE);
if (!draw_osm_file(map_osm_zoom, current_tile_x, current_tile_y, draw_pos_x, draw_pos_y)) {
if (!draw_osm_file(map_osm_real_zoom, current_tile_x, current_tile_y, draw_pos_x, draw_pos_y)) {
// already blanked it.
}
}
@@ -615,7 +626,7 @@ bool GeoMap::on_touch(const TouchEvent event) {
on_move(p.x() / 2.0 * lon_ratio, p.y() / 2.0 * lat_ratio, false);
} else {
p = event.point - screen_rect().location();
on_move(tile_pixel_x_to_lon(p.x() + viewport_top_left_px, map_osm_zoom), tile_pixel_y_to_lat(p.y() + viewport_top_left_py, map_osm_zoom), true);
on_move(tile_pixel_x_to_lon(p.x() + viewport_top_left_px, map_osm_real_zoom), tile_pixel_y_to_lat(p.y() + viewport_top_left_py, map_osm_real_zoom), true);
}
return true;
}
+3 -2
View File
@@ -255,7 +255,7 @@ class GeoMap : public Widget {
void map_read_line_bin(ui::Color* buffer, uint16_t pixels);
// open street map related
uint8_t find_osm_file_tile();
void set_osm_max_zoom();
void set_osm_max_zoom(bool changeboth = false);
bool draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int relative_y);
int lon2tile(double lon, int zoom);
int lat2tile(double lat, int zoom);
@@ -263,7 +263,8 @@ class GeoMap : public Widget {
double lat_to_pixel_y_tile(double lat, int zoom);
double tile_pixel_x_to_lon(int x, int zoom);
double tile_pixel_y_to_lat(int y, int zoom);
uint8_t map_osm_zoom{3};
uint8_t map_osm_zoom{5};
uint8_t map_osm_real_zoom{5};
double viewport_top_left_px = 0;
double viewport_top_left_py = 0;
+3 -4
View File
@@ -769,9 +769,9 @@ void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
}
// clang-format off
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos) {
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile ) {
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
if (externalItems.empty()) {
if (externalItems.empty() && show_error_tile) {
grid.insert_item({"ExtAppErr",
Theme::getInstance()->error_dark->foreground,
nullptr,
@@ -848,8 +848,7 @@ void TranceiversMenuView::on_populate() {
add_items({{"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_apps(nav_, *this, TRX);
// add_external_items(nav_, app_location_t::TRX, *this, return_icon ? 1 : 0);
// this folder doesn't have external apps, comment to prevent pop the err msg.
add_external_items(nav_, app_location_t::TRX, *this, return_icon ? 1 : 0, false); // this folder doesn't have external apps, so don't show error for it.
// NB: when has external app someday, uncomment this.
}
+1 -1
View File
@@ -61,7 +61,7 @@ using namespace sd_card;
namespace ui {
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos);
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile = true);
bool verify_sdcard_format();
enum modal_t {
+2 -2
View File
@@ -175,7 +175,7 @@ void RecordView::start() {
text_record_filename.set("");
text_record_dropped.set("");
trim_path = {};
trim_path = "";
if (sampling_rate == 0) {
return;
@@ -348,7 +348,7 @@ void RecordView::trim_capture() {
trim_ui.clear();
}
trim_path = {};
trim_path = "";
}
void RecordView::on_gps(const GPSPosDataMessage* msg) {
+19 -5
View File
@@ -159,13 +159,27 @@ void BitExtractor::configure(uint32_t sample_rate) {
// without needing to know exact transition boundaries.
for (auto& rate : known_rates_)
rate.sample_interval = sample_rate / (2.0 * rate.baud_rate);
if (baud_config_ >= 0 && baud_config_ < static_cast<int8_t>(known_rates_.size())) {
current_rate_ = &known_rates_[baud_config_];
} else {
current_rate_ = nullptr;
}
}
void BitExtractor::reset() {
current_rate_ = nullptr;
for (auto& rate : known_rates_)
rate.reset();
if (baud_config_ >= 0 && baud_config_ < static_cast<int8_t>(known_rates_.size())) {
current_rate_ = &known_rates_[baud_config_];
} else {
current_rate_ = nullptr;
}
}
void BitExtractor::set_baud_config(int8_t baud_config) {
baud_config_ = baud_config;
}
uint16_t BitExtractor::baud_rate() const {
@@ -352,7 +366,7 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
void POCSAGProcessor::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::POCSAGConfigure:
configure();
configure(reinterpret_cast<const POCSAGConfigureMessage*>(message)->baud_config);
break;
case Message::ID::NBFMConfigure: {
@@ -370,7 +384,7 @@ void POCSAGProcessor::on_message(const Message* const message) {
}
}
void POCSAGProcessor::configure() {
void POCSAGProcessor::configure(int8_t baud_config) {
constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor;
constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor;
constexpr size_t channel_filter_output_fs = decim_1_output_fs / 2;
@@ -383,7 +397,7 @@ void POCSAGProcessor::configure() {
// Don't process the audio stream.
audio_output.configure(false);
bit_extractor.set_baud_config(baud_config);
bit_extractor.configure(demod_input_fs);
// Set ready to process data.
+3 -2
View File
@@ -84,6 +84,7 @@ class BitExtractor {
void extract_bits(const buffer_f32_t& audio);
void configure(uint32_t sample_rate);
void reset();
void set_baud_config(int8_t baud_config);
uint16_t baud_rate() const;
private:
@@ -117,7 +118,7 @@ class BitExtractor {
RateInfo{2400}};
BitQueue& bits_;
int8_t baud_config_ = -1;
uint32_t sample_rate_ = 0;
RateInfo* current_rate_ = nullptr;
};
@@ -207,7 +208,7 @@ class POCSAGProcessor : public BasebandProcessor {
static constexpr uint32_t stat_update_threshold =
baseband_fs / stat_update_interval;
void configure();
void configure(int8_t baud_config = -1);
void flush();
void reset();
void send_stats() const;
+85 -88
View File
@@ -69,7 +69,7 @@
* @init
*/
void i2cInit(void) {
i2c_lld_init();
i2c_lld_init();
}
/**
@@ -79,21 +79,20 @@ void i2cInit(void) {
*
* @init
*/
void i2cObjectInit(I2CDriver *i2cp) {
i2cp->state = I2C_STOP;
i2cp->config = NULL;
void i2cObjectInit(I2CDriver* i2cp) {
i2cp->state = I2C_STOP;
i2cp->config = NULL;
#if I2C_USE_MUTUAL_EXCLUSION
#if CH_USE_MUTEXES
chMtxInit(&i2cp->mutex);
chMtxInit(&i2cp->mutex);
#else
chSemInit(&i2cp->semaphore, 1);
chSemInit(&i2cp->semaphore, 1);
#endif /* CH_USE_MUTEXES */
#endif /* I2C_USE_MUTUAL_EXCLUSION */
#if defined(I2C_DRIVER_EXT_INIT_HOOK)
I2C_DRIVER_EXT_INIT_HOOK(i2cp);
I2C_DRIVER_EXT_INIT_HOOK(i2cp);
#endif
}
@@ -105,19 +104,18 @@ void i2cObjectInit(I2CDriver *i2cp) {
*
* @api
*/
void i2cStart(I2CDriver *i2cp, const I2CConfig *config) {
void i2cStart(I2CDriver* i2cp, const I2CConfig* config) {
chDbgCheck((i2cp != NULL) && (config != NULL), "i2cStart");
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
(i2cp->state == I2C_LOCKED),
"i2cStart(), #1",
"invalid state");
chDbgCheck((i2cp != NULL) && (config != NULL), "i2cStart");
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
(i2cp->state == I2C_LOCKED),
"i2cStart(), #1",
"invalid state");
chSysLock();
i2cp->config = config;
i2c_lld_start(i2cp);
i2cp->state = I2C_READY;
chSysUnlock();
chSysLock();
i2cp->config = config;
i2c_lld_start(i2cp);
i2cp->state = I2C_READY;
chSysUnlock();
}
/**
@@ -127,18 +125,17 @@ void i2cStart(I2CDriver *i2cp, const I2CConfig *config) {
*
* @api
*/
void i2cStop(I2CDriver *i2cp) {
void i2cStop(I2CDriver* i2cp) {
chDbgCheck(i2cp != NULL, "i2cStop");
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
(i2cp->state == I2C_LOCKED),
"i2cStop(), #1",
"invalid state");
chDbgCheck(i2cp != NULL, "i2cStop");
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
(i2cp->state == I2C_LOCKED),
"i2cStop(), #1",
"invalid state");
chSysLock();
i2c_lld_stop(i2cp);
i2cp->state = I2C_STOP;
chSysUnlock();
chSysLock();
i2c_lld_stop(i2cp);
i2cp->state = I2C_STOP;
chSysUnlock();
}
/**
@@ -149,11 +146,10 @@ void i2cStop(I2CDriver *i2cp) {
*
* @api
*/
i2cflags_t i2cGetErrors(I2CDriver *i2cp) {
i2cflags_t i2cGetErrors(I2CDriver* i2cp) {
chDbgCheck(i2cp != NULL, "i2cGetErrors");
chDbgCheck(i2cp != NULL, "i2cGetErrors");
return i2c_lld_get_errors(i2cp);
return i2c_lld_get_errors(i2cp);
}
/**
@@ -182,35 +178,37 @@ i2cflags_t i2cGetErrors(I2CDriver *i2cp) {
*
* @api
*/
msg_t i2cMasterTransmitTimeout(I2CDriver *i2cp,
msg_t i2cMasterTransmitTimeout(I2CDriver* i2cp,
i2caddr_t addr,
const uint8_t *txbuf,
const uint8_t* txbuf,
size_t txbytes,
uint8_t *rxbuf,
uint8_t* rxbuf,
size_t rxbytes,
systime_t timeout) {
msg_t rdymsg;
msg_t rdymsg;
chDbgCheck((i2cp != NULL) && (addr != 0) &&
(txbytes > 0) && (txbuf != NULL) &&
((rxbytes == 0) || ((rxbytes > 0) && (rxbuf != NULL))) &&
(timeout != TIME_IMMEDIATE),
"i2cMasterTransmitTimeout");
chDbgCheck((i2cp != NULL) && (addr != 0) &&
(txbytes > 0) && (txbuf != NULL) &&
((rxbytes == 0) || ((rxbytes > 0) && (rxbuf != NULL))) &&
(timeout != TIME_IMMEDIATE),
"i2cMasterTransmitTimeout");
chDbgAssert(i2cp->state == I2C_READY,
"i2cMasterTransmitTimeout(), #1", "not ready");
// chDbgAssert(i2cp->state == I2C_READY, "i2cMasterTransmitTimeout(), #1", "not ready");
if (i2cp->state != I2C_READY) {
return RDY_TIMEOUT;
}
chSysLock();
i2cp->errors = I2CD_NO_ERROR;
i2cp->state = I2C_ACTIVE_TX;
rdymsg = i2c_lld_master_transmit_timeout(i2cp, addr, txbuf, txbytes,
rxbuf, rxbytes, timeout);
if (rdymsg == RDY_TIMEOUT)
i2cp->state = I2C_LOCKED;
else
i2cp->state = I2C_READY;
chSysUnlock();
return rdymsg;
chSysLock();
i2cp->errors = I2CD_NO_ERROR;
i2cp->state = I2C_ACTIVE_TX;
rdymsg = i2c_lld_master_transmit_timeout(i2cp, addr, txbuf, txbytes,
rxbuf, rxbytes, timeout);
if (rdymsg == RDY_TIMEOUT)
i2cp->state = I2C_LOCKED;
else
i2cp->state = I2C_READY;
chSysUnlock();
return rdymsg;
}
/**
@@ -233,32 +231,33 @@ msg_t i2cMasterTransmitTimeout(I2CDriver *i2cp,
*
* @api
*/
msg_t i2cMasterReceiveTimeout(I2CDriver *i2cp,
msg_t i2cMasterReceiveTimeout(I2CDriver* i2cp,
i2caddr_t addr,
uint8_t *rxbuf,
uint8_t* rxbuf,
size_t rxbytes,
systime_t timeout){
systime_t timeout) {
msg_t rdymsg;
msg_t rdymsg;
chDbgCheck((i2cp != NULL) && (addr != 0) &&
(rxbytes > 0) && (rxbuf != NULL) &&
(timeout != TIME_IMMEDIATE),
"i2cMasterReceiveTimeout");
chDbgCheck((i2cp != NULL) && (addr != 0) &&
(rxbytes > 0) && (rxbuf != NULL) &&
(timeout != TIME_IMMEDIATE),
"i2cMasterReceiveTimeout");
// chDbgAssert(i2cp->state == I2C_READY, "i2cMasterReceive(), #1", "not ready");
if (i2cp->state != I2C_READY) {
return RDY_TIMEOUT;
}
chDbgAssert(i2cp->state == I2C_READY,
"i2cMasterReceive(), #1", "not ready");
chSysLock();
i2cp->errors = I2CD_NO_ERROR;
i2cp->state = I2C_ACTIVE_RX;
rdymsg = i2c_lld_master_receive_timeout(i2cp, addr, rxbuf, rxbytes, timeout);
if (rdymsg == RDY_TIMEOUT)
i2cp->state = I2C_LOCKED;
else
i2cp->state = I2C_READY;
chSysUnlock();
return rdymsg;
chSysLock();
i2cp->errors = I2CD_NO_ERROR;
i2cp->state = I2C_ACTIVE_RX;
rdymsg = i2c_lld_master_receive_timeout(i2cp, addr, rxbuf, rxbytes, timeout);
if (rdymsg == RDY_TIMEOUT)
i2cp->state = I2C_LOCKED;
else
i2cp->state = I2C_READY;
chSysUnlock();
return rdymsg;
}
#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__)
@@ -273,14 +272,13 @@ msg_t i2cMasterReceiveTimeout(I2CDriver *i2cp,
*
* @api
*/
void i2cAcquireBus(I2CDriver *i2cp) {
chDbgCheck(i2cp != NULL, "i2cAcquireBus");
void i2cAcquireBus(I2CDriver* i2cp) {
chDbgCheck(i2cp != NULL, "i2cAcquireBus");
#if CH_USE_MUTEXES
chMtxLock(&i2cp->mutex);
chMtxLock(&i2cp->mutex);
#elif CH_USE_SEMAPHORES
chSemWait(&i2cp->semaphore);
chSemWait(&i2cp->semaphore);
#endif
}
@@ -293,14 +291,13 @@ void i2cAcquireBus(I2CDriver *i2cp) {
*
* @api
*/
void i2cReleaseBus(I2CDriver *i2cp) {
chDbgCheck(i2cp != NULL, "i2cReleaseBus");
void i2cReleaseBus(I2CDriver* i2cp) {
chDbgCheck(i2cp != NULL, "i2cReleaseBus");
#if CH_USE_MUTEXES
chMtxUnlock();
chMtxUnlock();
#elif CH_USE_SEMAPHORES
chSemSignal(&i2cp->semaphore);
chSemSignal(&i2cp->semaphore);
#endif
}
#endif /* I2C_USE_MUTUAL_EXCLUSION */
+47
View File
@@ -200,6 +200,53 @@ bool BMPFile::read_next_px(ui::Color& px, bool seek = true) {
return true;
}
bool BMPFile::read_next_px_cnt(ui::Color* px, uint32_t count, bool seek) {
if (!is_opened) return false;
size_t bytesneeded = byte_per_px * count;
while (bytesneeded > 0) { // read in batches
size_t currusedbytes = bytesneeded > 512 ? 170 * byte_per_px : bytesneeded; // don't mind this magic number.
uint8_t buffer[currusedbytes];
auto res = bmpimage.read(buffer, currusedbytes);
if (res.is_error()) return false;
for (uint32_t i = 0; i < currusedbytes; i += byte_per_px, px++) {
switch (type) {
case 5: {
// ARGB1555
uint16_t val = buffer[i] | (buffer[i + 1] << 8);
// Extract components
//*a = (val >> 15) & 0x01; // 1-bit alpha
uint8_t r = (val >> 10) & 0x1F; // 5-bit red
uint8_t g = (val >> 5) & 0x1F; // 5-bit green
uint8_t b = (val)&0x1F; // 5-bit blue
// expand
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
*px = ui::Color(r, g, b);
break;
}
case 2: // 32
*px = ui::Color(buffer[i + 2], buffer[i + 1], buffer[i]);
break;
case 4: { // 8-bit
// uint8_t index = buffer[0];
// px = ui::Color(color_palette[index][2], color_palette[index][1], color_palette[index][0]); // Palette is BGR
// px = ui::Color(buffer[0]); // niy, since needs a lot of ram for the palette
break;
}
case 1: // 24
default:
*px = ui::Color(buffer[i + 2], buffer[i + 1], buffer[i]);
break;
}
}
bytesneeded -= currusedbytes;
}
if (seek) advance_curr_px(count);
return true;
}
// if you set this, then the expanded part (or the newly created) will be filled with this color. but the expansion or the creation will be slower.
void BMPFile::set_bg_color(ui::Color background) {
bg = background;
+1
View File
@@ -42,6 +42,7 @@ class BMPFile {
uint32_t getbpr() { return byte_per_row; };
bool read_next_px(ui::Color& px, bool seek);
bool read_next_px_cnt(ui::Color* px, uint32_t count, bool seek);
bool write_next_px(ui::Color& px);
uint32_t get_real_height();
uint32_t get_width();
+1 -1
View File
@@ -32,7 +32,7 @@ namespace i2cdev {
bool I2cDev_BH1750::init(uint8_t addr_) {
if (addr_ != I2CDEV_BH1750_ADDR_1) return false;
addr = addr_;
model = I2CDECMDL_BH1750;
model = I2CDEVMDL_BH1750;
query_interval = 3;
// power up
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 HTotoo.
*
* 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 "i2cdev_sht4x.hpp"
namespace i2cdev {
bool I2cDev_SHT4x::init(uint8_t addr_) {
if (addr_ != I2CDEV_SHT4X_ADDR_1 && addr_ != I2CDEV_SHT4X_ADDR_2) return false;
addr = addr_; // store the addr
model = I2C_DEVMDL::I2CDEVMDL_SHT4X; // set the device model!!!!!!!!!!!!!!!!!!
query_interval = 10; // set update interval in sec
chThdSleepMilliseconds(50);
uint8_t tmp[1]; // command buffer
uint8_t rx[6]; // response buffer
tmp[0] = SHT4X_SERIAL_NUMBER_CMD_ID;
if (!i2c_write(nullptr, 0, tmp, 1)) return false; // get serial number
chThdSleepMilliseconds(10);
if (!i2c_read(nullptr, 0, rx, 6)) return false; // read serial number
return true;
}
void I2cDev_SHT4x::update() {
float temp = 0;
float hum = 0;
uint8_t cmd[1];
cmd[0] = SHT4X_MEASURE_MEDIUM_PRECISION_TICKS_CMD_ID;
uint8_t res[6];
if (!i2c_write(nullptr, 0, cmd, 1)) return;
chThdSleepMilliseconds(5); // wait for measurement, 10 for high precision, 5 for medium, 2 for low
if (!i2c_read(nullptr, 0, res, 6)) return;
uint16_t temptick = (uint16_t)res[0] << 8 | (uint16_t)res[1];
uint16_t humtick = (uint16_t)res[3] << 8 | (uint16_t)res[4];
temp = -45 + (((float)temptick * 175.0) / 65535.0);
hum = -6 + 125.0 * (float)humtick / 65535.0;
if (hum < 0) hum = 0;
if (hum > 100) hum = 100;
EnvironmentDataMessage msg{temp, hum}; // create the system message
EventDispatcher::send_message(msg); // and send it
}
} // namespace i2cdev
+53
View File
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2025 HTotoo.
*
* 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 __I2CDEV_SHT4X_H__
#define __I2CDEV_SHT4X_H__
#include "i2cdevmanager.hpp"
namespace i2cdev {
class I2cDev_SHT4x : public I2cDev {
public:
typedef enum {
SHT4X_MEASURE_HIGH_PRECISION_TICKS_CMD_ID = 0xfd,
SHT4X_MEASURE_MEDIUM_PRECISION_TICKS_CMD_ID = 0xf6,
SHT4X_MEASURE_LOWEST_PRECISION_TICKS_CMD_ID = 0xe0,
SHT4X_ACTIVATE_HIGHEST_HEATER_POWER_LONG_TICKS_CMD_ID = 0x39,
SHT4X_ACTIVATE_HIGHEST_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x32,
SHT4X_ACTIVATE_MEDIUM_HEATER_POWER_LONG_TICKS_CMD_ID = 0x2f,
SHT4X_ACTIVATE_MEDIUM_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x24,
SHT4X_ACTIVATE_LOWEST_HEATER_POWER_LONG_TICKS_CMD_ID = 0x1e,
SHT4X_ACTIVATE_LOWEST_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x15,
SHT4X_SERIAL_NUMBER_CMD_ID = 0x89,
SHT4X_SOFT_RESET_CMD_ID = 0x94,
} SHT4xCmdId;
bool init(uint8_t addr_) override; // sets the addr to our local variable, set the model, try to init the module, and only return true if it is really that module, and inited ok
void update() override; // query the module for recent data, and send it to the system via the corresponding Message
private:
float read_temperature();
float read_humidity();
};
} // namespace i2cdev
#endif
+5 -1
View File
@@ -34,14 +34,18 @@ enum I2C_DEVMDL {
I2CDEVMDL_SHT3X,
I2CDEVMDL_BMP280,
I2CDEVMDL_BME280,
I2CDECMDL_BH1750,
I2CDEVMDL_BH1750,
I2CDECMDL_PPMOD,
I2CDEVMDL_SHT4X,
};
#define I2CDEV_BMX280_ADDR_1 0x76
#define I2CDEV_BMX280_ADDR_2 0x77
#define I2CDEV_SHT3X_ADDR_1 0x44
#define I2CDEV_SHT3X_ADDR_2 0x45
// sadly sht4x uses the same addr as sht3x, but the init script can decide which is which
#define I2CDEV_SHT4X_ADDR_1 0x44
#define I2CDEV_SHT4X_ADDR_2 0x45
#define I2CDEV_MAX17055_ADDR_1 0x36
#define I2CDEV_ADS1110_ADDR_1 0x48
+6
View File
@@ -32,6 +32,7 @@
#include "i2cdev_ads1110.hpp"
#include "i2cdev_bh1750.hpp"
#include "i2cdev_ppmod.hpp"
#include "i2cdev_sht4x.hpp"
namespace i2cdev {
@@ -73,6 +74,11 @@ bool I2CDevManager::found(uint8_t addr) {
if (!item.dev->init(addr)) item.dev = nullptr;
}
if (!item.dev && (addr == I2CDEV_SHT4X_ADDR_1 || addr == I2CDEV_SHT4X_ADDR_2)) {
item.dev = std::make_unique<I2cDev_SHT4x>();
if (!item.dev->init(addr)) item.dev = nullptr;
}
if (!item.dev && (addr == I2CDEV_MAX17055_ADDR_1)) {
item.dev = std::make_unique<I2cDev_MAX17055>();
if (!item.dev->init(addr)) item.dev = nullptr;
+3 -2
View File
@@ -1187,9 +1187,10 @@ class FSKRxConfigureMessage : public Message {
class POCSAGConfigureMessage : public Message {
public:
constexpr POCSAGConfigureMessage()
: Message{ID::POCSAGConfigure} {
constexpr POCSAGConfigureMessage(int8_t baud_config = -1)
: Message{ID::POCSAGConfigure}, baud_config(baud_config) {
}
int8_t baud_config; //-1 auto, 0=512,1=1200,2=2400
};
class APRSPacketMessage : public Message {
+126 -2
View File
@@ -64,7 +64,7 @@ Packet::Packet(
type_ = Type::Meteomodem_M10;
else if (id_byte == 0x648F)
type_ = Type::Meteomodem_M2K2;
else if (id_byte == 0x4520) // https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
else if (id_byte == 0x4520 || id_byte == 0x4320) // https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
type_ = Type::Meteomodem_M20;
}
}
@@ -145,7 +145,7 @@ uint32_t Packet::battery_voltage() const {
if (type_ == Type::Meteomodem_M10)
return (reader_bi_m.read(69 * 8, 8) + (reader_bi_m.read(70 * 8, 8) << 8)) * 1000 / 150;
else if (type_ == Type::Meteomodem_M20) {
return 0; // NOT SUPPPORTED YET
return reader_bi_m.read(0x26 * 8, 8) * (3.3f / 255.0) * 1000; // based on https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
} else if (type_ == Type::Meteomodem_M2K2)
return reader_bi_m.read(69 * 8, 8) * 66; // Actually 65.8
else if (type_ == Type::Vaisala_RS41_SG) {
@@ -160,10 +160,46 @@ uint32_t Packet::frame() const {
if (type_ == Type::Vaisala_RS41_SG) {
uint32_t frame_number = vaisala_descramble(pos_FrameNb) | (vaisala_descramble(pos_FrameNb + 1) << 8);
return frame_number;
} else if (type_ == Type::Meteomodem_M20) {
return reader_bi_m.read(0x15 * 8, 8);
} else {
return 0; // Unknown
}
}
uint8_t Packet::getFwVerM20() const {
size_t pos_fw = 0x43;
int flen = reader_bi_m.read(0, 8);
if (flen != 0x45) {
int auxLen = flen - 0x45;
if (auxLen < 0) {
pos_fw = flen - 2;
}
}
return reader_bi_m.read(pos_fw, 8);
}
float Packet::get_pressure() const {
float pressure = 0.0f;
if (type_ == Type::Meteomodem_M20) {
float hPa = 0.0f;
uint32_t val = ((uint32_t)reader_bi_m.read(0x25 * 8, 8) << 8) | (uint32_t)reader_bi_m.read(0x24 * 8, 8); // cf. DF9DQ
uint8_t p0 = 0x00;
uint8_t fwVer = getFwVerM20();
if (fwVer >= 0x07) { // SPI1_P[0]
p0 = reader_bi_m.read(0x16 * 8, 8);
}
val = (val << 8) | p0;
if (val > 0) {
hPa = val / (float)(16 * 256); // 4096=0x1000
}
if (hPa > 2560.0f) { // val > 0xA00000
hPa = -1.0f;
}
pressure = hPa;
}
return pressure;
}
temp_humid Packet::get_temp_humid() const {
temp_humid result;
@@ -331,6 +367,78 @@ temp_humid Packet::get_temp_humid() const {
result.humid = rh;
}
}
if (type_ == Type::Meteomodem_M20) {
float p0 = 1.07303516e-03,
p1 = 2.41296733e-04,
p2 = 2.26744154e-06,
p3 = 6.52855181e-08;
float Rs[3] = {12.1e3, 36.5e3, 475.0e3}; // bias/series
float Rp[3] = {1e20, 330.0e3, 2000.0e3}; // parallel, Rp[0]=inf
uint8_t scT = 0; // {0,1,2}, range/scale voltage divider
uint16_t ADC_RT; // ADC12
// ui16_t Tcal[2];
float x, R;
float T = 0; // T/Kelvin
uint32_t b2 = reader_bi_m.read(0x5 * 8, 8);
uint32_t b1 = reader_bi_m.read(0x4 * 8, 8);
ADC_RT = (b2 << 8) | b1;
if (ADC_RT > 8191) {
scT = 2;
ADC_RT -= 8192;
} else if (ADC_RT > 4095) {
scT = 1;
ADC_RT -= 4096;
} else {
scT = 0;
} // also if (ADC_RT>>12)&3 == 3
// ADC12 , 4096 = 1<<12, max: 4095
x = (4095.0 - ADC_RT) / ADC_RT; // (Vcc-Vout)/Vout = Vcc/Vout - 1
R = Rs[scT] / (x - Rs[scT] / Rp[scT]);
if (R > 0) T = 1.0 / (p0 + p1 * log(R) + p2 * log(R) * log(R) + p3 * log(R) * log(R) * log(R));
if (T - 273.15 < -120.0 || T - 273.15 > 60.0) T = 0; // T < -120C, T > 60C invalid
result.temp = T - 273.15; // celsius
// humidity
// humi helper tntc2:
float Rsq = 22.1e3; // P5.6=Vcc
float R25 = 2.2e3; // 0.119e3; //2.2e3;
float b = 3650.0; // B/Kelvin
float T25 = 25.0 + 273.15; // T0=25C, R0=R25=5k
// -> Steinhart-Hart coefficients (polyfit):
T = 0.0; // T/Kelvin
uint16_t ADC_ntc0; // M10: ADC12 P6.4(A4)
float xq, Rq;
uint32_t bq2 = reader_bi_m.read(0x7 * 8, 8);
uint32_t bq1 = reader_bi_m.read(0x6 * 8, 8);
ADC_ntc0 = (bq2 << 8) | bq1; // M10: 0x40,0x3F
xq = (4095.0 - ADC_ntc0) / ADC_ntc0; // (Vcc-Vout)/Vout
Rq = Rsq / xq;
if (Rq > 0) T = 1.0 / (1.0 / T25 + 1.0 / b * log(Rq / R25));
// really the humidity
float TU = T - 273.15;
float RH = -1.0f;
float xqq;
uint16_t humval = ((uint32_t)reader_bi_m.read(0x03 * 8, 8) << 8) | (uint32_t)reader_bi_m.read(0x02 * 8, 8);
uint16_t rh_cal = ((uint32_t)reader_bi_m.read(0x30 * 8, 8) << 8) | (uint32_t)reader_bi_m.read(0x2F * 8, 8);
float humidityCalibration = 6.4e8f / (rh_cal + 80000.0f);
xqq = (humval + 80000.0f) * humidityCalibration * (1.0f - 5.8e-4f * (TU - 25.0f));
xqq = 4.16e9f / xqq;
xqq = 10.087f * xqq * xqq * xqq - 211.62f * xqq * xqq + 1388.2f * xqq - 2797.0f;
RH = -1.0f;
if (humval < 48000) {
if (xqq > -20.0f && xqq < 120.f) {
RH = xqq;
if (RH < 0.0f) RH = 0.0f;
if (RH > 100.0f) RH = 100.0f;
}
}
result.humid = RH;
}
return result;
}
@@ -378,6 +486,10 @@ std::string Packet::serial_number() const {
}
}
return serial_id;
} else if (type_ == Type::Meteomodem_M20) {
// Inspired by https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
uint32_t sn = reader_bi_m.read(0x12 * 8, 8) | (reader_bi_m.read(0x13 * 8, 8) << 8) | (reader_bi_m.read(0x14 * 8, 8) << 16);
return to_string_dec_uint(sn); // Serial is 3 bytes at byte #12
} else {
return "?";
}
@@ -404,11 +516,23 @@ bool Packet::crc_ok() const {
return crc_ok_M10();
case Type::Vaisala_RS41_SG:
return crc_ok_RS41();
case Type::Meteomodem_M20:
return check_ok_M20();
default:
return true; // euquiq: it was false, but if no crc routine, then no way to check
}
}
bool Packet::check_ok_M20() const {
uint8_t b1 = reader_bi_m.read(0, 8);
uint8_t b2 = reader_bi_m.read(8, 8);
if ((b1 != 0x45 && b1 != 0x43) || b2 != 0x20)
return false;
if (packet_.size() / 8 < b1)
return false;
return true;
}
// each data block has a 2 byte header, data, and 2 byte tail:
// 1st byte: block ID
// 2nd byte: data length (without header or tail)
+77 -8
View File
@@ -36,6 +36,16 @@ struct GPS_data {
uint32_t alt{0};
float lat{0};
float lon{0};
bool is_valid() const {
if (lat >= -0.01 && lat <= 0.01 && lon >= -0.01 && lon <= 0.01)
return false;
if (lat < -90.0 || lat > 90.0)
return false;
if (lon < -180.0 || lon > 180.0)
return false;
return true;
}
};
struct temp_humid {
@@ -68,21 +78,79 @@ class Packet {
GPS_data get_GPS_data() const;
uint32_t frame() const;
temp_humid get_temp_humid() const;
float get_pressure() const;
FormattedSymbols symbols_formatted() const;
bool crc_ok() const;
private:
uint8_t getFwVerM20() const;
static constexpr uint8_t vaisala_mask[64] = {
0x96, 0x83, 0x3E, 0x51, 0xB1, 0x49, 0x08, 0x98,
0x32, 0x05, 0x59, 0x0E, 0xF9, 0x44, 0xC6, 0x26,
0x21, 0x60, 0xC2, 0xEA, 0x79, 0x5D, 0x6D, 0xA1,
0x54, 0x69, 0x47, 0x0C, 0xDC, 0xE8, 0x5C, 0xF1,
0xF7, 0x76, 0x82, 0x7F, 0x07, 0x99, 0xA2, 0x2C,
0x93, 0x7C, 0x30, 0x63, 0xF5, 0x10, 0x2E, 0x61,
0xD0, 0xBC, 0xB4, 0xB6, 0x06, 0xAA, 0xF4, 0x23,
0x78, 0x6E, 0x3B, 0xAE, 0xBF, 0x7B, 0x4C, 0xC1};
0x96,
0x83,
0x3E,
0x51,
0xB1,
0x49,
0x08,
0x98,
0x32,
0x05,
0x59,
0x0E,
0xF9,
0x44,
0xC6,
0x26,
0x21,
0x60,
0xC2,
0xEA,
0x79,
0x5D,
0x6D,
0xA1,
0x54,
0x69,
0x47,
0x0C,
0xDC,
0xE8,
0x5C,
0xF1,
0xF7,
0x76,
0x82,
0x7F,
0x07,
0x99,
0xA2,
0x2C,
0x93,
0x7C,
0x30,
0x63,
0xF5,
0x10,
0x2E,
0x61,
0xD0,
0xBC,
0xB4,
0xB6,
0x06,
0xAA,
0xF4,
0x23,
0x78,
0x6E,
0x3B,
0xAE,
0xBF,
0x7B,
0x4C,
0xC1};
GPS_data ecef_to_gps() const;
@@ -97,6 +165,7 @@ class Packet {
bool crc_ok_M10() const;
bool crc_ok_RS41() const;
bool check_ok_M20() const;
bool crc16rs41(uint32_t field_start) const;
};
+1 -1
View File
@@ -148,7 +148,7 @@ struct standalone_application_information_t {
void (*shutdown)();
void (*PaintViewMirror)();
void (*OnTouchEvent)(int x, int y, uint32_t type);
bool (*OnTouchEvent)(int x, int y, uint32_t type);
void (*OnFocus)();
bool (*OnKeyEvent)(uint8_t key);
bool (*OnEncoder)(int32_t delta);
Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

+48 -1
View File
@@ -173,7 +173,7 @@ bool BMPFile::read_next_px(ui::Color& px, bool seek = true) {
//*a = (val >> 15) & 0x01; // 1-bit alpha
uint8_t r = (val >> 10) & 0x1F; // 5-bit red
uint8_t g = (val >> 5) & 0x1F; // 5-bit green
uint8_t b = (val)&0x1F; // 5-bit blue
uint8_t b = (val) & 0x1F; // 5-bit blue
// expand
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
@@ -200,6 +200,53 @@ bool BMPFile::read_next_px(ui::Color& px, bool seek = true) {
return true;
}
bool BMPFile::read_next_px_cnt(ui::Color* px, uint32_t count, bool seek) {
if (!is_opened) return false;
size_t bytesneeded = byte_per_px * count;
while (bytesneeded > 0) { // read in batches
size_t currusedbytes = bytesneeded > 256 ? 85 * byte_per_px : bytesneeded; // don't mind this magic number.
uint8_t buffer[currusedbytes];
auto res = bmpimage.read(buffer, currusedbytes);
if (res.is_error()) return false;
for (uint32_t i = 0; i < currusedbytes; i += byte_per_px, px++) {
switch (type) {
case 5: {
// ARGB1555
uint16_t val = buffer[i] | (buffer[i + 1] << 8);
// Extract components
//*a = (val >> 15) & 0x01; // 1-bit alpha
uint8_t r = (val >> 10) & 0x1F; // 5-bit red
uint8_t g = (val >> 5) & 0x1F; // 5-bit green
uint8_t b = (val) & 0x1F; // 5-bit blue
// expand
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
*px = ui::Color(r, g, b);
break;
}
case 2: // 32
*px = ui::Color(buffer[i + 2], buffer[i + 1], buffer[i]);
break;
case 4: { // 8-bit
// uint8_t index = buffer[0];
// px = ui::Color(color_palette[index][2], color_palette[index][1], color_palette[index][0]); // Palette is BGR
// px = ui::Color(buffer[0]); // niy, since needs a lot of ram for the palette
break;
}
case 1: // 24
default:
*px = ui::Color(buffer[i + 2], buffer[i + 1], buffer[i]);
break;
}
}
bytesneeded -= currusedbytes;
}
if (seek) advance_curr_px(count);
return true;
}
// if you set this, then the expanded part (or the newly created) will be filled with this color. but the expansion or the creation will be slower.
void BMPFile::set_bg_color(ui::Color background) {
bg = background;
@@ -42,6 +42,7 @@ class BMPFile {
uint32_t getbpr() { return byte_per_row; };
bool read_next_px(ui::Color& px, bool seek);
bool read_next_px_cnt(ui::Color* px, uint32_t count, bool seek);
bool write_next_px(ui::Color& px);
uint32_t get_real_height();
uint32_t get_width();
+15 -7
View File
@@ -451,14 +451,22 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
return false;
}
std::vector<ui::Color> line(clip_w);
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
for (int x = 0; x < clip_w; ++x) {
bmp.read_next_px(line[x], true);
if (bmp.is_bottomup()) {
for (int y = clip_h - 1; y >= 0; --y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
painter.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
} else {
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
painter.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
painter.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
return true;
}
@@ -77,7 +77,7 @@ ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) {
ui::Widget* captured_widget{nullptr};
void OnTouchEvent(int, int, uint32_t) {
bool OnTouchEvent(int, int, uint32_t) {
if (standaloneViewMirror) {
_api->exit_app();
/* //left here for example, but not used in digital rain
@@ -96,6 +96,7 @@ void OnTouchEvent(int, int, uint32_t) {
captured_widget->on_touch(event);
*/
}
return false;
}
void OnFocus() {
@@ -40,7 +40,7 @@ void shutdown();
void OnFocus();
bool OnKeyEvent(uint8_t);
bool OnEncoder(int32_t);
void OnTouchEvent(int, int, uint32_t);
bool OnTouchEvent(int, int, uint32_t);
bool OnKeyboad(uint8_t);
void PaintViewMirror();
+22 -9
View File
@@ -23,11 +23,25 @@
#include "pacman.hpp"
#include <memory>
bool notouch(int, int, uint32_t) {
// do nothing
return false;
}
void nothing() {
// do nothing
}
bool noencoder(int32_t) {
return false;
}
bool nokeyboard(uint8_t) {
return false;
}
const standalone_application_api_t* _api;
extern "C" {
__attribute__((section(".standalone_application_information"), used)) standalone_application_information_t _standalone_application_information = {
/*.header_version = */ 1,
/*.header_version = */ 2,
/*.app_name = */ "Pac-Man",
/*.bitmap_data = */ {
@@ -70,18 +84,17 @@ __attribute__((section(".standalone_application_information"), used)) standalone
/*.initialize_app = */ initialize,
/*.on_event = */ on_event,
/*.shutdown = */ shutdown,
/*PaintViewMirror */ NULL,
/*OnTouchEvent */ NULL,
/*OnFocus */ NULL,
/*OnKeyEvent */ NULL,
/*OnEncoder */ NULL,
/*OnKeyboard */ NULL};
/*PaintViewMirror */ nothing,
/*OnTouchEvent */ notouch,
/*OnFocus */ nothing,
/*OnKeyEvent */ on_key_event,
/*OnEncoder */ noencoder,
/*OnKeyboard */ nokeyboard};
}
/* Implementing abort() eliminates requirement for _getpid(), _kill(), _exit(). */
extern "C" void abort() {
while (true)
;
while (true);
}
// replace memory allocations to use heap from chibios
+50 -21
View File
@@ -39,8 +39,36 @@ void initialize(const standalone_application_api_t& api) {
_api = &api;
}
bool on_key_event(uint8_t key_val) {
ui::KeyEvent key = (ui::KeyEvent)key_val;
if (key == ui::KeyEvent::Right) {
but_RIGHT = true;
but_LEFT = false;
but_DOWN = false;
but_UP = false;
} else if (key == ui::KeyEvent::Left) {
but_LEFT = true;
but_RIGHT = false;
but_DOWN = false;
but_UP = false;
} else if (key == ui::KeyEvent::Down) {
but_DOWN = true;
but_RIGHT = false;
but_LEFT = false;
but_UP = false;
} else if (key == ui::KeyEvent::Up) {
but_UP = true;
but_RIGHT = false;
but_LEFT = false;
but_DOWN = false;
} else if (key == ui::KeyEvent::Select) {
but_A = true;
}
return true;
}
void on_event(const uint32_t& events) {
static bool wait_for_button_release{false};
// static bool wait_for_button_release{false};
if (!_playfield) {
_playfield = std::make_unique<Playfield>();
@@ -48,29 +76,30 @@ void on_event(const uint32_t& events) {
}
if (events & 1) {
auto switches_raw = _api->swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
auto switches_debounced = _api->get_switches_state();
/*
auto switches_raw = _api->swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
auto switches_debounced = _api->get_switches_state();
// For the Select (Start/Pause) button, wait for release to avoid repeat
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
if (wait_for_button_release) {
if ((switches_debounced & buttons_to_wait_for) == 0)
wait_for_button_release = false;
switches_debounced &= ~buttons_to_wait_for;
} else {
if (switches_debounced & buttons_to_wait_for)
wait_for_button_release = true;
}
// For the Select (Start/Pause) button, wait for release to avoid repeat
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
if (wait_for_button_release) {
if ((switches_debounced & buttons_to_wait_for) == 0)
wait_for_button_release = false;
switches_debounced &= ~buttons_to_wait_for;
} else {
if (switches_debounced & buttons_to_wait_for)
wait_for_button_release = true;
}
// For the directional buttons, use the raw inputs for fastest response time
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
// For the directional buttons, use the raw inputs for fastest response time
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
*/
_playfield->Step();
}
}
+1
View File
@@ -26,6 +26,7 @@
void initialize(const standalone_application_api_t& api);
void on_event(const uint32_t& events);
bool on_key_event(uint8_t key_val);
void shutdown();
extern const standalone_application_api_t* _api;
+97 -25
View File
@@ -133,39 +133,111 @@ const byte _initSprites[] =
// Ghost colors
const byte _palette2[] =
{
0, 11, 1, 15, // BINKY red
0, 11, 3, 15, // PINKY pink
0, 11, 5, 15, // INKY cyan
0, 11, 7, 15, // CLYDE brown
0, 11, 9, 9, // PACMAN yellow
0, 11, 15, 15, // FRIGHTENED
0, 11, 0, 15, // DEADEYES
0,
11,
1,
15, // BINKY red
0,
11,
3,
15, // PINKY pink
0,
11,
5,
15, // INKY cyan
0,
11,
7,
15, // CLYDE brown
0,
11,
9,
9, // PACMAN yellow
0,
11,
15,
15, // FRIGHTENED
0,
11,
0,
15, // DEADEYES
0, 1, 15, 2, // cherry
0, 1, 15, 12, // strawberry
0, 7, 2, 12, // peach
0, 9, 15, 0, // bell
0,
1,
15,
2, // cherry
0,
1,
15,
12, // strawberry
0,
7,
2,
12, // peach
0,
9,
15,
0, // bell
0, 15, 1, 2, // apple
0, 12, 15, 5, // grape
0, 11, 9, 1, // galaxian
0, 5, 15, 15, // key
0,
15,
1,
2, // apple
0,
12,
15,
5, // grape
0,
11,
9,
1, // galaxian
0,
5,
15,
15, // key
};
const byte _paletteIcon2[] =
{
0, 9, 9, 9, // PACMAN
0,
9,
9,
9, // PACMAN
0, 2, 15, 1, // cherry
0, 12, 15, 1, // strawberry
0, 12, 2, 7, // peach
0, 0, 15, 9, // bell
0,
2,
15,
1, // cherry
0,
12,
15,
1, // strawberry
0,
12,
2,
7, // peach
0,
0,
15,
9, // bell
0, 2, 15, 1, // apple
0, 12, 15, 5, // grape
0, 1, 9, 11, // galaxian
0, 5, 15, 15, // key
0,
2,
15,
1, // apple
0,
12,
15,
5, // grape
0,
1,
9,
11, // galaxian
0,
5,
15,
15, // key
};
#define PACMANICON 1
@@ -571,7 +643,7 @@ class Playfield {
}
#endif
x += (240 - 224) / 2;
x += (*(_api->screen_width) - 224) / 2;
y += (320 - 288) / 2 + 8;
// Should be a direct Graphics call