mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-07-12 22:28:48 +00:00
Add vendor name in bluetooth rx app (#2696)
* add macaddress db, add vendor name in bluetooth rx app * show "missing macaddress.db" instead of unknown if db not found * bluetooth rx list with colors based on mac vendor * bug fix
This commit is contained in:
parent
18bc2cf11c
commit
fa4b74fd6f
@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@ -83,6 +84,50 @@ void reverse_byte_array(uint8_t* arr, int length) {
|
||||
}
|
||||
}
|
||||
|
||||
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
|
||||
static bool db_checked = false;
|
||||
static bool db_exists = false;
|
||||
|
||||
if (!db_checked) {
|
||||
database db;
|
||||
database::MacAddressDBRecord dummy_record;
|
||||
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
|
||||
db_exists = (test_result != DATABASE_NOT_FOUND);
|
||||
db_checked = true;
|
||||
}
|
||||
|
||||
if (!db_exists) {
|
||||
vendor_name = "macaddress.db not found";
|
||||
return MAC_DB_NOT_FOUND;
|
||||
}
|
||||
|
||||
database db;
|
||||
database::MacAddressDBRecord record;
|
||||
|
||||
// Convert MAC address to hex string
|
||||
std::string mac_hex = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Only need first 3 bytes for OUI
|
||||
mac_hex += to_string_hex(mac_address[i], 2);
|
||||
}
|
||||
|
||||
int result = db.retrieve_macaddress_record(&record, mac_hex);
|
||||
|
||||
if (result == DATABASE_RECORD_FOUND) {
|
||||
vendor_name = std::string(record.vendor_name);
|
||||
return MAC_VENDOR_FOUND;
|
||||
} else {
|
||||
vendor_name = "Unknown";
|
||||
return MAC_VENDOR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
std::string lookup_mac_vendor(const uint8_t* mac_address) {
|
||||
std::string vendor_name;
|
||||
lookup_mac_vendor_status(mac_address, vendor_name);
|
||||
return vendor_name;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
std::string pdu_type_to_string(ADV_PDU_TYPE type) {
|
||||
@ -165,7 +210,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line += pad_string_with_spaces(db_spacing) + dbStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
|
||||
|
||||
painter.draw_string(target_rect.location(), row_style, line);
|
||||
}
|
||||
|
||||
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
|
||||
@ -178,10 +226,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
&text_mac_address,
|
||||
&label_pdu_type,
|
||||
&text_pdu_type,
|
||||
&label_vendor,
|
||||
&text_vendor,
|
||||
&labels});
|
||||
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
nav.pop();
|
||||
@ -370,6 +422,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
|
||||
|
||||
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@ -952,6 +1008,11 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.pduType = pdu_type;
|
||||
entry.channelNumber = channel_number;
|
||||
|
||||
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
|
||||
std::string vendor_name;
|
||||
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
|
||||
}
|
||||
|
||||
// Parse Data Section into buffer to be interpretted later.
|
||||
for (int i = 0; i < packet->dataLen; i++) {
|
||||
entry.packetData.data[i] = packet->data[i];
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@ -33,6 +34,7 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "database.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "usb_serial_thread.hpp"
|
||||
@ -72,6 +74,13 @@ typedef enum {
|
||||
RESERVED8 = 15
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
typedef enum {
|
||||
MAC_VENDOR_UNKNOWN = 0,
|
||||
MAC_VENDOR_FOUND = 1,
|
||||
MAC_VENDOR_NOT_FOUND = 2,
|
||||
MAC_DB_NOT_FOUND = 3
|
||||
} MAC_VENDOR_STATUS;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
@ -87,6 +96,7 @@ struct BleRecentEntry {
|
||||
uint16_t numHits;
|
||||
ADV_PDU_TYPE pduType;
|
||||
uint8_t channelNumber;
|
||||
MAC_VENDOR_STATUS vendor_status;
|
||||
bool entryFound;
|
||||
|
||||
BleRecentEntry()
|
||||
@ -105,6 +115,7 @@ struct BleRecentEntry {
|
||||
numHits{},
|
||||
pduType{},
|
||||
channelNumber{},
|
||||
vendor_status{MAC_VENDOR_UNKNOWN},
|
||||
entryFound{} {
|
||||
}
|
||||
|
||||
@ -152,6 +163,13 @@ class BleRecentEntryDetailView : public View {
|
||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_vendor{
|
||||
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_vendor{
|
||||
{7 * 8, 2 * 16, 23 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
|
||||
file_path = macaddress_dir / u"macaddress.db";
|
||||
index_item_length = 7;
|
||||
record_length = 64;
|
||||
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@ -60,6 +61,12 @@ class database {
|
||||
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
struct MacAddressDBRecord {
|
||||
char vendor_name[64]; // vendor/manufacturer name
|
||||
};
|
||||
|
||||
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path = ""; // path including filename
|
||||
int index_item_length = 0; // length of index item
|
||||
@ -77,4 +84,4 @@ class database {
|
||||
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
#endif /*__DATABASE_H__*/
|
@ -52,3 +52,4 @@ const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
|
||||
const std::filesystem::path macaddress_dir = u"MACADDRESS";
|
||||
|
@ -54,5 +54,6 @@ extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
extern const std::filesystem::path waterfalls_dir;
|
||||
extern const std::filesystem::path macaddress_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
130
firmware/tools/make_macaddress_db/make_macaddress_db.py
Normal file
130
firmware/tools/make_macaddress_db/make_macaddress_db.py
Normal file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create macaddress.db, used for BLE receiver application, using
|
||||
# https://standards-oui.ieee.org/oui/oui.txt as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
import urllib.request
|
||||
import unicodedata
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def download_oui_file() -> str:
|
||||
"""Download the OUI file from IEEE"""
|
||||
url = "https://standards-oui.ieee.org/oui/oui.txt"
|
||||
print(f"Downloading OUI database from {url}...")
|
||||
try:
|
||||
with urllib.request.urlopen(url) as response:
|
||||
content = response.read().decode("utf-8")
|
||||
print("Download completed successfully.")
|
||||
return content
|
||||
except Exception as e:
|
||||
print(f"Error downloading OUI file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_oui_data(content: str) -> List[Tuple[str, str]]:
|
||||
"""Parse the OUI file content and extract MAC prefixes and vendor names"""
|
||||
entries = []
|
||||
lines = content.split("\n")
|
||||
|
||||
for _, line in enumerate(lines):
|
||||
# Look for lines with (hex) pattern
|
||||
if "(hex)" in line:
|
||||
# Extract MAC prefix and vendor name
|
||||
match = re.match(
|
||||
r"^([0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2})\s+\(hex\)\s+(.+)$",
|
||||
line.strip(),
|
||||
)
|
||||
if match:
|
||||
# Remove dashes: "28-6F-B9" -> "286FB9"
|
||||
mac_prefix = match.group(1).replace(
|
||||
"-", ""
|
||||
)
|
||||
vendor_name = match.group(2).strip()
|
||||
|
||||
# Normalize vendor name and limit to 63 characters
|
||||
vendor_name = (
|
||||
unicodedata.normalize("NFKD", vendor_name[:63])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
|
||||
if mac_prefix and vendor_name:
|
||||
entries.append((mac_prefix, vendor_name))
|
||||
|
||||
print(f"Parsed {len(entries)} MAC address entries.")
|
||||
return entries
|
||||
|
||||
|
||||
def create_database(
|
||||
entries: List[Tuple[str, str]], output_filename: str = "macaddress.db"
|
||||
):
|
||||
"""Create the binary database file."""
|
||||
# Sort entries by MAC prefix for binary search
|
||||
entries.sort(key=lambda x: x[0])
|
||||
|
||||
mac_prefixes = bytearray()
|
||||
vendor_data = bytearray()
|
||||
row_count = 0
|
||||
|
||||
for mac_prefix, vendor_name in entries:
|
||||
# MAC prefix: 6 hex chars + null terminator = 7 bytes
|
||||
mac_prefixes += bytearray(mac_prefix + "\0", encoding="ascii")
|
||||
|
||||
# Vendor name: pad to 64 bytes
|
||||
vendor_bytes = vendor_name.encode("ascii", "ignore")
|
||||
vendor_padding = bytearray("\0" * (64 - len(vendor_bytes)), encoding="ascii")
|
||||
vendor_data += vendor_bytes + vendor_padding
|
||||
|
||||
row_count += 1
|
||||
|
||||
# Write database: index section followed by data section
|
||||
with open(output_filename, "wb") as database:
|
||||
database.write(mac_prefixes + vendor_data)
|
||||
|
||||
print(f"Created database '{output_filename}' with {row_count} MAC address entries.")
|
||||
print(f"Index section: {len(mac_prefixes)} bytes")
|
||||
print(f"Data section: {len(vendor_data)} bytes")
|
||||
print(f"Total database size: {len(mac_prefixes) + len(vendor_data)} bytes")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to create the MAC address database."""
|
||||
try:
|
||||
oui_content = download_oui_file()
|
||||
entries = parse_oui_data(oui_content)
|
||||
|
||||
if not entries:
|
||||
print("No valid entries found in OUI file!")
|
||||
return
|
||||
|
||||
create_database(entries)
|
||||
|
||||
print("MAC address database creation completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating MAC address database: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
BIN
sdcard/MACADDRESS/macaddress.db
Normal file
BIN
sdcard/MACADDRESS/macaddress.db
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user