Phase 3: Add comprehensive documentation and tests

Co-authored-by: Genaker <9213670+Genaker@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-10 08:51:40 +00:00
parent 7fe3cc1997
commit 8c4d5267b0
6 changed files with 941 additions and 0 deletions
+138
View File
@@ -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
View File
@@ -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! 🎉
+1
View File
@@ -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:
+267
View File
@@ -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.
+216
View File
@@ -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)