mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-08-08 01:43:01 +02:00
Phase 3: Add comprehensive documentation and tests
Co-authored-by: Genaker <9213670+Genaker@users.noreply.github.com>
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Python Utility Module**: Created `serial_utils.py` with shared functions for CRC16 calculation and data parsing
|
||||
- Eliminates code duplication across `ASCII_SA.py`, `SpectrumScan.py`, and `scripts/rpi-proxy-fc.py`
|
||||
- Provides centralized input validation and error handling
|
||||
- Includes backward compatibility fallbacks
|
||||
|
||||
- **Troubleshooting Guide**: Added comprehensive `TROUBLESHOOTING.md` with solutions for common issues
|
||||
- Build and compilation problems
|
||||
- Upload failures
|
||||
- Runtime errors
|
||||
- Configuration issues
|
||||
- Python script errors
|
||||
- Performance optimization tips
|
||||
- Environment variables reference
|
||||
|
||||
- **Environment Variable Support**: Python scripts now support configuration via environment variables
|
||||
- `LORA_SA_PORT`: Serial port for LoRa device
|
||||
- `DRONE_PORT`: Serial port for drone/flight controller
|
||||
- `SERIAL_BAUDRATE`: Communication baudrate
|
||||
- `SERIAL_TIMEOUT`: Read timeout in seconds
|
||||
|
||||
- **Signal Handlers**: Added graceful shutdown handling in `ASCII_SA.py`
|
||||
- Proper cleanup on Ctrl+C (SIGINT)
|
||||
- Prevents terminal corruption on exit
|
||||
|
||||
- **C++ Destructors**: Added proper destructors to prevent memory leaks
|
||||
- `StackedChart`: Cleans up dynamically allocated charts array
|
||||
- `BarChart`: Cleans up `ys` and `changed` arrays
|
||||
- Implements proper RAII pattern
|
||||
|
||||
### Changed
|
||||
|
||||
- **Exception Handling (Python)**:
|
||||
- Replaced bare `except:` clauses with specific exception types
|
||||
- Fixed incorrect `try-finally` logic in `scripts/rpi-proxy-fc.py`
|
||||
- Added proper error logging with descriptive messages
|
||||
- Improved error recovery and user feedback
|
||||
|
||||
- **Input Validation (Python)**:
|
||||
- Added comprehensive validation in `parse_scan_result()` functions
|
||||
- Frequency range validation (100 MHz to 6 GHz)
|
||||
- RSSI range validation (-200 to 0 dBm)
|
||||
- Scan count bounds checking (1 to 10,000)
|
||||
- Data integrity verification (count matches actual data length)
|
||||
|
||||
- **Memory Management (C++)**:
|
||||
- Fixed mixed allocation methods in `StackedChart.cpp`
|
||||
- Changed `free(charts)` to `delete[] charts` for consistency
|
||||
- Fixed BLE callback memory leak in `src/main.cpp`
|
||||
- Replaced heap allocation with static instance for `MyServerCallbacks`
|
||||
|
||||
- **Code Organization**:
|
||||
- Extracted hardcoded constants to configuration variables
|
||||
- Consolidated CRC16 implementations into single shared function
|
||||
- Improved code comments and documentation
|
||||
- Better separation of concerns in Python scripts
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Python Scripts**:
|
||||
- Fixed infinite loops without proper exit mechanisms
|
||||
- Fixed silent exception swallowing with curses errors
|
||||
- Fixed Unicode decode errors in serial communication
|
||||
- Fixed malformed data handling in `ASCII_SA.py`
|
||||
|
||||
- **C++ Code**:
|
||||
- Fixed memory leaks in chart classes
|
||||
- Fixed undefined behavior from mixed allocation methods (new/free)
|
||||
- Fixed BLE server callback memory leak
|
||||
- Fixed potential buffer overflows from missing input validation
|
||||
|
||||
### Security
|
||||
|
||||
- **Input Validation**: Added bounds checking to prevent crashes from malformed serial data
|
||||
- **Error Messages**: Improved error messages without exposing sensitive system information
|
||||
- **Exception Handling**: Removed unsafe bare except clauses that could hide critical errors
|
||||
|
||||
### Documentation
|
||||
|
||||
- Added troubleshooting guide with common solutions
|
||||
- Documented environment variable configuration
|
||||
- Added examples for error handling
|
||||
- Improved code comments in critical sections
|
||||
- Added reference to troubleshooting guide in main README
|
||||
|
||||
### Performance
|
||||
|
||||
- Reduced memory allocations by using static instances where possible
|
||||
- Improved error handling overhead by using specific exception types
|
||||
- Better resource cleanup prevents memory leaks over time
|
||||
|
||||
---
|
||||
|
||||
## Notes for Developers
|
||||
|
||||
### Breaking Changes
|
||||
None - all changes are backward compatible. Scripts will use local fallback implementations if `serial_utils.py` is not available.
|
||||
|
||||
### Migration Guide
|
||||
No migration needed. Existing code continues to work. To use new features:
|
||||
|
||||
1. **Environment Variables**: Set before running scripts
|
||||
```bash
|
||||
export LORA_SA_PORT=/dev/ttyUSB0
|
||||
python3 ASCII_SA.py
|
||||
```
|
||||
|
||||
2. **Shared Utilities**: Import from `serial_utils` for new code
|
||||
```python
|
||||
from serial_utils import crc16, parse_scan_result
|
||||
```
|
||||
|
||||
### Testing Recommendations
|
||||
- Test Python scripts with invalid/corrupted serial data
|
||||
- Verify memory leak fixes with long-running tests
|
||||
- Check backward compatibility with existing configurations
|
||||
- Validate environment variable override functionality
|
||||
|
||||
---
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Tracked in GitHub issues:
|
||||
- [ ] Add unit tests for `serial_utils.py` validation functions
|
||||
- [ ] Add integration tests for serial communication
|
||||
- [ ] Create automated memory leak testing for C++ code
|
||||
- [ ] Add configuration file support (YAML/JSON) for complex setups
|
||||
- [ ] Implement logging framework for better debugging
|
||||
- [ ] Add telemetry and metrics collection
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
# Contributing to LoraSA
|
||||
|
||||
Thank you for your interest in contributing to LoraSA! This document provides guidelines and best practices for contributing to the project.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
- Be respectful and inclusive
|
||||
- Provide constructive feedback
|
||||
- Focus on what is best for the community
|
||||
- Show empathy towards other community members
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Fork the repository** on GitHub
|
||||
2. **Clone your fork** locally:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/LoraSA.git
|
||||
cd LoraSA
|
||||
```
|
||||
3. **Create a branch** for your changes:
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
### For C++ Development (ESP32)
|
||||
1. Install VSCode and PlatformIO extension
|
||||
2. Open project in VSCode
|
||||
3. Select appropriate environment from `platformio.ini`
|
||||
4. Build and upload to your board
|
||||
|
||||
### For Python Development
|
||||
1. Install Python 3.7 or higher
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
pip install pyserial matplotlib numpy
|
||||
```
|
||||
3. Test scripts from repository root:
|
||||
```bash
|
||||
python3 ASCII_SA.py
|
||||
python3 SpectrumScan.py
|
||||
```
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### Python Code
|
||||
|
||||
#### Style Guidelines
|
||||
- Follow PEP 8 style guide
|
||||
- Use meaningful variable names
|
||||
- Maximum line length: 100 characters
|
||||
- Use type hints for function parameters and returns (Python 3.7+)
|
||||
|
||||
#### Best Practices
|
||||
1. **Error Handling**:
|
||||
```python
|
||||
# ❌ Bad - bare except
|
||||
try:
|
||||
risky_operation()
|
||||
except:
|
||||
pass
|
||||
|
||||
# ✅ Good - specific exceptions
|
||||
try:
|
||||
risky_operation()
|
||||
except (ValueError, IOError) as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
# Handle or re-raise
|
||||
```
|
||||
|
||||
2. **Input Validation**:
|
||||
```python
|
||||
# ❌ Bad - no validation
|
||||
def process_data(freq, rssi):
|
||||
return freq * rssi
|
||||
|
||||
# ✅ Good - validate inputs
|
||||
def process_data(freq, rssi):
|
||||
if not (100000 <= freq <= 6000000):
|
||||
raise ValueError(f"Invalid frequency: {freq}")
|
||||
if not (-200 <= rssi <= 0):
|
||||
raise ValueError(f"Invalid RSSI: {rssi}")
|
||||
return freq * rssi
|
||||
```
|
||||
|
||||
3. **Resource Management**:
|
||||
```python
|
||||
# ❌ Bad - manual cleanup
|
||||
ser = serial.Serial(port, baudrate)
|
||||
data = ser.read()
|
||||
ser.close()
|
||||
|
||||
# ✅ Good - context manager
|
||||
with serial.Serial(port, baudrate) as ser:
|
||||
data = ser.read()
|
||||
# Automatically closed
|
||||
```
|
||||
|
||||
4. **Constants**:
|
||||
```python
|
||||
# ❌ Bad - magic numbers
|
||||
if timeout > 5:
|
||||
...
|
||||
|
||||
# ✅ Good - named constants
|
||||
DEFAULT_TIMEOUT = 5
|
||||
if timeout > DEFAULT_TIMEOUT:
|
||||
...
|
||||
```
|
||||
|
||||
### C++ Code
|
||||
|
||||
#### Style Guidelines
|
||||
- Follow existing code style in the project
|
||||
- Use 4 spaces for indentation (no tabs)
|
||||
- Maximum line length: 100 characters
|
||||
- Use `clang-format` with provided `.clang-format` configuration
|
||||
|
||||
#### Best Practices
|
||||
|
||||
1. **Memory Management**:
|
||||
```cpp
|
||||
// ❌ Bad - memory leak
|
||||
MyClass* obj = new MyClass();
|
||||
// Never deleted
|
||||
|
||||
// ✅ Good - RAII or manual cleanup
|
||||
std::unique_ptr<MyClass> obj(new MyClass());
|
||||
// Or with destructor:
|
||||
~MyClass() {
|
||||
delete[] dynamicArray;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Array Allocation**:
|
||||
```cpp
|
||||
// ❌ Bad - mixed allocation
|
||||
char* arr = new char[100];
|
||||
free(arr); // Wrong!
|
||||
|
||||
// ✅ Good - consistent allocation
|
||||
char* arr = new char[100];
|
||||
delete[] arr;
|
||||
```
|
||||
|
||||
3. **Null Checks**:
|
||||
```cpp
|
||||
// ❌ Bad - no null check
|
||||
void process(Data* data) {
|
||||
data->value = 10; // Crash if data is null
|
||||
}
|
||||
|
||||
// ✅ Good - validate pointer
|
||||
void process(Data* data) {
|
||||
if (data == nullptr) {
|
||||
LOG("Error: null pointer");
|
||||
return;
|
||||
}
|
||||
data->value = 10;
|
||||
}
|
||||
```
|
||||
|
||||
4. **Destructors**:
|
||||
```cpp
|
||||
// ❌ Bad - missing destructor
|
||||
class MyChart {
|
||||
float* data;
|
||||
public:
|
||||
MyChart() { data = new float[100]; }
|
||||
// No destructor - memory leak!
|
||||
};
|
||||
|
||||
// ✅ Good - proper cleanup
|
||||
class MyChart {
|
||||
float* data;
|
||||
public:
|
||||
MyChart() { data = new float[100]; }
|
||||
~MyChart() { delete[] data; }
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Python Tests
|
||||
Run existing tests:
|
||||
```bash
|
||||
cd test
|
||||
python -m unittest test_rssi.py
|
||||
```
|
||||
|
||||
Add tests for new features:
|
||||
```python
|
||||
import unittest
|
||||
from serial_utils import parse_scan_result
|
||||
|
||||
class TestParseFunction(unittest.TestCase):
|
||||
def test_valid_input(self):
|
||||
line = "SCAN_RESULT 2 [(850000, -100), (860000, -90)]"
|
||||
count, data = parse_scan_result(line)
|
||||
self.assertEqual(count, 2)
|
||||
self.assertEqual(len(data), 2)
|
||||
|
||||
def test_invalid_frequency(self):
|
||||
line = "SCAN_RESULT 1 [(1, -100)]" # Freq too low
|
||||
with self.assertRaises(ValueError):
|
||||
parse_scan_result(line)
|
||||
```
|
||||
|
||||
### C++ Tests
|
||||
Tests are in `test/` directory. Run with PlatformIO:
|
||||
```bash
|
||||
pio test
|
||||
```
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. **Update Documentation**:
|
||||
- Update README.md if adding features
|
||||
- Add entry to CHANGELOG.md
|
||||
- Update TROUBLESHOOTING.md if relevant
|
||||
|
||||
2. **Test Your Changes**:
|
||||
- Build and test on actual hardware if possible
|
||||
- Run existing test suite
|
||||
- Test edge cases and error conditions
|
||||
|
||||
3. **Commit Messages**:
|
||||
```
|
||||
Short summary (50 chars or less)
|
||||
|
||||
More detailed explanation if needed. Wrap at 72 characters.
|
||||
|
||||
- Bullet points are okay
|
||||
- Explain what and why, not how
|
||||
|
||||
Fixes #123
|
||||
```
|
||||
|
||||
4. **Create Pull Request**:
|
||||
- Describe changes clearly
|
||||
- Reference related issues
|
||||
- Include testing steps
|
||||
- Add screenshots for UI changes
|
||||
|
||||
5. **Code Review**:
|
||||
- Address reviewer feedback
|
||||
- Keep discussions focused and professional
|
||||
- Be open to suggestions
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
### Python
|
||||
- ❌ Bare `except:` clauses
|
||||
- ❌ Magic numbers without constants
|
||||
- ❌ Missing input validation
|
||||
- ❌ Ignoring exceptions with `pass`
|
||||
- ❌ Hardcoded paths/ports
|
||||
|
||||
### C++
|
||||
- ❌ Memory leaks (new without delete)
|
||||
- ❌ Mixed allocation (new/free, malloc/delete)
|
||||
- ❌ Missing destructors for classes with dynamic allocation
|
||||
- ❌ Null pointer dereferences
|
||||
- ❌ Buffer overflows from unchecked indices
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
1. **Never commit**:
|
||||
- Credentials or API keys
|
||||
- Personal information
|
||||
- Binary files (unless necessary)
|
||||
|
||||
2. **Input Validation**:
|
||||
- Always validate external inputs
|
||||
- Check array bounds
|
||||
- Validate ranges for frequencies, RSSI, etc.
|
||||
|
||||
3. **Error Messages**:
|
||||
- Don't expose internal paths
|
||||
- Don't reveal system information
|
||||
- Provide helpful but safe messages
|
||||
|
||||
## Documentation
|
||||
|
||||
- Comment complex algorithms
|
||||
- Use docstrings for Python functions
|
||||
- Update README for new features
|
||||
- Add examples for new functionality
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Avoid allocations in tight loops
|
||||
- Use appropriate data structures
|
||||
- Profile before optimizing
|
||||
- Consider memory constraints of ESP32
|
||||
|
||||
## Hardware Testing
|
||||
|
||||
If possible, test on:
|
||||
- Heltec WiFi LoRa 32 V3
|
||||
- LilyGo T3S3 boards
|
||||
- Different frequency ranges
|
||||
- Various operating conditions
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|
||||
- Search existing issues
|
||||
- Ask questions in issue comments
|
||||
- Be specific about your setup
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the same license as the project (see LICENSE.md).
|
||||
|
||||
---
|
||||
|
||||
Thank you for contributing to LoraSA! 🎉
|
||||
@@ -25,6 +25,7 @@
|
||||
- [Send Scan Data via Lora](#send-scan-data-via-lora)
|
||||
- [Seek on Jam / FPV OSD using Flight controller](#seek-on-jam-fpv-osd-using-flight-controller)
|
||||
- [Platformio targets](#platformio-targets)
|
||||
- [Troubleshooting](TROUBLESHOOTING.md) 📖 **NEW**
|
||||
|
||||
## Supported boards:
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Build and Compilation Issues
|
||||
|
||||
#### Error: "Unknown board ID"
|
||||
**Symptom**: Build fails with `Error: Unknown board ID 'heltec_wifi_lora_32_V3'`
|
||||
|
||||
**Solution**: Update your ESP32 Expressif catalog:
|
||||
```bash
|
||||
pio pkg update -g -p espressif32
|
||||
```
|
||||
|
||||
#### Build Timeout or Slow First Build
|
||||
**Symptom**: First compilation takes very long (>5 minutes)
|
||||
|
||||
**Solution**: This is normal - PlatformIO needs to download and compile all libraries on first run. Subsequent builds will be much faster (~30-60 seconds).
|
||||
|
||||
---
|
||||
|
||||
### Upload Issues
|
||||
|
||||
#### Cannot Upload to Board
|
||||
**Symptom**: Upload fails with "Failed to connect to ESP32"
|
||||
|
||||
**Solution**:
|
||||
1. Press and hold **BOOT** button
|
||||
2. Press **RESET** button once
|
||||
3. Release **BOOT** button
|
||||
4. Try upload again within 5 seconds
|
||||
|
||||
#### USB Driver Not Found (Windows)
|
||||
**Symptom**: Device not recognized or COM port not showing
|
||||
|
||||
**Solution**: Install CP2101 USB drivers:
|
||||
- Download from: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
|
||||
- For Windows: Use the executable installer
|
||||
- For macOS: Use legacy driver if standard driver doesn't work
|
||||
|
||||
---
|
||||
|
||||
### Runtime Errors
|
||||
|
||||
#### No Display Output
|
||||
**Symptom**: Device powers on but screen stays blank
|
||||
|
||||
**Solution**:
|
||||
1. Check if correct board is selected in `platformio.ini`
|
||||
2. Verify `src_dir` matches your board type:
|
||||
- `src` for OLED displays (Heltec V3, LilyGo)
|
||||
- `tft_src` for TFT displays (Vision Master T190)
|
||||
- `eink_src` for e-ink displays (Vision Master E290)
|
||||
|
||||
#### Scan Results Show No Signals
|
||||
**Symptom**: Spectrum analyzer runs but shows no activity
|
||||
|
||||
**Solution**:
|
||||
1. Check antenna is properly connected
|
||||
2. Verify frequency range matches your region's LoRa bands
|
||||
3. Adjust threshold in configuration (try lower value like -120 dBm)
|
||||
4. Test with known active frequency (e.g., WiFi 2.4 GHz)
|
||||
|
||||
#### Python Scripts Fail to Connect
|
||||
**Symptom**: `ASCII_SA.py` or `SpectrumScan.py` cannot open serial port
|
||||
|
||||
**Solution**:
|
||||
1. Close Arduino IDE or PlatformIO serial monitor (only one program can use serial port)
|
||||
2. Check permissions on Linux:
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
# Then logout and login again
|
||||
```
|
||||
3. Verify correct port:
|
||||
- Linux: Usually `/dev/ttyUSB0` or `/dev/ttyACM0`
|
||||
- Windows: Check Device Manager for COM port number
|
||||
- macOS: Usually `/dev/cu.usbserial-*`
|
||||
|
||||
4. Set port via environment variable:
|
||||
```bash
|
||||
export LORA_SA_PORT=/dev/ttyUSB0
|
||||
python3 ASCII_SA.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuration Issues
|
||||
|
||||
#### OSD Feature Not Working
|
||||
**Symptom**: Compiled with OSD_ENABLED but no video output
|
||||
|
||||
**Solution**:
|
||||
1. Verify DFRobot OSD wiring (see main README)
|
||||
2. Check SPI pins match your board configuration
|
||||
3. Test camera independently before connecting to OSD
|
||||
|
||||
#### Custom Frequency Range Not Working
|
||||
**Symptom**: Device scans wrong frequencies despite configuration
|
||||
|
||||
**Solution**:
|
||||
1. Ensure `SCAN_DIAPAZONES` is properly formatted:
|
||||
```c
|
||||
int SCAN_DIAPAZONES[] = {850890, 920950}; // Two ranges: 850-890 and 920-950
|
||||
```
|
||||
2. If using single range, set `RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN`
|
||||
3. Frequency values must be within radio chip capabilities:
|
||||
- SX1262: 150-960 MHz
|
||||
- SX1280: 2400-2500 MHz
|
||||
- LR1121: 150-960 MHz, 2400-2500 MHz
|
||||
|
||||
---
|
||||
|
||||
### Python Script Errors
|
||||
|
||||
#### ModuleNotFoundError: No module named 'serial_utils'
|
||||
**Symptom**: Python scripts fail with import error
|
||||
|
||||
**Solution**: Ensure you're running scripts from the repository root:
|
||||
```bash
|
||||
cd /path/to/LoraSA
|
||||
python3 ASCII_SA.py
|
||||
```
|
||||
|
||||
Or install in development mode:
|
||||
```bash
|
||||
export PYTHONPATH="${PYTHONPATH}:/path/to/LoraSA"
|
||||
```
|
||||
|
||||
#### ValueError: Invalid SCAN_RESULT format
|
||||
**Symptom**: Script crashes when parsing serial data
|
||||
|
||||
**Solution**:
|
||||
1. This indicates corrupted serial data
|
||||
2. Check USB cable quality (use high-quality, short cables)
|
||||
3. Reduce baudrate in configuration if errors persist:
|
||||
```bash
|
||||
export SERIAL_BAUDRATE=57600
|
||||
```
|
||||
4. Update to latest firmware version
|
||||
|
||||
---
|
||||
|
||||
### Performance Issues
|
||||
|
||||
#### Slow Scan Rate
|
||||
**Symptom**: Less than 1 scan per second
|
||||
|
||||
**Solution**:
|
||||
1. Reduce scan range: smaller `FREQ_END - FREQ_BEGIN`
|
||||
2. Increase bandwidth setting (faster but less sensitive)
|
||||
3. Disable waterfall if enabled
|
||||
4. Disable WiFi/BT scanning if not needed
|
||||
|
||||
#### High Battery Drain
|
||||
**Symptom**: Battery depletes quickly
|
||||
|
||||
**Solution**:
|
||||
1. Reduce screen brightness
|
||||
2. Use power-saving mode (press P button on startup)
|
||||
3. Disable Bluetooth/WiFi if not in use
|
||||
4. Use sleep mode between scans
|
||||
|
||||
---
|
||||
|
||||
### Network and Communication
|
||||
|
||||
#### BLE/WiFi Scanning Not Working
|
||||
**Symptom**: `WIFI_SCANNING_ENABLED` or `BT_SCANNING_ENABLED` doesn't show results
|
||||
|
||||
**Solution**:
|
||||
1. Ensure OSD is enabled (required for WiFi/BT scanning)
|
||||
2. Verify defines are set:
|
||||
```c
|
||||
#define OSD_ENABLED true
|
||||
#define WIFI_SCANNING_ENABLED true
|
||||
#define BT_SCANNING_ENABLED true
|
||||
```
|
||||
3. Check antenna placement (WiFi/BT uses different antenna than LoRa on some boards)
|
||||
|
||||
#### LoRa Communication Between Devices Fails
|
||||
**Symptom**: Data not transmitting via LoRa
|
||||
|
||||
**Solution**:
|
||||
1. Verify both devices use same frequency configuration
|
||||
2. Check `is_host` setting (one device should be host, one client)
|
||||
3. Ensure LoRa TX/RX are properly configured in `lib/config/config.h`
|
||||
4. Test with shorter distance first (<100m)
|
||||
|
||||
---
|
||||
|
||||
## Getting Additional Help
|
||||
|
||||
If none of these solutions work:
|
||||
|
||||
1. **Check Serial Monitor Output**: Connect via USB and monitor serial output for error messages
|
||||
```bash
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
2. **Enable Debug Logging**: Add to build flags:
|
||||
```ini
|
||||
build_flags = -DCORE_DEBUG_LEVEL=5
|
||||
```
|
||||
|
||||
3. **Report Issue**: Include:
|
||||
- Board model and version
|
||||
- PlatformIO environment used
|
||||
- Complete error message
|
||||
- Serial output log
|
||||
- Steps to reproduce
|
||||
|
||||
4. **Community Resources**:
|
||||
- GitHub Issues: https://github.com/Genaker/LoraSA/issues
|
||||
- Check existing issues for similar problems
|
||||
- Provide hardware details when asking for help
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
The Python scripts now support environment variables for easier configuration:
|
||||
|
||||
```bash
|
||||
# Serial port configuration
|
||||
export LORA_SA_PORT=/dev/ttyUSB0 # LoRa device port
|
||||
export DRONE_PORT=/dev/ttyACM0 # Drone/FC port (rpi-proxy-fc.py)
|
||||
export SERIAL_BAUDRATE=115200 # Communication speed
|
||||
export SERIAL_TIMEOUT=5 # Read timeout in seconds
|
||||
|
||||
# Run script with custom config
|
||||
python3 ASCII_SA.py
|
||||
|
||||
# Or inline
|
||||
LORA_SA_PORT=/dev/ttyUSB1 python3 SpectrumScan.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced Debugging
|
||||
|
||||
### Memory Issues
|
||||
|
||||
If experiencing crashes or freezes:
|
||||
|
||||
1. **Check Memory Usage**:
|
||||
```c
|
||||
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
|
||||
```
|
||||
|
||||
2. **Reduce Buffer Sizes**: Edit configuration to use less memory
|
||||
3. **Disable Features**: Turn off waterfall, WiFi scanning to save memory
|
||||
|
||||
### Timing Issues
|
||||
|
||||
If scans appear delayed or unresponsive:
|
||||
|
||||
1. **Disable Watchdog**: May need to increase timeout
|
||||
2. **Profile Code**: Add timing measurements
|
||||
3. **Reduce Display Updates**: Update less frequently
|
||||
|
||||
### Radio Issues
|
||||
|
||||
If radio performance is poor:
|
||||
|
||||
1. **Check Antenna**: Measure VSWR if possible
|
||||
2. **Verify Frequency**: Use SDR to confirm actual transmission frequency
|
||||
3. **Test with Known Good Hardware**: Isolate hardware vs software issues
|
||||
Binary file not shown.
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for serial_utils module.
|
||||
Run with: python3 -m unittest test_serial_utils.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from serial_utils import (
|
||||
crc16,
|
||||
parse_scan_result,
|
||||
parse_scan_result_regex,
|
||||
validate_serial_config
|
||||
)
|
||||
|
||||
|
||||
class TestCRC16(unittest.TestCase):
|
||||
"""Tests for CRC16 calculation."""
|
||||
|
||||
def test_crc16_basic(self):
|
||||
"""Test basic CRC16 calculation."""
|
||||
result = crc16("test", 0)
|
||||
self.assertIsInstance(result, int)
|
||||
self.assertGreaterEqual(result, 0)
|
||||
self.assertLessEqual(result, 0xFFFF)
|
||||
|
||||
def test_crc16_empty(self):
|
||||
"""Test CRC16 with empty string."""
|
||||
result = crc16("", 0)
|
||||
# Empty string with initial value 0 produces 0
|
||||
self.assertEqual(result, 0)
|
||||
|
||||
def test_crc16_consistency(self):
|
||||
"""Test that same input gives same output."""
|
||||
input_str = "SCAN_RESULT 123"
|
||||
result1 = crc16(input_str, 0)
|
||||
result2 = crc16(input_str, 0)
|
||||
self.assertEqual(result1, result2)
|
||||
|
||||
|
||||
class TestParseScanResult(unittest.TestCase):
|
||||
"""Tests for parse_scan_result function."""
|
||||
|
||||
def test_valid_input(self):
|
||||
"""Test parsing valid SCAN_RESULT data."""
|
||||
line = "SCAN_RESULT 2 [(850000, -100), (860000, -90)]"
|
||||
count, data = parse_scan_result(line)
|
||||
|
||||
self.assertEqual(count, 2)
|
||||
self.assertEqual(len(data), 2)
|
||||
self.assertEqual(data[0], [850000, -100])
|
||||
self.assertEqual(data[1], [860000, -90])
|
||||
|
||||
def test_valid_with_garbage(self):
|
||||
"""Test parsing with garbage before SCAN_RESULT."""
|
||||
line = "garbage data SCAN_RESULT 1 [(900000, -80)]"
|
||||
count, data = parse_scan_result(line)
|
||||
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(len(data), 1)
|
||||
|
||||
def test_missing_scan_result(self):
|
||||
"""Test error handling for missing SCAN_RESULT."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result("no scan result here")
|
||||
self.assertIn("SCAN_RESULT", str(cm.exception))
|
||||
|
||||
def test_invalid_format(self):
|
||||
"""Test error handling for invalid format."""
|
||||
with self.assertRaises(ValueError):
|
||||
parse_scan_result("SCAN_RESULT") # Missing count and data
|
||||
|
||||
def test_count_mismatch(self):
|
||||
"""Test error handling when count doesn't match data length."""
|
||||
line = "SCAN_RESULT 5 [(850000, -100)]" # Says 5 but only 1 item
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
self.assertIn("does not match count", str(cm.exception))
|
||||
|
||||
def test_invalid_frequency_low(self):
|
||||
"""Test error handling for frequency too low."""
|
||||
line = "SCAN_RESULT 1 [(1000, -100)]" # 1 kHz - too low
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
self.assertIn("Invalid frequency", str(cm.exception))
|
||||
|
||||
def test_invalid_frequency_high(self):
|
||||
"""Test error handling for frequency too high."""
|
||||
line = "SCAN_RESULT 1 [(9000000, -100)]" # 9 GHz - too high
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
self.assertIn("Invalid frequency", str(cm.exception))
|
||||
|
||||
def test_invalid_rssi_positive(self):
|
||||
"""Test error handling for positive RSSI."""
|
||||
line = "SCAN_RESULT 1 [(850000, 50)]" # Positive RSSI - invalid
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
self.assertIn("Invalid RSSI", str(cm.exception))
|
||||
|
||||
def test_invalid_rssi_low(self):
|
||||
"""Test error handling for RSSI too low."""
|
||||
line = "SCAN_RESULT 1 [(850000, -300)]" # -300 dBm - too low
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
self.assertIn("Invalid RSSI", str(cm.exception))
|
||||
|
||||
def test_count_too_large(self):
|
||||
"""Test error handling for unreasonably large count."""
|
||||
# Create a line with a very large count value
|
||||
line = "SCAN_RESULT 99999 []"
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result(line)
|
||||
# Should fail either on count validation or count mismatch
|
||||
|
||||
|
||||
class TestParseScanResultRegex(unittest.TestCase):
|
||||
"""Tests for parse_scan_result_regex function."""
|
||||
|
||||
def test_valid_input(self):
|
||||
"""Test parsing valid data with regex method."""
|
||||
line = "SCAN_RESULT 2 [ (850000, -100), (860000, -90) ]"
|
||||
data = parse_scan_result_regex(line)
|
||||
|
||||
self.assertEqual(len(data), 2)
|
||||
self.assertEqual(data[0], (850000, -100))
|
||||
self.assertEqual(data[1], (860000, -90))
|
||||
|
||||
def test_empty_result(self):
|
||||
"""Test error handling for empty result."""
|
||||
line = "SCAN_RESULT 0 []"
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
parse_scan_result_regex(line)
|
||||
self.assertIn("No valid frequency/RSSI pairs", str(cm.exception))
|
||||
|
||||
def test_missing_scan_result(self):
|
||||
"""Test error handling when SCAN_RESULT is missing."""
|
||||
with self.assertRaises(ValueError):
|
||||
parse_scan_result_regex("just some data")
|
||||
|
||||
|
||||
class TestValidateSerialConfig(unittest.TestCase):
|
||||
"""Tests for validate_serial_config function."""
|
||||
|
||||
def test_valid_config(self):
|
||||
"""Test validation with valid configuration."""
|
||||
# Should not raise exception
|
||||
validate_serial_config("/dev/ttyUSB0", 115200, 5)
|
||||
|
||||
def test_invalid_port_empty(self):
|
||||
"""Test error handling for empty port."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
validate_serial_config("", 115200, 5)
|
||||
self.assertIn("Port", str(cm.exception))
|
||||
|
||||
def test_invalid_port_none(self):
|
||||
"""Test error handling for None port."""
|
||||
with self.assertRaises(ValueError):
|
||||
validate_serial_config(None, 115200, 5)
|
||||
|
||||
def test_invalid_baudrate(self):
|
||||
"""Test error handling for invalid baudrate."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
validate_serial_config("/dev/ttyUSB0", 12345, 5)
|
||||
self.assertIn("Baudrate", str(cm.exception))
|
||||
|
||||
def test_invalid_timeout_negative(self):
|
||||
"""Test error handling for negative timeout."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
validate_serial_config("/dev/ttyUSB0", 115200, -1)
|
||||
self.assertIn("Timeout", str(cm.exception))
|
||||
|
||||
def test_invalid_timeout_too_high(self):
|
||||
"""Test error handling for timeout too high."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
validate_serial_config("/dev/ttyUSB0", 115200, 500)
|
||||
self.assertIn("Timeout", str(cm.exception))
|
||||
|
||||
def test_valid_baudrates(self):
|
||||
"""Test that all common baudrates are accepted."""
|
||||
valid_baudrates = [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]
|
||||
for baudrate in valid_baudrates:
|
||||
validate_serial_config("/dev/ttyUSB0", baudrate, 5)
|
||||
|
||||
|
||||
class TestIntegration(unittest.TestCase):
|
||||
"""Integration tests combining multiple functions."""
|
||||
|
||||
def test_full_parsing_pipeline(self):
|
||||
"""Test complete parsing pipeline."""
|
||||
# Simulate real data from device
|
||||
raw_data = "other data SCAN_RESULT 3 [(850000, -110), (851000, -105), (852000, -100)]"
|
||||
|
||||
# Parse the data
|
||||
count, data = parse_scan_result(raw_data)
|
||||
|
||||
# Verify results
|
||||
self.assertEqual(count, 3)
|
||||
self.assertEqual(len(data), 3)
|
||||
|
||||
# Check individual values
|
||||
for freq, rssi in data:
|
||||
self.assertGreaterEqual(freq, 100000)
|
||||
self.assertLessEqual(freq, 6000000)
|
||||
self.assertGreaterEqual(rssi, -200)
|
||||
self.assertLessEqual(rssi, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run tests with verbose output
|
||||
unittest.main(verbosity=2)
|
||||
Reference in New Issue
Block a user