Compare commits

...

9 Commits

Author SHA1 Message Date
Brumi-2021
60d95447fb Improved sensitivity/selectivity Weather App (#1628) 2023-12-08 21:33:48 +01:00
Mark Thompson
a474bf8fe3 Add files via upload (#1629) 2023-12-08 19:20:59 +01:00
Totoo
1bf0d2b3e6 BLE spam ext app (#1624)
* BLESpam ext app first release

* Ios partly added

* Fix missing!

* Maybe fix Ios

* Add comment

* Fixes

* Added samsung and windows

* cleaning a bit

* Partial size fix

* Move continuity to blespam namespace.

* Optimize code layout, so not using fw space

* Ui changes
2023-12-06 08:35:23 -05:00
Mark Thompson
ea02d98a59 Preserve BLE RX app settings across runs, and changed BLE app settings file names (#1622)
* Add files via upload

* Add files via upload

* Add files via upload
2023-12-04 12:06:07 -05:00
Mark Thompson
9624d7b429 Fix compiler warning (#1620) 2023-12-03 08:19:04 +01:00
Totoo
4f440055cb Fix to_string_hex_array string reserve (#1619) 2023-12-02 16:42:09 -06:00
Netro
7354ba8fd5 Random data selection feature done. (#1617) 2023-12-02 08:05:29 +01:00
Netro
b7b4a10485 Refined Tx Random Data (Still WIP) (#1616)
* managing initial cursor
* work on adding marked cursors
2023-11-30 21:57:22 +01:00
Totoo
cca0e18f5a Weather improvements (#1615)
* Added Acurite986 protocol
* Added signal age
* Added myself to about screen
2023-11-30 12:36:59 +01:00
22 changed files with 2103 additions and 86 deletions

View File

@@ -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>();

View File

@@ -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{};

View File

@@ -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) {

View File

@@ -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},

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View 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_{};
};

View File

@@ -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);

View File

@@ -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{

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_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
};
}

File diff suppressed because it is too large Load Diff

View 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__*/

View File

@@ -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
)

View File

@@ -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
}

View File

@@ -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);

View 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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;