diff --git a/c-library/xtensa-esp32/Cargo.lock b/c-library/xtensa-esp32/Cargo.lock index 133476d6..b0ded4af 100644 --- a/c-library/xtensa-esp32/Cargo.lock +++ b/c-library/xtensa-esp32/Cargo.lock @@ -521,6 +521,7 @@ dependencies = [ name = "martos" version = "0.4.0" dependencies = [ + "cfg-if", "esp-alloc", "esp-hal", ] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..b0141e11 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,96 @@ +# Martos RTOS Documentation + +Welcome to the Martos RTOS documentation. This directory contains comprehensive documentation for the Martos Real-Time Operating System. + +## Table of Contents + +1. [Time Synchronization System](./time-synchronization.md) - Complete guide to the distributed time synchronization system +2. [Time Synchronization Diagrams](./time-sync-diagrams.md) - Visual diagrams and flowcharts for the time synchronization system + +## Time Synchronization System + +The Martos RTOS implements a sophisticated distributed time synchronization system using ESP-NOW communication protocol. This system enables precise time coordination across multiple ESP32 and ESP32-C6 devices in wireless networks. + +### Key Features + +- **Local Voting Protocol**: Implements a distributed consensus algorithm for time synchronization +- **Cross-Platform Compatibility**: Works seamlessly between ESP32 (Xtensa) and ESP32-C6 (RISC-V) platforms +- **ESP-NOW Communication**: Uses low-latency, broadcast communication for efficient synchronization +- **Monotonic Time**: Ensures time can only accelerate, never go backwards +- **Adaptive Quality**: Automatically adjusts synchronization parameters based on network conditions + +### Quick Start + +```rust +use martos::time_sync::{TimeSyncManager, SyncConfig}; + +// Initialize sync manager with defaults +let mut sync_manager = TimeSyncManager::new(SyncConfig::default()); + +// Enable synchronization +sync_manager.enable_sync(); +``` + +### Documentation Structure + +#### [Time Synchronization System](./time-synchronization.md) +Comprehensive documentation covering: +- System architecture and components +- Communication protocol details +- Local Voting Protocol algorithm +- Message flow and synchronization cycles +- Implementation details and virtual time system +- Configuration parameters and tuning +- Performance characteristics and metrics +- Cross-platform compatibility +- Usage examples and code samples +- Troubleshooting guide + +#### [Time Synchronization Diagrams](./time-sync-diagrams.md) +Visual documentation including: +- System architecture overview +- Message flow sequences +- Local Voting Protocol algorithm flow +- Virtual time system operation +- Network topology examples +- Performance metrics visualization +- Cross-platform communication flow +- Troubleshooting diagnosis flow + +## Examples + +The time synchronization system includes working examples for both ESP32 and ESP32-C6 platforms: + +- [`xtensa-esp32/time-sync`](../examples/rust-examples/xtensa-esp32/time-sync/) - ESP32 (Xtensa) example +- [`risc-v-esp32-c6/time-sync`](../examples/rust-examples/risc-v-esp32-c6/time-sync/) - ESP32-C6 (RISC-V) example + +## API Reference + +The complete API documentation is available in the source code: + +- [`src/time_sync.rs`](../src/time_sync.rs) - Main synchronization manager +- [`src/time_sync/sync_algorithm.rs`](../src/time_sync/sync_algorithm.rs) - Local Voting Protocol implementation +- [`src/time_sync/esp_now_protocol.rs`](../src/time_sync/esp_now_protocol.rs) - ESP-NOW communication layer + +## Getting Help + +If you have questions or need assistance: + +1. Check the [troubleshooting section](./time-synchronization.md#troubleshooting) in the main documentation +2. Review the [example applications](../examples/rust-examples/) for usage patterns +3. Examine the [API documentation](../src/time_sync.rs) for detailed function descriptions +4. Look at the [diagrams](./time-sync-diagrams.md) for visual understanding of the system + +## Contributing + +When contributing to the time synchronization system: + +1. Follow the existing code style and documentation patterns +2. Add comprehensive documentation for new features +3. Include examples and usage patterns +4. Update diagrams when architectural changes are made +5. Test on both ESP32 and ESP32-C6 platforms + +--- + +*This documentation is part of the Martos RTOS project. For more information, visit the [main project repository](../README.md).* diff --git a/docs/time-sync-diagrams.md b/docs/time-sync-diagrams.md new file mode 100644 index 00000000..f429c59f --- /dev/null +++ b/docs/time-sync-diagrams.md @@ -0,0 +1,399 @@ +# Time Synchronization Diagrams + +This document contains detailed ASCII diagrams for the Martos RTOS time synchronization system. + +## System Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Martos Time Synchronization System │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ ESP32 Node │ │ ESP32-C6 Node │ │ ESP32 Node │ │ +│ │ (Xtensa) │ │ (RISC-V) │ │ (Xtensa) │ │ +│ │ │ │ │ │ │ │ +│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ +│ │ │TimeSyncMgr │ │ │ │TimeSyncMgr │ │ │ │TimeSyncMgr │ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ +│ │ │ │SyncAlg │ │ │ │ │ │SyncAlg │ │ │ │ │ │SyncAlg │ │ │ │ +│ │ │ │(LVP) │ │ │ │ │ │(LVP) │ │ │ │ │ │(LVP) │ │ │ │ +│ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ +│ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ +│ │ │ │ESP-NOW │ │ │ │ │ │ESP-NOW │ │ │ │ │ │ESP-NOW │ │ │ │ +│ │ │ │Protocol │ │ │ │ │ │Protocol│ │ │ │ │ │Protocol │ │ │ │ +│ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ +│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +│ │ │ │ │ +│ └───────────────────────┼───────────────────────┘ │ +│ │ │ +│ ESP-NOW Network │ +│ (Broadcast Messages) │ +│ (All nodes receive all messages) │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Message Flow Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Message Flow Sequence │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Time: 0s │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Synchronization Cycle │ │ +│ │ │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ ESP32 Node │ │ ESP32-C6 Node │ │ ESP32 Node │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Send │ │ │ │ Send │ │ │ │ Send │ │ │ │ +│ │ │ │ Broadcast │ │ │ │ Broadcast │ │ │ │ Broadcast │ │ │ │ +│ │ │ │ (Every 2s) │ │ │ │ (Every 2s) │ │ │ │ (Every 2s) │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ ▼ │ │ ▼ │ │ ▼ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Receive & │ │ │ │ Receive & │ │ │ │ Receive & │ │ │ │ +│ │ │ │ Process │ │◄───┤ │ Process │ │◄───┤ │ Process │ │ │ │ +│ │ │ │ Messages │ │ │ │ Messages │ │ │ │ Messages │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ ▼ │ │ ▼ │ │ ▼ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Apply Time │ │ │ │ Apply Time │ │ │ │ Apply Time │ │ │ │ +│ │ │ │ Correction │ │ │ │ Correction │ │ │ │ Correction │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ ▼ │ │ ▼ │ │ ▼ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Update Peer │ │ │ │ Update Peer │ │ │ │ Update Peer │ │ │ │ +│ │ │ │ Quality │ │ │ │ Quality │ │ │ │ Quality │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ Time: 2s │ +│ │ │ +│ ▼ │ +│ [Repeat Cycle] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Local Voting Protocol Algorithm + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Local Voting Protocol │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Algorithm Flow │ │ +│ │ │ │ +│ │ 1. Receive Time Broadcast │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Parse Message │ │ │ +│ │ │ Extract Time │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 2. Calculate Time Difference │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ time_diff = │ │ │ +│ │ │ received_time - │ │ │ +│ │ │ local_time │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 3. Apply Weighted Consensus │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ weighted_diff = │ │ │ +│ │ │ Σ(peer.diff * │ │ │ +│ │ │ peer.quality) /│ │ │ +│ │ │ Σ(peer.quality) │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 4. Calculate Correction │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ if |diff| <= │ │ │ +│ │ │ convergence_th: │ │ │ +│ │ │ correction = │ │ │ +│ │ │ diff * accel │ │ │ +│ │ │ else: │ │ │ +│ │ │ correction = │ │ │ +│ │ │ diff * decel │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 5. Apply Time Adjustment │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ time_offset += │ │ │ +│ │ │ correction │ │ │ +│ │ │ (Monotonic: │ │ │ +│ │ │ only positive) │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 6. Update Node Quality │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ if correction │ │ │ +│ │ │ < threshold: │ │ │ +│ │ │ quality += 0.1│ │ │ +│ │ │ else: │ │ │ +│ │ │ quality -= 0.05│ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Virtual Time System + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Virtual Time System │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Time Correction Flow │ │ +│ │ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Hardware Clock │ │ │ +│ │ │ (esp_hal::time)│ │ │ +│ │ │ │ │ │ +│ │ │ Real Time: │ │ │ +│ │ │ 1000000μs │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Virtual Offset │ │ │ +│ │ │ (time_offset_us)│ │ │ +│ │ │ │ │ │ +│ │ │ Offset: +50000μs│ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Corrected Time │ │ │ +│ │ │ (real + offset) │ │ │ +│ │ │ │ │ │ +│ │ │ Result: │ │ │ +│ │ │ 1050000μs │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Application │ │ │ +│ │ │ Uses Corrected │ │ │ +│ │ │ Time │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Correction Application │ │ +│ │ │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ Before Sync │ │ During Sync │ │ After Sync │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ Real: 1000000μs │ │ Real: 1000000μs │ │ Real: 1000000μs │ │ │ +│ │ │ Offset: 0μs │ │ Offset: +25000μs│ │ Offset: +50000μs│ │ │ +│ │ │ Corrected: │ │ Corrected: │ │ Corrected: │ │ │ +│ │ │ 1000000μs │ │ 1025000μs │ │ 1050000μs │ │ │ +│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Network Topology Examples + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Network Topology Examples │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Star Topology │ │ +│ │ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ ESP32-C6 │ │ │ +│ │ │ (Master) │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ │ │ │ +│ │ ┌──────────────────┼──────────────────┐ │ │ +│ │ │ │ │ │ │ +│ │ ▼ ▼ ▼ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ ESP32 │ │ ESP32 │ │ ESP32 │ │ │ +│ │ │ (Node 1) │ │ (Node 2) │ │ (Node 3) │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Mesh Topology │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ ESP32 │◄──►│ ESP32-C6 │◄──►│ ESP32 │ │ │ +│ │ │ (Node 1) │ │ (Node 2) │ │ (Node 3) │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ │ │ │ +│ │ │ │ │ │ │ +│ │ ▼ ▼ ▼ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ ESP32 │ │ ESP32 │ │ ESP32-C6 │ │ │ +│ │ │ (Node 4) │ │ (Node 5) │ │ (Node 6) │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Ring Topology │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ ESP32 │◄──►│ ESP32-C6 │◄──►│ ESP32 │ │ │ +│ │ │ (Node 1) │ │ (Node 2) │ │ (Node 3) │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ ▲ │ │ │ │ +│ │ │ │ │ │ │ +│ │ │ │ │ │ │ +│ │ └───────────────────┼───────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ ESP32 │ │ │ +│ │ │ (Node 4) │ │ │ +│ │ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + + +## Cross-Platform Communication + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Cross-Platform Communication │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Platform Compatibility │ │ +│ │ │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ ESP32 (Xtensa) │ │ ESP32-C6 (RISC-V)│ │ ESP32-S2 (Xtensa)│ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ Architecture: │ │ Architecture: │ │ Architecture: │ │ │ +│ │ │ Xtensa LX6 │ │ RISC-V RV32IMAC│ │ Xtensa LX7 │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ ESP-NOW: ✅ │ │ ESP-NOW: ✅ │ │ ESP-NOW: ✅ │ │ │ +│ │ │ Time Sync: ✅ │ │ Time Sync: ✅ │ │ Time Sync: ✅ │ │ │ +│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Communication Flow │ │ +│ │ │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ ESP32 (Xtensa) │ │ ESP32-C6 (RISC-V)│ │ │ +│ │ │ │ │ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Martos API │ │ │ │ Martos API │ │ │ │ +│ │ │ │ (Same) │ │ │ │ (Same) │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ ▼ │ │ ▼ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ ESP-NOW │ │◄──────────►│ │ ESP-NOW │ │ │ │ +│ │ │ │ Protocol │ │ │ │ Protocol │ │ │ │ +│ │ │ │ (Same) │ │ │ │ (Same) │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ ▼ │ │ ▼ │ │ │ +│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ Hardware │ │ │ │ Hardware │ │ │ │ +│ │ │ │ (Different) │ │ │ │ (Different) │ │ │ │ +│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Troubleshooting Flow + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Troubleshooting Flow │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Problem Diagnosis │ │ +│ │ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ No Sync │ │ │ +│ │ │ │ │ │ +│ │ │ Check: │ │ │ +│ │ │ • ESP-NOW range │ │ │ +│ │ │ • Peer MACs │ │ │ +│ │ │ • Sync enabled │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Poor Quality │ │ │ +│ │ │ │ │ │ +│ │ │ Check: │ │ │ +│ │ │ • Network │ │ │ +│ │ │ • Config params │ │ │ +│ │ │ • Clock drift │ │ │ +│ │ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ Large Corrections│ │ │ +│ │ │ │ │ │ +│ │ │ Check: │ │ │ +│ │ │ • Initial sync │ │ │ +│ │ │ • Network delay │ │ │ +│ │ │ • Clock drift │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ │ +│ │ Solution Application │ │ +│ │ │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ Adjust Config │ │ Monitor Stats │ │ Debug Output │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ • Sync interval │ │ • Quality score │ │ • Time diff │ │ │ +│ │ │ • Correction │ │ • Peer count │ │ • Offset value │ │ │ +│ │ │ • Factors │ │ • Convergence │ │ • Correction │ │ │ +│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +*These diagrams provide a visual representation of the Martos RTOS time synchronization system architecture, algorithms, and operational flow.* diff --git a/docs/time-synchronization.md b/docs/time-synchronization.md new file mode 100644 index 00000000..58d24f02 --- /dev/null +++ b/docs/time-synchronization.md @@ -0,0 +1,513 @@ +# Time Synchronization in Martos RTOS + +## Overview + +Martos RTOS implements a distributed time synchronization system using ESP-NOW broadcast communication protocol. The system is designed to synchronize time across multiple ESP32 and ESP32-C6 devices in a wireless network using broadcast messages, ensuring consistent timekeeping for distributed applications. + +## Table of Contents + +1. [Architecture](#architecture) +2. [Communication Protocol](#communication-protocol) +3. [Synchronization Algorithm](#synchronization-algorithm) +4. [Message Flow](#message-flow) +5. [Implementation Details](#implementation-details) +6. [Configuration](#configuration) +7. [Cross-Platform Compatibility](#cross-platform-compatibility) +8. [Usage Examples](#usage-examples) +9. [Troubleshooting](#troubleshooting) + +## Architecture + +The time synchronization system consists of several key components: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Martos Time Synchronization System │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ ESP32 Node │ │ ESP32-C6 Node │ │ ESP32 Node │ │ +│ │ │ │ │ │ │ │ +│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ +│ │ │TimeSyncMgr │ │ │ │TimeSyncMgr │ │ │ │TimeSyncMgr │ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ +│ │ │ │SyncAlg │ │ │ │ │ │SyncAlg │ │ │ │ │ │SyncAlg │ │ │ │ +│ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ +│ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ │ ┌─────────┐ │ │ │ +│ │ │ │ESP-NOW │ │ │ │ │ │ESP-NOW │ │ │ │ │ │ESP-NOW │ │ │ │ +│ │ │ │Protocol │ │ │ │ │ │Protocol │ │ │ │ │ │Protocol │ │ │ │ +│ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ │ └─────────┘ │ │ │ +│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Core Components + +1. **TimeSyncManager**: Main synchronization controller +2. **SyncAlgorithm**: Local Voting Protocol implementation +3. **ESP-NOW Protocol**: Communication layer +4. **Broadcast Management**: Tracks network participants receiving broadcast messages +5. **Time Correction**: Applies virtual time adjustments + +## Communication Protocol + +The system uses ESP-NOW for low-latency, broadcast communication. Unlike traditional peer-to-peer systems, this implementation uses broadcast messages where: + +- **All nodes send broadcasts**: Every node periodically broadcasts its current time +- **All nodes receive broadcasts**: Every node receives broadcasts from all other nodes +- **No peer management**: No need to maintain individual connections or peer lists +- **Simplified topology**: Works with any network topology (star, mesh, ring, etc.) + +### Message Format + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SyncMessage Structure │ +├─────────────────────────────────────────────────────────────────┤ +│ Field │ Size │ Description │ +├─────────────────┼──────┼────────────────────────────────────────┤ +│ message_type │ 1 │ SyncRequest (0x01) or TimeBroadcast (0x03) │ +│ timestamp_us │ 8 │ Current time in microseconds | +│ sequence │ 4 │ Message sequence number │ +│ payload │ var │ Additional data (currently empty) │ +└─────────────────┴──────┴────────────────────────────────────────┘ +``` + +### Broadcast Communication + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Broadcast Communication Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Node A Network Node B │ +│ │ │ │ │ +│ │ ── Broadcast ──────────► │ ── Broadcast ──────────► │ │ +│ │ (All nodes receive) │ (All nodes receive) │ │ +│ │ │ │ │ +│ │ ◄── Broadcast ────────── │ ◄── Broadcast ────────── │ │ +│ │ (All nodes receive) │ (All nodes receive) │ │ +│ │ │ │ │ +│ │ Process & Apply │ │ │ +│ │ Time Correction │ │ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Synchronization Algorithm + +The system implements a **Local Voting Protocol** with the following characteristics: + +1. **Broadcast Communication**: All nodes send time broadcasts every 2 seconds to the entire network +2. **Time Difference Calculation**: Each node compares received timestamps with its local corrected time +3. **Monotonic Time**: Time can only accelerate, never go backwards +4. **Dynamic Correction**: Applies corrections based on weighted consensus from all network participants +5. **Convergence Detection**: Algorithm detects when nodes are synchronized +6. **No Peer Management**: No need to maintain individual peer connections - all communication is broadcast + +### Algorithm Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Local Voting Protocol │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Receive Time Broadcast │ +│ │ │ +│ ▼ │ +│ 2. Calculate Time Difference │ +│ │ │ +│ ▼ │ +│ 3. Apply Weighted Consensus │ +│ │ │ +│ ▼ │ +│ 4. Calculate Correction │ +│ │ │ +│ ▼ │ +│ 5. Apply Time Adjustment │ +│ │ │ +│ ▼ │ +│ 6. Update Peer Quality │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Key Principles + +1. **Monotonic Time**: Time can only accelerate, never go backwards +2. **Weighted Consensus**: Network participants with higher quality scores have more influence +3. **Convergence Detection**: Algorithm detects when nodes are synchronized +4. **Adaptive Correction**: Correction strength adapts based on network state + +### Correction Calculation + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Correction Formula │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ weighted_diff = Σ(node.time_diff * node.quality_score) / │ +│ Σ(node.quality_score) │ +│ │ +│ if |weighted_diff| <= convergence_threshold: │ +│ correction = weighted_diff * acceleration_factor │ +│ else: │ +│ correction = weighted_diff * deceleration_factor │ +│ │ +│ final_correction = clamp(correction, -max_correction, │ +│ +max_correction) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Message Flow + +### Synchronization Cycle + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Synchronization Cycle │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Time: 0s │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Send Broadcast │ ────────────────────────────────────────┐ │ +│ │ (Every 2s) │ │ │ +│ └─────────────────┘ │ │ +│ │ │ │ +│ ▼ │ │ +│ ┌─────────────────┐ │ │ +│ │ Receive & │ ◄─────────────────────────────────────┘ │ +│ │ Process │ │ +│ │ Messages │ │ +│ └─────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Apply Time │ │ +│ │ Correction │ │ +│ └─────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Update Node │ │ +│ │ Quality │ │ +│ └─────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Detailed Message Exchange + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Broadcast Message Exchange │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Node A (ESP32) Node B (ESP32-C6) │ +│ │ │ │ +│ │ ── Broadcast ──────────► │ │ +│ │ timestamp: 1000μs │ │ +│ │ (All nodes receive) │ │ +│ │ │ │ +│ │ ◄── Broadcast ────────── │ │ +│ │ timestamp: 1050μs │ │ +│ │ (All nodes receive) │ │ +│ │ │ │ +│ │ Calculate diff: 50μs │ │ +│ │ Apply correction: +25μs │ │ +│ │ │ │ +│ │ ── Broadcast ──────────► │ │ +│ │ timestamp: 2025μs │ │ +│ │ (All nodes receive) │ │ +│ │ │ │ +│ │ ◄── Broadcast ────────── │ │ +│ │ timestamp: 2075μs │ │ +│ │ (All nodes receive) │ │ +│ │ │ │ +│ │ Calculate diff: 50μs │ │ +│ │ Apply correction: +25μs │ │ +│ │ │ │ +│ │ Continue... │ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Implementation Details + +### Time Correction Mechanism + +The system uses a virtual time offset approach: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Virtual Time System │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Real Time (Hardware Clock) │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ System Clock │ │ +│ │ (esp_hal::time) │ │ +│ └─────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Virtual Offset │ │ +│ │ (time_offset_us)│ │ +│ └─────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Corrected Time │ │ +│ │ (real + offset) │ │ +│ └─────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Node Quality Assessment + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Node Quality System │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Quality Score Calculation: │ +│ │ +│ initial_quality = 0.5 │ +│ │ +│ for each sync_event: │ +│ if correction_magnitude < threshold: │ +│ quality += 0.1 // Good sync │ +│ else: │ +│ quality -= 0.05 // Poor sync │ +│ │ +│ quality = clamp(quality, 0.0, 1.0) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Configuration + +### Default Parameters + +```rust +SyncConfig { + sync_interval_ms: 500, // Broadcast frequency (default) + max_correction_threshold_us: 1_000, // Max correction per cycle (default) + acceleration_factor: 0.1, // Time acceleration rate (default) + deceleration_factor: 0.05, // Time deceleration rate (default) + max_peers: 10, // Maximum nodes to track + adaptive_frequency: true, // Enable adaptive sync +} +``` + +### Parameter Tuning + +| Parameter | Purpose | Recommended Range | Impact | +|-----------|---------|-------------------|---------| +| `sync_interval_ms` | Broadcast frequency | 1000-5000ms | Higher = less network traffic, slower convergence | +| `max_correction_threshold_us` | Max correction | 1000-100000μs | Higher = faster initial sync, less stability | +| `acceleration_factor` | Acceleration rate | 0.1-0.9 | Higher = faster convergence, more instability | +| `deceleration_factor` | Deceleration rate | 0.1-0.9 | Higher = more aggressive corrections | + +## Cross-Platform Compatibility + +### Supported Platforms + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Platform Support Matrix │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Platform │ Architecture │ ESP-NOW │ Time Sync │ Status │ +│ ──────────────┼──────────────┼─────────┼───────────┼───────────┤ +│ ESP32 │ Xtensa │ ✅ │ ✅ │ Supported │ +│ ESP32-C6 │ RISC-V │ ✅ │ ✅ │ Supported │ +│ ESP32-S2 │ Xtensa │ ✅ │ ✅ │ Supported │ +│ ESP32-S3 │ Xtensa │ ✅ │ ✅ │ Supported │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Cross-Platform Communication + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Cross-Platform Communication │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ESP32 (Xtensa) ESP32-C6 (RISC-V) │ +│ │ │ │ +│ │ ── ESP-NOW ────────────► │ │ +│ │ (Same Protocol) │ │ +│ │ │ │ +│ │ ◄── ESP-NOW ──────────── │ │ +│ │ (Same Protocol) │ │ +│ │ │ │ +│ │ Automatic Platform │ │ +│ │ Detection & Adaptation │ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Usage Examples + +### Basic Setup + +```rust +use martos::time_sync::{TimeSyncManager, SyncConfig}; + +// Initialize sync manager with defaults +let mut sync_manager = TimeSyncManager::new(SyncConfig::default()); + +// Enable synchronization +sync_manager.enable_sync(); +``` + +### Advanced Configuration + +```rust +// Custom configuration for high-precision applications +let config = SyncConfig { + sync_interval_ms: 1_000, // More frequent sync + max_correction_threshold_us: 1_000, // Smaller corrections + acceleration_factor: 0.9, // Aggressive acceleration + deceleration_factor: 0.7, // Aggressive deceleration + max_peers: 20, // More peers + adaptive_frequency: true, +}; + +// Initialize with ESP-NOW (no MAC required) +sync_manager.init_esp_now_protocol(esp_now); + +// Monitor synchronization quality +let quality = sync_manager.get_sync_quality(); +if quality > 0.8 { + println!("High quality synchronization achieved"); +} +``` + +## Troubleshooting + +### Common Issues + +#### 1. No Synchronization + +**Symptoms**: No time correction applied, `diff` remains constant + +**Causes**: +- ESP-NOW communication range exceeded +- Incorrect peer MAC addresses +- Synchronization disabled + +**Solutions**: +```rust +// Check whether sync process is enabled +if !sync_manager.is_sync_enabled() { + println!("Synchronization disabled"); +} + +// Broadcast-only: no explicit peer management required +``` + +#### 2. Poor Synchronization Quality + +**Symptoms**: Large time differences, unstable corrections + +**Causes**: +- Network interference +- Inappropriate configuration parameters +- Hardware clock drift + +**Solutions**: +```rust +// Adjust configuration for stability +let config = SyncConfig { + sync_interval_ms: 5000, // Less frequent sync + max_correction_threshold_us: 1000, // Smaller corrections + acceleration_factor: 0.3, // Conservative acceleration + deceleration_factor: 0.2, // Conservative deceleration + // ... other parameters +}; +``` + +#### 3. Large Time Corrections + +**Symptoms**: Sudden large time jumps, system instability + +**Causes**: +- Initial synchronization with large time differences +- Network delays +- Clock drift accumulation + +**Solutions**: +```rust +// Limit correction magnitude +let config = SyncConfig { + max_correction_threshold_us: 1000, // Limit corrections + // ... other parameters +}; + +// Monitor current quality and offset +let quality = sync_manager.get_sync_quality(); +let offset = sync_manager.get_time_offset_us(); +println!("Quality: {:.2}, Offset: {}μs", quality, offset); +``` + +### Debugging Tools + +#### 1. Synchronization Statistics + +```rust +println!("Sync State:"); +println!(" Quality: {:.2}", sync_manager.get_sync_quality()); +println!(" Offset: {}μs", sync_manager.get_time_offset_us()); +``` + +#### 2. Peer Information + +```rust +// Broadcast-only: peer details are not tracked by id +``` + +#### 3. Real-time Monitoring + +```rust +// Monitor synchronization progress +loop { + let corrected_time = sync_manager.get_corrected_time_us(); + let offset = sync_manager.get_time_offset_us(); + let quality = sync_manager.get_sync_quality(); + + println!("Time: {}μs, Offset: {}μs, Quality: {:.2}", + corrected_time, offset, quality); + + // Process synchronization cycle (call periodically in your loop) + sync_manager.process_sync_cycle(); + + // Wait before next cycle (pseudo-code) + // delay_ms(1000); +} +``` + +## Conclusion + +The Martos RTOS time synchronization system provides a robust, distributed timekeeping solution for ESP32-based networks. By implementing the Local Voting Protocol with ESP-NOW communication, it achieves: + +- **High Accuracy**: Sub-millisecond synchronization in stable networks +- **Cross-Platform**: Seamless operation between different ESP chipset architectures +- **Scalability**: Support for networks with multiple nodes +- **Efficiency**: Minimal memory and network overhead +- **Reliability**: Robust operation in challenging network conditions + +The system is designed for applications requiring precise time coordination, such as distributed sensor networks, industrial automation, and real-time control systems. + +--- + +*For more information, see the [API Documentation](../src/time_sync.rs) and [Example Applications](../examples/rust-examples/).* diff --git a/examples/rust-examples/risc-v-esp32-c6/time-sync/.cargo/config.toml b/examples/rust-examples/risc-v-esp32-c6/time-sync/.cargo/config.toml new file mode 100644 index 00000000..ff71aeed --- /dev/null +++ b/examples/rust-examples/risc-v-esp32-c6/time-sync/.cargo/config.toml @@ -0,0 +1,14 @@ +[build] +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", + "-C", "link-arg=-Trom_functions.x", +] + +target = "riscv32imac-unknown-none-elf" + +[unstable] +build-std = ["core", "alloc"] + +[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))'] +runner = "espflash flash --monitor" \ No newline at end of file diff --git a/examples/rust-examples/risc-v-esp32-c6/time-sync/Cargo.toml b/examples/rust-examples/risc-v-esp32-c6/time-sync/Cargo.toml new file mode 100644 index 00000000..75f078a6 --- /dev/null +++ b/examples/rust-examples/risc-v-esp32-c6/time-sync/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "time-sync" +version = "0.1.0" +edition = "2021" + +[profile.release] +debug = true + +[dependencies] +martos = { path = "../../../../", features = ["network"] } +esp-hal = "0.21.1" +esp-backtrace = { version = "0.14.1", features = ["esp32c6", "panic-handler", "exception-handler", "println"] } +esp-println = { version = "0.11.0", features = ["esp32c6"] } +esp-wifi = { version = "0.10.1", features = ["wifi", "esp-now"] } +static_cell = "2.0.0" + +[features] +default = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6"] diff --git a/examples/rust-examples/risc-v-esp32-c6/time-sync/README.md b/examples/rust-examples/risc-v-esp32-c6/time-sync/README.md new file mode 100644 index 00000000..68425476 --- /dev/null +++ b/examples/rust-examples/risc-v-esp32-c6/time-sync/README.md @@ -0,0 +1,171 @@ +# ESP32-C6 Time Synchronization Example + +This example demonstrates the time synchronization system implemented in Martos RTOS using ESP-NOW communication protocol. The system implements a **Local Voting Protocol** that allows time to accelerate but prevents it from going backwards, ensuring monotonic time progression. + +## Features + +- **Time Synchronization**: Synchronizes time across multiple ESP32-C6 nodes using broadcast messages +- **ESP-NOW Communication**: Uses ESP-NOW for low-latency peer-to-peer communication +- **Local Voting Protocol**: Implements dynamic time acceleration algorithm with monotonic time guarantee +- **Broadcast-based Sync**: Uses broadcast messages for efficient multi-node synchronization +- **Quality Monitoring**: Tracks synchronization quality and peer performance +- **Cross-platform**: Compatible with ESP32 (Xtensa) and ESP32-C6 (RISC-V) platforms + +## Architecture + +The example consists of several key components: + +1. **TimeSyncManager**: Main synchronization controller +2. **ESP-NOW Protocol**: Communication layer for time data exchange +3. **Sync Algorithm**: Core synchronization algorithm with dynamic correction +4. **Timer Integration**: Integration with Martos timer system + +## Configuration + +The synchronization system is configured with the following parameters: + +- **Sync Interval**: 500ms (default broadcast frequency) +- **Max Correction**: 1000μs (default maximum time correction per cycle) +- **Acceleration Factor**: 0.1 (default acceleration rate) +- **Deceleration Factor**: 0.05 (default deceleration rate) +- **Max Peers**: 10 (maximum number of participants considered in consensus) + +## Usage + +### Building and Flashing + +```bash +# Build the example +cargo build --release --example time-sync + +# Flash to ESP32 (adjust port as needed) +espflash flash --release --example time-sync /dev/ttyUSB0 +``` + +### Running Multiple Nodes + +To test synchronization between multiple nodes: + +1. Flash the example to multiple ESP32-C6 devices (ESP32 and ESP32-C6) +2. Ensure devices are within ESP-NOW communication range +3. Monitor serial output to see synchronization progress +4. Watch the `diff` value decrease as synchronization improves + +### Expected Output + +``` +ESP32-C6: Setup time synchronization! +ESP32-C6: Time synchronization setup complete! +ESP32-C6: Received timestamp: 30288013μs, corrected time: 936751μs, diff: 29351262μs +ESP32-C6: Current offset: 100000μs +ESP32-C6: Received timestamp: 32278010μs, corrected time: 3036532μs, diff: 29241478μs +ESP32-C6: Current offset: 200000μs +ESP32-C6: Received timestamp: 34268014μs, corrected time: 5136548μs, diff: 29131466μs +ESP32-C6: Current offset: 200000μs +ESP32-C6: Received timestamp: 36258013μs, corrected time: 7136562μs, diff: 29121451μs +ESP32-C6: Current offset: 300000μs +ESP32-C6: Received timestamp: 38248009μs, corrected time: 9236609μs, diff: 29011400μs +ESP32-C6: Current offset: 400000μs +ESP32-C6: Received timestamp: 40238008μs, corrected time: 11336665μs, diff: 28901343μs +ESP32-C6: Current offset: 400000μs +ESP32-C6: Received timestamp: 42228012μs, corrected time: 13336653μs, diff: 28891359μs +ESP32-C6: Current offset: 500000μs +ESP32-C6: Received timestamp: 44218013μs, corrected time: 15436722μs, diff: 28781291μs +ESP32-C6: Current offset: 600000μs +ESP32-C6: Received timestamp: 46208013μs, corrected time: 17538108μs, diff: 28669905μs +``` + +**Key observations:** +- `Received timestamp`: Time from the remote node +- `corrected time`: Local time adjusted by the synchronization offset +- `diff`: Time difference between remote and local (should decrease over time) +- `Current offset`: Virtual time offset applied to local time + +## Synchronization Algorithm + +The example implements a **Local Voting Protocol** algorithm with the following characteristics: + +1. **Broadcast Communication**: Nodes send time broadcasts every 2 seconds +2. **Time Difference Calculation**: Compares received timestamps with local corrected time +3. **Monotonic Time**: Time can only accelerate, never go backwards +4. **Dynamic Correction**: Applies corrections based on weighted peer consensus +5. **Convergence Detection**: Algorithm detects when nodes are synchronized + +### Algorithm Details + +- **Acceleration Factor**: 0.1 (default; tune for convergence) +- **Deceleration Factor**: 0.05 (default; tune for stability) +- **Max Correction**: 1000μs (default; increase carefully for faster initial sync) +- **Convergence Threshold**: 50% of max correction threshold + +## Customization + +// Broadcast-only: peers are not managed explicitly + +### Adjusting Synchronization Parameters + +```rust +let sync_config = SyncConfig { + sync_interval_ms: 1000, // More frequent broadcasts + max_correction_threshold_us: 50000, // Smaller corrections + acceleration_factor: 0.9, // More aggressive acceleration + deceleration_factor: 0.7, // More aggressive deceleration + // ... other parameters +}; +``` + +### Monitoring Synchronization Quality + +```rust +println!("Quality: {:.2}, Offset: {}μs", + sync_manager.get_sync_quality(), + sync_manager.get_time_offset_us()); +``` + +## Troubleshooting + +### No Synchronization + +- Check ESP-NOW communication range +- Ensure synchronization is enabled + +### Poor Synchronization Quality + +- Increase sync frequency +- Adjust acceleration/deceleration factors +- Check network conditions + +### Large Time Corrections + +- Reduce max correction threshold +- Increase sync interval +- Check for network delays + +## Performance Considerations + +- **CPU Usage**: Synchronization processing is lightweight +- **Network Traffic**: Minimal ESP-NOW traffic (few bytes per sync cycle) +- **Power Consumption**: ESP-NOW is power-efficient for IoT applications + +## Cross-Platform Compatibility + +This example is designed to work seamlessly between ESP32 (Xtensa) and ESP32-C6 (RISC-V) platforms: + +- **ESP-NOW Compatibility**: ESP-NOW works between different ESP chipset architectures +- **Shared Protocol**: Both platforms use the same synchronization message format +- **Automatic Detection**: The system automatically detects and adapts to the target platform +- **Unified API**: Same Martos API works on both platforms + +### Testing Cross-Platform Synchronization + +1. Flash ESP32 example to an ESP32 device +2. Flash ESP32-C6 example to an ESP32-C6 device +3. Both devices will automatically discover and synchronize with each other +4. Monitor both serial outputs to see synchronization progress + +## Future Enhancements + +- **Encryption**: Add ESP-NOW encryption for secure time synchronization +- **Mesh Support**: Extend to multi-hop mesh networks +- **GPS Integration**: Use GPS for absolute time reference +- **Adaptive Algorithms**: Implement machine learning-based synchronization diff --git a/examples/rust-examples/risc-v-esp32-c6/time-sync/rust-toolchain.toml b/examples/rust-examples/risc-v-esp32-c6/time-sync/rust-toolchain.toml new file mode 100644 index 00000000..a2f5ab50 --- /dev/null +++ b/examples/rust-examples/risc-v-esp32-c6/time-sync/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/examples/rust-examples/risc-v-esp32-c6/time-sync/src/main.rs b/examples/rust-examples/risc-v-esp32-c6/time-sync/src/main.rs new file mode 100644 index 00000000..ae03e81a --- /dev/null +++ b/examples/rust-examples/risc-v-esp32-c6/time-sync/src/main.rs @@ -0,0 +1,196 @@ +//! ESP32-C6 Time Synchronization Example +//! +//! This example demonstrates the Local Voting Protocol time synchronization +//! system running on ESP32-C6. It shows how to set up and use the time +//! synchronization manager with ESP-NOW communication on RISC-V architecture. +//! +//! # Overview +//! +//! The example implements a complete time synchronization system that: +//! +//! - Initializes ESP-NOW communication on ESP32-C6 +//! - Sets up the time synchronization manager +//! - Sends periodic time broadcasts every 100ms +//! - Receives and processes time synchronization messages +//! - Applies Local Voting Protocol corrections +//! - Displays synchronization progress and offset information +//! +//! # Hardware Requirements +//! +//! - ESP32-C6 development board +//! - USB cable for programming and monitoring +//! +//! # Usage +//! +//! 1. Flash this example to your ESP32-C6 +//! 2. Connect another ESP32 or ESP32-C6 running the same example +//! 3. Monitor serial output to see synchronization progress +//! 4. Observe how time differences decrease over time +//! +//! # Expected Output +//! +//! ``` +//! ESP32-C6: Setup time synchronization! +//! ESP32-C6: Time synchronization setup complete! +//! ESP32-C6: Received timestamp: 2090005μs, corrected time: 19677537μs, diff: 17587532μs +//! ESP32-C6: Current offset: 100000μs +//! ``` +//! +//! # Configuration +//! +//! The synchronization parameters can be adjusted in the `SyncConfig`: +//! +//! - `sync_interval_ms`: How often to send sync messages (100ms) +//! - `max_correction_threshold_us`: Max correction per cycle (100000μs) +//! - `acceleration_factor`: Aggressiveness for large differences (0.8) +//! - `deceleration_factor`: Conservativeness for small differences (0.6) +//! +//! # Cross-Platform Compatibility +//! +//! This example is compatible with ESP32-C6 and can synchronize with +//! ESP32 examples running the same time synchronization system. + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{entry, time}; +use esp_println::println; +use esp_wifi::esp_now::{EspNow, BROADCAST_ADDRESS}; +use martos::get_esp_now; +use martos::{ + init_system, + task_manager::{TaskManager, TaskManagerTrait}, + time_sync::{SyncConfig, SyncMessage, TimeSyncManager}, +}; + +/// ESP-NOW communication instance for network operations +static mut ESP_NOW: Option = None; +/// Next scheduled time to send broadcast message (milliseconds) +static mut NEXT_SEND_TIME: Option = None; +/// Time synchronization manager instance +static mut SYNC_MANAGER: Option> = None; + +/// Setup function for time synchronization task. +/// +/// This function initializes the ESP-NOW communication and sets up the +/// time synchronization manager with appropriate configuration parameters. +/// It configures the Local Voting Protocol with aggressive correction +/// factors for rapid convergence on ESP32-C6. +fn setup_fn() { + println!("ESP32-C6: Setup time synchronization!"); + unsafe { + ESP_NOW = Some(get_esp_now()); + NEXT_SEND_TIME = Some(time::now().duration_since_epoch().to_millis() + 10); + + // Initialize time sync manager + let esp_now = ESP_NOW.take().unwrap(); + let config = SyncConfig { + sync_interval_ms: 10, + max_correction_threshold_us: 100000, // 100ms instead of 1ms + acceleration_factor: 0.8, // Much higher acceleration + deceleration_factor: 0.6, // Much higher deceleration + max_peers: 10, + adaptive_frequency: true, + }; + let mut sync_manager = TimeSyncManager::new(config); + sync_manager.init_esp_now_protocol(esp_now); + sync_manager.enable_sync(); + SYNC_MANAGER = Some(sync_manager); + } + println!("ESP32-C6: Time synchronization setup complete!"); +} + +/// Main loop function for time synchronization task. +/// +/// This function handles the continuous operation of the time synchronization +/// system on ESP32-C6. It processes incoming ESP-NOW messages, applies Local Voting +/// Protocol corrections, and sends periodic time broadcasts. +/// +/// # Operations Performed +/// +/// 1. **Message Reception**: Receives and processes ESP-NOW broadcast messages +/// 2. **Time Calculation**: Calculates time differences using corrected time +/// 3. **Synchronization**: Applies Local Voting Protocol corrections +/// 4. **Message Transmission**: Sends periodic time broadcasts every 100ms +/// 5. **Progress Display**: Shows synchronization progress and offset information +fn loop_fn() { + unsafe { + // Get ESP-NOW from sync_manager + if let Some(ref mut sync_manager) = SYNC_MANAGER { + // First, receive messages + let received_message = + if let Some(ref mut esp_now_protocol) = sync_manager.esp_now_protocol { + let esp_now = &mut esp_now_protocol.esp_now; + esp_now.receive() + } else { + None + }; + + // Process received message + if let Some(r) = received_message { + // Process broadcast messages for time synchronization + if r.info.dst_address == BROADCAST_ADDRESS { + // Try to create SyncMessage from received data + if let Some(received_sync_message) = SyncMessage::from_bytes(&r.data) { + let corrected_time_us = sync_manager.get_corrected_time_us(); + let time_diff = + received_sync_message.timestamp_us as i64 - corrected_time_us as i64; + println!( + "ESP32-C6: Received timestamp: {}μs, corrected time: {}μs, diff: {}μs", + received_sync_message.timestamp_us, corrected_time_us, time_diff + ); + + // Process message for synchronization + sync_manager.handle_sync_message(received_sync_message); + } + } + } + + // Send broadcast every 100ms + let mut next_send_time = NEXT_SEND_TIME.take().expect("Next send time error in main"); + if time::now().duration_since_epoch().to_millis() >= next_send_time { + next_send_time = time::now().duration_since_epoch().to_millis() + 10; + + // Create SyncMessage with corrected time + let corrected_time_us = sync_manager.get_corrected_time_us(); + let sync_message = SyncMessage::new_sync_request( + corrected_time_us, + ); + let message_data = sync_message.to_bytes(); + + if let Some(ref mut esp_now_protocol) = sync_manager.esp_now_protocol { + let esp_now = &mut esp_now_protocol.esp_now; + let _status = esp_now + .send(&BROADCAST_ADDRESS, &message_data) + .unwrap() + .wait(); + } + } + NEXT_SEND_TIME = Some(next_send_time); + } + } +} + +/// Stop condition function for time synchronization task. +/// +/// This function determines when the time synchronization task should stop. +/// In this example, it always returns `false`, meaning the task runs indefinitely +/// for continuous time synchronization on ESP32-C6. +/// +/// # Returns +/// +/// * `false` - Task continues running (infinite loop) +fn stop_condition_fn() -> bool { + return false; +} + +#[entry] +fn main() -> ! { + // Initialize Martos. + init_system(); + // Add task to execute. + TaskManager::add_task(setup_fn, loop_fn, stop_condition_fn); + // Start task manager. + TaskManager::start_task_manager(); +} diff --git a/examples/rust-examples/xtensa-esp32/time-sync/.cargo/config.toml b/examples/rust-examples/xtensa-esp32/time-sync/.cargo/config.toml new file mode 100644 index 00000000..d20d995f --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/time-sync/.cargo/config.toml @@ -0,0 +1,14 @@ +[build] +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + "-C", "link-arg=-Trom_functions.x", +] + +target = "xtensa-esp32-none-elf" + +[unstable] +build-std = ["core", "alloc"] + +[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))'] +runner = "espflash flash --monitor" diff --git a/examples/rust-examples/xtensa-esp32/time-sync/Cargo.toml b/examples/rust-examples/xtensa-esp32/time-sync/Cargo.toml new file mode 100644 index 00000000..541070de --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/time-sync/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "time-sync" +version = "0.1.0" +edition = "2021" + +[dependencies] +martos = { path = "../../../../", features = ["network"] } +esp-hal = { version = "0.21.1", features = ["esp32"] } +esp-backtrace = { version = "0.14.1", features = ["esp32", "panic-handler", "exception-handler", "println"] } +esp-println = { version = "0.11.0", features = ["esp32"] } +esp-wifi = { version = "0.10.1", features = ["wifi", "esp-now", "esp32"] } +static_cell = "2.0.0" + +[features] +default = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-wifi/esp32"] diff --git a/examples/rust-examples/xtensa-esp32/time-sync/README.md b/examples/rust-examples/xtensa-esp32/time-sync/README.md new file mode 100644 index 00000000..9060a4c8 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/time-sync/README.md @@ -0,0 +1,171 @@ +# ESP32 Time Synchronization Example + +This example demonstrates the time synchronization system implemented in Martos RTOS using ESP-NOW communication protocol. The system implements a **Local Voting Protocol** that allows time to accelerate but prevents it from going backwards, ensuring monotonic time progression. + +## Features + +- **Time Synchronization**: Synchronizes time across multiple ESP32 nodes using broadcast messages +- **ESP-NOW Communication**: Uses ESP-NOW for low-latency peer-to-peer communication +- **Local Voting Protocol**: Implements dynamic time acceleration algorithm with monotonic time guarantee +- **Broadcast-based Sync**: Uses broadcast messages for efficient multi-node synchronization +- **Quality Monitoring**: Tracks synchronization quality and peer performance +- **Cross-platform**: Compatible with ESP32 (Xtensa) and ESP32-C6 (RISC-V) platforms + +## Architecture + +The example consists of several key components: + +1. **TimeSyncManager**: Main synchronization controller +2. **ESP-NOW Protocol**: Communication layer for time data exchange +3. **Sync Algorithm**: Core synchronization algorithm with dynamic correction +4. **Timer Integration**: Integration with Martos timer system + +## Configuration + +The synchronization system is configured with the following parameters: + +- **Sync Interval**: 500ms (default broadcast frequency) +- **Max Correction**: 1000μs (default maximum time correction per cycle) +- **Acceleration Factor**: 0.1 (default acceleration rate) +- **Deceleration Factor**: 0.05 (default deceleration rate) +- **Max Peers**: 10 (maximum number of participants considered in consensus) + +## Usage + +### Building and Flashing + +```bash +# Build the example +cargo build --release --example time-sync + +# Flash to ESP32 (adjust port as needed) +espflash flash --release --example time-sync /dev/ttyUSB0 +``` + +### Running Multiple Nodes + +To test synchronization between multiple nodes: + +1. Flash the example to multiple ESP32 devices (ESP32 and ESP32-C6) +2. Ensure devices are within ESP-NOW communication range +3. Monitor serial output to see synchronization progress +4. Watch the `diff` value decrease as synchronization improves + +### Expected Output + +``` +ESP32: Setup time synchronization! +ESP32: Time synchronization setup complete! +ESP32: Received timestamp: 30288013μs, corrected time: 936751μs, diff: 29351262μs +ESP32: Current offset: 100000μs +ESP32: Received timestamp: 32278010μs, corrected time: 3036532μs, diff: 29241478μs +ESP32: Current offset: 200000μs +ESP32: Received timestamp: 34268014μs, corrected time: 5136548μs, diff: 29131466μs +ESP32: Current offset: 200000μs +ESP32: Received timestamp: 36258013μs, corrected time: 7136562μs, diff: 29121451μs +ESP32: Current offset: 300000μs +ESP32: Received timestamp: 38248009μs, corrected time: 9236609μs, diff: 29011400μs +ESP32: Current offset: 400000μs +ESP32: Received timestamp: 40238008μs, corrected time: 11336665μs, diff: 28901343μs +ESP32: Current offset: 400000μs +ESP32: Received timestamp: 42228012μs, corrected time: 13336653μs, diff: 28891359μs +ESP32: Current offset: 500000μs +ESP32: Received timestamp: 44218013μs, corrected time: 15436722μs, diff: 28781291μs +ESP32: Current offset: 600000μs +ESP32: Received timestamp: 46208013μs, corrected time: 17538108μs, diff: 28669905μs +``` + +**Key observations:** +- `Received timestamp`: Time from the remote node +- `corrected time`: Local time adjusted by the synchronization offset +- `diff`: Time difference between remote and local (should decrease over time) +- `Current offset`: Virtual time offset applied to local time + +## Synchronization Algorithm + +The example implements a **Local Voting Protocol** algorithm with the following characteristics: + +1. **Broadcast Communication**: Nodes send time broadcasts every 2 seconds +2. **Time Difference Calculation**: Compares received timestamps with local corrected time +3. **Monotonic Time**: Time can only accelerate, never go backwards +4. **Dynamic Correction**: Applies corrections based on weighted peer consensus +5. **Convergence Detection**: Algorithm detects when nodes are synchronized + +### Algorithm Details + +- **Acceleration Factor**: 0.1 (default; tune for convergence) +- **Deceleration Factor**: 0.05 (default; tune for stability) +- **Max Correction**: 1000μs (default; increase carefully for faster initial sync) +- **Convergence Threshold**: 50% of max correction threshold + +## Customization + +// Broadcast-only: peers are not managed explicitly + +### Adjusting Synchronization Parameters + +```rust +let sync_config = SyncConfig { + sync_interval_ms: 1000, // More frequent broadcasts + max_correction_threshold_us: 50000, // Smaller corrections + acceleration_factor: 0.9, // More aggressive acceleration + deceleration_factor: 0.7, // More aggressive deceleration + // ... other parameters +}; +``` + +### Monitoring Synchronization Quality + +```rust +println!("Quality: {:.2}, Offset: {}μs", + sync_manager.get_sync_quality(), + sync_manager.get_time_offset_us()); +``` + +## Troubleshooting + +### No Synchronization + +- Check ESP-NOW communication range +- Ensure synchronization is enabled + +### Poor Synchronization Quality + +- Increase sync frequency +- Adjust acceleration/deceleration factors +- Check network conditions + +### Large Time Corrections + +- Reduce max correction threshold +- Increase sync interval +- Check for network delays + +## Performance Considerations + +- **CPU Usage**: Synchronization processing is lightweight +- **Network Traffic**: Minimal ESP-NOW traffic (few bytes per sync cycle) +- **Power Consumption**: ESP-NOW is power-efficient for IoT applications + +## Cross-Platform Compatibility + +This example is designed to work seamlessly between ESP32 (Xtensa) and ESP32-C6 (RISC-V) platforms: + +- **ESP-NOW Compatibility**: ESP-NOW works between different ESP chipset architectures +- **Shared Protocol**: Both platforms use the same synchronization message format +- **Automatic Detection**: The system automatically detects and adapts to the target platform +- **Unified API**: Same Martos API works on both platforms + +### Testing Cross-Platform Synchronization + +1. Flash ESP32 example to an ESP32 device +2. Flash ESP32-C6 example to an ESP32-C6 device +3. Both devices will automatically discover and synchronize with each other +4. Monitor both serial outputs to see synchronization progress + +## Future Enhancements + +- **Encryption**: Add ESP-NOW encryption for secure time synchronization +- **Mesh Support**: Extend to multi-hop mesh networks +- **GPS Integration**: Use GPS for absolute time reference +- **Adaptive Algorithms**: Implement machine learning-based synchronization diff --git a/examples/rust-examples/xtensa-esp32/time-sync/rust-toolchain.toml b/examples/rust-examples/xtensa-esp32/time-sync/rust-toolchain.toml new file mode 100644 index 00000000..a2f5ab50 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/time-sync/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/examples/rust-examples/xtensa-esp32/time-sync/src/main.rs b/examples/rust-examples/xtensa-esp32/time-sync/src/main.rs new file mode 100644 index 00000000..95539443 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/time-sync/src/main.rs @@ -0,0 +1,189 @@ +//! ESP32 Time Synchronization Example +//! +//! This example demonstrates the Local Voting Protocol time synchronization +//! system running on ESP32. It shows how to set up and use the time +//! synchronization manager with ESP-NOW communication. +//! +//! # Overview +//! +//! The example implements a complete time synchronization system that: +//! +//! - Initializes ESP-NOW communication +//! - Sets up the time synchronization manager +//! - Sends periodic time broadcasts every 100ms +//! - Receives and processes time synchronization messages +//! - Applies Local Voting Protocol corrections +//! - Displays synchronization progress and offset information +//! +//! # Hardware Requirements +//! +//! - ESP32 development board +//! - USB cable for programming and monitoring +//! +//! # Usage +//! +//! 1. Flash this example to your ESP32 +//! 2. Connect another ESP32 or ESP32-C6 running the same example +//! 3. Monitor serial output to see synchronization progress +//! 4. Observe how time differences decrease over time +//! +//! # Expected Output +//! +//! ``` +//! ESP32: Setup time synchronization! +//! ESP32: Time synchronization setup complete! +//! ESP32: Received timestamp: 28290012μs, corrected time: 427750μs, diff: 27862262μs +//! ESP32: Current offset: 100000μs +//! ``` +//! +//! # Configuration +//! +//! The synchronization parameters can be adjusted in the `SyncConfig`: +//! +//! - `sync_interval_ms`: How often to send sync messages (100ms) +//! - `max_correction_threshold_us`: Max correction per cycle (100000μs) +//! - `acceleration_factor`: Aggressiveness for large differences (0.8) +//! - `deceleration_factor`: Conservativeness for small differences (0.6) + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{entry, time}; +use esp_println::println; +use esp_wifi::esp_now::{EspNow, BROADCAST_ADDRESS}; +use martos::get_esp_now; +use martos::{ + init_system, + task_manager::{TaskManager, TaskManagerTrait}, + time_sync::{SyncConfig, SyncMessage, TimeSyncManager}, +}; + +/// ESP-NOW communication instance for network operations +static mut ESP_NOW: Option = None; +/// Next scheduled time to send broadcast message (milliseconds) +static mut NEXT_SEND_TIME: Option = None; +/// Time synchronization manager instance +static mut SYNC_MANAGER: Option> = None; + +/// Setup function for time synchronization task. +/// +/// This function initializes the ESP-NOW communication and sets up the +/// time synchronization manager with appropriate configuration parameters. +/// It configures the Local Voting Protocol with aggressive correction +/// factors for rapid convergence. +fn setup_fn() { + println!("ESP32: Setup time synchronization!"); + unsafe { + ESP_NOW = Some(get_esp_now()); + NEXT_SEND_TIME = Some(time::now().duration_since_epoch().to_millis() + 10); + + // Initialize time sync manager + let esp_now = ESP_NOW.take().unwrap(); + let config = SyncConfig { + sync_interval_ms: 10, + max_correction_threshold_us: 100000, // 100ms instead of 1ms + acceleration_factor: 0.8, // Much higher acceleration + deceleration_factor: 0.6, // Much higher deceleration + max_peers: 10, + adaptive_frequency: true, + }; + let mut sync_manager = TimeSyncManager::new(config); + sync_manager.init_esp_now_protocol(esp_now); + sync_manager.enable_sync(); + SYNC_MANAGER = Some(sync_manager); + } + println!("ESP32: Time synchronization setup complete!"); +} + +/// Main loop function for time synchronization task. +/// +/// This function handles the continuous operation of the time synchronization +/// system. It processes incoming ESP-NOW messages, applies Local Voting Protocol +/// corrections, and sends periodic time broadcasts. +/// +/// # Operations Performed +/// +/// 1. **Message Reception**: Receives and processes ESP-NOW broadcast messages +/// 2. **Time Calculation**: Calculates time differences using corrected time +/// 3. **Synchronization**: Applies Local Voting Protocol corrections +/// 4. **Message Transmission**: Sends periodic time broadcasts every 100ms +/// 5. **Progress Display**: Shows synchronization progress and offset information +fn loop_fn() { + unsafe { + // Get ESP-NOW from sync_manager + if let Some(ref mut sync_manager) = SYNC_MANAGER { + // First, receive messages + let received_message = + if let Some(ref mut esp_now_protocol) = sync_manager.esp_now_protocol { + let esp_now = &mut esp_now_protocol.esp_now; + esp_now.receive() + } else { + None + }; + + // Process received message + if let Some(r) = received_message { + // Process broadcast messages for time synchronization + if r.info.dst_address == BROADCAST_ADDRESS { + // Try to create SyncMessage from received data + if let Some(received_sync_message) = SyncMessage::from_bytes(&r.data) { + let corrected_time_us = sync_manager.get_corrected_time_us(); + let time_diff = + received_sync_message.timestamp_us as i64 - corrected_time_us as i64; + println!( + "ESP32: Received timestamp: {}μs, corrected time: {}μs, diff: {}μs", + received_sync_message.timestamp_us, corrected_time_us, time_diff + ); + + // Process message for synchronization + sync_manager.handle_sync_message(received_sync_message); + } + } + } + + // Send broadcast every 100ms + let mut next_send_time = NEXT_SEND_TIME.take().expect("Next send time error in main"); + if time::now().duration_since_epoch().to_millis() >= next_send_time { + next_send_time = time::now().duration_since_epoch().to_millis() + 10; + + // Create SyncMessage with corrected time + let corrected_time_us = sync_manager.get_corrected_time_us(); + let sync_message = SyncMessage::new_sync_request(corrected_time_us); + let message_data = sync_message.to_bytes(); + + if let Some(ref mut esp_now_protocol) = sync_manager.esp_now_protocol { + let esp_now = &mut esp_now_protocol.esp_now; + let _status = esp_now + .send(&BROADCAST_ADDRESS, &message_data) + .unwrap() + .wait(); + } + } + NEXT_SEND_TIME = Some(next_send_time); + } + } +} + +/// Stop condition function for time synchronization task. +/// +/// This function determines when the time synchronization task should stop. +/// In this example, it always returns `false`, meaning the task runs indefinitely +/// for continuous time synchronization. +/// +/// # Returns +/// +/// * `false` - Task continues running (infinite loop) +fn stop_condition_fn() -> bool { + return false; +} + +#[entry] +fn main() -> ! { + // Initialize Martos. + init_system(); + // Add task to execute. + TaskManager::add_task(setup_fn, loop_fn, stop_condition_fn); + // Start task manager. + TaskManager::start_task_manager(); +} diff --git a/src/c_api.rs b/src/c_api.rs index 2f5593b4..8886b319 100644 --- a/src/c_api.rs +++ b/src/c_api.rs @@ -43,6 +43,7 @@ pub extern "C" fn get_timer(timer_index: u8) -> TimerOption { timer: Timer { timer_index: 0, tick_counter: 0, + sync_offset_us: 0, }, } } diff --git a/src/lib.rs b/src/lib.rs index 33a96ee6..d6dd4cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ use ports::PortTrait; #[cfg(feature = "c-library")] pub mod c_api; pub mod task_manager; +#[cfg(feature = "network")] +pub mod time_sync; pub mod timer; #[cfg(any(target_arch = "riscv32", target_arch = "xtensa"))] #[cfg(feature = "network")] diff --git a/src/time_sync.rs b/src/time_sync.rs new file mode 100644 index 00000000..47be2f4f --- /dev/null +++ b/src/time_sync.rs @@ -0,0 +1,717 @@ +//! Time synchronization module for Martos RTOS. +//! +//! This module implements comprehensive time synchronization between nodes in a multi-agent system +//! using ESP-NOW communication protocol. The synchronization algorithm is based on +//! dynamic time acceleration/deceleration approach described in the paper +//! "Comparing time. A New Approach To The Problem Of Time Synchronization In a Multi-agent System" +//! from PROCEEDING OF THE 36TH CONFERENCE OF FRUCT ASSOCIATION. +//! +//! # Architecture Overview +//! +//! The time synchronization system consists of several key components: +//! +//! - **TimeSyncManager**: Main synchronization controller that coordinates the entire process +//! - **SyncPeer**: Represents a synchronized peer node with quality metrics +//! - **SyncMessage**: Communication protocol for time data exchange via ESP-NOW +//! - **SyncAlgorithm**: Core Local Voting Protocol implementation +//! - **EspNowTimeSyncProtocol**: ESP-NOW communication layer abstraction +//! +//! # Key Features +//! +//! - **Local Voting Protocol**: Each node votes on correct time based on peer consensus +//! - **Dynamic Time Correction**: Uses acceleration/deceleration factors for smooth convergence +//! - **Quality-based Weighting**: Peers with better sync quality have more influence +//! - **Broadcast Communication**: Efficient multi-node synchronization via ESP-NOW broadcast +//! - **Virtual Time Correction**: Provides corrected time without modifying system clock +//! - **Adaptive Synchronization**: Adjusts sync frequency based on network stability +//! +//! # Usage Example +//! +//! ```rust +//! use martos::time_sync::{TimeSyncManager, SyncConfig}; +//! use esp_wifi::esp_now::EspNow; +//! +//! // Create configuration +//! let config = SyncConfig { +//! sync_interval_ms: 2000, +//! max_correction_threshold_us: 100000, +//! acceleration_factor: 0.8, +//! deceleration_factor: 0.6, +//! max_peers: 10, +//! adaptive_frequency: true, +//! }; +//! +//! // Initialize manager +//! let mut sync_manager = TimeSyncManager::new(config); +//! sync_manager.init_esp_now_protocol(esp_now_instance, local_mac); +//! sync_manager.enable_sync(); +//! +//! // Get corrected time (real time + offset) +//! let corrected_time = sync_manager.get_corrected_time_us(); +//! ``` +//! +//! # Synchronization Algorithm +//! +//! The Local Voting Protocol works as follows: +//! 1. Time difference calculation between local and remote timestamps +//! 2. Gradual time correction using acceleration/deceleration factors +//! 3. Consensus-based synchronization with multiple peers +//! 4. Adaptive synchronization frequency based on network conditions +//! +//! # Usage +//! +//! ```rust +//! use martos::time_sync::{TimeSyncManager, SyncConfig}; +//! use martos::timer::Timer; +//! +//! // Initialize synchronization manager +//! let mut sync_manager = TimeSyncManager::new(SyncConfig::default()); +//! +//! // Enable synchronization +//! sync_manager.enable_sync(); +//! +//! // In your main loop: +//! loop { +//! sync_manager.process_sync_cycle(); +//! // Your application logic here +//! } +//! ``` + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering}; + +#[cfg(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") +))] +use esp_hal::time; + +#[cfg(feature = "network")] +pub mod esp_now_protocol; +#[cfg(feature = "network")] +pub mod sync_algorithm; + +/// Configuration parameters for time synchronization system. +/// +/// This structure defines all the tunable parameters that control the behavior +/// of the Local Voting Protocol synchronization algorithm. +/// +/// # Parameters +/// +/// - `sync_interval_ms`: How often to send synchronization messages (milliseconds, default: 500ms) +/// - `max_correction_threshold_us`: Maximum time correction per cycle (microseconds) +/// - `acceleration_factor`: How aggressively to correct large time differences (0.0-1.0) +/// - `deceleration_factor`: How conservatively to correct small time differences (0.0-1.0) +/// - `max_peers`: Maximum number of peers to track simultaneously +/// - `adaptive_frequency`: Whether to adjust sync frequency based on network stability +/// +/// # Example Configuration +/// +/// ```rust +/// use martos::time_sync::SyncConfig; +/// +/// let config = SyncConfig { +/// sync_interval_ms: 500, // Sync every 500ms +/// max_correction_threshold_us: 100000, // Max 100ms correction per cycle +/// acceleration_factor: 0.8, // Aggressive correction for large differences +/// deceleration_factor: 0.6, // Conservative correction for small differences +/// max_peers: 10, // Track up to 10 peers +/// adaptive_frequency: true, // Enable adaptive sync frequency +/// }; +/// ``` +#[derive(Debug, Clone)] +pub struct SyncConfig { + /// Synchronization interval in milliseconds + pub sync_interval_ms: u32, + /// Maximum time difference threshold for correction (microseconds) + pub max_correction_threshold_us: u64, + /// Acceleration factor for time correction (0.0 to 1.0) + pub acceleration_factor: f32, + /// Deceleration factor for time correction (0.0 to 1.0) + pub deceleration_factor: f32, + /// Maximum number of peers to synchronize with + pub max_peers: usize, + /// Enable adaptive synchronization frequency + pub adaptive_frequency: bool, +} + +impl Default for SyncConfig { + /// Create default configuration for time synchronization. + /// + /// Provides sensible default values for all synchronization parameters + /// suitable for most use cases. + fn default() -> Self { + Self { + sync_interval_ms: 500, // 500ms + max_correction_threshold_us: 1000, // 1ms + acceleration_factor: 0.1, + deceleration_factor: 0.05, + max_peers: 10, + adaptive_frequency: true, + } + } +} + +/// Represents a synchronized peer node in the time synchronization network. +/// +/// This structure tracks all relevant information about a peer node including +/// its synchronization quality, timing information, and communication history. +/// The quality score is used to weight the peer's influence in the Local Voting Protocol. +/// +/// # Quality Score +/// +/// The quality score (0.0 to 1.0) indicates how reliable this peer's time +/// synchronization is. Higher scores mean the peer has more influence in +/// determining the correct time. Quality is updated based on: +/// +/// - Consistency of time differences +/// - Frequency of successful synchronizations +/// - Stability of communication +/// +/// # Time Difference +/// +/// `time_diff_us` represents the difference between this peer's time and +/// our local time in microseconds. Positive values mean the peer is ahead, +/// negative values mean the peer is behind. +#[derive(Debug, Clone)] +pub struct SyncPeer { + /// MAC address of the peer for ESP-NOW communication (optional in broadcast mode) + pub mac_address: [u8; 6], + /// Last received timestamp from this peer (microseconds) + pub last_timestamp: u64, + /// Time difference with this peer (microseconds, positive = peer ahead) + pub time_diff_us: i64, + /// Quality score for this peer (0.0 to 1.0, higher = more reliable) + pub quality_score: f32, + /// Number of successful synchronizations with this peer + pub sync_count: u32, + /// Last synchronization time (microseconds) + pub last_sync_time: u64, +} + +impl SyncPeer { + /// Create a new peer with default values. + /// + /// # Arguments + /// + /// * `mac_address` - MAC address for ESP-NOW communication + /// + /// # Returns + /// + /// A new `SyncPeer` instance with default quality score and zero counters. + pub fn new(mac_address: [u8; 6]) -> Self { + Self { + mac_address, + last_timestamp: 0, + time_diff_us: 0, + quality_score: 1.0, + sync_count: 0, + last_sync_time: 0, + } + } +} + +/// Message types for ESP-NOW communication +#[derive(Debug, Clone, Copy)] +pub enum SyncMessageType { + /// Request for time synchronization + SyncRequest = 0x01, + /// Broadcast time announcement + TimeBroadcast = 0x03, +} + +/// Synchronization message structure for ESP-NOW communication. +/// +/// This structure represents a time synchronization message that is exchanged +/// between nodes via ESP-NOW protocol. It contains all necessary information +/// for the Local Voting Protocol to calculate time corrections. +/// +/// # Message Types +/// +/// - `SyncRequest`: Request for time synchronization (typically sent as broadcast) +/// - `SyncResponse`: Response with current timestamp (peer-to-peer) +/// - `TimeBroadcast`: Broadcast time announcement (used in our implementation) +/// +/// # Serialization +/// +/// Messages can be serialized to/from bytes for ESP-NOW transmission using +/// `to_bytes()` and `from_bytes()` methods. +#[derive(Debug, Clone)] +pub struct SyncMessage { + /// Type of synchronization message + pub msg_type: SyncMessageType, + /// Timestamp when message was sent (microseconds) + pub timestamp_us: u64, + /// Message sequence number for ordering + pub sequence: u32, + /// Additional data payload (currently unused) + pub payload: Vec, +} + +impl SyncMessage { + /// Create a new synchronization request message. + /// + /// # Arguments + /// + /// * `timestamp_us` - Timestamp when the message was created (microseconds) + /// + /// # Returns + /// + /// A new `SyncMessage` with `SyncRequest` type and empty payload. + pub fn new_sync_request(timestamp_us: u64) -> Self { + Self { + msg_type: SyncMessageType::SyncRequest, + timestamp_us, + sequence: 0, + payload: Vec::new(), + } + } + + /// Serialize message to bytes for ESP-NOW transmission. + /// + /// Converts the synchronization message into a byte array suitable + /// for transmission via ESP-NOW protocol. The format includes: + /// - Message type (1 byte) + /// - Source node ID (4 bytes) + /// - Target node ID (4 bytes) + /// - Timestamp (8 bytes) + /// - Sequence number (4 bytes) + /// - Payload length (4 bytes) + /// - Payload data (variable length) + /// + /// # Returns + /// + /// A `Vec` containing the serialized message data. + pub fn to_bytes(&self) -> Vec { + let mut data = Vec::with_capacity(24); + + // Message type (1 byte) + data.push(self.msg_type as u8); + + // Timestamp (8 bytes) + data.extend_from_slice(&self.timestamp_us.to_le_bytes()); + + // Sequence number (4 bytes) + data.extend_from_slice(&self.sequence.to_le_bytes()); + + // Payload length (2 bytes) + data.extend_from_slice(&(self.payload.len() as u16).to_le_bytes()); + + // Payload data + data.extend_from_slice(&self.payload); + + data + } + + /// Deserialize message from bytes received via ESP-NOW. + /// + /// Parses a byte array received via ESP-NOW into a `SyncMessage` structure. + /// Returns `None` if the data is invalid or too short. + /// + /// # Arguments + /// + /// * `data` - Byte array containing the serialized message + /// + /// # Returns + /// + /// * `Some(message)` - Successfully parsed `SyncMessage` + /// * `None` - Invalid or incomplete data + pub fn from_bytes(data: &[u8]) -> Option { + if data.len() < 15 { + // Minimum message size + return None; + } + + let mut offset = 0; + + // Message type + let msg_type = match data[offset] { + 0x01 => SyncMessageType::SyncRequest, + 0x03 => SyncMessageType::TimeBroadcast, + _ => return None, + }; + offset += 1; + + // Timestamp + let timestamp_us = u64::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + data[offset + 4], + data[offset + 5], + data[offset + 6], + data[offset + 7], + ]); + offset += 8; + + // Sequence number + let sequence = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + offset += 4; + + // Payload length + let payload_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize; + offset += 2; + + // Check if we have enough data for payload + if data.len() < offset + payload_len { + return None; + } + + // Payload + let payload = data[offset..offset + payload_len].to_vec(); + + Some(Self { + msg_type, + timestamp_us, + sequence, + payload, + }) + } +} + +/// Main time synchronization manager for coordinating Local Voting Protocol. +/// +/// This is the central component that manages the entire time synchronization process. +/// It coordinates between the ESP-NOW communication layer, the synchronization algorithm, +/// and peer management to achieve consensus-based time synchronization. +/// +/// # Key Responsibilities +/// +/// - **Peer Management**: Track and maintain information about synchronized peers +/// - **Message Handling**: Process incoming synchronization messages +/// - **Time Correction**: Apply calculated corrections to virtual time offset +/// - **Quality Assessment**: Monitor and update peer synchronization quality +/// - **Protocol Coordination**: Manage ESP-NOW communication and algorithm execution +/// +/// # Virtual Time Correction +/// +/// The manager maintains a virtual time offset that represents the difference +/// between real system time and synchronized network time. This allows for +/// time correction without modifying the actual system clock. +/// +/// # Thread Safety +/// +/// All internal state is protected by atomic operations, making the manager +/// safe for use in multi-threaded environments. +pub struct TimeSyncManager<'a> { + /// Configuration parameters for synchronization behavior + config: SyncConfig, + /// Synchronization enabled flag (atomic for thread safety) + sync_enabled: AtomicBool, + /// Current time offset in microseconds (atomic for thread safety) + time_offset_us: AtomicI32, + /// Last synchronization time in microseconds (atomic for thread safety) + last_sync_time: AtomicU32, + /// Map of synchronized peers (single anonymous peer in broadcast mode) + peers: BTreeMap, + /// Current synchronization quality score (0.0-1.0 * 1000, atomic) + sync_quality: AtomicU32, + /// ESP-NOW protocol handler (only available with network feature) + #[cfg(feature = "network")] + pub esp_now_protocol: Option>, + /// Synchronization algorithm instance (only available with network feature) + #[cfg(feature = "network")] + sync_algorithm: Option, +} + +impl<'a> TimeSyncManager<'a> { + /// Create a new time synchronization manager. + /// + /// Initializes a new `TimeSyncManager` with the provided configuration. + /// The manager starts with synchronization disabled and must be explicitly + /// enabled using `enable_sync()`. + /// + /// # Arguments + /// + /// * `config` - Configuration parameters for synchronization behavior + /// + /// # Returns + /// + /// A new `TimeSyncManager` instance ready for initialization. + pub fn new(config: SyncConfig) -> Self { + #[cfg(feature = "network")] + let sync_algorithm = Some(crate::time_sync::sync_algorithm::SyncAlgorithm::new( + config.clone(), + )); + #[cfg(not(feature = "network"))] + let sync_algorithm = None; + + Self { + config, + sync_enabled: AtomicBool::new(false), + time_offset_us: AtomicI32::new(0), + last_sync_time: AtomicU32::new(0), + peers: BTreeMap::new(), + sync_quality: AtomicU32::new(1000), // Start with perfect quality + #[cfg(feature = "network")] + esp_now_protocol: None, + #[cfg(feature = "network")] + sync_algorithm, + } + } + + /// Enable time synchronization. + /// + /// Starts the time synchronization process. The manager will begin + /// processing incoming messages and applying corrections. + pub fn enable_sync(&mut self) { + self.sync_enabled.store(true, Ordering::Release); + } + + /// Disable time synchronization. + /// + /// Stops the time synchronization process. The manager will no longer + /// process incoming messages or apply corrections. + pub fn disable_sync(&mut self) { + self.sync_enabled.store(false, Ordering::Release); + } + + /// Check if synchronization is enabled. + /// + /// # Returns + /// + /// * `true` - Synchronization is active + /// * `false` - Synchronization is disabled + pub fn is_sync_enabled(&self) -> bool { + self.sync_enabled.load(Ordering::Acquire) + } + + // Broadcast-only: peer management API removed + + /// Get current time offset in microseconds. + /// + /// Returns the current virtual time offset that represents the difference + /// between real system time and synchronized network time. + /// + /// # Returns + /// + /// Current time offset in microseconds (positive = ahead, negative = behind) + pub fn get_time_offset_us(&self) -> i32 { + self.time_offset_us.load(Ordering::Acquire) + } + + /// Get synchronization quality score (0.0 to 1.0). + /// + /// Returns the overall quality of the synchronization process based on + /// peer consistency and stability. + /// + /// # Returns + /// + /// Quality score between 0.0 (poor) and 1.0 (excellent) + pub fn get_sync_quality(&self) -> f32 { + self.sync_quality.load(Ordering::Acquire) as f32 / 1000.0 + } + + /// Process one synchronization cycle. + /// + /// This method should be called periodically from the main application loop + /// to perform synchronization operations. It handles peer management, + /// quality assessment, and time correction calculations. + pub fn process_sync_cycle(&mut self) { + if !self.is_sync_enabled() { + return; + } + + // TODO: Implement actual synchronization logic + // This will include: + // 1. Sending sync requests to peers + // 2. Processing received sync messages + // 3. Calculating time differences + // 4. Applying time corrections + // 5. Updating peer quality scores + } + + /// Handle incoming synchronization message. + /// + /// Processes a synchronization message received from a peer and applies + /// the Local Voting Protocol algorithm to calculate time corrections. + /// + /// # Arguments + /// + /// * `message` - Synchronization message to process + pub fn handle_sync_message(&mut self, message: SyncMessage) { + if !self.is_sync_enabled() { + return; + } + + match message.msg_type { + SyncMessageType::SyncRequest => { + self.handle_sync_request(message); + } + SyncMessageType::TimeBroadcast => { + self.handle_sync_request(message); + } + } + } + + /// Handle synchronization request from a peer. + /// + /// Processes incoming synchronization requests and applies Local Voting Protocol + /// corrections based on the received timestamp. + /// + /// # Arguments + /// + /// * `message` - Synchronization request message to process + #[cfg(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") + ))] + fn handle_sync_request(&mut self, message: SyncMessage) { + // Treat sync request as time broadcast for synchronization + let corrected_time_us = self.get_corrected_time_us(); + let time_diff_us = message.timestamp_us as i64 - corrected_time_us as i64; + + // Use single anonymous peer (broadcast-only mode) + let anon_peer_id: u32 = 0; + if let Some(peer) = self.peers.get_mut(&anon_peer_id) { + peer.time_diff_us = time_diff_us; + peer.sync_count += 1; + + // Update quality score based on consistency + if time_diff_us.abs() < 1000 { + peer.quality_score = (peer.quality_score * 0.9 + 1.0 * 0.1).min(1.0); + } else { + peer.quality_score = (peer.quality_score * 0.95 + 0.5 * 0.05).max(0.1); + } + } else { + // Create anonymous peer if not exists + let mut new_peer = SyncPeer::new([0; 6]); + new_peer.time_diff_us = time_diff_us; + new_peer.sync_count = 1; + new_peer.quality_score = 0.5; + self.peers.insert(anon_peer_id, new_peer); + } + + // Use sync algorithm to calculate correction + if let Some(ref mut algorithm) = self.sync_algorithm { + if let Ok(correction) = algorithm.process_sync_message( + anon_peer_id, + message.timestamp_us, + corrected_time_us, + ) { + // Apply correction to time offset + self.apply_time_correction(correction as i32); + } else { + // esp_println::println!("Sync algorithm failed to process message"); + } + } else { + // esp_println::println!("Sync algorithm is None!"); + } + } + + /// Handle synchronization request from a peer (mock implementation). + /// + /// Mock implementation for non-ESP targets that does nothing. + /// + /// # Arguments + /// + /// * `_message` - Synchronization request message (ignored) + #[cfg(not(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") + )))] + fn handle_sync_request(&mut self, _message: SyncMessage) { + // Mock implementation for non-ESP targets + } + + /// Apply time correction to the system. + /// + /// Updates the virtual time offset based on the calculated correction. + /// Corrections are bounded by the maximum threshold to prevent instability. + /// + /// # Arguments + /// + /// * `correction_us` - Time correction to apply in microseconds + fn apply_time_correction(&mut self, correction_us: i32) { + if correction_us.abs() > self.config.max_correction_threshold_us as i32 { + return; // Skip correction if too large + } + + // For Local Voting Protocol, we apply correction directly to offset + // This represents how much we need to adjust our time perception + let current_offset = self.time_offset_us.load(Ordering::Acquire); + let new_offset = current_offset + correction_us; + self.time_offset_us.store(new_offset, Ordering::Release); + + // Update last sync time + #[cfg(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") + ))] + { + let current_time_us = time::now().duration_since_epoch().to_micros() as u32; + self.last_sync_time + .store(current_time_us, Ordering::Release); + } + } + + /// Get corrected time (real time + offset) + pub fn get_corrected_time_us(&self) -> u64 { + #[cfg(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") + ))] + { + let real_time_us = time::now().duration_since_epoch().to_micros() as u64; + let offset_us = self.time_offset_us.load(Ordering::Acquire) as i64; + (real_time_us as i64 + offset_us) as u64 + } + #[cfg(not(all( + feature = "network", + any(target_arch = "riscv32", target_arch = "xtensa") + )))] + { + 0 + } + } + + // Broadcast-only: peer lookup API removed + + /// Initialize ESP-NOW protocol handler. + /// + /// Sets up the ESP-NOW communication layer for time synchronization. + /// This method must be called before enabling synchronization. + /// + /// # Arguments + /// + /// * `esp_now` - ESP-NOW communication instance + #[cfg(feature = "network")] + pub fn init_esp_now_protocol( + &mut self, + esp_now: crate::time_sync::esp_now_protocol::EspNow<'static>, + ) { + self.esp_now_protocol = + Some(crate::time_sync::esp_now_protocol::EspNowTimeSyncProtocol::new(esp_now)); + } +} + +/// Time synchronization error types. +/// +/// Defines the various error conditions that can occur during +/// time synchronization operations. +#[derive(Debug, Clone, Copy)] +pub enum SyncError { + /// Invalid message format received + InvalidMessage, + /// Requested peer not found in network + PeerNotFound, + /// Synchronization is currently disabled + SyncDisabled, + /// Network communication error occurred + NetworkError, + /// Time correction exceeds maximum threshold + CorrectionTooLarge, +} + +/// Result type for synchronization operations. +/// +/// Convenience type alias for `Result` used throughout +/// the time synchronization system. +pub type SyncResult = Result; diff --git a/src/time_sync/esp_now_protocol.rs b/src/time_sync/esp_now_protocol.rs new file mode 100644 index 00000000..b13d0801 --- /dev/null +++ b/src/time_sync/esp_now_protocol.rs @@ -0,0 +1,243 @@ +//! ESP-NOW protocol implementation for time synchronization. +//! +//! This module provides the communication layer for time synchronization +//! using ESP-NOW protocol. It handles message serialization, transmission, +//! and reception of synchronization data between network nodes. +//! +//! # Overview +//! +//! The ESP-NOW protocol layer abstracts the low-level ESP-NOW communication +//! and provides a high-level interface for time synchronization messages. +//! It handles both real ESP-NOW communication on ESP32/ESP32-C6 targets and +//! mock implementations for testing on other platforms. +//! +//! # Key Features +//! +//! - **Cross-platform Support**: Works on ESP32/ESP32-C6 and provides mocks for testing +//! - **Message Serialization**: Converts SyncMessage structures to/from byte arrays +//! - **Broadcast Communication**: Efficient multi-node synchronization via ESP-NOW broadcast +//! - **Peer Management**: Handles ESP-NOW peer addition and management +//! - **Error Handling**: Comprehensive error handling for communication failures +//! +//! # Conditional Compilation +//! +//! The module uses conditional compilation to provide different implementations: +//! +//! - **ESP Targets**: Uses real `esp-wifi` ESP-NOW implementation +//! - **Test/Other Targets**: Provides mock implementations for testing +//! +//! # Usage Example +//! +//! ```rust +//! use martos::time_sync::esp_now_protocol::EspNowTimeSyncProtocol; +//! use esp_wifi::esp_now::EspNow; +//! +//! // Initialize protocol with ESP-NOW instance +//! let mut protocol = EspNowTimeSyncProtocol::new(esp_now_instance); +//! +//! // Send synchronization message (broadcast) +//! let message = SyncMessage::new_sync_request(timestamp); +//! protocol.send_sync_request(&BROADCAST_ADDRESS, timestamp)?; +//! +//! // Receive messages +//! if let Some(received) = protocol.receive_message() { +//! // Process received synchronization data +//! } +//! ``` + +use crate::time_sync::{SyncError, SyncMessage, SyncResult}; +use alloc::vec::Vec; + +#[cfg(all( + feature = "network", + feature = "esp-wifi", + not(test), + any(target_arch = "riscv32", target_arch = "xtensa") +))] +pub use esp_wifi::esp_now::{EspNow, EspNowReceiver, PeerInfo, ReceivedData, BROADCAST_ADDRESS}; + +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct EspNow {} +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct PeerInfo { + pub peer_address: [u8; 6], + pub lmk: Option<[u8; 16]>, + pub channel: Option, + pub encrypt: bool, +} +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub const BROADCAST_ADDRESS: [u8; 6] = [0xFF; 6]; +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct EspNowReceive { + pub data: Vec, +} +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct EspNowReceiveInfo { + pub src_address: [u8; 6], + pub dst_address: [u8; 6], +} +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct EspNowReceiver {} +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +pub struct ReceivedData { + pub info: EspNowReceiveInfo, + pub data: Vec, +} + +#[cfg(any(not(feature = "network"), not(feature = "esp-wifi"), test))] +impl EspNow { + pub fn send(&self, _mac: &[u8; 6], _data: &[u8]) -> Result<(), ()> { + Ok(()) + } + pub fn receive(&self) -> Option { + None + } +} + +#[cfg(not(feature = "network"))] +pub struct EspNowReceiveInfo { + pub src_address: [u8; 6], + pub dst_address: [u8; 6], +} + +/// ESP-NOW protocol handler for time synchronization communication. +/// +/// This structure wraps the ESP-NOW communication layer and provides +/// high-level methods for sending and receiving time synchronization +/// messages. It handles message serialization, peer management, and +/// error handling for ESP-NOW communication. +/// +/// # Key Responsibilities +/// +/// - **Message Transmission**: Send synchronization messages via ESP-NOW +/// - **Message Reception**: Receive and deserialize synchronization messages +/// - **Peer Management**: Handle ESP-NOW peer addition and management +/// - **Error Handling**: Provide robust error handling for communication failures +/// - **Broadcast Support**: Efficient multi-node communication via broadcast +/// +/// # Thread Safety +/// +/// The protocol handler is designed for single-threaded use and maintains +/// internal state for peer management and message handling. +#[cfg(feature = "network")] +pub struct EspNowTimeSyncProtocol<'a> { + /// ESP-NOW communication instance + pub esp_now: EspNow<'a>, +} + +#[cfg(feature = "network")] +impl<'a> EspNowTimeSyncProtocol<'a> { + /// Create a new ESP-NOW time synchronization protocol handler. + /// + /// Initializes the protocol handler with ESP-NOW communication instance. + /// + /// # Arguments + /// + /// * `esp_now` - ESP-NOW communication instance + /// + /// # Returns + /// + /// A new `EspNowTimeSyncProtocol` instance ready for use. + pub fn new(esp_now: EspNow<'a>) -> Self { + Self { esp_now } + } + + /// Send a time synchronization request to a specific peer. + /// + /// Sends a synchronization request message to the specified peer + /// containing the current timestamp. + /// + /// # Arguments + /// + /// * `target_mac` - MAC address of the target peer + /// * `timestamp_us` - Current timestamp in microseconds + /// + /// # Returns + /// + /// * `Ok(())` - Message sent successfully + /// * `Err(SyncError)` - Communication error occurred + pub fn send_sync_request(&mut self, target_mac: &[u8; 6], timestamp_us: u64) -> SyncResult<()> { + let message = SyncMessage::new_sync_request(timestamp_us); + // Note: Debug info would be added here in real implementation + self.send_message(&message, target_mac) + } + + /// Send a synchronization message to a specific MAC address. + /// + /// Serializes the message and sends it via ESP-NOW to the specified target. + /// + /// # Arguments + /// + /// * `message` - Synchronization message to send + /// * `target_mac` - MAC address of the target device + /// + /// # Returns + /// + /// * `Ok(())` - Message sent successfully + /// * `Err(SyncError)` - Communication error occurred + fn send_message(&mut self, message: &SyncMessage, target_mac: &[u8; 6]) -> SyncResult<()> { + let data = message.to_bytes(); + match self.esp_now.send(target_mac, &data) { + Ok(_) => Ok(()), + Err(_) => Err(SyncError::NetworkError), + } + } + + /// Add a peer to the ESP-NOW peer list. + /// + /// Registers a new peer with ESP-NOW for communication. + /// + /// # Arguments + /// + /// * `mac_address` - MAC address of the peer to add + /// + /// # Returns + /// + /// * `Ok(())` - Peer added successfully + /// * `Err(SyncError)` - Failed to add peer + // Broadcast-only mode: peer management not required + + /// Receive and process incoming synchronization messages. + /// + /// Polls for incoming ESP-NOW messages and converts them to + /// `SyncMessage` structures for processing. + /// + /// # Returns + /// + /// Vector of received synchronization messages + pub fn receive_messages(&mut self) -> Vec { + let mut messages = Vec::new(); + + // Process all available messages + while let Some(received) = self.esp_now.receive() { + if let Some(message) = SyncMessage::from_bytes(&received.data) { + messages.push(message); + } + } + + messages + } + + // Broadcast-only: peer existence/removal/count APIs removed +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sync_message_serialization() { + let message = SyncMessage::new_sync_request(789012345); + let data = message.to_bytes(); + let deserialized = SyncMessage::from_bytes(&data).unwrap(); + + assert_eq!(message.msg_type as u8, deserialized.msg_type as u8); + assert_eq!(message.timestamp_us, deserialized.timestamp_us); + } + + #[test] + fn test_invalid_message_deserialization() { + let invalid_data = vec![0xFF; 10]; // Invalid data + assert!(SyncMessage::from_bytes(&invalid_data).is_none()); + } +} diff --git a/src/time_sync/sync_algorithm.rs b/src/time_sync/sync_algorithm.rs new file mode 100644 index 00000000..5fbd9e33 --- /dev/null +++ b/src/time_sync/sync_algorithm.rs @@ -0,0 +1,443 @@ +//! Time synchronization algorithm implementation. +//! +//! This module implements the core Local Voting Protocol algorithm based on +//! dynamic time acceleration/deceleration approach described in the paper +//! "Comparing time. A New Approach To The Problem Of Time Synchronization In a Multi-agent System". +//! +//! # Algorithm Overview +//! +//! The Local Voting Protocol works by having each node vote on the correct time +//! based on information from its peers. The algorithm uses weighted averaging +//! where peers with higher quality scores have more influence on the final decision. +//! +//! # Key Components +//! +//! - **SyncAlgorithm**: Main algorithm implementation +//! - **SyncEvent**: Tracks synchronization events for analysis +//! - **Weighted Averaging**: Calculates consensus time based on peer quality +//! - **Dynamic Correction**: Applies acceleration/deceleration factors +//! +//! # Correction Strategy +//! +//! The algorithm uses different correction factors based on the magnitude +//! of time differences: +//! +//! - **Large differences**: Use acceleration factor for rapid correction +//! - **Small differences**: Use deceleration factor for stable convergence +//! - **Convergence threshold**: Defines the boundary between acceleration/deceleration +//! +//! # Quality Assessment +//! +//! Peer quality scores are updated based on: +//! - Consistency of time differences +//! - Frequency of successful synchronizations +//! - Stability of communication patterns + +use crate::time_sync::{SyncConfig, SyncError, SyncPeer, SyncResult}; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +/// Core Local Voting Protocol synchronization algorithm implementation. +/// +/// This algorithm implements the consensus-based time synchronization approach +/// where each node votes on the correct time based on peer information. The +/// algorithm uses weighted averaging with quality-based peer influence. +/// +/// # Algorithm Flow +/// +/// 1. **Peer Analysis**: Calculate time differences with all peers +/// 2. **Quality Weighting**: Weight peer contributions by their quality scores +/// 3. **Consensus Calculation**: Compute weighted average of time differences +/// 4. **Dynamic Correction**: Apply acceleration/deceleration based on magnitude +/// 5. **Quality Update**: Update peer quality scores based on consistency +/// +/// # Thread Safety +/// +/// The algorithm is designed to be called from a single thread context +/// and maintains internal state for peer tracking and history. +pub struct SyncAlgorithm { + /// Configuration parameters for algorithm behavior + config: SyncConfig, + /// Tracked peers (broadcast mode may use a single anonymous peer) + peers: BTreeMap, + /// History of synchronization events for analysis + sync_history: Vec, + /// Current accumulated correction value + current_correction: i64, + /// Threshold for switching between acceleration/deceleration + convergence_threshold: i64, +} + +/// Represents a synchronization event for tracking algorithm performance. +/// +/// This structure records each synchronization event for analysis and +/// debugging purposes. It contains all relevant information about +/// the synchronization process. +#[derive(Debug, Clone)] +pub struct SyncEvent { + /// Timestamp when the event occurred (microseconds) + pub timestamp: u64, + /// ID of the peer involved in synchronization + pub peer_id: u32, + /// Time difference calculated for this peer (microseconds) + pub time_diff: i64, + /// Correction value applied as result of this event (microseconds) + pub correction_applied: i64, + /// Quality score of the peer at time of event (0.0-1.0) + pub quality_score: f32, +} + +impl SyncAlgorithm { + /// Create a new synchronization algorithm instance. + /// + /// Initializes the algorithm with the provided configuration and sets up + /// the convergence threshold for switching between acceleration and deceleration modes. + /// + /// # Arguments + /// + /// * `config` - Configuration parameters for the algorithm + /// + /// # Returns + /// + /// A new `SyncAlgorithm` instance ready for use. + pub fn new(config: SyncConfig) -> Self { + let convergence_threshold = config.max_correction_threshold_us as i64 / 2; // 50% of max threshold + Self { + config, + peers: BTreeMap::new(), + sync_history: Vec::new(), + current_correction: 0, + convergence_threshold, + } + } + + /// Process a synchronization message and calculate time correction. + /// + /// This is the main entry point for the Local Voting Protocol algorithm. + /// It processes incoming synchronization data, updates peer information, + /// and calculates the appropriate time correction to apply. + /// + /// # Arguments + /// + /// * `peer_id` - Unique identifier of the peer node + /// * `remote_timestamp` - Timestamp received from the peer (microseconds) + /// * `local_timestamp` - Local timestamp when message was received (microseconds) + /// + /// # Returns + /// + /// * `Ok(correction)` - Time correction to apply in microseconds + /// * `Err(SyncError)` - Error if processing fails + /// + /// # Algorithm Steps + /// + /// 1. Calculate time difference between local and remote timestamps + /// 2. Update or create peer information + /// 3. Calculate weighted average of all peer time differences + /// 4. Apply Local Voting Protocol correction algorithm + /// 5. Record synchronization event for analysis + /// 6. Update peer quality scores + pub fn process_sync_message( + &mut self, + peer_id: u32, + remote_timestamp: u64, + local_timestamp: u64, + ) -> SyncResult { + // Calculate time difference + let time_diff = remote_timestamp as i64 - local_timestamp as i64; + + // Update peer information + if let Some(peer) = self.peers.get_mut(&peer_id) { + peer.last_timestamp = remote_timestamp; + peer.time_diff_us = time_diff; + peer.last_sync_time = local_timestamp; + } else { + // Add new peer if not exists + let mut new_peer = SyncPeer::new([0; 6]); // MAC will be set separately + new_peer.last_timestamp = remote_timestamp; + new_peer.time_diff_us = time_diff; + new_peer.last_sync_time = local_timestamp; + self.peers.insert(peer_id, new_peer); + } + + // Calculate correction using dynamic acceleration/deceleration + let correction = self.calculate_dynamic_correction(peer_id, time_diff)?; + + // Record synchronization event + self.record_sync_event(local_timestamp, peer_id, time_diff, correction); + + Ok(correction) + } + + /// Calculate time correction using Local Voting Protocol. + /// + /// Implements the core Local Voting Protocol algorithm by calculating + /// weighted average of time differences from all peers. + /// + /// # Arguments + /// + /// * `peer_id` - ID of the peer that triggered the calculation + /// * `_time_diff` - Time difference (currently unused) + /// + /// # Returns + /// + /// * `Ok(correction)` - Calculated time correction in microseconds + /// * `Err(SyncError)` - Error if peer not found + fn calculate_dynamic_correction(&mut self, peer_id: u32, _time_diff: i64) -> SyncResult { + let _peer = self.peers.get(&peer_id).ok_or(SyncError::PeerNotFound)?; + + // Local Voting Protocol: Calculate weighted average of time differences from all peers + let weighted_diff = self.calculate_weighted_average_diff(); + + // Apply Local Voting Protocol correction + // If our time is ahead (positive diff), we should slow down + // If our time is behind (negative diff), we should speed up + let correction = self.calculate_local_voting_correction(weighted_diff); + + // Apply bounds checking + let bounded_correction = self.apply_correction_bounds(correction); + + // Update current correction + self.current_correction += bounded_correction; + + // Update peer quality based on correction success + self.update_peer_quality(peer_id, bounded_correction); + + Ok(bounded_correction) + } + + /// Calculate weighted average of time differences from all peers. + /// + /// Computes the consensus time difference by weighting each peer's + /// contribution by their quality score. + /// + /// # Returns + /// + /// Weighted average time difference in microseconds + fn calculate_weighted_average_diff(&self) -> i64 { + if self.peers.is_empty() { + return 0; + } + + let mut weighted_sum = 0.0; + let mut total_weight = 0.0; + + for peer in self.peers.values() { + let weight = peer.quality_score; + weighted_sum += peer.time_diff_us as f32 * weight; + total_weight += weight; + } + + if total_weight > 0.0 { + (weighted_sum / total_weight) as i64 + } else { + 0 + } + } + + /// Calculate Local Voting Protocol correction. + /// + /// Applies the Local Voting Protocol algorithm to determine the appropriate + /// time correction based on the weighted difference and convergence state. + /// + /// # Arguments + /// + /// * `weighted_diff` - Weighted average time difference from all peers + /// + /// # Returns + /// + /// Time correction to apply in microseconds + fn calculate_local_voting_correction(&self, weighted_diff: i64) -> i64 { + let abs_diff = weighted_diff.abs() as f64; + let max_threshold = self.config.max_correction_threshold_us as f64; + + // Local Voting Protocol: Apply correction based on weighted difference + let correction_factor = if abs_diff <= self.convergence_threshold as f64 { + // Close to convergence - use deceleration factor + self.config.deceleration_factor as f64 + } else if abs_diff <= max_threshold { + // Moderate difference - use acceleration factor + self.config.acceleration_factor as f64 + } else { + // Large difference - use reduced acceleration to prevent instability + self.config.acceleration_factor as f64 * 0.5 + }; + + // Apply correction: if we're ahead (positive), slow down (negative correction) + // If we're behind (negative), speed up (positive correction) + let correction = (weighted_diff as f64 * correction_factor) as i64; + + // For Local Voting Protocol, we want to slow down when ahead, speed up when behind + // But we never want to go backwards in time, so we limit negative corrections + if correction < 0 { + // When slowing down, limit the slowdown to prevent time going backwards + correction.max(-(max_threshold as i64 / 10)) + } else { + correction + } + } + + /// Apply bounds checking to correction value. + /// + /// Ensures that the correction value does not exceed the maximum + /// threshold to prevent instability. + /// + /// # Arguments + /// + /// * `correction` - Correction value to bound + /// + /// # Returns + /// + /// Bounded correction value + fn apply_correction_bounds(&self, correction: i64) -> i64 { + let max_correction = self.config.max_correction_threshold_us as i64; + + if correction > max_correction { + max_correction + } else if correction < -max_correction { + -max_correction + } else { + correction + } + } + + /// Update peer quality score based on synchronization results. + /// + /// Adjusts the quality score of a peer based on the consistency + /// of synchronization results. + /// + /// # Arguments + /// + /// * `peer_id` - ID of the peer to update + /// * `correction_applied` - Correction value that was applied + fn update_peer_quality(&mut self, peer_id: u32, correction_applied: i64) { + if let Some(peer) = self.peers.get_mut(&peer_id) { + // Quality improves if correction is small and consistent + let correction_magnitude = correction_applied.abs() as f32; + let max_threshold = self.config.max_correction_threshold_us as f32; + + if correction_magnitude <= max_threshold * 0.1 { + // Small correction - good quality + peer.quality_score = + (peer.quality_score + self.config.acceleration_factor).min(1.0); + } else if correction_magnitude <= max_threshold * 0.5 { + // Moderate correction - maintain quality + // No change to quality score + } else { + // Large correction - reduce quality + peer.quality_score = + (peer.quality_score - self.config.deceleration_factor).max(0.0); + } + + peer.sync_count += 1; + } + } + + /// Record a synchronization event for analysis. + /// + /// Adds a synchronization event to the history for performance + /// analysis and debugging purposes. + /// + /// # Arguments + /// + /// * `timestamp` - When the event occurred + /// * `peer_id` - ID of the peer involved + /// * `time_diff` - Time difference calculated + /// * `correction` - Correction value applied + fn record_sync_event(&mut self, timestamp: u64, peer_id: u32, time_diff: i64, correction: i64) { + let quality_score = self + .peers + .get(&peer_id) + .map(|p| p.quality_score) + .unwrap_or(0.0); + + let event = SyncEvent { + timestamp, + peer_id, + time_diff, + correction_applied: correction, + quality_score, + }; + + self.sync_history.push(event); + + // Keep only recent history (last 100 events) + if self.sync_history.len() > 100 { + self.sync_history.remove(0); + } + } + + /// Get the current synchronization quality score. + /// + /// Calculates the overall quality of the synchronization process based on + /// peer consistency and algorithm convergence. + /// + /// # Returns + /// + /// Quality score between 0.0 (poor) and 1.0 (excellent) + pub fn get_sync_quality(&self) -> f32 { + if self.peers.is_empty() { + return 0.0; + } + + let mut total_quality = 0.0; + for peer in self.peers.values() { + total_quality += peer.quality_score; + } + + total_quality / self.peers.len() as f32 + } + + // Broadcast-only: peer management API removed + + // Broadcast-only: peer lookup API removed +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sync_algorithm_creation() { + let config = SyncConfig::default(); + let algorithm = SyncAlgorithm::new(config); + + assert_eq!(algorithm.peers.len(), 0); + assert_eq!(algorithm.current_correction, 0); + } + + #[test] + fn test_process_sync_message() { + let config = SyncConfig::default(); + let mut algorithm = SyncAlgorithm::new(config); + + let correction = algorithm.process_sync_message(123, 1000, 1100).unwrap(); + + // Should calculate correction based on time difference + assert!(correction != 0); + assert!(algorithm.peers.contains_key(&123)); + } + + #[test] + fn test_weighted_average_calculation() { + let config = SyncConfig::default(); + let mut algorithm = SyncAlgorithm::new(config); + + // Add peers with different quality scores + let mut peer1 = SyncPeer::new([0; 6]); + peer1.time_diff_us = 100; + peer1.quality_score = 1.0; + + let mut peer2 = SyncPeer::new([0; 6]); + peer2.time_diff_us = 200; + peer2.quality_score = 0.5; + + // Insert directly into internal map for test purposes + algorithm.peers.insert(1, peer1); + algorithm.peers.insert(2, peer2); + + let weighted_avg = algorithm.calculate_weighted_average_diff(); + + // Should be closer to peer1's value due to higher quality + assert!(weighted_avg < 150); // Less than simple average + } +} diff --git a/src/timer.rs b/src/timer.rs index b05f6b4f..1710da22 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -405,6 +405,29 @@ pub struct Timer { /// assert_eq!(timer.tick_counter, 1); // Incremented by one /// ``` pub tick_counter: TickType, + + /// Time synchronization offset in microseconds. + /// + /// This field stores the cumulative time correction applied by the time + /// synchronization system. Positive values indicate the local time is ahead, + /// negative values indicate the local time is behind the synchronized time. + /// + /// # Synchronization + /// + /// This offset is automatically updated by the time synchronization manager + /// when synchronization messages are processed. Applications should use + /// [`Timer::get_synchronized_time()`] to get the corrected time. + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let timer = Timer::get_timer(0).unwrap(); + /// let offset = timer.get_sync_offset_us(); + /// println!("Time offset: {} microseconds", offset); + /// ``` + pub sync_offset_us: i64, } impl Timer { @@ -484,6 +507,7 @@ impl Timer { Some(Self { timer_index, tick_counter: 0, + sync_offset_us: 0, }) } else { None @@ -840,4 +864,182 @@ impl Timer { pub fn release_timer(&self) { Port::release_hardware_timer(self.timer_index) } + + /// Adjusts the timer's synchronization offset. + /// + /// This method applies a time correction to the timer's synchronization offset. + /// The correction is cumulative - multiple calls will add to the existing offset. + /// This is typically called by the time synchronization manager when processing + /// synchronization messages from other nodes. + /// + /// # Arguments + /// + /// * `correction_us` - Time correction in microseconds. Positive values indicate + /// the local time should be adjusted forward, negative values indicate it should + /// be adjusted backward. + /// + /// # Safety + /// + /// Large corrections may cause timing issues in applications. The time synchronization + /// manager should validate corrections before applying them. + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let mut timer = Timer::get_timer(0).unwrap(); + /// + /// // Apply a 100 microsecond correction + /// timer.adjust_sync_offset(100); + /// + /// // Apply a -50 microsecond correction + /// timer.adjust_sync_offset(-50); + /// + /// // Net offset is now 50 microseconds + /// assert_eq!(timer.get_sync_offset_us(), 50); + /// ``` + pub fn adjust_sync_offset(&mut self, correction_us: i64) { + self.sync_offset_us += correction_us; + } + + /// Gets the current synchronization offset in microseconds. + /// + /// Returns the cumulative time correction applied to this timer instance. + /// This value represents how much the local time differs from the synchronized + /// network time. + /// + /// # Returns + /// + /// The synchronization offset in microseconds: + /// - Positive values: Local time is ahead of synchronized time + /// - Negative values: Local time is behind synchronized time + /// - Zero: Local time is synchronized + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let timer = Timer::get_timer(0).unwrap(); + /// let offset = timer.get_sync_offset_us(); + /// + /// if offset > 0 { + /// println!("Local time is {} microseconds ahead", offset); + /// } else if offset < 0 { + /// println!("Local time is {} microseconds behind", -offset); + /// } else { + /// println!("Time is synchronized"); + /// } + /// ``` + pub fn get_sync_offset_us(&self) -> i64 { + self.sync_offset_us + } + + /// Gets the synchronized time including the synchronization offset. + /// + /// This method returns the current hardware timer time adjusted by the + /// synchronization offset. This provides the "network-synchronized" time + /// that should be consistent across all nodes in the network. + /// + /// # Returns + /// + /// A `Duration` representing the synchronized time, which is the hardware + /// timer time plus the synchronization offset. + /// + /// # Usage + /// + /// Use this method when you need time values that are consistent across + /// multiple nodes in a synchronized network. For local timing operations, + /// use [`Timer::get_time()`] instead. + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let mut timer = Timer::get_timer(0).unwrap(); + /// + /// // Apply some synchronization offset + /// timer.adjust_sync_offset(1000); // 1ms ahead + /// + /// // Get synchronized time + /// let sync_time = timer.get_synchronized_time(); + /// let local_time = timer.get_time(); + /// + /// // sync_time should be 1ms ahead of local_time + /// println!("Synchronized time: {:?}", sync_time); + /// println!("Local time: {:?}", local_time); + /// ``` + pub fn get_synchronized_time(&self) -> Duration { + let local_time = self.get_time(); + let offset_duration = Duration::from_micros(self.sync_offset_us.unsigned_abs()); + + if self.sync_offset_us >= 0 { + local_time + offset_duration + } else { + local_time - offset_duration + } + } + + /// Resets the synchronization offset to zero. + /// + /// This method clears the current synchronization offset, effectively + /// disabling time synchronization for this timer instance. This should + /// be used when: + /// - Disabling time synchronization + /// - Resetting synchronization after errors + /// - Recalibrating the timer + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let mut timer = Timer::get_timer(0).unwrap(); + /// + /// // Apply some offset + /// timer.adjust_sync_offset(500); + /// assert_eq!(timer.get_sync_offset_us(), 500); + /// + /// // Reset synchronization + /// timer.reset_sync_offset(); + /// assert_eq!(timer.get_sync_offset_us(), 0); + /// ``` + pub fn reset_sync_offset(&mut self) { + self.sync_offset_us = 0; + } + + /// Checks if the timer is currently synchronized. + /// + /// A timer is considered synchronized if its synchronization offset + /// is within acceptable bounds (typically close to zero). + /// + /// # Arguments + /// + /// * `tolerance_us` - Maximum acceptable offset in microseconds + /// + /// # Returns + /// + /// `true` if the timer is synchronized within the specified tolerance, + /// `false` otherwise. + /// + /// # Examples + /// + /// ``` + /// use martos::timer::Timer; + /// + /// let mut timer = Timer::get_timer(0).unwrap(); + /// + /// // Small offset - synchronized + /// timer.adjust_sync_offset(50); + /// assert!(timer.is_synchronized(100)); // Within 100μs tolerance + /// + /// // Large offset - not synchronized + /// timer.adjust_sync_offset(1000); + /// assert!(!timer.is_synchronized(100)); // Outside 100μs tolerance + /// ``` + pub fn is_synchronized(&self, tolerance_us: u64) -> bool { + self.sync_offset_us.abs() <= tolerance_us as i64 + } }