Rust API for writing client applications and device drivers for astronomy equipment using the INDIGO protocol and architecture.
The project has been successfully refactored into a multi-crate workspace! See REFACTORING_COMPLETE.md for full details.
This project is organized as a Cargo workspace with multiple crates:
| Crate | Purpose | FFI Dependencies |
|---|---|---|
libindigo |
Core API, traits, and types | None |
libindigo-rs |
Pure Rust implementation | None |
libindigo-ffi |
FFI-based implementation | Yes (via libindigo-sys) |
libindigo-sys |
Raw C bindings | Yes |
┌─────────────────┐
│ libindigo │ ← Core API (traits, types, constants)
│ (root crate) │
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│libindigo│ │ libindigo-ffi│
│ -rs │ │ │
└─────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ libindigo-sys│
└──────────────┘
- A pure Rust API that is 100% compatible with the INDIGO platform and its default C-implementation
- ✅ Provide an API that uses idiomatic Rust for integrating with the INDIGO Bus
- ✅ Provide a Service Provider Interface (SPI) for decoupling the API from its implementation
- ✅ Provide a default RS (Rust) implementation of the SPI without any FFI bindings to the INDIGO C-libraries or other non-rust dependencies
- ✅ Provide an FFI implementation of the SPI that uses the INDIGO C-library with any necessary dependencies
The crate libindigo-rs must be imported as libindigo_rs (with underscore) in your code.
Rust automatically converts hyphens to underscores in crate names. Always use:
use libindigo_rs::{...}; // ✅ CORRECT (underscore)
// NOT: use libindigo::{...}; // ❌ WRONGAdd to your Cargo.toml:
[dependencies]
libindigo-rs = "0.3"
tokio = { version = "1.35", features = ["full"] }Example code:
use libindigo_rs::{Client, ClientBuilder, RsClientStrategy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client with pure Rust strategy
let strategy = RsClientStrategy::new();
let mut client = ClientBuilder::new()
.with_strategy(strategy)
.build();
// Connect to INDIGO server
client.connect("localhost:7624").await?;
// Enumerate all properties
client.enumerate_properties(None).await?;
// Disconnect
client.disconnect().await?;
Ok(())
}Add to your Cargo.toml:
[dependencies]
libindigo-ffi = "0.3"
tokio = { version = "1.35", features = ["full"] }Example code:
use libindigo_ffi::{Client, ClientBuilder, FfiClientStrategy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Note: FFI implementation is currently stubbed
let strategy = FfiClientStrategy::new()?;
let mut client = ClientBuilder::new()
.with_strategy(strategy)
.build();
client.connect("localhost:7624").await?;
client.enumerate_properties(None).await?;
client.disconnect().await?;
Ok(())
}Pure Rust Strategy (libindigo-rs)
The pure Rust strategy provides a complete INDIGO client implementation without C FFI dependencies:
- ✅ Zero FFI: No C dependencies, pure Rust implementation
- ✅ Async-First: Built on tokio for efficient async I/O
- ✅ Type Safe: Leverages Rust's type system for protocol correctness
- ✅ Cross-Platform: Works anywhere Rust compiles
- ✅ Dual Protocol: Full INDIGO JSON and XML protocol support with automatic negotiation
- ✅ JSON-First: Defaults to modern JSON protocol with XML fallback
- ✅ mDNS Discovery: Optional pure Rust server discovery (no FFI)
Feature Flags:
client(default): Client functionalitydevice: Device driver support (stub for future)discovery: mDNS server discovery via pure Rustmdns-sdcrate
FFI Strategy (libindigo-ffi)
The FFI strategy wraps the official C INDIGO library:
⚠️ Status: Structure in place, implementation pending- ✅ Maximum Compatibility: Uses the official INDIGO C library
- ✅ Async Support: Async wrappers around synchronous FFI calls
- ✅ Battle-Tested: Leverages mature C implementation
- ✅ Feature Complete: Access to all INDIGO features (when implemented)
Feature Flags:
client(default): Client functionalitydevice: Device driver support (stub for future)async: Async wrapper for non-blocking operations
libindigo-rs supports both INDIGO JSON and XML protocols with intelligent negotiation:
The client automatically negotiates the best protocol with the server:
use libindigo_rs::{Client, ClientBuilder, RsClientStrategy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// JSON-first with XML fallback (default behavior)
let strategy = RsClientStrategy::new();
let mut client = ClientBuilder::new()
.with_strategy(strategy)
.build();
client.connect("localhost:7624").await?;
// Client automatically negotiates protocol with server
// Prefers JSON, falls back to XML if server doesn't support JSON
client.enumerate_properties(None).await?;
client.disconnect().await?;
Ok(())
}| Feature | JSON Protocol | XML Protocol |
|---|---|---|
| Version | 512 (numeric) | "2.0" (string) |
| Switch Values | true/false |
On/Off |
| Number Format | Native JSON numbers | String with format |
| BLOBs | URL only | URL or BASE64 |
| Parsing Speed | ⚡ Faster | Slightly slower |
| Size | 📦 More compact | More verbose |
| Use Case | Modern clients, web apps | Legacy compatibility |
| Server Support | INDIGO 2.0+ | All INDIGO versions |
See rs/src/lib.rs documentation for advanced protocol negotiation options.
The pure Rust implementation includes optional mDNS server discovery:
[dependencies]
libindigo-rs = { version = "0.3", features = ["discovery"] }use libindigo_rs::discovery::{DiscoveryBuilder, DiscoveryEvent};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut discovery = DiscoveryBuilder::new()
.with_timeout(std::time::Duration::from_secs(5))
.build()?;
let mut receiver = discovery.start().await?;
while let Some(event) = receiver.recv().await {
match event {
DiscoveryEvent::ServerFound { name, address, port } => {
println!("Found server: {} at {}:{}", name, address, port);
}
DiscoveryEvent::ServerLost { name } => {
println!("Lost server: {}", name);
}
}
}
Ok(())
}See rs/PHASE5_DISCOVERY_MIGRATION.md for migration details.
| Feature | libindigo-rs | libindigo-ffi |
|---|---|---|
| C Dependencies | ❌ None | ✅ Required |
| Async Support | ✅ Native | ✅ Wrapped |
| Cross-Platform | ✅ Excellent | |
| Performance | ✅ Fast | ✅ Fast |
| JSON Protocol | ✅ Yes | |
| XML Protocol | ✅ Yes | ✅ Yes |
| Protocol Negotiation | ✅ Automatic | ❌ No |
| mDNS Discovery | ✅ Pure Rust | ❌ No |
| Maturity | ✅ Production | |
| Use Case | Modern apps | Legacy compat |
use libindigo_rs::{Client, ClientBuilder, RsClientStrategy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut strategy = RsClientStrategy::new();
// Connect to server
strategy.connect("localhost:7624").await?;
// Get property receiver
let mut rx = strategy.property_receiver().await.unwrap();
// Spawn task to receive properties
tokio::spawn(async move {
while let Some(property) = rx.recv().await {
println!("Property: {}.{} = {:?}",
property.device,
property.name,
property.values
);
}
});
// Enumerate properties
strategy.enumerate_properties(None).await?;
// Keep running...
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
strategy.disconnect().await?;
Ok(())
}use libindigo_rs::{Client, ClientBuilder, RsClientStrategy};
use libindigo_rs::types::{Property, PropertyType, PropertyValue, SwitchState};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let strategy = RsClientStrategy::new();
let mut client = ClientBuilder::new()
.with_strategy(strategy)
.build();
client.connect("localhost:7624").await?;
// Create a switch property to connect a device
let mut property = Property::new(
"CCD Simulator".to_string(),
"CONNECTION".to_string(),
PropertyType::Switch,
);
property.values.push(PropertyValue::Switch {
name: "CONNECTED".to_string(),
label: Some("Connected".to_string()),
value: SwitchState::On,
});
// Send the property update
client.send_property(property).await?;
client.disconnect().await?;
Ok(())
}- This README: Overview and quick start
libindigo- Core API documentation in sourcelibindigo-rs- Pure Rust implementation (seers/src/lib.rs)libindigo-ffi- FFI implementation (seeffi/README.md)libindigo-sys- Raw bindings (seesys/README.md)
REFACTORING_COMPLETE.md- Complete refactoring summarydoc/reviews/issue-6-refactoring-review.md- Detailed reviewdoc/architecture/client-strategies.md- Strategy patternplans/code-review-and-architecture.md- Architecture plan
plans/archive/phase1-complete.md- Foundation & Core Typesplans/archive/phase2-complete.md- Async FFI Strategyplans/archive/phase3-complete.md- Rust Client Strategyplans/archive/phase3-json-complete.md- JSON Protocolrs/PHASE5_DISCOVERY_MIGRATION.md- Discovery MigrationPHASE6_CONSTANTS_EXTRACTION.md- Constants Extractionsys/REFACTORING_PHASE4.md- FFI Refactoring
doc/constants-extraction.md- Constants generationBUILD.md- Build instructionsCHANGES.md- Changelog
cargo test --workspace# All pure Rust tests (including JSON protocol tests)
cargo test -p libindigo-rs
# JSON protocol tests only (61 tests)
cargo test -p libindigo-rs --test json_protocol_tests
# Protocol negotiation tests only (59 tests)
cargo test -p libindigo-rs --test protocol_negotiation_testscargo test -p libindigo-rs --features discoveryIntegration tests require a running INDIGO server:
# Start INDIGO server (in another terminal)
indigo_server
# Run integration tests
cargo test --test discovery_tests --features discovery| Test Suite | Tests | Coverage |
|---|---|---|
| JSON Protocol | 61 | All PROTOCOLS.md examples + edge cases |
| Protocol Negotiation | 59 | Auto-detection, fallback, preferences |
| Rust Client | ~50 | Connection, properties, lifecycle |
| Discovery | ~20 | mDNS discovery, filtering |
| Total | ~190 | Comprehensive coverage |
The examples/ directory contains usage examples:
discover_servers.rs- Server discoverydiscovery_with_filter.rs- Filtered discoveryauto_connect.rs- Auto-connect to discovered servers (⚠️ needs update)continuous_discovery.rs- Continuous discovery (⚠️ needs update)
Run examples:
# Server discovery
cargo run --example discover_servers --features discovery
# Discovery with filter
cargo run --example discovery_with_filter --features discoveryNote: Some examples use deprecated features and need updating. See REFACTORING_COMPLETE.md for details.
libindigo-rs/
├── Cargo.toml # Workspace root
├── README.md # This file
├── REFACTORING_COMPLETE.md # Refactoring summary
├── src/ # libindigo (core API)
│ ├── lib.rs # Main library entry point
│ ├── error.rs # Error types
│ ├── constants.rs # INDIGO protocol constants
│ ├── client/ # Client API
│ │ ├── mod.rs
│ │ ├── builder.rs # Client builder
│ │ └── strategy.rs # ClientStrategy trait (SPI)
│ └── types/ # Core types
│ ├── mod.rs
│ ├── property.rs # Property types
│ ├── device.rs # Device types
│ └── value.rs # Value types
├── rs/ # libindigo-rs (pure Rust)
│ ├── Cargo.toml
│ ├── src/
│ │ ├── lib.rs # Re-exports + RS strategy
│ │ ├── client.rs # RsClientStrategy
│ │ ├── protocol.rs # XML protocol parser
│ │ ├── protocol_json.rs # JSON protocol parser
│ │ ├── protocol_negotiation.rs
│ │ ├── transport.rs # TCP transport layer
│ │ └── discovery/ # mDNS discovery (optional)
│ │ ├── mod.rs
│ │ ├── api.rs
│ │ ├── error.rs
│ │ └── mdns_impl.rs
├── ffi/ # libindigo-ffi (FFI-based)
│ ├── Cargo.toml
│ ├── README.md
│ └── src/
│ ├── lib.rs # Re-exports + FFI strategy
│ ├── ffi.rs # FfiClientStrategy
│ └── async_ffi.rs # AsyncFfiStrategy
├── sys/ # libindigo-sys (raw bindings)
│ ├── Cargo.toml
│ ├── README.md
│ ├── build.rs # C library build
│ └── src/lib.rs # bindgen output
├── relm/ # libindigo-relm (GTK demo, excluded)
├── examples/ # Usage examples
├── tests/ # Integration tests
├── doc/ # Documentation
├── plans/ # Planning documents
└── scripts/ # Utility scripts
└── update_constants.sh # Update INDIGO constants
If you're upgrading from the old monolithic API, see the migration guide in REFACTORING_COMPLETE.md.
Quick summary:
# Cargo.toml
- libindigo = { version = "0.1", features = ["rs"] }
+ libindigo-rs = "0.3"
# Code
- use libindigo::strategies::RsClientStrategy;
- use libindigo::client::ClientBuilder;
+ use libindigo_rs::{RsClientStrategy, ClientBuilder};If you see this error after adding libindigo-rs to your dependencies:
error[E0432]: unresolved import `libindigo`
Solution: The crate name uses a hyphen (libindigo-rs) but imports must use an underscore (libindigo_rs).
// ❌ WRONG - causes "unresolved module" error
use libindigo::{Client, ClientBuilder};
// ✅ CORRECT - use underscore in imports
use libindigo_rs::{Client, ClientBuilder};This is a Rust convention: hyphens in crate names are automatically converted to underscores for imports.
Contributions are welcome! Please:
- Read
doc/ways-of-working.md - Follow
doc/roo-workflow-scheme.md - Use appropriate issue templates
- Write tests for new features
- Update documentation
See REFACTORING_COMPLETE.md for:
- FFI implementation status
- BLOB handling improvements
- Device driver support (future)
- Example updates needed
This project is licensed under the MIT License - see the LICENSE.md file for details.
- INDIGO Astronomy - The INDIGO protocol and C library
- The Rust community for excellent async ecosystem tools
- INDIGO - Official C implementation
libindigo-sys- Low-level FFI bindings to INDIGO C library
Current Version: 0.3.0
Status: Production-ready for pure Rust client applications
- ✅ libindigo-rs: Complete and production-ready
⚠️ libindigo-ffi: Structure in place, implementation pending- ✅ Multi-crate refactoring: Complete (see
REFACTORING_COMPLETE.md)
For production use, we recommend:
- Pure Rust Strategy (
libindigo-rs) for new projects without C dependencies - FFI Strategy (
libindigo-ffi) for maximum compatibility (when implementation is complete)
For questions, issues, or contributions:
- Open an issue on GitHub
- Check existing documentation
- Review the architecture plan