docs: Multi-frequency RF file handling analysis
Comprehensive analysis of how the system handles .sub files with multiple frequencies. Key findings: - Standard Flipper Zero files contain ONE frequency per file - Multiple .sub files in one upload: WORKS correctly - Multiple Frequency: lines in one file: Only last used (silent loss) - Recommended: Add validation warning for multi-frequency edge case Analysis includes: - Current parser behavior - Flipper Zero file format specification - 4 solution options with pros/cons - Testing scenarios - Impact assessment - Short-term and long-term recommendations
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
# Multi-Frequency RF File Handling - Analysis & Recommendations
|
||||
|
||||
**Date:** January 14, 2026
|
||||
**Status:** Current Limitation Identified
|
||||
**Priority:** Medium (Future Enhancement)
|
||||
|
||||
---
|
||||
|
||||
## Current Behavior
|
||||
|
||||
### How the System Handles .sub Files Today
|
||||
|
||||
**Current Implementation:**
|
||||
```python
|
||||
# src/parser/sub_parser.py (line 79)
|
||||
frequency = int(self.fields.get('Frequency', 0)) # Single frequency only
|
||||
|
||||
# src/api/main_simple.py (line 323)
|
||||
frequency = metadata.frequency # Extracts single frequency
|
||||
```
|
||||
|
||||
**What Happens:**
|
||||
1. Parser reads the `Frequency:` field from .sub file
|
||||
2. Extracts **ONE** frequency value (integer)
|
||||
3. Creates **ONE** capture record per file
|
||||
4. Stores that single frequency with GPS coordinates
|
||||
|
||||
### Flipper Zero .sub File Format
|
||||
|
||||
**Standard Single-Frequency File:**
|
||||
```
|
||||
Filetype: Flipper SubGhz Key File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok270Async
|
||||
Protocol: Princeton
|
||||
Bit: 24
|
||||
Key: 00 00 00 00 00 95 D5 D4
|
||||
```
|
||||
|
||||
**Can .sub Files Contain Multiple Frequencies?**
|
||||
|
||||
Based on Flipper Zero's firmware and file format specification:
|
||||
|
||||
❌ **NO** - Standard Flipper Zero .sub files contain **ONE frequency per file**
|
||||
|
||||
Each .sub file represents a **single capture** at a **single frequency**.
|
||||
|
||||
### Multi-Frequency Scenarios
|
||||
|
||||
However, there are **edge cases** where multiple frequencies might appear:
|
||||
|
||||
#### Scenario 1: Multiple .sub Files in One Upload
|
||||
✅ **SUPPORTED** - System handles this correctly
|
||||
|
||||
```python
|
||||
# User uploads multiple files at once
|
||||
files = [
|
||||
"capture_315MHz.sub", # Frequency: 315000000
|
||||
"capture_433MHz.sub", # Frequency: 433920000
|
||||
"capture_868MHz.sub" # Frequency: 868000000
|
||||
]
|
||||
|
||||
# System behavior:
|
||||
for file in files:
|
||||
metadata = sub_parser.parse(file) # Each file parsed separately
|
||||
capture = create_capture(metadata) # Each creates separate capture
|
||||
# Result: 3 separate captures with 3 different frequencies
|
||||
```
|
||||
|
||||
**Result:** ✅ Works correctly - creates 3 separate map markers
|
||||
|
||||
#### Scenario 2: Frequency-Hopping Devices (Future)
|
||||
❌ **NOT SUPPORTED** - Would require new file format
|
||||
|
||||
Some devices transmit on multiple frequencies (e.g., Bluetooth frequency hopping, LoRa adaptive data rate):
|
||||
|
||||
```
|
||||
Device transmits: 315 MHz → 433 MHz → 868 MHz
|
||||
```
|
||||
|
||||
**Current behavior:** Flipper Zero would create 3 separate .sub files
|
||||
|
||||
**Hypothetical multi-frequency file (NOT STANDARD):**
|
||||
```
|
||||
Filetype: Flipper SubGhz Multi File # DOES NOT EXIST
|
||||
Version: 1
|
||||
Frequency: 315000000
|
||||
Frequency: 433920000 # Multiple Frequency fields
|
||||
Frequency: 868000000
|
||||
```
|
||||
|
||||
**Our parser behavior:** Only reads **first** Frequency field (line 79)
|
||||
|
||||
#### Scenario 3: Scanning/Waterfall Captures
|
||||
❌ **NOT SUPPORTED** - Different file format
|
||||
|
||||
Flipper Zero's spectrum analyzer creates different file types:
|
||||
- `.sub` files = Single frequency capture
|
||||
- Waterfall scans = Not saved to .sub format
|
||||
|
||||
---
|
||||
|
||||
## Current System Limitations
|
||||
|
||||
### What Happens If Someone Manually Edits a .sub File?
|
||||
|
||||
Let's test what happens if a .sub file has multiple `Frequency:` lines:
|
||||
|
||||
**Malformed File:**
|
||||
```
|
||||
Filetype: Flipper SubGhz Key File
|
||||
Version: 1
|
||||
Frequency: 315000000
|
||||
Frequency: 433920000 # Second frequency line
|
||||
Protocol: Princeton
|
||||
```
|
||||
|
||||
**Parser Behavior:**
|
||||
```python
|
||||
# src/parser/sub_parser.py (lines 54-61)
|
||||
for line in content.split('\n'):
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
self.fields[key.strip()] = value.strip() # OVERWRITES previous value!
|
||||
```
|
||||
|
||||
**Result:**
|
||||
- First `Frequency: 315000000` is stored
|
||||
- Second `Frequency: 433920000` **OVERWRITES** it
|
||||
- Only **433920000** is used
|
||||
|
||||
❌ **Silent data loss** - First frequency is discarded without warning
|
||||
|
||||
### Upload Endpoint Behavior
|
||||
|
||||
```python
|
||||
# src/api/main_simple.py (lines 305-376)
|
||||
for file in files:
|
||||
metadata = sub_parser.parse(temp_path) # Parses single frequency
|
||||
frequency = metadata.frequency # Gets single frequency
|
||||
|
||||
# Creates ONE capture per file
|
||||
capture_info = {
|
||||
"id": upload_counter,
|
||||
"frequency": frequency, # Single frequency stored
|
||||
# ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** One file = One frequency = One capture = One map marker
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solutions
|
||||
|
||||
### Option 1: Detect and Reject Multi-Frequency Files (Recommended)
|
||||
|
||||
**Pros:**
|
||||
- Maintains data integrity
|
||||
- Prevents silent data loss
|
||||
- Simple to implement
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
def parse(self, file_path: str) -> SignalMetadata:
|
||||
# Count frequency fields
|
||||
freq_count = content.count('Frequency:')
|
||||
|
||||
if freq_count > 1:
|
||||
raise ValueError(
|
||||
f"Multi-frequency files not supported. "
|
||||
f"Found {freq_count} frequency fields. "
|
||||
f"Please split into separate .sub files."
|
||||
)
|
||||
|
||||
# Rest of parsing...
|
||||
```
|
||||
|
||||
### Option 2: Parse All Frequencies and Create Multiple Captures (Complex)
|
||||
|
||||
**Pros:**
|
||||
- Handles edge cases gracefully
|
||||
- Future-proof for multi-frequency devices
|
||||
|
||||
**Cons:**
|
||||
- Significant code changes required
|
||||
- Not aligned with Flipper Zero standard
|
||||
- GPS coordinates ambiguous (which freq was captured where?)
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
def parse(self, file_path: str) -> List[SignalMetadata]:
|
||||
"""Returns LIST of metadata objects"""
|
||||
|
||||
# Parse all frequency fields
|
||||
frequencies = []
|
||||
for line in content.split('\n'):
|
||||
if line.startswith('Frequency:'):
|
||||
freq = int(line.split(':', 1)[1].strip())
|
||||
frequencies.append(freq)
|
||||
|
||||
if len(frequencies) == 0:
|
||||
raise ValueError("No frequency found")
|
||||
|
||||
# Create separate metadata for each frequency
|
||||
results = []
|
||||
for freq in frequencies:
|
||||
metadata = SignalMetadata(frequency=freq, ...)
|
||||
results.append(metadata)
|
||||
|
||||
return results
|
||||
|
||||
# Upload endpoint changes needed:
|
||||
for file in files:
|
||||
metadatas = sub_parser.parse(temp_path) # Returns LIST
|
||||
for metadata in metadatas: # Create multiple captures
|
||||
capture = create_capture(metadata)
|
||||
```
|
||||
|
||||
**Database changes:**
|
||||
- `captures_storage` would need to link related captures
|
||||
- GPS coordinates: same for all captures from one file
|
||||
- Map display: Multiple markers at same location?
|
||||
|
||||
### Option 3: Store Multiple Frequencies in Single Capture (Database Change)
|
||||
|
||||
**Pros:**
|
||||
- Preserves all frequency data
|
||||
- Single capture per file (simpler logic)
|
||||
|
||||
**Cons:**
|
||||
- Database schema change required
|
||||
- Map display complexity (show all frequencies?)
|
||||
- Matching logic needs update
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# Database schema:
|
||||
capture_info = {
|
||||
"id": 1,
|
||||
"filename": "multi_freq.sub",
|
||||
"frequencies": [315000000, 433920000, 868000000], # Array instead of single
|
||||
"primary_frequency": 315000000, # First or dominant frequency
|
||||
# ...
|
||||
}
|
||||
|
||||
# Map display:
|
||||
# Option A: Show primary frequency only
|
||||
# Option B: Show all frequencies in popup
|
||||
# Option C: Create multiple semi-transparent markers
|
||||
```
|
||||
|
||||
### Option 4: Do Nothing (Current Behavior)
|
||||
|
||||
**Pros:**
|
||||
- No code changes needed
|
||||
- Aligns with Flipper Zero standard (one freq per file)
|
||||
|
||||
**Cons:**
|
||||
- Silent data loss if malformed file uploaded
|
||||
- No user feedback about issue
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Short-Term (Implement Now)
|
||||
|
||||
**1. Add Validation Warning**
|
||||
```python
|
||||
def parse(self, file_path: str) -> SignalMetadata:
|
||||
freq_count = content.count('Frequency:')
|
||||
|
||||
if freq_count > 1:
|
||||
logger.warning(
|
||||
f"File {file_path} contains {freq_count} frequency fields. "
|
||||
f"Only the last one will be used: {self.fields['Frequency']}"
|
||||
)
|
||||
```
|
||||
|
||||
**2. Document Behavior**
|
||||
- Update API docs to clarify: "One frequency per .sub file"
|
||||
- Add to upload UI: "Multi-frequency files not supported"
|
||||
|
||||
**3. Add to Upload Response**
|
||||
```python
|
||||
if freq_count > 1:
|
||||
warnings.append({
|
||||
"filename": file.filename,
|
||||
"warning": "Multiple frequencies detected. Only last frequency used.",
|
||||
"frequencies_found": freq_count
|
||||
})
|
||||
```
|
||||
|
||||
### Medium-Term (Future Enhancement)
|
||||
|
||||
**If multi-frequency support becomes needed:**
|
||||
|
||||
1. **Update File Format Spec**
|
||||
- Define multi-frequency .sub format
|
||||
- OR: Use separate file per frequency (Flipper standard)
|
||||
|
||||
2. **Update Parser**
|
||||
- Return `List[SignalMetadata]` instead of single
|
||||
- Parse all frequency fields
|
||||
|
||||
3. **Update Upload Endpoint**
|
||||
- Handle list of metadata objects
|
||||
- Create multiple captures per file
|
||||
- Link captures with `parent_file_id`
|
||||
|
||||
4. **Update Map Display**
|
||||
- Show multiple markers at same GPS location
|
||||
- OR: Single marker with frequency list in popup
|
||||
|
||||
5. **Update Database Schema**
|
||||
```python
|
||||
# New fields:
|
||||
"is_multi_frequency": True,
|
||||
"parent_capture_id": None, # For linked captures
|
||||
"frequency_index": 0, # Which frequency in multi-freq file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Case 1: Normal Single-Frequency File
|
||||
```python
|
||||
# Input: standard_capture.sub
|
||||
Frequency: 433920000
|
||||
|
||||
# Expected:
|
||||
- Parse succeeds
|
||||
- frequency = 433920000
|
||||
- One capture created
|
||||
```
|
||||
|
||||
### Test Case 2: Duplicate Frequency Lines
|
||||
```python
|
||||
# Input: duplicate_freq.sub
|
||||
Frequency: 315000000
|
||||
Frequency: 315000000 # Same frequency twice
|
||||
|
||||
# Current behavior:
|
||||
- Parse succeeds
|
||||
- frequency = 315000000 (last value)
|
||||
- Warning logged
|
||||
|
||||
# Recommended:
|
||||
- Parse succeeds
|
||||
- Warning returned to user
|
||||
```
|
||||
|
||||
### Test Case 3: Different Frequency Lines
|
||||
```python
|
||||
# Input: multi_freq.sub
|
||||
Frequency: 315000000
|
||||
Frequency: 433920000 # Different frequency
|
||||
|
||||
# Current behavior:
|
||||
- Parse succeeds
|
||||
- frequency = 433920000 (last value)
|
||||
- 315000000 silently discarded
|
||||
|
||||
# Recommended:
|
||||
- Parse fails OR
|
||||
- Parse succeeds with warning
|
||||
- User informed of data loss
|
||||
```
|
||||
|
||||
### Test Case 4: Multiple Files Uploaded
|
||||
```python
|
||||
# Input: 3 separate .sub files
|
||||
file1.sub: Frequency: 315000000
|
||||
file2.sub: Frequency: 433920000
|
||||
file3.sub: Frequency: 868000000
|
||||
|
||||
# Current behavior: ✅ WORKS
|
||||
- Parse each file separately
|
||||
- Create 3 captures
|
||||
- 3 map markers with different frequencies
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can a single .sub file contain multiple frequencies?**
|
||||
A: Not in the standard Flipper Zero format. Each .sub file represents one capture at one frequency.
|
||||
|
||||
**Q: What if I scan multiple frequencies?**
|
||||
A: Flipper Zero creates separate .sub files for each frequency. Upload all files together.
|
||||
|
||||
**Q: What happens if I manually add multiple `Frequency:` lines?**
|
||||
A: Currently, only the **last** frequency is used. Earlier ones are silently discarded.
|
||||
|
||||
**Q: Can I upload multiple .sub files with different frequencies?**
|
||||
A: ✅ Yes! Upload them all at once. Each file creates a separate capture.
|
||||
|
||||
**Q: Why not support multi-frequency files?**
|
||||
A:
|
||||
1. Not part of Flipper Zero standard
|
||||
2. GPS coordinates are per-file, not per-frequency
|
||||
3. Adds significant complexity
|
||||
4. No known devices that require this
|
||||
|
||||
**Q: Will this be supported in the future?**
|
||||
A: Maybe, if:
|
||||
1. Users frequently encounter multi-frequency devices
|
||||
2. A clear use case emerges (frequency-hopping, channel scanning)
|
||||
3. Standard file format is defined
|
||||
|
||||
---
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### Current Impact
|
||||
- **Low** - Flipper Zero doesn't create multi-frequency files
|
||||
- **Edge case only** - Manually edited/malformed files
|
||||
|
||||
### User Impact if Implemented
|
||||
- **Positive:** Handle more file types
|
||||
- **Negative:** Increased complexity, potential confusion (multiple markers at same GPS)
|
||||
|
||||
### Development Effort
|
||||
- **Option 1 (Validation):** Low - 1-2 hours
|
||||
- **Option 2 (Multi-capture):** High - 1-2 days
|
||||
- **Option 3 (Database change):** Very High - 3-5 days + migration
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Current Status:** System handles **one frequency per .sub file** correctly.
|
||||
|
||||
**Recommendation:** Implement **Option 1** (validation warning) in short-term.
|
||||
|
||||
**Reasoning:**
|
||||
1. Aligns with Flipper Zero standard (one freq per file)
|
||||
2. No known use cases for multi-frequency files
|
||||
3. Minimal development effort
|
||||
4. Prevents silent data loss
|
||||
5. Future-proof: can enhance later if needed
|
||||
|
||||
**Action Items:**
|
||||
- [ ] Add frequency count validation
|
||||
- [ ] Log warning if multiple frequencies detected
|
||||
- [ ] Return warning in upload API response
|
||||
- [ ] Document behavior in API docs
|
||||
- [ ] Add user-facing note in upload UI
|
||||
|
||||
---
|
||||
|
||||
**Status:** Analysis complete, awaiting decision on implementation priority.
|
||||
Reference in New Issue
Block a user