This guide provides information for developers who want to contribute to the MeshCore One project.
- Xcode 26.0+ (as specified in
project.yml) - Swift 6.2+
- XcodeGen: Required for project file generation.
brew install xcodegen
- SwiftGen: Required for localization code generation (runs as an Xcode pre-build script).
brew install swiftgen
- xcsift (optional): Transforms verbose Xcode output into concise JSON.
brew install xcsift
- Clone the repository.
- Generate the Xcode project from
project.yml:xcodegen generate
Note: Do not edit
MC1.xcodeprojdirectly — it is generated fromproject.ymland will be overwritten. Make project configuration changes inproject.ymland regenerate. - Open
MC1.xcodeproj.
MeshCore One uses a modular structure with Swift Packages:
MeshCore: The protocol framework.MC1Services: The business logic framework.MeshCore One: The main iOS application.
xcodebuild -project MC1.xcodeproj \
-scheme MeshCore One \
-destination "platform=iOS Simulator,name=iPhone 16e" \
buildxcsift transforms verbose Xcode output into concise, structured JSON:
# Basic build with JSON output
xcodebuild build 2>&1 | xcsift
# Show detailed warnings
xcodebuild build 2>&1 | xcsift --warnings
# Quiet mode (suppress output on success)
xcodebuild build 2>&1 | xcsift --quiet
# Treat warnings as errors
xcodebuild build 2>&1 | xcsift --Werror
# Code coverage summary
xcodebuild test -enableCodeCoverage YES 2>&1 | xcsift --coverageMeshCore One emphasizes comprehensive testing at all layers.
- MeshCoreTests: Tests packet building, parsing, LPP decoding, and session state.
- MC1ServicesTests: Tests business logic services, actor isolation, and persistence.
- MC1Tests: Tests app state and view models.
# Run all tests
xcodebuild test -project MC1.xcodeproj \
-scheme MeshCore One \
-destination "platform=iOS Simulator,name=iPhone 16e"
# With xcsift for concise output
xcodebuild test 2>&1 | xcsift
# With code coverage
xcodebuild test -enableCodeCoverage YES 2>&1 | xcsift --coverageFor testing without physical hardware:
let mock = MockTransport()
let session = MeshCoreSession(transport: mock)
// Simulate device response
await mock.simulateReceive(testPacket)
// Helper methods for common responses
await mock.simulateOK()
await mock.simulateError(code: 0x01)
// Verify sent data (sentData is an array)
let sent = await mock.sentData
#expect(sent.count > 0)
#expect(sent.last == expectedPacket)
// Clear sent data history
await mock.clearSentData()For testing services without SwiftData:
let mockStore = MockPersistenceStore()
let service = MessageService(session: session, dataStore: mockStore)
// Verify persistence calls
#expect(mockStore.savedMessages.count == 1)We use the modern Swift Testing framework (@Test, @Suite, #expect) for all new tests:
@Suite("MessageService Tests")
struct MessageServiceTests {
@Test("Send message creates pending message")
func sendMessageCreatesAck() async throws {
let service = MessageService(...)
let message = try await service.sendDirectMessage(text: "Hello", to: contact)
#expect(message.status == .pending)
}
}- Strict Concurrency: The project is compiled with
SWIFT_STRICT_CONCURRENCY: complete. - Actor Isolation: Use actors for shared state and services.
- MainActor: All UI-related code must be isolated to the
@MainActor. - Sendable: Ensure all data types passed between actors conform to
Sendable.
- Services: Suffix with
Service(e.g.,MessageService). - Data Objects: Suffix with
DTOwhen used for cross-boundary data transfer (e.g.,MessageDTO). - Persistence: Use
PersistenceStore(alias:DataStore) for data access. - ViewModels: Suffix with
ViewModel(e.g.,ChatViewModel).
- Use
@Observableclasses, notObservableObject. - Use
foregroundStyle()notforegroundColor(). - Use
NavigationStacknotNavigationView. - Use
TabAPI nottabItem(). - Prefer
ButtonoveronTapGesture().
- SwiftData: All persistence should use SwiftData models defined in
MC1Services. - No Direct Store Access: Services should interact with data via the
PersistenceStoreactor.
The project uses SwiftLint for style enforcement, configured via .swiftlint.yml at the repo root.
# Check for violations
swiftlint lint
# Auto-fix trivial violations (trailing whitespace, etc.)
swiftlint --fixThe configuration sets thresholds slightly above current codebase maximums. These will be tightened incrementally. Rules that overlap with planned refactoring work (e.g., file_length, type_body_length) are disabled until that work lands.
If SwiftLint is not installed: brew install swiftlint
Before committing:
# Lint
swiftlint lint
# Build and test
xcodebuild test 2>&1 | xcsift --WerrorThe project uses DocC for inline documentation. All public APIs should be documented using standard Swift documentation comments.
To generate the documentation site:
xcodebuild docbuild -scheme MeshCoreThe project integrates Emojibase for emoji picker data. This dependency is configured in project.yml:
packages:
Emojibase:
url: https://github.com/matrix-org/emojibase-bindings
from: "1.5.0"For iOS 18+ device pairing, the project uses AccessorySetupKit. The AccessorySetupKitService in MC1Services handles the pairing flow.
Required Info.plist keys:
<key>NSAccessorySetupKitSupports</key>
<array>
<string>Bluetooth</string>
</array>
<key>NSAccessorySetupBluetoothServices</key>
<array>
<string>6E400001-B5A3-F393-E0A9-E50E24DCCA9E</string>
</array>
<key>NSAccessorySetupBluetoothNames</key>
<array>
<string>MeshCore-</string>
</array>MeshCore One leverages several modern iOS 18+ APIs and frameworks:
AccessorySetupKit:
- Native device discovery and pairing interface
- User-friendly pairing experience (no manual Bluetooth device selection)
- Automatic permission handling during pairing flow
Advanced SwiftUI APIs:
@Observablemacro for modern observation patternNavigationStackfor programmatic navigationTabAPI for tab bar configuration- Modern layout APIs (
containerRelativeFrame,visualEffect)
Swift 6 Concurrency:
- Strict concurrency model for thread safety
- Actor-based services for data isolation
- Sendable protocol for cross-boundary data transfer
SwiftData:
- Declarative persistence layer
- Automatic migration support
- Type-safe data models
The app requires Bluetooth and location permissions plus background BLE mode:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>MeshCore One uses Bluetooth to maintain connections with MeshCore radios, even in the background, so you can send and receive messages without opening the app.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>MeshCore One uses Bluetooth to connect to MeshCore radio devices for mesh messaging.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>MeshCore One can share your location with contacts on the mesh network.</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>- MeshCore/Sources/MeshCore/Transport/BLETransport.swift: Base BLE transport protocol implementation
- MC1Services/Transport/iOSBLETransport.swift: iOS-specific BLE transport with CoreBluetooth integration
- MC1Services/Transport/BLEStateMachine.swift: Connection state management
- MC1Services/Services/AccessorySetupKitService.swift: iOS 18+ pairing flow
MeshCore One also supports WiFi transport for MeshCore firmware devices:
- MeshCore/Sources/MeshCore/Transport/WiFiTransport.swift: TCP transport (Network.framework) that conforms to
MeshTransport - Connection Type: Automatic detection based on device capability
Testing WiFi Transport:
# WiFi transport testing requires a MeshCore device with WiFi capability
# Connect to device's WiFi hotspot
# Use same connection flow as BLE - ConnectionManager handles both transports
# Debug WiFi connection
# Add debug logging to track connection lifecycle:
logger.info("Connecting to WiFi transport", metadata: ["ip": deviceIP])WiFi vs BLE:
| Aspect | BLE | WiFi |
|---|---|---|
| Range | Short (~10-50m) | Long (~100-300m) |
| Power Consumption | Low | Medium |
| Setup | AccessorySetupKit (iOS 18+) | Manual hotspot connection |
| Throughput | Lower (~250 bytes/sec) | Higher (~1KB/sec) |
| Use Case | Mobile devices, battery-powered | Fixed installations, repeaters |
When developing or testing on iPad, consider the following:
The iPad interface uses split-view navigation:
- Test Both Panels: Ensure list and detail panels work independently
- Test Navigation: Verify both panels maintain their own navigation stacks
- Test State Changes: Ensure updates in one panel don't disrupt the other
- Test Orientation: Verify layout adjusts correctly between portrait/landscape
# Build for iPad Simulator
xcodebuild test \
-project MC1.xcodeproj \
-scheme MeshCore One \
-destination "platform=iOS Simulator,name=iPad Pro (13-inch)"
# Test on physical iPad (requires development team)
xcodebuild test \
-destination 'platform=iOS,name=My iPad'- Larger Screens: Test with various iPad screen sizes (11", 12.9", 13")
- Keyboard Support: Test keyboard shortcuts and hardware keyboard interaction
- Window Management: Test Stage Manager and multiple windows (if applicable)
- Accessibility: Test with iPad accessibility features (Dynamic Type, VoiceOver)
// Test different size classes with the #Preview macro
#Preview("iPhone Portrait") {
MyView()
.previewInterfaceOrientation(.portrait)
}
#Preview("iPad Portrait", traits: .fixedLayout(width: 1024, height: 1366)) {
MyView()
.previewInterfaceOrientation(.portrait)
}
#Preview("iPad Landscape", traits: .fixedLayout(width: 1366, height: 1024)) {
MyView()
.previewInterfaceOrientation(.landscapeLeft)
}When working on diagnostic features (Line of Sight, Trace Path, RX Log), keep these considerations in mind:
Testing without internet:
- Elevation service uses Open-Meteo API (requires internet for first fetch)
- Mock elevation data for offline testing
- Test with cached elevation data after initial fetch
Testing terrain analysis:
- Test with known obstructed paths (verify red clearance status)
- Test with clear paths (verify green clearance status)
- Verify Fresnel zone calculations with different frequencies
- Test edge cases (very short paths, very long paths)
Testing path discovery:
- Mock network topology for repeatable tests
- Test with single-hop and multi-hop paths
- Test with no available paths (verify error handling)
- Test saved path persistence and retrieval
Testing packet capture:
- Test with MockTransport to simulate various packet types
- Verify real-time updates in SwiftUI
- Test filtering by packet type, source, destination
- Test log export functionality
Testing PersistentLogger:
- Verify SwiftData persistence
- Test automatic cleanup of old logs
- Verify log redaction (sensitive data masking)
- Test log export with time range filtering
Testing DebugLogBuffer:
- Test circular buffer behavior (overflow handling)
- Verify thread safety of concurrent log writes
- Test memory usage with high log volume