mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-12-01 23:23:48 +00:00
Compare commits
9 Commits
nightly-ta
...
nightly-ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d95447fb | ||
|
|
a474bf8fe3 | ||
|
|
1bf0d2b3e6 | ||
|
|
ea02d98a59 | ||
|
|
9624d7b429 | ||
|
|
4f440055cb | ||
|
|
7354ba8fd5 | ||
|
|
b7b4a10485 | ||
|
|
cca0e18f5a |
@@ -428,6 +428,8 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
nav_.push<BleRecentEntryDetailView>(entry);
|
||||
};
|
||||
|
||||
filterBuffer = filter;
|
||||
|
||||
button_filter.on_select = [this](Button&) {
|
||||
text_prompt(
|
||||
nav_,
|
||||
@@ -469,34 +471,38 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||
};
|
||||
|
||||
check_name.set_value(true);
|
||||
check_name.set_value(name_enable);
|
||||
|
||||
check_name.on_select = [this](Checkbox&, bool v) {
|
||||
name_enable = v;
|
||||
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
options_channel.on_change = [this](size_t, int32_t i) {
|
||||
options_channel.on_change = [this](size_t index, int32_t v) {
|
||||
channel_index = (uint8_t)index;
|
||||
|
||||
// If we selected Auto don't do anything and Auto will handle changing.
|
||||
if (i == 40) {
|
||||
if (v == 40) {
|
||||
auto_channel = true;
|
||||
return;
|
||||
} else {
|
||||
auto_channel = false;
|
||||
}
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
||||
channel_number = i;
|
||||
field_frequency.set_value(get_freq_by_channel_number(v));
|
||||
channel_number = v;
|
||||
|
||||
baseband::set_btlerx(channel_number);
|
||||
};
|
||||
|
||||
options_sort.on_change = [this](size_t, int32_t index) {
|
||||
handle_entries_sort(index);
|
||||
options_sort.on_change = [this](size_t index, int32_t v) {
|
||||
sort_index = (uint8_t)index;
|
||||
handle_entries_sort(v);
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(0, true);
|
||||
options_sort.set_selected_index(0, true);
|
||||
options_channel.set_selected_index(channel_index, true);
|
||||
options_sort.set_selected_index(sort_index, true);
|
||||
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
|
||||
@@ -205,8 +205,22 @@ class BLERxView : public View {
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
|
||||
uint8_t channel_index{0};
|
||||
uint8_t sort_index{0};
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool name_enable{true};
|
||||
app_settings::SettingsManager settings_{
|
||||
"BLE Rx", app_settings::Mode::RX};
|
||||
"rx_ble",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"channel_index"sv, &channel_index},
|
||||
{"sort_index"sv, &sort_index},
|
||||
{"filter"sv, &filter},
|
||||
{"log"sv, &logging},
|
||||
{"name"sv, &name_enable},
|
||||
}};
|
||||
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
@@ -217,7 +231,6 @@ class BLERxView : public View {
|
||||
int16_t timer_period{6}; // 100ms
|
||||
|
||||
std::string filterBuffer{};
|
||||
std::string filter{};
|
||||
std::string listFileBuffer{};
|
||||
std::string headerStr = "Timestamp, MAC Address, Name, Packet Type, Data, Hits, dB, Channel";
|
||||
uint16_t maxLineLength = 140;
|
||||
@@ -296,7 +309,6 @@ class BLERxView : public View {
|
||||
"Tx"};
|
||||
|
||||
std::string str_log{""};
|
||||
bool logging{false};
|
||||
|
||||
std::unique_ptr<BLELogger> logger{};
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ std::vector<std::string> splitIntoStrings(const char* input) {
|
||||
|
||||
while (start < length) {
|
||||
int remaining = length - start;
|
||||
int chunkSize = (remaining > 30) ? 30 : remaining;
|
||||
int chunkSize = (remaining > 29) ? 29 : remaining;
|
||||
result.push_back(std::string(input + start, chunkSize));
|
||||
start += chunkSize;
|
||||
}
|
||||
@@ -189,27 +189,47 @@ void BLETxView::start() {
|
||||
}
|
||||
|
||||
char advertisementData[63] = {0};
|
||||
|
||||
strcpy(advertisementData, packets[current_packet].advertisementData);
|
||||
|
||||
if (!randomString.empty()) {
|
||||
// Check if the substring exists within the larger string
|
||||
const char* result = strstr(advertisementData, randomString.c_str());
|
||||
// TODO: Make this a checkbox.
|
||||
if (!markedBytes.empty()) {
|
||||
for (size_t i = 0; i < strlen(advertisementData); i++) {
|
||||
bool found = false;
|
||||
|
||||
if (result != NULL) {
|
||||
// Calculate the start and end positions of the substring
|
||||
int startPos = result - advertisementData;
|
||||
int endPos = startPos + randomString.length();
|
||||
auto it = std::find(markedBytes.begin(), markedBytes.end(), i);
|
||||
|
||||
for (int i = startPos; i < endPos; i++) {
|
||||
int min = 0;
|
||||
int max = 15;
|
||||
if (it != markedBytes.end()) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
int hexDigit = min + std::rand() % (max - min + 1);
|
||||
if (found) {
|
||||
uint8_t hexDigit;
|
||||
switch (marked_data_sequence.selected_index_value()) {
|
||||
case 0:
|
||||
hexDigit = marked_counter++;
|
||||
break;
|
||||
case 1:
|
||||
hexDigit = marked_counter--;
|
||||
break;
|
||||
case 2: {
|
||||
uint8_t min = 0x00;
|
||||
uint8_t max = 0x0F;
|
||||
|
||||
// Map the random number to a hexadecimal digit
|
||||
char randomHexChar = (hexDigit < 10) ? ('0' + hexDigit) : ('A' + hexDigit - 10);
|
||||
advertisementData[i] = randomHexChar;
|
||||
hexDigit = min + std::rand() % (max - min + 1);
|
||||
} break;
|
||||
default:
|
||||
hexDigit = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
advertisementData[i] = uint_to_char(hexDigit, 16);
|
||||
|
||||
// Bounding to Hex.
|
||||
if (marked_counter == 16) {
|
||||
marked_counter = 0;
|
||||
} else if (marked_counter == 255) {
|
||||
marked_counter = 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +330,8 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&options_speed,
|
||||
&options_channel,
|
||||
&options_adv_type,
|
||||
&label_marked_data,
|
||||
&marked_data_sequence,
|
||||
&label_packet_index,
|
||||
&text_packet_index,
|
||||
&label_packets_sent,
|
||||
@@ -317,9 +339,10 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch,
|
||||
&dataEditView});
|
||||
&button_switch});
|
||||
|
||||
field_frequency.set_step(0);
|
||||
|
||||
@@ -383,6 +406,33 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
button_switch.on_select = [&nav](Button&) {
|
||||
nav.replace<BLERxView>();
|
||||
};
|
||||
|
||||
dataEditView.on_select = [this] {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
auto it = std::find(markedBytes.begin(), markedBytes.end(), dataBytePos);
|
||||
|
||||
if (it != markedBytes.end()) {
|
||||
markedBytes.erase(it);
|
||||
} else {
|
||||
markedBytes.push_back(dataBytePos);
|
||||
}
|
||||
|
||||
dataEditView.cursor_mark_selected();
|
||||
}
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
dataEditView.cursor_clear_marked();
|
||||
};
|
||||
}
|
||||
|
||||
BLETxView::BLETxView(
|
||||
@@ -460,10 +510,6 @@ void BLETxView::on_data(uint32_t value, bool is_data) {
|
||||
}
|
||||
}
|
||||
|
||||
void BLETxView::on_random_data_change(std::string value) {
|
||||
randomString = value;
|
||||
}
|
||||
|
||||
void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex) {
|
||||
std::string formattedMacAddress = to_string_formatted_mac_address(packet.macAddress);
|
||||
|
||||
@@ -497,6 +543,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
@@ -132,7 +132,7 @@ class BLETxView : public View {
|
||||
4'000'000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"ble_tx_app", app_settings::Mode::TX};
|
||||
"tx_ble", app_settings::Mode::TX};
|
||||
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
@@ -158,12 +158,17 @@ class BLETxView : public View {
|
||||
bool random_mac = false;
|
||||
bool file_override = false;
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 16, 240, 240}};
|
||||
typedef struct {
|
||||
uint16_t line;
|
||||
uint16_t col;
|
||||
} CursorPos;
|
||||
|
||||
std::unique_ptr<FileWrapper> dataFileWrapper{};
|
||||
File dataFile{};
|
||||
std::filesystem::path dataTempFilePath{u"BLETX/dataFileTemp.TXT"};
|
||||
std::vector<uint16_t> markedBytes{};
|
||||
CursorPos cursor_pos{};
|
||||
uint8_t marked_counter = 0;
|
||||
|
||||
static constexpr uint8_t mac_address_size_str{12};
|
||||
static constexpr uint8_t max_packet_size_str{62};
|
||||
@@ -171,15 +176,12 @@ class BLETxView : public View {
|
||||
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
|
||||
static constexpr uint32_t max_num_packets{32};
|
||||
|
||||
std::string randomBuffer{};
|
||||
std::string randomString{};
|
||||
|
||||
BLETxPacket packets[max_num_packets];
|
||||
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
static constexpr auto header_height = 9 * 16;
|
||||
static constexpr auto switch_button_height = 3 * 16;
|
||||
static constexpr auto header_height = 10 * 16;
|
||||
static constexpr auto switch_button_height = 6 * 16;
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
@@ -195,7 +197,7 @@ class BLETxView : public View {
|
||||
Checkbox check_rand_mac{
|
||||
{21 * 8, 1 * 16},
|
||||
6,
|
||||
"Random",
|
||||
"?? Mac",
|
||||
true};
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
@@ -250,32 +252,49 @@ class BLETxView : public View {
|
||||
{"SCAN_RSP", PKT_TYPE_SCAN_RSP},
|
||||
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
|
||||
|
||||
Labels label_marked_data{
|
||||
{{0 * 8, 4 * 16}, "Marked Data:", Color::light_grey()}};
|
||||
|
||||
OptionsField marked_data_sequence{
|
||||
{12 * 8, 8 * 8},
|
||||
8,
|
||||
{{"Ascend", 0},
|
||||
{"Descend", 1},
|
||||
{"Random", 2}}};
|
||||
|
||||
Labels label_packet_index{
|
||||
{{0 * 8, 10 * 8}, "Packet Index:", Color::light_grey()}};
|
||||
{{0 * 8, 12 * 8}, "Packet Index:", Color::light_grey()}};
|
||||
|
||||
Text text_packet_index{
|
||||
{13 * 8, 5 * 16, 12 * 8, 16},
|
||||
{13 * 8, 6 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_packets_sent{
|
||||
{{0 * 8, 12 * 8}, "Packets Left:", Color::light_grey()}};
|
||||
{{0 * 8, 14 * 8}, "Repeat Count:", Color::light_grey()}};
|
||||
|
||||
Text text_packets_sent{
|
||||
{13 * 8, 6 * 16, 14 * 8, 16},
|
||||
{13 * 8, 7 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_mac_address{
|
||||
{{0 * 8, 14 * 8}, "Mac Address:", Color::light_grey()}};
|
||||
{{0 * 8, 16 * 8}, "Mac Address:", Color::light_grey()}};
|
||||
|
||||
Text text_mac_address{
|
||||
{12 * 8, 7 * 16, 20 * 8, 16},
|
||||
{12 * 8, 8 * 16, 20 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 8 * 16}, "Packet Data:", Color::light_grey()}};
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Color::light_grey()}};
|
||||
|
||||
Console console{
|
||||
{0, 9 * 16, 240, 240}};
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
Button button_clear_marked{
|
||||
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
|
||||
"Clear Marked"};
|
||||
|
||||
Button button_save_packet{
|
||||
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
|
||||
|
||||
@@ -37,7 +37,7 @@ void AboutView::update() {
|
||||
break;
|
||||
case 2:
|
||||
console.writeln("NotherNgineer,zxkmm,u-foka");
|
||||
console.writeln("Netro");
|
||||
console.writeln("Netro,HTotoo");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
|
||||
@@ -131,10 +131,10 @@ bool MorseView::start_tx() {
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
|
||||
|
||||
if (mode_cw) {
|
||||
ookthread = chThdCreateStatic(ookthread_wa, sizeof(ookthread_wa), NORMALPRIO + 10, ookthread_fn, this);
|
||||
} else {
|
||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -72,6 +72,11 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_state_.redraw_text = false;
|
||||
}
|
||||
|
||||
if (paint_state_.redraw_marked) {
|
||||
paint_marked(painter);
|
||||
paint_state_.redraw_marked = false;
|
||||
}
|
||||
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
@@ -118,8 +123,9 @@ bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text) {
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -140,6 +146,32 @@ void TextViewer::cursor_end() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_set(uint16_t line, uint16_t col) {
|
||||
cursor_.line = line;
|
||||
cursor_.col = col;
|
||||
}
|
||||
|
||||
void TextViewer::cursor_mark_selected() {
|
||||
LineColPair newMarker = std::make_pair(cursor_.line, cursor_.col);
|
||||
auto it = std::find(lineColPair.begin(), lineColPair.end(), newMarker);
|
||||
|
||||
if (it != lineColPair.end()) {
|
||||
lineColPair.erase(it);
|
||||
} else {
|
||||
lineColPair.push_back(newMarker);
|
||||
}
|
||||
|
||||
// Mark pending change.
|
||||
cursor_.mark_change = false;
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_clear_marked() {
|
||||
lineColPair.clear();
|
||||
redraw(true, true);
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
@@ -247,12 +279,52 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
};
|
||||
|
||||
if (paint_state_.line != UINT32_MAX) // only XOR old cursor if it still appears on the screen
|
||||
xor_cursor(paint_state_.line, paint_state_.col);
|
||||
{
|
||||
// Only reset previous cursor if we aren't marking.
|
||||
if (paint_state_.mark_change) {
|
||||
xor_cursor(paint_state_.line, paint_state_.col);
|
||||
}
|
||||
}
|
||||
|
||||
xor_cursor(cursor_.line, cursor_.col);
|
||||
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
paint_state_.mark_change = cursor_.mark_change;
|
||||
|
||||
// Reset marking and wait for new change.
|
||||
cursor_.mark_change = true;
|
||||
}
|
||||
|
||||
void TextViewer::paint_marked(Painter& painter) {
|
||||
auto xor_cursor = [this, &painter](int32_t line, uint16_t col) {
|
||||
int cursor_width = char_width + 1;
|
||||
int x = (col - paint_state_.first_col) * char_width - 1;
|
||||
if (x < 0) { // cursor is one pixel narrower when in left column
|
||||
cursor_width--;
|
||||
x = 0;
|
||||
}
|
||||
int y = screen_rect().top() + (line - paint_state_.first_line) * char_height;
|
||||
|
||||
// Converting one row at a time to reduce buffer size
|
||||
auto pbuf8 = cursor_.pixel_buffer8;
|
||||
auto pbuf = cursor_.pixel_buffer;
|
||||
for (auto col = 0; col < char_height; col++) {
|
||||
// Annoyingly, read_pixels uses a 24-bit pixel format vs draw_pixels which uses 16-bit
|
||||
portapack::display.read_pixels({x, y + col, cursor_width, 1}, pbuf8, cursor_width);
|
||||
for (auto i = 0; i < cursor_width; i++)
|
||||
pbuf[i] = Color(pbuf8[i].r, pbuf8[i].g, pbuf8[i].b).v ^ 0xFFFF;
|
||||
portapack::display.draw_pixels({x, y + col, cursor_width, 1}, pbuf, cursor_width);
|
||||
}
|
||||
};
|
||||
|
||||
auto it = lineColPair.begin();
|
||||
|
||||
while (it != lineColPair.end()) {
|
||||
LineColPair entry = (LineColPair)*it;
|
||||
xor_cursor(entry.first, entry.second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::reset_file(FileWrapper* file) {
|
||||
|
||||
@@ -59,7 +59,7 @@ class TextViewer : public Widget {
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
|
||||
void redraw(bool redraw_text = false);
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
void set_file(FileWrapper& file) { reset_file(&file); }
|
||||
void clear_file() { reset_file(); }
|
||||
@@ -71,6 +71,12 @@ class TextViewer : public Widget {
|
||||
|
||||
void cursor_home();
|
||||
void cursor_end();
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
|
||||
// Gets the length of the current line.
|
||||
uint16_t line_length();
|
||||
@@ -97,6 +103,7 @@ class TextViewer : public Widget {
|
||||
|
||||
void paint_text(Painter& painter, uint32_t line, uint16_t col);
|
||||
void paint_cursor(Painter& painter);
|
||||
void paint_marked(Painter& painter);
|
||||
|
||||
void reset_file(FileWrapper* file = nullptr);
|
||||
|
||||
@@ -111,6 +118,8 @@ class TextViewer : public Widget {
|
||||
uint32_t first_line{};
|
||||
uint16_t first_col{};
|
||||
bool redraw_text{true};
|
||||
bool redraw_marked{false};
|
||||
bool mark_change{true};
|
||||
} paint_state_{};
|
||||
|
||||
struct {
|
||||
@@ -121,6 +130,8 @@ class TextViewer : public Widget {
|
||||
// Pixel buffer used for cursor XOR'ing - Max cursor width = Max char width + 1
|
||||
ColorRGB888 pixel_buffer8[ui::char_width + 1]{};
|
||||
Color pixel_buffer[ui::char_width + 1]{};
|
||||
|
||||
bool mark_change{true};
|
||||
} cursor_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -40,12 +40,7 @@ void WeatherRecentEntryDetailView::update_data() {
|
||||
text_hum.set(to_string_dec_uint(entry_.humidity) + "%");
|
||||
text_ch.set(to_string_dec_uint(entry_.channel));
|
||||
text_batt.set(to_string_dec_uint(entry_.battery_low) + " " + ((entry_.battery_low == 0) ? "OK" : "LOW"));
|
||||
}
|
||||
|
||||
void WeatherRecentEntryDetailView::set_entry(const WeatherRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
update_data();
|
||||
set_dirty();
|
||||
text_age.set(to_string_dec_uint(entry_.age) + " sec");
|
||||
}
|
||||
|
||||
WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry)
|
||||
@@ -58,6 +53,7 @@ WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav,
|
||||
&text_hum,
|
||||
&text_ch,
|
||||
&text_batt,
|
||||
&text_age,
|
||||
&labels});
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
@@ -106,6 +102,16 @@ WeatherView::WeatherView(NavigationView& nav)
|
||||
};
|
||||
baseband::set_weather();
|
||||
receiver_model.enable();
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
void WeatherView::on_tick_second() {
|
||||
for (auto& entry : recent) {
|
||||
entry.inc_age(1);
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||
@@ -113,6 +119,7 @@ void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||
auto matching_recent = find(recent, key.key());
|
||||
if (matching_recent != std::end(recent)) {
|
||||
// Found within. Move to front of list, increment counter.
|
||||
(*matching_recent).reset_age();
|
||||
recent.push_front(*matching_recent);
|
||||
recent.erase(matching_recent);
|
||||
} else {
|
||||
@@ -123,6 +130,7 @@ void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||
}
|
||||
|
||||
WeatherView::~WeatherView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -165,6 +173,8 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
return "TX 8300";
|
||||
case FPW_WENDOX_W6726:
|
||||
return "Wendox W6726";
|
||||
case FPW_Acurite986:
|
||||
return "Acurite986";
|
||||
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
@@ -187,19 +197,21 @@ void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
|
||||
line.reserve(30);
|
||||
|
||||
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
|
||||
if (line.length() < 13) {
|
||||
line += WeatherView::pad_string_with_spaces(13 - line.length());
|
||||
if (line.length() < 10) {
|
||||
line += WeatherView::pad_string_with_spaces(10 - line.length());
|
||||
} else {
|
||||
line = truncate(line, 13);
|
||||
line = truncate(line, 10);
|
||||
}
|
||||
|
||||
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 2));
|
||||
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
|
||||
std::string humStr = to_string_dec_uint(entry.humidity) + "%";
|
||||
std::string chStr = to_string_dec_uint(entry.channel);
|
||||
std::string ageStr = to_string_dec_uint(entry.age);
|
||||
|
||||
line += WeatherView::pad_string_with_spaces(7 - temp.length()) + temp;
|
||||
line += WeatherView::pad_string_with_spaces(6 - temp.length()) + temp;
|
||||
line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr;
|
||||
line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr;
|
||||
line += WeatherView::pad_string_with_spaces(4 - ageStr.length()) + ageStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
@@ -48,6 +48,7 @@ struct WeatherRecentEntry {
|
||||
uint8_t humidity = 0xFF;
|
||||
uint8_t battery_low = 0xFF;
|
||||
uint8_t channel = 0xFF;
|
||||
uint16_t age = 0; // updated on each seconds, show how long the signal was last seen
|
||||
|
||||
WeatherRecentEntry() {}
|
||||
WeatherRecentEntry(
|
||||
@@ -71,6 +72,12 @@ struct WeatherRecentEntry {
|
||||
(static_cast<uint64_t>(battery_low) & 0xF) << 4 |
|
||||
(static_cast<uint64_t>(channel) & 0xF);
|
||||
}
|
||||
void inc_age(int delta) {
|
||||
if (UINT16_MAX - delta > age) age += delta;
|
||||
}
|
||||
void reset_age() {
|
||||
age = 0;
|
||||
}
|
||||
};
|
||||
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
|
||||
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
|
||||
@@ -87,13 +94,14 @@ class WeatherView : public View {
|
||||
static std::string pad_string_with_spaces(int snakes);
|
||||
|
||||
private:
|
||||
void on_tick_second();
|
||||
void on_data(const WeatherDataMessage* data);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
433'920'000 /* frequency */,
|
||||
1'750'000 /* bandwidth */,
|
||||
2'000'000 /* sampling rate */,
|
||||
2'500'000 /* bandwidth max283x*/,
|
||||
4'000'000 /* sampling rate */,
|
||||
ReceiverModel::Mode::AMAudio};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_weather",
|
||||
@@ -122,6 +130,8 @@ class WeatherView : public View {
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
Button button_clear_list{
|
||||
{0, 16, 7 * 8, 32},
|
||||
"Clear"};
|
||||
@@ -129,11 +139,11 @@ class WeatherView : public View {
|
||||
static constexpr auto header_height = 3 * 16;
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Type", 13},
|
||||
{"Temp", 6},
|
||||
{"Type", 10},
|
||||
{"Temp", 5},
|
||||
{"Hum", 4},
|
||||
{"Ch", 3},
|
||||
|
||||
{"Age", 3},
|
||||
}};
|
||||
WeatherRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
@@ -149,9 +159,6 @@ class WeatherRecentEntryDetailView : public View {
|
||||
public:
|
||||
WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry);
|
||||
|
||||
void set_entry(const WeatherRecentEntry& new_entry);
|
||||
const WeatherRecentEntry& entry() const { return entry_; };
|
||||
|
||||
void update_data();
|
||||
void focus() override;
|
||||
|
||||
@@ -164,6 +171,7 @@ class WeatherRecentEntryDetailView : public View {
|
||||
Text text_hum{{11 * 8, 4 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_ch{{11 * 8, 5 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_batt{{11 * 8, 6 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_age{{11 * 8, 7 * 16, 6 * 8, 16}, "?"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Weather station type:", Color::light_grey()},
|
||||
@@ -172,6 +180,7 @@ class WeatherRecentEntryDetailView : public View {
|
||||
{{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()},
|
||||
{{0 * 8, 5 * 16}, "Channel:", Color::light_grey()},
|
||||
{{0 * 8, 6 * 16}, "Battery:", Color::light_grey()},
|
||||
{{0 * 8, 7 * 16}, "Age:", Color::light_grey()},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
|
||||
82
firmware/application/external/blespam/main.cpp
vendored
Normal file
82
firmware/application/external/blespam/main.cpp
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_blespam.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::blespam {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BLESpamView>();
|
||||
}
|
||||
} // namespace ui::external_app::blespam
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_blespam.application_information"), used)) application_information_t _application_information_blespam = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::blespam::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "BLESpam",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0x04,
|
||||
0x20,
|
||||
0x02,
|
||||
0x40,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'B', 'T', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
1292
firmware/application/external/blespam/ui_blespam.cpp
vendored
Normal file
1292
firmware/application/external/blespam/ui_blespam.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
250
firmware/application/external/blespam/ui_blespam.hpp
vendored
Normal file
250
firmware/application/external/blespam/ui_blespam.hpp
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* 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/Flipper-XFW/Xtreme-Apps/tree/04c3a60093e2c2378e79498b4505aa8072980a42/ble_spam/protocols
|
||||
// Thanks for the work of the original creators!
|
||||
// Saying thanks in the main view!
|
||||
|
||||
#ifndef __UI_BLESPAM_H__
|
||||
#define __UI_BLESPAM_H__
|
||||
|
||||
#include "ui.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::blespam {
|
||||
|
||||
enum ATK_TYPE {
|
||||
ATK_ANDROID,
|
||||
ATK_IOS,
|
||||
ATK_IOS_CRASH,
|
||||
ATK_WINDOWS,
|
||||
ATK_SAMSUNG
|
||||
};
|
||||
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 BLESpamView : public View {
|
||||
public:
|
||||
BLESpamView(NavigationView& nav);
|
||||
~BLESpamView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override {
|
||||
return "BLESpam";
|
||||
};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
2'402'000'000 /* frequency */,
|
||||
4'000'000 /* bandwidth */,
|
||||
4'000'000 /* sampling rate */
|
||||
};
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 0 * 16},
|
||||
/*short_ui*/ true};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_blespam", app_settings::Mode::TX};
|
||||
|
||||
Button button_startstop{
|
||||
{0, 3 * 16, 96, 24},
|
||||
"Start"};
|
||||
Checkbox chk_randdev{{100, 16}, 10, "Rnd device", true};
|
||||
|
||||
Console console{
|
||||
{0, 70, 240, 220}};
|
||||
|
||||
OptionsField options_atkmode{
|
||||
{0 * 8, 2 * 8},
|
||||
10,
|
||||
{{"Android", 0},
|
||||
{"iOs", 1},
|
||||
{"iOs crash", 2},
|
||||
{"Windows", 3},
|
||||
{"Samsung", 4}}};
|
||||
|
||||
bool is_running{false};
|
||||
|
||||
uint8_t counter = 0; // for packet change
|
||||
uint8_t displayCounter = 0; // for packet display
|
||||
|
||||
ATK_TYPE attackType = ATK_ANDROID;
|
||||
|
||||
bool randomMac{true};
|
||||
bool randomDev{true};
|
||||
|
||||
uint8_t channel_number = 37;
|
||||
char mac[13] = "010203040507";
|
||||
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void reset();
|
||||
void createFastPairPacket();
|
||||
void createIosPacket(bool crash);
|
||||
void createSamsungPacket();
|
||||
void createWindowsPacket();
|
||||
void changePacket(bool forced);
|
||||
void on_timer();
|
||||
uint64_t get_freq_by_channel_number(uint8_t channel_number);
|
||||
void randomizeMac();
|
||||
void randomChn();
|
||||
|
||||
void furi_hal_random_fill_buf(uint8_t* buf, uint32_t len);
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
|
||||
// continuity
|
||||
|
||||
typedef enum {
|
||||
PayloadModeRandom,
|
||||
PayloadModeValue,
|
||||
PayloadModeBruteforce,
|
||||
} PayloadMode;
|
||||
|
||||
typedef enum {
|
||||
ContinuityTypeAirDrop = 0x05,
|
||||
ContinuityTypeProximityPair = 0x07,
|
||||
ContinuityTypeAirplayTarget = 0x09,
|
||||
ContinuityTypeHandoff = 0x0C,
|
||||
ContinuityTypeTetheringSource = 0x0E,
|
||||
ContinuityTypeNearbyAction = 0x0F,
|
||||
ContinuityTypeNearbyInfo = 0x10,
|
||||
|
||||
ContinuityTypeCustomCrash,
|
||||
ContinuityTypeCOUNT
|
||||
} ContinuityType;
|
||||
|
||||
typedef enum {
|
||||
ContinuityPpBruteforceModel,
|
||||
ContinuityPpBruteforceColor,
|
||||
} ContinuityPpBruteforce;
|
||||
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
// const char* name;
|
||||
} ContinuityColor;
|
||||
|
||||
static const uint8_t pp_prefixes_count;
|
||||
|
||||
static const uint8_t na_actions_count;
|
||||
static const uint8_t pp_models_count;
|
||||
static const ContinuityColor colors_beats_studio_buds_[];
|
||||
static const ContinuityColor colors_beats_fit_pro[];
|
||||
static const ContinuityColor colors_beats_studio_pro[];
|
||||
static const ContinuityColor colors_beats_studio_3[];
|
||||
static const ContinuityColor colors_beats_x[];
|
||||
static const ContinuityColor colors_beats_studio_buds[];
|
||||
static const ContinuityColor colors_beats_solo_pro[];
|
||||
static const ContinuityColor colors_powerbeats_pro[];
|
||||
static const ContinuityColor colors_powerbeats_3[];
|
||||
static const ContinuityColor colors_beats_solo_3[];
|
||||
static const ContinuityColor colors_beats_flex[];
|
||||
static const ContinuityColor colors_airpods_max[];
|
||||
static const ContinuityColor colors_white[];
|
||||
typedef struct {
|
||||
uint16_t value;
|
||||
// const char* name;
|
||||
const ContinuityColor* colors;
|
||||
const uint8_t colors_count;
|
||||
} contiModels;
|
||||
static const contiModels pp_models[];
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
} contiU8;
|
||||
static const contiU8 pp_prefixes[];
|
||||
static const contiU8 na_actions[];
|
||||
|
||||
// fastpair:
|
||||
|
||||
static const uint16_t fastpairModels_count;
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
const char* name; // could be moved too
|
||||
} fpUi32;
|
||||
static const fpUi32 fastpairModels[];
|
||||
|
||||
// easysetup:
|
||||
static const uint8_t watch_models_count;
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
} easyU8;
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
} easyU32;
|
||||
typedef enum {
|
||||
EasysetupTypeBuds = 0x01, // Skip 0 as it means unset
|
||||
EasysetupTypeWatch,
|
||||
EasysetupTypeCOUNT,
|
||||
} EasysetupType;
|
||||
static const easyU8 watch_models[];
|
||||
static const uint8_t buds_models_count;
|
||||
static const easyU32 buds_models[];
|
||||
};
|
||||
}; // namespace ui::external_app::blespam
|
||||
|
||||
#endif /*__UI_BLESPAM_H__*/
|
||||
6
firmware/application/external/external.cmake
vendored
6
firmware/application/external/external.cmake
vendored
@@ -15,6 +15,11 @@ set(EXTCPPSRC
|
||||
#font_viewer
|
||||
external/font_viewer/main.cpp
|
||||
external/font_viewer/ui_font_viewer.cpp
|
||||
|
||||
#blespam
|
||||
external/blespam/main.cpp
|
||||
external/blespam/ui_blespam.cpp
|
||||
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -22,4 +27,5 @@ set(EXTAPPLIST
|
||||
afsk_rx
|
||||
calculator
|
||||
font_viewer
|
||||
blespam
|
||||
)
|
||||
|
||||
8
firmware/application/external/external.ld
vendored
8
firmware/application/external/external.ld
vendored
@@ -21,6 +21,7 @@ MEMORY
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xEEEB0000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xEEEC0000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xEEED0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -48,4 +49,11 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_font_viewer.application_information));
|
||||
*(*ui*external_app*font_viewer*);
|
||||
} > ram_external_app_font_viewer
|
||||
|
||||
|
||||
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_blespam.application_information));
|
||||
*(*ui*external_app*blespam*);
|
||||
} > ram_external_app_blespam
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ std::string to_string_hex(uint64_t value, int32_t length) {
|
||||
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length) {
|
||||
std::string str_return;
|
||||
str_return.reserve(length);
|
||||
str_return.reserve(length * 2);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++)
|
||||
str_return += to_string_hex(array[i], 2);
|
||||
|
||||
138
firmware/baseband/fprotos/w-acurite986.hpp
Normal file
138
firmware/baseband/fprotos/w-acurite986.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
|
||||
#ifndef __FPROTO_Acurite_986_H__
|
||||
#define __FPROTO_Acurite_986_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Acurite_986DecoderStepReset = 0,
|
||||
Acurite_986DecoderStepSync1,
|
||||
Acurite_986DecoderStepSync2,
|
||||
Acurite_986DecoderStepSync3,
|
||||
Acurite_986DecoderStepSaveDuration,
|
||||
Acurite_986DecoderStepCheckDuration,
|
||||
} Acurite_986DecoderStep;
|
||||
|
||||
class FProtoWeatherAcurite986 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAcurite986() {
|
||||
sensorType = FPW_Acurite986;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Acurite_986DecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_long) < te_delta * 15)) {
|
||||
// Found 1st sync bit
|
||||
parser_step = Acurite_986DecoderStepSync1;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync1:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSync2;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync2:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSync3;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync3:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Acurite_986DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short) <
|
||||
te_delta * 10) {
|
||||
if (duration < te_short) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
} else {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
}
|
||||
} else {
|
||||
// Found syncPostfix
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
if ((decode_count_bit == min_count_bit_for_found) && ws_protocol_acurite_986_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_acurite_986_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 800;
|
||||
uint32_t te_long = 1750;
|
||||
uint32_t te_delta = 50;
|
||||
uint32_t min_count_bit_for_found = 40;
|
||||
|
||||
void ws_protocol_acurite_986_remote_controller() {
|
||||
int temp;
|
||||
|
||||
id = subghz_protocol_blocks_reverse_key(data >> 24, 8);
|
||||
id = (id << 8) | subghz_protocol_blocks_reverse_key(data >> 16, 8);
|
||||
battery_low = (data >> 14) & 1;
|
||||
channel = ((data >> 15) & 1) + 1;
|
||||
|
||||
temp = subghz_protocol_blocks_reverse_key(data >> 32, 8);
|
||||
if (temp & 0x80) {
|
||||
temp = -(temp & 0x7F);
|
||||
}
|
||||
temp = locale_fahrenheit_to_celsius((float)temp);
|
||||
btn = WS_NO_BTN;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
}
|
||||
|
||||
bool ws_protocol_acurite_986_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
(uint8_t)(decode_data >> 32),
|
||||
(uint8_t)(decode_data >> 24),
|
||||
(uint8_t)(decode_data >> 16),
|
||||
(uint8_t)(decode_data >> 8)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_crc8(msg, 4, 0x07, 0x00);
|
||||
return (crc == (decode_data & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -183,6 +183,26 @@ class FProtoWeatherBase {
|
||||
}
|
||||
return reverse_key;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_crc8(
|
||||
uint8_t const message[],
|
||||
size_t size,
|
||||
uint8_t polynomial,
|
||||
uint8_t init) {
|
||||
uint8_t remainder = init;
|
||||
|
||||
for (size_t byte = 0; byte < size; ++byte) {
|
||||
remainder ^= message[byte];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
if (remainder & 0x80) {
|
||||
remainder = (remainder << 1) ^ polynomial;
|
||||
} else {
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return remainder;
|
||||
}
|
||||
|
||||
// General weather data holder
|
||||
uint8_t sensorType = FPW_Invalid;
|
||||
uint32_t id = WS_NO_ID;
|
||||
|
||||
@@ -21,7 +21,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons
|
||||
#include "w-thermoprotx4.hpp"
|
||||
#include "w-tx8300.hpp"
|
||||
#include "w-wendox-w6726.hpp"
|
||||
|
||||
#include "w-acurite986.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "portapack_shared_memory.hpp"
|
||||
@@ -51,6 +51,7 @@ class WeatherProtos {
|
||||
protos.push_back(std::make_unique<FProtoWeatherThermoProTx4>()); // 16
|
||||
protos.push_back(std::make_unique<FProtoWeatherTX8300>()); // 17
|
||||
protos.push_back(std::make_unique<FProtoWeatherWendoxW6726>()); // 18
|
||||
protos.push_back(std::make_unique<FProtoWeatherAcurite986>()); // 19
|
||||
|
||||
// set callback for them
|
||||
for (const auto& obj : protos) {
|
||||
|
||||
@@ -27,8 +27,8 @@ enum FPROTO_WEATHER_SENSOR {
|
||||
FPW_OREGONv1 = 15,
|
||||
FPW_THERMOPROTX4 = 16,
|
||||
FPW_TX_8300 = 17,
|
||||
FPW_WENDOX_W6726 = 18
|
||||
|
||||
FPW_WENDOX_W6726 = 18,
|
||||
FPW_Acurite986 = 19
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -27,10 +27,22 @@
|
||||
void WeatherProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
int8_t re = buffer.p[i].real();
|
||||
int8_t im = buffer.p[i].imag();
|
||||
// SR = 4Mhz , and we are decimating by /8 in total , decim1_out clock 4Mhz /8= 500khz samples/sec.
|
||||
// buffer has 2048 complex i8 I,Q signed samples
|
||||
// decim0 out: 2048/4 = 512 complex i16 I,Q signed samples
|
||||
// decim1 out: 512/2 = 256 complex i16 I,Q signed samples
|
||||
// Regarding Filters, we are re-using existing FIR filters, @4Mhz, FIR decim1 ilter, BW =+-220Khz (at -3dB's). BW = 440kHZ.
|
||||
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer); // Input:2048 complex/4 (decim factor) = 512_output complex (1024 I/Q samples)
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); // Input:512 complex/2 (decim factor) = 256_output complex ( 512 I/Q samples)
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
int16_t re = decim_1_out.p[i].real();
|
||||
int16_t im = decim_1_out.p[i].imag();
|
||||
uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im);
|
||||
|
||||
mag = (mag >> 12); // Decim samples are calculated with saturated gain . (we could also reduce that sat. param at configure time)
|
||||
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
if (meashl == currentHiLow && currentDuration < 10'000'000) // allow pass 'end' signal
|
||||
@@ -42,7 +54,8 @@ void WeatherProcessor::execute(const buffer_c8_t& buffer) {
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
}
|
||||
cnt += buffer.count;
|
||||
|
||||
cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor.
|
||||
if (cnt > 30'000) {
|
||||
threshold = (tm / cnt) / 2;
|
||||
cnt = 0;
|
||||
@@ -58,6 +71,12 @@ void WeatherProcessor::on_message(const Message* const message) {
|
||||
}
|
||||
|
||||
void WeatherProcessor::configure(const WeatherRxConfigureMessage& message) {
|
||||
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;
|
||||
|
||||
decim_0.configure(taps_200k_wfm_decim_0.taps);
|
||||
decim_1.configure(taps_200k_wfm_decim_1.taps);
|
||||
|
||||
(void)message;
|
||||
configured = true;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
#include "message.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#include "fprotos/weatherprotos.hpp"
|
||||
|
||||
@@ -39,8 +40,20 @@ class WeatherProcessor : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
static constexpr uint32_t usperTick = 500; // we nees ms to has to divide by 1000
|
||||
static constexpr size_t baseband_fs = 1'750'000;
|
||||
static constexpr size_t baseband_fs = 4'000'000; // it works, I think we need to write that master clock in the baseband_threat , even later we decimate it.
|
||||
static constexpr uint32_t usperTick = 500 * 8; // In current sw , we do not scale it due to clock. We scaled it due to less array buffer sampes due to /8 decimation.
|
||||
// TODO , Pending to investigate , why ticks are not proportional to the SR clock, 500 nseg (2Mhz) , 250 nseg (4Mhz) ??? ;previous comment : "we nees ms to has to divide by 1000"
|
||||
|
||||
/* Array Buffer aux. used in decim0 and decim1 IQ c16 signed data ; (decim0 defines the max length of the array) */
|
||||
std::array<complex16_t, 512> dst{}; // decim0 /4 , 2048/4 = 512 complex I,Q
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
|
||||
/* Decimates */
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
|
||||
uint32_t currentDuration = 0;
|
||||
uint32_t threshold = 0x0630; // will overwrite after the first iteration
|
||||
bool currentHiLow = false;
|
||||
|
||||
Reference in New Issue
Block a user