This is a Rust implementation of the standard ASCOM Alpaca API for astronomy devices.
It implements main Alpaca API clients and servers, as well as transparent support for auto-discovery mechanism and ImageBytes
encoding for camera images.
This crate defines two sets of compilation features that help to keep binary size & compilation speed in check by opting into only the features you need.
First set is along the client-server axis:
client
: Enables client-side access to Alpaca-capable devices.server
: Allows to expose your own devices as Alpaca servers.The second set of features is based on the device type and enables the corresponding trait:
all-devices
: Enables all of the below. Not recommended unless you're building a universal astronomy application.camera
: Enables support for cameras via the Camera
trait.covercalibrator
: Enables [...] the CoverCalibrator
trait.dome
: Enables Dome
.filterwheel
: Enables FilterWheel
.focuser
: Enables Focuser
.observingconditions
: Enables ObservingConditions
.rotator
: Enables Rotator
.switch
: Enables Switch
.telescope
: Enables Telescope
.Once you decided on the features you need, you can add this crate to your Cargo.toml
. For example, if I'm implementing an Alpaca camera driver, I'd add the following to my Cargo.toml
:
toml
[dependencies]
ascom-alpaca = { version = "0.1", features = ["client", "camera"] }
All the device type trait methods are async and correspond to the ASCOM Alpaca API. They all returns ASCOMResult<...>
, which is an alias for Result<..., ASCOMError>
.
All those traits additionally inherit from a special Device
supertrait. It includes "ASCOM Methods Common To All Devices" from the Alpaca API, as well as few custom metadata methods used for the device registration:
fn static_name(&self) -> &str
: Returns the static device name. Might differ from the async name
method result.fn unique_id(&self) -> &str
: Returns globally-unique device ID.Since async traits are not yet natively supported on stable Rust, the traits are implemented using the async_trait crate. Other than that, you should implement trait with all the Alpaca methods as usual:
```rust use ascomalpaca::ASCOMResult; use ascomalpaca::api::{Device, Camera}; use asynctrait::asynctrait;
struct MyCamera { // ... }
impl Device for MyCamera { fn static_name(&self) -> &str { "My Camera" }
fn unique_id(&self) -> &str {
"insert GUID here"
}
// ...
}
impl Camera for MyCamera {
async fn bayeroffsetx(&self) -> ASCOMResult
async fn bayer_offset_y(&self) -> ASCOMResult<i32> {
Ok(0)
}
// ...
} ```
Once implemented, you can create a server, register your device(s), and start listening:
```rust use ascomalpaca::Server; use ascomalpaca::api::CargoServerInfo; use std::convert::Infallible;
// ...implement MyCamera...
async fn main() -> eyre::Result
// By default, the server will listen on [::] with a randomly assigned port.
// You can change that by modifying the `listen_addr` field:
server.listen_addr.set_port(8000);
// Register your device(s).
let my_camera = MyCamera { /* ... */ };
server.devices.register(my_camera);
// Start the infinite server loop.
server.start().await
} ```
This will start both the main Alpaca server as well as an auto-discovery responder.
See examples/camera-server.rs
for a complete example that implements Alpaca Camera
server for a webcam.
If you know address of the device server you want to access, you can access it directly via Client
struct:
```rust use ascom_alpaca::Client;
let client = Client::new("http://localhost:8000")?;
// get_server_info
returns high-level metadata of the server.
println!("Server info: {:#?}", client.getserverinfo().await?);
// get_devices
returns an iterator over all the devices registered on the server.
// Each is represented as a TypedDevice
tagged enum encompassing all the device types as corresponding trait objects.
// You can either match on them to select the devices you're interested in, or, say, just print all of them:
println!("Devices: {:#?}", client.get_devices().await?.collect::
If you want to discover device servers on the local network, you can do that via the discovery::DiscoveryClient
struct:
```rust use ascomalpaca::discovery::DiscoveryClient; use ascomalpaca::Client; use futures::prelude::*;
// This holds configuration for the discovery client.
// You can customize prior to binding if you want.
let discoveryclient = DiscoveryClient::new();
// This results in a discovery client bound to a local socket.
// It's intentionally split out into a separate API step to encourage reuse,
// for example so that user could click "Refresh devices" button in the UI
// and the application wouldn't have to re-bind the socket every time.
let mut boundclient = discoveryclient.bind().await?;
// Now you can discover devices on the local networks.
boundclient.discoveraddrs()
// create a Client
for each discovered address
.map(Client::newfromaddr)
.tryfor_each(|client| async move {
/* ... */
Ok(())
})
.await?;
```
Keep in mind that discovery is a UDP-based protocol, so it's not guaranteed to be reliable.
Also, same device server can be discovered multiple times if it's available on multiple network interfaces.
While it's not possible to reliably deduplicate servers, you can deduplicate devices by storing them in something like HashMap
.
It will leverage unique_id
for device comparisons under the hood.
You can find a simple discovery example in examples/discover.rs
and a cross-platform GUI client example for cameras in examples/camera-client.rs
.
This crate uses tracing
framework for logging spans and events, integrating with the Alpaca ClientID
, ClientTransactionID
and ServerTransactionID
fields.
You can enable logging in your app by using any of the subscriber crates.
For example, tracing_subscriber::fmt
will log all the events to stderr depending on the RUST_LOG
environment variable:
rust
tracing_subscriber::fmt::init();
Since this is a library for communicating to networked devices, it should be tested against real devices at a higher level.
In particular, if you're implementing an Alpaca device, make sure to run ConformU - ASCOM's official conformance checker - against your device server.
Licensed under either of