mirror of
https://github.com/RfidResearchGroup/mayhem-firmware.git
synced 2026-05-12 11:27:21 -07:00
Implementation of EPIRB receiver (#2754)
* Implementation of EPIRB receiver * Baseband processing of EPIRB signal * UI to ddecode and display EPIRB message with display on a map * External application * External proc element * Delete CLAUDE.md
This commit is contained in:
@@ -62,6 +62,7 @@ cmake-build-debug/
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
.claude
|
||||
|
||||
# VSCodium extensions
|
||||
.history
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 EPIRB Decoder Implementation
|
||||
*
|
||||
* 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_epirb_rx.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::epirb_rx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<EPIRBAppView>();
|
||||
}
|
||||
} // namespace ui::external_app::epirb_rx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_epirb_rx.application_information"), used)) application_information_t _application_information_epirb_rx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::epirb_rx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "EPIRB RX",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7C,
|
||||
0x3E,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xF7,
|
||||
0xEF,
|
||||
0xE3,
|
||||
0xC7,
|
||||
0xE3,
|
||||
0xC7,
|
||||
0xE3,
|
||||
0xC7,
|
||||
0xE3,
|
||||
0xC7,
|
||||
0xF7,
|
||||
0xEF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0x7C,
|
||||
0x3E,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::red().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_epirb_rx */ {'P', 'E', 'P', 'I'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (C) 2024 EPIRB Decoder Implementation
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_EPIRB_RX_H__
|
||||
#define __UI_EPIRB_RX_H__
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "message.hpp"
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "baseband_packet.hpp"
|
||||
|
||||
/* #include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <array> */
|
||||
|
||||
namespace ui::external_app::epirb_rx {
|
||||
|
||||
// EPIRB 406 MHz beacon types
|
||||
enum class BeaconType : uint8_t {
|
||||
OrbitingLocationBeacon = 0,
|
||||
PersonalLocatorBeacon = 1,
|
||||
EmergencyLocatorTransmitter = 2,
|
||||
SerialELT = 3,
|
||||
NationalELT = 4,
|
||||
Other = 15
|
||||
};
|
||||
|
||||
// EPIRB distress and emergency types
|
||||
enum class EmergencyType : uint8_t {
|
||||
Fire = 0,
|
||||
Flooding = 1,
|
||||
Collision = 2,
|
||||
Grounding = 3,
|
||||
Sinking = 4,
|
||||
Disabled = 5,
|
||||
Abandoning = 6,
|
||||
Piracy = 7,
|
||||
Man_Overboard = 8,
|
||||
Other = 15
|
||||
};
|
||||
|
||||
struct EPIRBLocation {
|
||||
float latitude; // degrees, -90 to +90
|
||||
float longitude; // degrees, -180 to +180
|
||||
bool valid;
|
||||
|
||||
EPIRBLocation()
|
||||
: latitude(0.0f), longitude(0.0f), valid(false) {}
|
||||
EPIRBLocation(float lat, float lon)
|
||||
: latitude(lat), longitude(lon), valid(true) {}
|
||||
};
|
||||
|
||||
struct EPIRBBeacon {
|
||||
uint32_t beacon_id;
|
||||
BeaconType beacon_type;
|
||||
EmergencyType emergency_type;
|
||||
EPIRBLocation location;
|
||||
uint32_t country_code;
|
||||
std::string vessel_name;
|
||||
rtc::RTC timestamp;
|
||||
uint32_t sequence_number;
|
||||
|
||||
EPIRBBeacon()
|
||||
: beacon_id(0), beacon_type(BeaconType::Other), emergency_type(EmergencyType::Other), location(), country_code(0), vessel_name(), timestamp(), sequence_number(0) {}
|
||||
};
|
||||
|
||||
class EPIRBDecoder {
|
||||
public:
|
||||
static EPIRBBeacon decode_packet(const baseband::Packet& packet);
|
||||
|
||||
private:
|
||||
static EPIRBLocation decode_location(const std::array<uint8_t, 16>& data);
|
||||
static BeaconType decode_beacon_type(uint8_t type_bits);
|
||||
static EmergencyType decode_emergency_type(uint8_t emergency_bits);
|
||||
static uint32_t decode_country_code(const std::array<uint8_t, 16>& data);
|
||||
static std::string decode_vessel_name(const std::array<uint8_t, 16>& data);
|
||||
};
|
||||
|
||||
class EPIRBLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void on_packet(const EPIRBBeacon& beacon);
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
// Forward declarations of formatting functions
|
||||
std::string format_beacon_type(BeaconType type);
|
||||
std::string format_emergency_type(EmergencyType type);
|
||||
|
||||
class EPIRBBeaconDetailView : public ui::View {
|
||||
public:
|
||||
std::function<void(void)> on_close{};
|
||||
|
||||
EPIRBBeaconDetailView(ui::NavigationView& nav);
|
||||
EPIRBBeaconDetailView(const EPIRBBeaconDetailView&) = delete;
|
||||
EPIRBBeaconDetailView& operator=(const EPIRBBeaconDetailView&) = delete;
|
||||
|
||||
void set_beacon(const EPIRBBeacon& beacon);
|
||||
const EPIRBBeacon& beacon() const { return beacon_; }
|
||||
|
||||
void focus() override;
|
||||
void paint(ui::Painter&) override;
|
||||
|
||||
ui::GeoMapView* get_geomap_view() { return geomap_view; }
|
||||
|
||||
private:
|
||||
EPIRBBeacon beacon_{};
|
||||
|
||||
ui::Button button_done{
|
||||
{125, 224, 96, 24},
|
||||
"Done"};
|
||||
ui::Button button_see_map{
|
||||
{19, 224, 96, 24},
|
||||
"See on map"};
|
||||
|
||||
ui::GeoMapView* geomap_view{nullptr};
|
||||
|
||||
ui::Rect draw_field(
|
||||
ui::Painter& painter,
|
||||
const ui::Rect& draw_rect,
|
||||
const ui::Style& style,
|
||||
const std::string& label,
|
||||
const std::string& value);
|
||||
};
|
||||
|
||||
class EPIRBAppView : public ui::View {
|
||||
public:
|
||||
EPIRBAppView(ui::NavigationView& nav);
|
||||
~EPIRBAppView();
|
||||
|
||||
void set_parent_rect(const ui::Rect new_parent_rect) override;
|
||||
void paint(ui::Painter&) override;
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "EPIRB RX"; }
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_epirb", app_settings::Mode::RX};
|
||||
|
||||
ui::NavigationView& nav_;
|
||||
|
||||
std::vector<EPIRBBeacon> recent_beacons{};
|
||||
std::unique_ptr<EPIRBLogger> logger{};
|
||||
|
||||
EPIRBBeaconDetailView beacon_detail_view{nav_};
|
||||
|
||||
static constexpr auto header_height = 3 * 16;
|
||||
|
||||
ui::Text label_frequency{
|
||||
{0 * 8, 0 * 16, 10 * 8, 1 * 16},
|
||||
"406.028 MHz"};
|
||||
|
||||
ui::RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
|
||||
ui::LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
|
||||
ui::VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
|
||||
ui::RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
ui::AudioVolumeField field_volume{
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
ui::Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
// Status display
|
||||
ui::Text label_status{
|
||||
{0 * 8, 1 * 16, 15 * 8, 1 * 16},
|
||||
"Listening..."};
|
||||
|
||||
ui::Text label_beacons_count{
|
||||
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
|
||||
"Beacons: 0"};
|
||||
|
||||
// Latest beacon info display
|
||||
ui::Text label_latest{
|
||||
{0 * 8, 2 * 16, 8 * 8, 1 * 16},
|
||||
"Latest:"};
|
||||
|
||||
ui::Text text_latest_info{
|
||||
{8 * 8, 2 * 16, 22 * 8, 1 * 16},
|
||||
""};
|
||||
|
||||
// Beacon list
|
||||
ui::Console console{
|
||||
{0, 3 * 16, 240, 168}};
|
||||
|
||||
ui::Button button_map{
|
||||
{0, 224, 60, 24},
|
||||
"Map"};
|
||||
|
||||
ui::Button button_clear{
|
||||
{64, 224, 60, 24},
|
||||
"Clear"};
|
||||
|
||||
ui::Button button_log{
|
||||
{128, 224, 60, 24},
|
||||
"Log"};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
uint32_t beacons_received = 0;
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::EPIRBPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const EPIRBPacketMessage*>(p);
|
||||
this->on_packet(message->packet);
|
||||
}};
|
||||
|
||||
void on_packet(const baseband::Packet& packet);
|
||||
void on_beacon_decoded(const EPIRBBeacon& beacon);
|
||||
void on_show_map();
|
||||
void on_clear_beacons();
|
||||
void on_toggle_log();
|
||||
void on_tick_second();
|
||||
|
||||
void update_display();
|
||||
std::string format_beacon_summary(const EPIRBBeacon& beacon);
|
||||
std::string format_location(const EPIRBLocation& location);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::epirb_rx
|
||||
|
||||
#endif // __UI_EPIRB_RX_H__
|
||||
+15
-9
@@ -110,12 +110,13 @@ set(EXTCPPSRC
|
||||
#wefax_rx
|
||||
external/wefax_rx/main.cpp
|
||||
external/wefax_rx/ui_wefax_rx.cpp
|
||||
|
||||
|
||||
|
||||
#noaaapt_rx
|
||||
external/noaaapt_rx/main.cpp
|
||||
external/noaaapt_rx/ui_noaaapt_rx.cpp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#shoppingcart_lock
|
||||
external/shoppingcart_lock/main.cpp
|
||||
@@ -215,15 +216,15 @@ set(EXTCPPSRC
|
||||
|
||||
#gfxEQ
|
||||
external/gfxeq/main.cpp
|
||||
external/gfxeq/ui_gfxeq.cpp
|
||||
external/gfxeq/ui_gfxeq.cpp
|
||||
|
||||
#detector_rx
|
||||
external/detector_rx/main.cpp
|
||||
external/detector_rx/ui_detector_rx.cpp
|
||||
external/detector_rx/ui_detector_rx.cpp
|
||||
|
||||
#space_invaders
|
||||
external/spaceinv/main.cpp
|
||||
external/spaceinv/ui_spaceinv.cpp
|
||||
external/spaceinv/ui_spaceinv.cpp
|
||||
|
||||
#blackjack
|
||||
external/blackjack/main.cpp
|
||||
@@ -231,11 +232,15 @@ set(EXTCPPSRC
|
||||
|
||||
#battleship
|
||||
external/battleship/main.cpp
|
||||
external/battleship/ui_battleship.cpp
|
||||
external/battleship/ui_battleship.cpp
|
||||
|
||||
#ert
|
||||
external/ert/main.cpp
|
||||
external/ert/ert_app.cpp
|
||||
external/ert/ert_app.cpp
|
||||
|
||||
#epirb_rx
|
||||
external/epirb_rx/main.cpp
|
||||
external/epirb_rx/ui_epirb_rx.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -264,7 +269,7 @@ set(EXTAPPLIST
|
||||
morse_tx
|
||||
sstvtx
|
||||
random_password
|
||||
#acars_rx
|
||||
acars_rx
|
||||
ookbrute
|
||||
ook_editor
|
||||
wefax_rx
|
||||
@@ -296,4 +301,5 @@ set(EXTAPPLIST
|
||||
blackjack
|
||||
battleship
|
||||
ert
|
||||
epirb_rx
|
||||
)
|
||||
|
||||
+13
-5
@@ -80,6 +80,7 @@ MEMORY
|
||||
ram_external_app_blackjack (rwx) : org = 0xADE70000, len = 32k
|
||||
ram_external_app_battleship (rwx) : org = 0xADE80000, len = 32k
|
||||
ram_external_app_ert (rwx) : org = 0xADE90000, len = 32k
|
||||
ram_external_app_epirb_rx (rwx) : org = 0xADEA0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -341,7 +342,7 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_stopwatch.application_information));
|
||||
*(*ui*external_app*stopwatch*);
|
||||
} > ram_external_app_stopwatch
|
||||
|
||||
|
||||
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_wefax_rx.application_information));
|
||||
@@ -364,19 +365,19 @@ SECTIONS
|
||||
{
|
||||
KEEP(*(.external_app.app_doom.application_information));
|
||||
*(*ui*external_app*doom*);
|
||||
} > ram_external_app_doom
|
||||
|
||||
} > ram_external_app_doom
|
||||
|
||||
.external_app_debug_pmem : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_debug_pmem.application_information));
|
||||
*(*ui*external_app*debug_pmem*);
|
||||
} > ram_external_app_debug_pmem
|
||||
} > ram_external_app_debug_pmem
|
||||
|
||||
.external_app_scanner : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_scanner.application_information));
|
||||
*(*ui*external_app*scanner*);
|
||||
} > ram_external_app_scanner
|
||||
} > ram_external_app_scanner
|
||||
|
||||
.external_app_level : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
@@ -425,5 +426,12 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_ert.application_information));
|
||||
*(*ui*external_app*ert*);
|
||||
} > ram_external_app_ert
|
||||
|
||||
.external_app_epirb_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_epirb_rx.application_information));
|
||||
*(*ui*external_app*epirb_rx*);
|
||||
} > ram_external_app_epirb_rx
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -762,7 +762,7 @@ void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, &app]() {
|
||||
[&nav, &app]() {
|
||||
i2cdev::I2CDevManager::set_autoscan_interval(0); //if i navigate away from any menu, turn off autoscan
|
||||
nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }},
|
||||
true);
|
||||
@@ -789,8 +789,8 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
error_tile_pos);
|
||||
} else {
|
||||
std::sort(externalItems.begin(), externalItems.end(), [](const auto &a, const auto &b)
|
||||
{
|
||||
return a.desired_position < b.desired_position;
|
||||
{
|
||||
return a.desired_position < b.desired_position;
|
||||
});
|
||||
|
||||
for (auto const& gridItem : externalItems) {
|
||||
@@ -799,7 +799,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
} else {
|
||||
grid.insert_item(gridItem, gridItem.desired_position, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
grid.update_items();
|
||||
|
||||
@@ -279,7 +279,7 @@ macro(DeclareTargets chunk_tag name)
|
||||
include_directories(. ${INCDIR} ${MODE_INCDIR})
|
||||
link_directories(${LLIBDIR})
|
||||
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
|
||||
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
|
||||
target_link_libraries(${PROJECT_NAME}.elf -Wl,--print-memory-usage)
|
||||
|
||||
@@ -578,6 +578,14 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PAFR afskrx)
|
||||
|
||||
### EPIRB
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_epirb.cpp
|
||||
)
|
||||
DeclareTargets(PEPI epirb_rx)
|
||||
|
||||
|
||||
### NRF RX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2024 EPIRB Receiver Implementation
|
||||
*
|
||||
* 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 "proc_epirb.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include <ch.h>
|
||||
|
||||
EPIRBProcessor::EPIRBProcessor() {
|
||||
// Configure the decimation filters for narrowband EPIRB signal
|
||||
// Target: Reduce 2.457600 MHz to ~38.4 kHz for 400 bps processing
|
||||
decim_0.configure(taps_11k0_decim_0.taps);
|
||||
decim_1.configure(taps_11k0_decim_1.taps);
|
||||
baseband_thread.start();
|
||||
}
|
||||
|
||||
void EPIRBProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
|
||||
// First decimation stage: 2.4576 MHz -> 307.2 kHz
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
|
||||
// Second decimation stage: 307.2 kHz -> 38.4 kHz
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
const auto decimator_out = decim_1_out;
|
||||
|
||||
/* 38.4kHz, 32 samples (approximately) */
|
||||
feed_channel_stats(decimator_out);
|
||||
|
||||
// Process each decimated sample through the matched filter
|
||||
for (size_t i = 0; i < decimator_out.count; i++) {
|
||||
// Apply matched filter for BPSK demodulation
|
||||
if (mf.execute_once(decimator_out.p[i])) {
|
||||
// Feed symbol to clock recovery when matched filter triggers
|
||||
clock_recovery(mf.get_output());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EPIRBProcessor::consume_symbol(const float raw_symbol) {
|
||||
// BPSK demodulation: positive = 1, negative = 0
|
||||
const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0;
|
||||
|
||||
// Decode bi-phase L encoding manually
|
||||
// In bi-phase L: 0 = no transition, 1 = transition
|
||||
// This is a simple edge detector
|
||||
const auto decoded_symbol = sliced_symbol ^ last_symbol;
|
||||
last_symbol = sliced_symbol;
|
||||
|
||||
// Build packet from decoded symbols
|
||||
packet_builder.execute(decoded_symbol);
|
||||
}
|
||||
|
||||
void EPIRBProcessor::payload_handler(const baseband::Packet& packet) {
|
||||
// EPIRB packet received - validate and process
|
||||
if (packet.size() >= 112) { // Minimum EPIRB data payload size (112 bits)
|
||||
packets_received++;
|
||||
last_packet_timestamp = Timestamp::now();
|
||||
|
||||
// Create and send EPIRB packet message to application layer
|
||||
const EPIRBPacketMessage message{packet};
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void EPIRBProcessor::on_message(const Message* const message) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<EPIRBProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2024 EPIRB Receiver Implementation
|
||||
*
|
||||
* 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 __PROC_EPIRB_H__
|
||||
#define __PROC_EPIRB_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
#include "channel_decimator.hpp"
|
||||
#include "matched_filter.hpp"
|
||||
#include "clock_recovery.hpp"
|
||||
#include "symbol_coding.hpp"
|
||||
#include "packet_builder.hpp"
|
||||
#include "baseband_packet.hpp"
|
||||
#include "message.hpp"
|
||||
#include "buffer.hpp"
|
||||
|
||||
// Forward declarations for types only used as pointers/references
|
||||
class Message;
|
||||
namespace baseband {
|
||||
class Packet;
|
||||
}
|
||||
|
||||
// EPIRB 406 MHz Emergency Position Indicating Radio Beacon
|
||||
// Signal characteristics:
|
||||
// - Frequency: 406.025 - 406.028 MHz (typically 406.028 MHz)
|
||||
// - Modulation: BPSK (Binary Phase Shift Keying)
|
||||
// - Data rate: 400 bps
|
||||
// - Encoding: Bi-phase L (Manchester)
|
||||
// - Transmission: Every 50 seconds ± 2.5 seconds
|
||||
// - Power: 5W ± 2dB
|
||||
// - Message length: 144 bits (including sync pattern)
|
||||
|
||||
// Matched filter for BPSK demodulation at 400 bps
|
||||
// Using raised cosine filter taps optimized for 400 bps BPSK
|
||||
static constexpr std::array<std::complex<float>, 64> bpsk_taps = {{// Raised cosine filter coefficients for BPSK 400 bps
|
||||
-5, -8, -12, -15, -17, -17, -15, -11,
|
||||
-5, 2, 11, 20, 29, 37, 43, 47,
|
||||
48, 46, 42, 35, 26, 16, 4, -8,
|
||||
-21, -33, -44, -53, -59, -62, -62, -58,
|
||||
-51, -41, -28, -13, 3, 19, 36, 51,
|
||||
64, 74, 80, 82, 80, 74, 64, 51,
|
||||
36, 19, 3, -13, -28, -41, -51, -58,
|
||||
-62, -62, -59, -53, -44, -33, -21, -8}};
|
||||
|
||||
class EPIRBProcessor : public BasebandProcessor {
|
||||
public:
|
||||
EPIRBProcessor();
|
||||
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
// EPIRB operates at 406 MHz with narrow bandwidth
|
||||
static constexpr size_t baseband_fs = 2457600;
|
||||
static constexpr uint32_t epirb_center_freq = 406028000; // 406.028 MHz
|
||||
static constexpr uint32_t symbol_rate = 400; // 400 bps
|
||||
static constexpr size_t decimation_factor = 64; // Decimate to ~38.4kHz
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
|
||||
// Decimation chain for 406 MHz EPIRB signal processing
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||
|
||||
dsp::matched_filter::MatchedFilter mf{bpsk_taps, 2};
|
||||
|
||||
// Clock recovery for 400 bps symbol rate
|
||||
// Sampling rate after decimation: ~38.4kHz
|
||||
// Symbols per sample: 38400 / 400 = 96 samples per symbol
|
||||
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery{
|
||||
38400, // sampling_rate
|
||||
400, // symbol_rate (400 bps)
|
||||
{0.0555f}, // error_filter coefficient
|
||||
[this](const float symbol) { this->consume_symbol(symbol); }};
|
||||
|
||||
// Simple bi-phase L decoder state
|
||||
uint_fast8_t last_symbol = 0;
|
||||
|
||||
// EPIRB packet structure:
|
||||
// - Sync pattern: 000101010101... (15 bits)
|
||||
// - Frame sync: 0111110 (7 bits)
|
||||
// - Data: 112 bits
|
||||
// - BCH error correction: 10 bits
|
||||
// Total: 144 bits
|
||||
PacketBuilder<BitPattern, BitPattern, BitPattern> packet_builder{
|
||||
{0b010101010101010, 15, 1}, // Preamble pattern
|
||||
{0b0111110, 7}, // Frame sync pattern
|
||||
{0b0111110, 7}, // End pattern (same as sync for simplicity)
|
||||
[this](const baseband::Packet& packet) {
|
||||
this->payload_handler(packet);
|
||||
}};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
|
||||
// Statistics
|
||||
uint32_t packets_received = 0;
|
||||
Timestamp last_packet_timestamp{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
};
|
||||
|
||||
#endif /*__PROC_EPIRB_H__*/
|
||||
@@ -135,6 +135,7 @@ class Message {
|
||||
NoaaAptRxStatusData = 78,
|
||||
NoaaAptRxImageData = 79,
|
||||
FSKPacket = 80,
|
||||
EPIRBPacket = 81,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -339,6 +340,17 @@ class AISPacketMessage : public Message {
|
||||
baseband::Packet packet;
|
||||
};
|
||||
|
||||
class EPIRBPacketMessage : public Message {
|
||||
public:
|
||||
constexpr EPIRBPacketMessage(
|
||||
const baseband::Packet& packet)
|
||||
: Message{ID::EPIRBPacket},
|
||||
packet{packet} {
|
||||
}
|
||||
|
||||
baseband::Packet packet;
|
||||
};
|
||||
|
||||
class TPMSPacketMessage : public Message {
|
||||
public:
|
||||
constexpr TPMSPacketMessage(
|
||||
|
||||
@@ -87,6 +87,7 @@ constexpr image_tag_t image_tag_am_audio{'P', 'A', 'M', 'A'};
|
||||
constexpr image_tag_t image_tag_am_tv{'P', 'A', 'M', 'T'};
|
||||
constexpr image_tag_t image_tag_capture{'P', 'C', 'A', 'P'};
|
||||
constexpr image_tag_t image_tag_ert{'P', 'E', 'R', 'T'};
|
||||
constexpr image_tag_t image_tag_epirb_rx{'P', 'E', 'P', 'I'};
|
||||
constexpr image_tag_t image_tag_nfm_audio{'P', 'N', 'F', 'M'};
|
||||
constexpr image_tag_t image_tag_pocsag{'P', 'P', 'O', 'C'};
|
||||
constexpr image_tag_t image_tag_pocsag2{'P', 'P', 'O', '2'};
|
||||
|
||||
@@ -98,6 +98,7 @@ for external_image_prefix in sys.argv[4:]:
|
||||
|
||||
# COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}_ext_pacman.bin --only-section=.external_app_pacman
|
||||
himg = "{}/external_app_{}.himg".format(binary_dir, external_image_prefix)
|
||||
print("Creating external application image for {}".format(external_image_prefix))
|
||||
subprocess.run([cmake_objcopy, "-v", "-O", "binary", "{}/application.elf".format(binary_dir), himg, "--only-section=.external_app_{}".format(external_image_prefix)])
|
||||
|
||||
external_application_image = read_image(himg)
|
||||
|
||||
Reference in New Issue
Block a user