Compare commits

...

5 Commits

Author SHA1 Message Date
gullradriel 4c5275247e update version (#2735)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-11 15:57:04 +02:00
gullradriel a0185d28eb update submodule (#2734)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-09 21:33:38 +02:00
RocketGod 2500df310f Add radio settings, new app icon, and other UI improvements (#2732) 2025-07-03 11:03:51 -07:00
Totoo 54f9ff116b AIS map improv (#2725)
* AIS map improv

* format code mismatch with vc
2025-06-29 22:47:18 +02:00
Totoo 47f231ad63 Add blue bar to subghzd+weather (#2724) 2025-06-29 19:31:05 +08:00
14 changed files with 398 additions and 144 deletions
+1 -1
View File
@@ -1 +1 @@
v2.0.2
v2.1.0
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.2.0
+40 -2
View File
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
}
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
if (geomap_view && send_updates) {
GeoMarker marker{};
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
marker.angle = entry.last_position.true_heading;
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
auto markerStored = geomap_view->store_marker(marker);
return markerStored == MARKER_STORED;
}
return false;
}
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
if (geomap_view && send_updates) {
geomap_view->clear_markers();
for (const auto& entry : entries) {
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
add_map_marker(entry);
// }
}
update_position(); // to update view
}
}
void AISRecentEntryDetailView::focus() {
@@ -415,14 +439,27 @@ AISAppView::AISAppView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
AISAppView::~AISAppView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
void AISAppView::on_tick_second() {
++timer_seconds;
if (timer_seconds % 10 == 0) {
if (recent_entry_detail_view.hidden()) return;
recent_entry_detail_view.update_map_markers(recent);
}
}
void AISAppView::focus() {
options_channel.focus();
}
@@ -461,6 +498,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.hidden(false);
recent_entry_detail_view.set_entry(entry);
recent_entry_detail_view.focus();
recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
+7
View File
@@ -125,10 +125,14 @@ class AISRecentEntryDetailView : public View {
void update_position();
void focus() override;
void paint(Painter&) override;
bool add_map_marker(const AISRecentEntry& entry);
void update_map_markers(AISRecentEntries& entries);
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
GeoMapView* get_geomap_view() { return geomap_view; }
private:
AISRecentEntry entry_{};
@@ -215,6 +219,8 @@ class AISAppView : public View {
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
SignalToken signal_token_tick_second{};
uint8_t timer_seconds = 0;
MessageHandlerRegistration message_handler_packet{
Message::ID::AISPacket,
@@ -229,6 +235,7 @@ class AISAppView : public View {
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_tick_second();
};
} /* namespace ui */
+1
View File
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
+3
View File
@@ -129,6 +129,9 @@ class SubGhzDView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
@@ -107,6 +107,7 @@ void WeatherView::focus() {
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -147,6 +147,9 @@ class WeatherView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
+17 -33
View File
@@ -27,39 +27,23 @@ __attribute__((section(".external_app.app_battleship.application_information"),
/*.app_name = */ "Battleship",
/*.bitmap_data = */ {
// Ship icon
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x1C,
0x38,
0x3E,
0x7C,
0x7F,
0xFE,
0xFF,
0xFF,
0xFF,
0xFF,
0x7F,
0xFE,
0x3F,
0xFC,
0x1F,
0xF8,
0x0F,
0xF0,
0x07,
0xE0,
0x03,
0xC0,
0x01,
0x80,
// Pirate galleon - 16x16
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0xC0, 0x01, // .......###......
0xE0, 0x03, // ......#####.....
0xF0, 0x07, // .....#######....
0xF8, 0x0F, // ....#########...
0xFC, 0x1F, // ...###########..
0xFE, 0x3F, // ..#############.
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0xFC, 0x3F, // ..############..
0xFE, 0x7F, // .##############.
0xFF, 0xFF, // ################
0xFC, 0x3F, // ..############..
},
/*.icon_color = */ ui::Color::blue().v,
/*.menu_location = */ app_location_t::GAMES,
+272 -60
View File
@@ -26,32 +26,97 @@ BattleshipView::BattleshipView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
add_children({&rssi,
&field_frequency,
&text_status,
&text_score,
&button_red_team,
&button_blue_team,
&button_rotate,
&button_place,
&button_fire,
&button_menu});
add_children({&text_title, &text_subtitle,
&rect_radio_settings, &label_radio, &button_frequency,
&label_rf_amp, &checkbox_rf_amp,
&label_lna, &field_lna,
&label_vga, &field_vga,
&label_tx_gain, &field_tx_gain,
&rect_audio_settings, &label_audio,
&checkbox_sound, &label_volume, &field_volume,
&rect_team_selection, &label_team,
&button_red_team, &button_blue_team,
&rssi, &text_status, &text_score,
&button_rotate, &button_place, &button_fire, &button_menu});
// Hide in-game elements
rssi.hidden(true);
text_status.hidden(true);
text_score.hidden(true);
button_rotate.hidden(true);
button_place.hidden(true);
button_fire.hidden(true);
button_menu.hidden(true);
field_frequency.set_value(DEFAULT_FREQUENCY);
field_frequency.on_change = [this](rf::Frequency freq) {
tx_frequency = freq;
rx_frequency = freq;
// Configure frequency button
button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
auto new_view = nav_.push<FrequencyKeypadView>(tx_frequency);
new_view->on_changed = [this, &button](rf::Frequency f) {
tx_frequency = f;
rx_frequency = f;
button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
if (!is_transmitting) {
receiver_model.set_target_frequency(rx_frequency);
}
};
};
button_frequency.on_change = [this]() {
int64_t def_step = 25000;
int64_t new_freq = static_cast<int64_t>(tx_frequency) + (button_frequency.get_encoder_delta() * def_step);
if (new_freq < 1) new_freq = 1;
if (new_freq > 7200000000LL) new_freq = 7200000000LL;
tx_frequency = static_cast<uint32_t>(new_freq);
rx_frequency = tx_frequency;
button_frequency.set_encoder_delta(0);
button_frequency.set_text("<" + to_string_short_freq(tx_frequency) + ">");
if (!is_transmitting) {
receiver_model.set_target_frequency(rx_frequency);
}
};
// Radio controls
checkbox_rf_amp.set_value(rf_amp_enabled);
checkbox_rf_amp.on_select = [this](Checkbox&, bool v) {
rf_amp_enabled = v;
transmitter_model.set_rf_amp(v);
receiver_model.set_rf_amp(v);
};
field_lna.set_value(lna_gain);
field_lna.on_change = [this](int32_t v) {
lna_gain = v;
receiver_model.set_lna(v);
};
field_vga.set_value(vga_gain);
field_vga.on_change = [this](int32_t v) {
vga_gain = v;
receiver_model.set_vga(v);
};
field_tx_gain.set_value(tx_gain);
field_tx_gain.on_change = [this](int32_t v) {
tx_gain = v;
transmitter_model.set_tx_gain(v);
};
// Audio controls
checkbox_sound.set_value(sound_enabled);
checkbox_sound.on_select = [this](Checkbox&, bool v) {
sound_enabled = v;
if (sound_enabled) {
audio::output::unmute();
} else {
audio::output::mute();
}
};
// Team selection
button_red_team.on_select = [this](Button&) {
start_team(true);
};
@@ -60,6 +125,7 @@ BattleshipView::BattleshipView(NavigationView& nav)
start_team(false);
};
// In-game controls
button_rotate.on_select = [this](Button&) {
placing_horizontal = !placing_horizontal;
set_dirty();
@@ -77,6 +143,28 @@ BattleshipView::BattleshipView(NavigationView& nav)
reset_game();
};
// Set proper rectangles for layout
button_frequency.set_parent_rect({17, 65, 11 * 8, 20});
checkbox_rf_amp.set_parent_rect({55, 90, 24, 24});
field_lna.set_parent_rect({50, 118, 32, 16});
field_vga.set_parent_rect({125, 118, 32, 16});
field_tx_gain.set_parent_rect({185, 118, 32, 16});
checkbox_sound.set_parent_rect({17, 187, 80, 24});
field_volume.set_parent_rect({165, 187, 32, 16});
button_red_team.set_parent_rect({25, 242, 85, 45});
button_blue_team.set_parent_rect({130, 242, 85, 45});
// Make menu elements focusable
button_frequency.set_focusable(true);
checkbox_rf_amp.set_focusable(true);
field_lna.set_focusable(true);
field_vga.set_focusable(true);
field_tx_gain.set_focusable(true);
checkbox_sound.set_focusable(true);
field_volume.set_focusable(true);
button_red_team.set_focusable(true);
button_blue_team.set_focusable(true);
set_focusable(true);
init_game();
}
@@ -90,7 +178,7 @@ BattleshipView::~BattleshipView() {
void BattleshipView::focus() {
if (game_state == GameState::MENU) {
button_red_team.focus();
button_frequency.focus();
} else {
View::focus();
}
@@ -131,20 +219,54 @@ void BattleshipView::reset_game() {
current_status = "Choose your team!";
update_score();
text_status.hidden(false);
text_score.hidden(true);
field_frequency.hidden(false);
button_red_team.hidden(false);
button_blue_team.hidden(false);
button_rotate.hidden(true);
button_place.hidden(true);
button_fire.hidden(true);
button_menu.hidden(true);
button_red_team.set_focusable(true);
button_blue_team.set_focusable(true);
button_red_team.focus();
// Toggle visibility
bool menu_vis = true;
bool game_vis = false;
text_title.hidden(!menu_vis);
text_subtitle.hidden(!menu_vis);
rect_radio_settings.hidden(!menu_vis);
label_radio.hidden(!menu_vis);
button_frequency.hidden(!menu_vis);
label_rf_amp.hidden(!menu_vis);
checkbox_rf_amp.hidden(!menu_vis);
label_lna.hidden(!menu_vis);
field_lna.hidden(!menu_vis);
label_vga.hidden(!menu_vis);
field_vga.hidden(!menu_vis);
label_tx_gain.hidden(!menu_vis);
field_tx_gain.hidden(!menu_vis);
rect_audio_settings.hidden(!menu_vis);
label_audio.hidden(!menu_vis);
checkbox_sound.hidden(!menu_vis);
label_volume.hidden(!menu_vis);
field_volume.hidden(!menu_vis);
rect_team_selection.hidden(!menu_vis);
label_team.hidden(!menu_vis);
button_red_team.hidden(!menu_vis);
button_blue_team.hidden(!menu_vis);
rssi.hidden(!game_vis);
text_status.hidden(!game_vis);
text_score.hidden(!game_vis);
button_rotate.hidden(!game_vis);
button_place.hidden(!game_vis);
button_fire.hidden(!game_vis);
button_menu.hidden(!game_vis);
// Restore focusability
button_frequency.set_focusable(menu_vis);
checkbox_rf_amp.set_focusable(menu_vis);
field_lna.set_focusable(menu_vis);
field_vga.set_focusable(menu_vis);
field_tx_gain.set_focusable(menu_vis);
checkbox_sound.set_focusable(menu_vis);
field_volume.set_focusable(menu_vis);
button_red_team.set_focusable(menu_vis);
button_blue_team.set_focusable(menu_vis);
button_frequency.focus();
set_dirty();
}
@@ -160,16 +282,40 @@ void BattleshipView::start_team(bool red) {
is_red_team = red;
game_state = GameState::PLACING_SHIPS;
field_frequency.hidden(true);
button_red_team.hidden(true);
button_blue_team.hidden(true);
// Toggle visibility
bool menu_vis = false;
bool game_vis = true;
text_title.hidden(!menu_vis);
text_subtitle.hidden(!menu_vis);
rect_radio_settings.hidden(!menu_vis);
label_radio.hidden(!menu_vis);
button_frequency.hidden(!menu_vis);
label_rf_amp.hidden(!menu_vis);
checkbox_rf_amp.hidden(!menu_vis);
label_lna.hidden(!menu_vis);
field_lna.hidden(!menu_vis);
label_vga.hidden(!menu_vis);
field_vga.hidden(!menu_vis);
label_tx_gain.hidden(!menu_vis);
field_tx_gain.hidden(!menu_vis);
rect_audio_settings.hidden(!menu_vis);
label_audio.hidden(!menu_vis);
checkbox_sound.hidden(!menu_vis);
label_volume.hidden(!menu_vis);
field_volume.hidden(!menu_vis);
rect_team_selection.hidden(!menu_vis);
label_team.hidden(!menu_vis);
button_red_team.hidden(!menu_vis);
button_blue_team.hidden(!menu_vis);
rssi.hidden(!game_vis);
text_status.hidden(true);
text_score.hidden(true);
button_rotate.hidden(false);
button_place.hidden(false);
button_menu.hidden(false);
text_status.hidden(true);
text_score.hidden(true);
current_status = "Place carrier (5)";
button_rotate.set_focusable(false);
@@ -177,10 +323,8 @@ void BattleshipView::start_team(bool red) {
button_menu.set_focusable(false);
focus();
is_transmitting = true;
configure_radio_rx();
set_dirty();
}
@@ -200,8 +344,8 @@ void BattleshipView::configure_radio_tx() {
transmitter_model.set_target_frequency(tx_frequency);
transmitter_model.set_sampling_rate(2280000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.set_rf_amp(false);
transmitter_model.set_tx_gain(35);
transmitter_model.set_rf_amp(rf_amp_enabled);
transmitter_model.set_tx_gain(tx_gain);
is_transmitting = true;
}
@@ -210,30 +354,29 @@ void BattleshipView::configure_radio_rx() {
if (is_transmitting) {
transmitter_model.disable();
baseband::shutdown();
chThdSleepMilliseconds(100);
}
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
chThdSleepMilliseconds(100);
receiver_model.set_target_frequency(rx_frequency);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_rf_amp(false);
receiver_model.set_lna(24);
receiver_model.set_vga(24);
receiver_model.set_rf_amp(rf_amp_enabled);
receiver_model.set_lna(lna_gain);
receiver_model.set_vga(vga_gain);
baseband::set_pocsag();
receiver_model.enable();
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
if (sound_enabled) {
audio::output::start();
}
is_transmitting = false;
current_status = "RX Ready";
set_dirty();
}
@@ -242,22 +385,59 @@ void BattleshipView::paint(Painter& painter) {
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
if (game_state == GameState::MENU) {
auto style_title = *ui::Theme::getInstance()->fg_light;
painter.draw_string({60, 20}, style_title, "BATTLESHIP");
painter.draw_string({40, 80}, style_title, "Choose your team:");
painter.draw_string({10, 180}, *ui::Theme::getInstance()->fg_medium, "Set same freq on both!");
draw_menu_screen(painter);
// Custom paint team buttons
if (!button_red_team.hidden()) {
Rect r = button_red_team.screen_rect();
painter.fill_rectangle(r, Color::dark_red());
painter.draw_rectangle(r, Color::red());
if (button_red_team.has_focus()) {
painter.draw_rectangle({r.location().x() - 1, r.location().y() - 1, r.size().width() + 2, r.size().height() + 2}, Color::yellow());
}
auto style_white = Style{
.font = ui::font::fixed_8x16,
.background = Color::dark_red(),
.foreground = Color::white()};
painter.draw_string(r.center() - Point(24, 16), style_white, "RED");
painter.draw_string(r.center() - Point(24, 0), style_white, "TEAM");
}
if (!button_blue_team.hidden()) {
Rect r = button_blue_team.screen_rect();
painter.fill_rectangle(r, Color::dark_blue());
painter.draw_rectangle(r, Color::blue());
if (button_blue_team.has_focus()) {
painter.draw_rectangle({r.location().x() - 1, r.location().y() - 1, r.size().width() + 2, r.size().height() + 2}, Color::yellow());
}
auto style_white = Style{
.font = ui::font::fixed_8x16,
.background = Color::dark_blue(),
.foreground = Color::white()};
painter.draw_string(r.center() - Point(24, 16), style_white, "BLUE");
painter.draw_string(r.center() - Point(24, 0), style_white, "TEAM");
}
return;
}
Color team_color = is_red_team ? Color::red() : Color::blue();
painter.fill_rectangle({0, 5, 240, 16}, team_color);
auto style_white = Style{
.font = ui::font::fixed_8x16,
.background = team_color,
.foreground = Color::white()};
painter.draw_string({85, 5}, style_white, is_red_team ? "RED TEAM" : "BLUE TEAM");
auto style_status = *ui::Theme::getInstance()->fg_light;
auto style_status = Style{
.font = ui::font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white()};
painter.fill_rectangle({0, 21, 240, 16}, Color::black());
painter.draw_string({10, 21}, style_status, current_status);
@@ -284,6 +464,32 @@ void BattleshipView::paint(Painter& painter) {
}
}
void BattleshipView::draw_menu_screen(Painter& painter) {
painter.draw_hline({12, 38}, 216, Color::dark_cyan());
painter.fill_rectangle({13, 41, 214, 116}, Color::dark_grey());
painter.draw_hline({12, 40}, 216, Color::cyan());
painter.draw_hline({12, 157}, 216, Color::cyan());
painter.fill_rectangle({13, 165, 214, 43}, Color::dark_grey());
painter.draw_hline({12, 164}, 216, Color::cyan());
painter.draw_hline({12, 208}, 216, Color::cyan());
painter.fill_rectangle({13, 218, 214, 73}, Color::dark_grey());
painter.draw_hline({12, 217}, 216, Color::cyan());
painter.draw_hline({12, 291}, 216, Color::cyan());
// Radio status indicator
Point indicator_pos{220, 53};
if (is_transmitting) {
painter.fill_rectangle({indicator_pos, {6, 6}}, Color::red());
painter.draw_rectangle({indicator_pos.x() - 1, indicator_pos.y() - 1, 8, 8}, Color::light_grey());
} else {
painter.fill_rectangle({indicator_pos, {6, 6}}, Color::green());
painter.draw_rectangle({indicator_pos.x() - 1, indicator_pos.y() - 1, 8, 8}, Color::light_grey());
}
}
void BattleshipView::draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid) {
painter.fill_rectangle({grid_x, grid_y, GRID_SIZE * CELL_SIZE, GRID_SIZE * CELL_SIZE},
Color::dark_blue());
@@ -497,16 +703,13 @@ void BattleshipView::send_message(const GameMessage& msg) {
configure_radio_tx();
// Use POCSAG encoding
uint32_t target_address = is_red_team ? BLUE_TEAM_ADDRESS : RED_TEAM_ADDRESS;
std::vector<uint32_t> codewords;
BCHCode BCH_code{{1, 0, 1, 0, 0, 1}, 5, 31, 21, 2};
// Use the pocsag namespace to access ALPHANUMERIC
pocsag::pocsag_encode(pocsag::MessageType::ALPHANUMERIC, BCH_code, 0, message, target_address, codewords);
// Copy codewords to shared memory
uint8_t* data_ptr = shared_memory.bb_data.data;
size_t bi = 0;
@@ -518,12 +721,11 @@ void BattleshipView::send_message(const GameMessage& msg) {
data_ptr[bi++] = codeword & 0xFF;
}
// Set baseband FSK data
baseband::set_fsk_data(
codewords.size() * 32, // Total bits
2280000 / 1200, // Bit duration (1200 baud)
4500, // Deviation
64); // Packet repeat
codewords.size() * 32,
2280000 / 1200,
4500,
64);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
@@ -537,13 +739,11 @@ void BattleshipView::on_pocsag_packet(const POCSAGPacketMessage* message) {
return;
}
// Decode POCSAG message
pocsag_state.codeword_index = 0;
pocsag_state.errors = 0;
while (pocsag::pocsag_decode_batch(message->packet, pocsag_state)) {
if (pocsag_state.out_type == pocsag::MESSAGE) {
// Check if message is for our team
uint32_t expected_address = is_red_team ? RED_TEAM_ADDRESS : BLUE_TEAM_ADDRESS;
if (pocsag_state.address == expected_address) {
process_message(pocsag_state.output);
@@ -783,6 +983,18 @@ bool BattleshipView::on_encoder(const EncoderEvent delta) {
}
bool BattleshipView::on_key(const KeyEvent key) {
if (game_state == GameState::MENU) {
if (key == KeyEvent::Up || key == KeyEvent::Down ||
key == KeyEvent::Left || key == KeyEvent::Right) {
return false;
}
if (key == KeyEvent::Select || key == KeyEvent::Back) {
return false;
}
return false;
}
// Game state key handling
if (key == KeyEvent::Select) {
if (game_state == GameState::PLACING_SHIPS) {
place_ship();
+48 -45
View File
@@ -18,25 +18,17 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "audio.hpp"
#include "portapack.hpp"
#include "message.hpp"
#include "pocsag.hpp"
#include "portapack_shared_memory.hpp"
#include <string>
#include <array>
#include <cstdint>
namespace ui::external_app::battleship {
using namespace portapack;
constexpr uint8_t GRID_SIZE = 10;
constexpr uint8_t CELL_SIZE = 24;
constexpr uint8_t GRID_OFFSET_X = 0;
constexpr uint8_t GRID_OFFSET_Y = 32;
constexpr uint16_t BUTTON_Y = 280;
constexpr uint32_t DEFAULT_FREQUENCY = 433920000;
enum class ShipType : uint8_t {
CARRIER = 5,
@@ -112,30 +104,36 @@ class BattleshipView : public View {
NavigationView& nav_;
RxRadioState rx_radio_state_{
DEFAULT_FREQUENCY /* frequency */,
433920000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
TxRadioState tx_radio_state_{
DEFAULT_FREQUENCY /* frequency */,
433920000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
// Settings
bool sound_enabled{true};
bool rf_amp_enabled{false};
uint8_t lna_gain{24};
uint8_t vga_gain{24};
uint8_t tx_gain{35};
app_settings::SettingsManager settings_{
"battleship",
app_settings::Mode::RX_TX,
{{"rx_freq"sv, &rx_frequency},
{"tx_freq"sv, &tx_frequency},
{"wins"sv, &wins},
{"losses"sv, &losses}}};
{"rf_amp"sv, &rf_amp_enabled}}};
GameState game_state{GameState::MENU};
bool is_red_team{false};
bool opponent_ready{false};
uint32_t wins{0};
uint32_t losses{0};
uint8_t wins{0};
uint8_t losses{0};
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> my_grid{};
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> enemy_grid{};
@@ -155,8 +153,8 @@ class BattleshipView : public View {
uint8_t target_y{0};
bool touch_enabled{true};
uint32_t tx_frequency{DEFAULT_FREQUENCY};
uint32_t rx_frequency{DEFAULT_FREQUENCY};
uint32_t tx_frequency{433920000};
uint32_t rx_frequency{433920000};
bool is_transmitting{false};
// POCSAG decoding state
@@ -164,48 +162,53 @@ class BattleshipView : public View {
pocsag::POCSAGState pocsag_state{&ecc};
uint32_t last_address{0};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
// UI Elements - Menu/Settings Screen
Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
FrequencyField field_frequency{
{10, 50}};
Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
Text text_status{
{10, 16, 220, 16},
"Choose your team!"};
// Radio controls
Text label_rf_amp{{17, 90, 35, 16}, "AMP:"};
Checkbox checkbox_rf_amp{{55, 90}, 3, "", false};
Text text_score{
{170, 16, 60, 16},
"W:0 L:0"};
Text label_lna{{17, 118, 30, 16}, "LNA:"};
NumberField field_lna{{50, 118}, 2, {0, 40}, 8, ' '};
Button button_red_team{
{20, 100, 90, 40},
"RED TEAM"};
Text label_vga{{90, 118, 30, 16}, "VGA:"};
NumberField field_vga{{125, 118}, 2, {0, 62}, 8, ' '};
Button button_blue_team{
{130, 100, 90, 40},
"BLUE TEAM"};
Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
Button button_rotate{
{10, BUTTON_Y, 60, 32},
"Rotate"};
Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
Text label_audio{{17, 169, 80, 16}, "AUDIO"};
Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
Text label_volume{{110, 187, 50, 16}, "Volume:"};
AudioVolumeField field_volume{{165, 187}};
Button button_place{
{80, BUTTON_Y, 60, 32},
"Place"};
Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
Button button_fire{
{80, BUTTON_Y, 60, 32},
"Fire!"};
Button button_menu{
{150, BUTTON_Y, 60, 32},
"Menu"};
// In-game UI elements
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
Text text_status{{10, 16, 220, 16}, ""};
Text text_score{{170, 16, 60, 16}, ""};
Button button_rotate{{10, 265, 65, 32}, "Rotate"};
Button button_place{{82, 265, 65, 32}, "Place"};
Button button_fire{{82, 265, 65, 32}, "Fire!"};
Button button_menu{{155, 265, 65, 32}, "Menu"};
// Methods
void init_game();
void reset_game();
void start_team(bool red);
void setup_ships();
void draw_menu_screen(Painter& painter);
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
void draw_ship_preview(Painter& painter);
+1
View File
@@ -110,6 +110,7 @@ GeoPos::GeoPos(
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
if (speed_unit_ == MPH) text_speed_unit.set("mph");
if (speed_unit_ == KNOTS) text_speed_unit.set("knots");
if (speed_unit_ == HIDDEN) {
text_speed_unit.hidden(true);
label_spd_position.hidden(true);
+2 -1
View File
@@ -81,6 +81,7 @@ class GeoPos : public View {
NONE = 0,
MPH,
KMPH,
KNOTS,
HIDDEN = 255
};
@@ -134,7 +135,7 @@ class GeoPos : public View {
{12 * 8, 0 * 16, 2 * 8, 16},
""};
Text text_speed_unit{
{25 * 8, 0 * 16, 4 * 8, 16},
{25 * 8, 0 * 16, 5 * 8, 16},
""};
NumberField field_lat_degrees{
+1 -1
Submodule hackrf updated: 65bf67f9b5...651d0a7f23