Thank you for your interest in contributing to PhyCMD! This document provides guidelines and instructions for contributing.
- Code of Conduct
- Getting Started
- Development Setup
- Making Changes
- Testing
- Documentation
- Pull Request Process
- Style Guidelines
- Be respectful and constructive
- Welcome newcomers and help them get started
- Focus on what is best for the project and community
- Show empathy towards other community members
- Rust 1.70 or later
- Linux: libudev-dev and pkg-config
- Git
- Arduino Due (for hardware testing)
git clone https://github.com/angleto/phycommander.git
cd phycommanderUbuntu/Debian:
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libudev-devcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/envcd physerver
cargo build# Host-only unit + integration tests (no hardware required).
# This is what CI runs.
cargo test --release --workspaceThe physerver/tests/selftest.rs suite lives behind #[ignore] so
CI and casual contributors don't run it. It needs physical loopback
wiring (DAC0↔ADC0, DAC1↔ADC1, DOUT[0..15]↔DIN[0..15]) on a flashed
PhyCommander connected over USB. To run:
# All hardware selftests
cargo test --release --test selftest -- --ignored
# One sub-suite at a time
cargo test --release --test selftest gpio -- --ignored
cargo test --release --test selftest analog -- --ignored
# Pick a specific serial port (otherwise auto-detect picks the first
# ACM device on the host — fine for a one-Due desk, wrong if you have
# multiple).
PHYCMD_PORT=/dev/ttyACM0 cargo test --release --test selftest -- --ignoredExpect the test run to take 10-30 s per sub-suite because of the
DAC/ADC settling waits and digital scan. If any assertion fires, the
firmware may not match the host-side protocol layout — the
const _: () = assert!(offset_of!(...)) compile-time checks in
protocol/types.rs are the first thing to verify.
cargo run -- --auto-detectmain- Stable releasesdevelop- Development branchfeature/*- New featuresbugfix/*- Bug fixesdocs/*- Documentation updates
git checkout develop
git pull origin develop
git checkout -b feature/your-feature-nameFollow Conventional Commits:
feat: add WebSocket authentication
fix: resolve serial port detection issue
docs: update API reference for GPIO endpoints
test: add integration tests for protocol codec
refactor: simplify transport abstraction
perf: optimize CRC calculation
chore: update dependencies
Examples:
feat(web): add health check endpointfix(transport): handle USB disconnection gracefullydocs(config): clarify real-time settings
# All tests
cargo test
# Specific module
cargo test protocol
# With output
cargo test -- --nocapture
# Integration tests
cargo test --test integration_test- Add unit tests in the same file as the code
- Add integration tests in
tests/directory - Test both success and failure cases
- Use descriptive test names
Example:
#[test]
fn test_encode_command_with_valid_data() {
let cmd = Command { /* ... */ };
let bytes = encode_command(&cmd);
assert_eq!(bytes.len(), 64);
assert_eq!(bytes[0], 0x55);
}
#[test]
fn test_decode_status_with_invalid_crc() {
let data = [/* invalid data */];
let result = decode_status(&data);
assert!(result.is_err());
}Use rustdoc comments for public APIs:
/// Encodes a command into a 64-byte message.
///
/// # Arguments
///
/// * `cmd` - The command to encode
///
/// # Returns
///
/// A 64-byte array containing the encoded message
///
/// # Examples
///
/// ```
/// let cmd = Command::default();
/// let bytes = encode_command(&cmd);
/// assert_eq!(bytes.len(), 64);
/// ```
pub fn encode_command(cmd: &Command) -> [u8; 64] {
// ...
}- Update relevant .md files when adding features
- Include code examples
- Test all code examples
- Cross-reference related documents
-
Ensure all tests pass:
cargo test -
Check formatting:
cargo fmt -- --check
-
Run clippy:
cargo clippy -- -D warnings
-
Update documentation if needed
-
Update CHANGELOG.md with your changes
-
Push your branch:
git push origin feature/your-feature-name
-
Create a pull request on GitHub
-
Fill out the PR template:
- Description of changes
- Related issue (if any)
- Testing performed
- Documentation updated
-
Address review feedback
-
Wait for CI to pass
- At least one maintainer approval required
- All CI checks must pass
- Code must be formatted and pass clippy
- Tests must pass
- Documentation must be updated
Follow standard Rust conventions:
// Use snake_case for functions and variables
fn calculate_crc(data: &[u8]) -> u16 {
// ...
}
// Use CamelCase for types
struct CommandMessage {
// ...
}
// Use SCREAMING_SNAKE_CASE for constants
const MESSAGE_SIZE: usize = 64;
// Prefer explicit types when clarity is important
let header: u16 = 0xAA55;
// Use Result for error handling
fn parse_data(data: &[u8]) -> Result<Status> {
// ...
}
// Prefer early returns
if data.len() != 64 {
return Err(ProtocolError::InvalidLength { /* ... */ });
}
// Use meaningful variable names
let calculated_crc = crc16_ccitt(&data[0..24]);- Use
anyhow::Resultfor application errors - Use custom error types (thiserror) for library code
- Provide context with
.context() - Don't panic in library code
- Avoid unnecessary allocations
- Use
&strinstead ofStringwhen possible - Use zero-copy where appropriate
- Profile before optimizing
- Implement the
Transporttrait - Add to
transport/mod.rs - Update configuration
- Add tests
- Update documentation
- Add route in
web/mod.rs - Implement handler function
- Add to OpenAPI/documentation
- Add tests
- Update API_REFERENCE.md
- Update protocol specification in PROTOCOL.md
- Update types in
protocol/types.rs - Update codec in
protocol/codec.rs - Update firmware (FIRMWARE_UPDATES.md)
- Add backward compatibility if needed
- Update tests
- Open an issue for bugs or feature requests
- Start a discussion for questions
- Check existing documentation
- Ask in pull request comments
Your contributions make PhyCMD better for everyone. We appreciate your time and effort!