Skip to content
This repository was archived by the owner on Mar 5, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]

members = ["anchor", "anchor_codegen", "anchor_macro", "testjig"]
members = ["anchor", "anchor_codegen", "anchor_macro", "anchor_types", "testjig"]

exclude = ["rp2040_demo", "esp32c3_demo", "nrf52840_rtic_demo"]
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,90 @@ This repo contains the following folders:
Implements the `proc_macro`s needed by `anchor`. You shouldn't have to mess
with this.

* `anchor_types`
Used internally to provide the `KlipperCommandFlags` struct which `anchor` re-exports.

Anchor powers the [Beacon3D Surface Scanner](https://beacon3d.com/).

## Features

### Error Handling

Commands can return errors using Rust's `Result` type:

```rust
#[derive(Debug)]
pub enum ConfigError {
NotFound,
}

#[klipper_command]
fn get_config() -> Result<(), ConfigError> {
// ... implementation ...
if config_not_found {
Err(ConfigError::NotFound)
} else {
Ok(())
}
}
```

Errors are automatically wrapped in a generated `KlipperCommandError` enum and can be handled in your main loop when calling `Transport::receive()`.

### Command Flags

Commands can be marked with flags to control their behavior. Currently supported flags:

- `HF_IN_SHUTDOWN`: Allows the command to execute during shutdown state

```rust
use anchor::KlipperCommandFlags;

#[klipper_command(flags = KlipperCommandFlags::HF_IN_SHUTDOWN)]
fn clear_shutdown() {
// This command can run even when system is in shutdown state
}
```

Commands without this flag will be ignored during shutdown, allowing only recovery commands to execute.

### Shutdown State Filtering

When the `shutdown-filtering` feature is enabled, Anchor can automatically filter commands based on shutdown state. This requires:

1. Enabling the feature in both `anchor` and `anchor_codegen`:
```toml
[dependencies]
anchor = { path = "../anchor", features = ["shutdown-filtering"] }

[build-dependencies]
anchor_codegen = { path = "../anchor_codegen", features = ["shutdown-filtering"] }
```

2. Implementing `CheckShutdown` on your context type:
```rust
use anchor::CheckShutdown;

struct MyContext<'a> {
is_shutdown: &'a bool,
}

impl<'a> CheckShutdown for MyContext<'a> {
fn is_shutdown(&self) -> bool {
*self.is_shutdown
}
}
```

3. Passing the context when calling `receive()`:
```rust
let mut is_shutdown = false;
let context = MyContext { is_shutdown: &is_shutdown };
transport.receive(&mut buffer, context)?;
```

Commands marked with `HF_IN_SHUTDOWN` will be allowed to execute during shutdown, while others will be automatically filtered out.

## Documentation

Documentation can be found [here](https://anchor.annex.engineering).
Expand Down
2 changes: 2 additions & 0 deletions anchor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition = "2021"

[dependencies]
anchor_macro = { path = "../anchor_macro" }
anchor_types = { path = "../anchor_types" }

[features]
std = []
shutdown-filtering = []
1 change: 1 addition & 0 deletions anchor/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::output_buffer::OutputBuffer;

/// Error type for representing a failed read
#[derive(Debug)]
pub struct ReadError;

/// Trait implemented for types that can be read from an input message
Expand Down
5 changes: 5 additions & 0 deletions anchor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@

#![cfg_attr(not(feature = "std"), no_std)]

// Re-export KlipperCommandFlags for use in #[klipper_command(flags = ...)]
pub use anchor_types::KlipperCommandFlags;

#[doc(hidden)]
pub mod encoding;
#[doc(hidden)]
Expand All @@ -147,5 +150,7 @@ pub use anchor_macro::*;
pub use fifo_buffer::FifoBuffer;
pub use input_buffer::{InputBuffer, SliceInputBuffer};
pub use output_buffer::{OutputBuffer, ScratchOutput};
#[cfg(feature = "shutdown-filtering")]
pub use transport::CheckShutdown;
pub use transport::Transport;
pub use transport_output::TransportOutput;
87 changes: 82 additions & 5 deletions anchor/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,52 @@ use crate::output_buffer::OutputBuffer;
use crate::transport_output::TransportOutput;
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};

/// Trait for checking if the system is in shutdown state
///
/// This trait allows efficient shutdown state checking from both the transport layer
/// and command handlers. Users can implement this trait on their context type to provide
/// zero-cost shutdown state access.
///
/// When the `shutdown-filtering` feature is enabled, commands without the `HF_IN_SHUTDOWN`
/// flag will be automatically filtered out when `is_shutdown()` returns `true`.
///
/// # Example
///
/// ```rust,ignore
/// use anchor::CheckShutdown;
///
/// struct MyContext<'a> {
/// is_shutdown: &'a bool,
/// }
///
/// impl<'a> CheckShutdown for MyContext<'a> {
/// fn is_shutdown(&self) -> bool {
/// *self.is_shutdown
/// }
/// }
/// ```
///
/// This trait is only required when the `shutdown-filtering` feature is enabled.
#[cfg(feature = "shutdown-filtering")]
pub trait CheckShutdown {
/// Returns `true` if the system is currently in shutdown state
///
/// When this returns `true`, only commands marked with `HF_IN_SHUTDOWN` will be
/// allowed to execute. All other commands will be silently ignored.
fn is_shutdown(&self) -> bool;
}

// Default implementation for common types when feature is disabled
#[cfg(not(feature = "shutdown-filtering"))]
pub trait CheckShutdown {
fn is_shutdown(&self) -> bool {
false
}
}

#[cfg(not(feature = "shutdown-filtering"))]
impl CheckShutdown for () {}

const MESSAGE_HEADER_SIZE: usize = 2;
const MESSAGE_TRAILER_SIZE: usize = 3;
const MESSAGE_LENGTH_MIN: usize = MESSAGE_HEADER_SIZE + MESSAGE_TRAILER_SIZE;
Expand All @@ -27,14 +73,38 @@ fn crc16(buf: &[u8]) -> u16 {
crc
}

/// Configuration trait for the transport layer
///
/// This trait defines the types and behavior for message handling in the Anchor
/// transport system. It is implemented by code generated by `klipper_config_generate!`.
pub trait Config {
/// The transport output type used for sending messages
type TransportOutput: TransportOutput;

/// The context type passed to command handlers
///
/// When the `shutdown-filtering` feature is enabled, this type must implement
/// `CheckShutdown` to allow shutdown state filtering.
#[cfg(feature = "shutdown-filtering")]
type Context<'c>: CheckShutdown;
#[cfg(not(feature = "shutdown-filtering"))]
type Context<'c>;

/// The error type returned by command handlers
///
/// This should be the generated `KlipperCommandError` enum that wraps all
/// custom error types from `#[klipper_command]` functions.
type CommandError: core::fmt::Debug;

/// Dispatches a command to the appropriate handler
///
/// This method is called by the transport layer for each command received.
/// It should match on the command ID and call the appropriate handler function.
fn dispatch<'c>(
cmd: u16,
frame: &mut &[u8],
context: &mut Self::Context<'c>,
) -> Result<(), ReadError>;
) -> Result<(), Self::CommandError>;
}

/// Protocol transport implementation
Expand All @@ -55,7 +125,10 @@ impl<C: Config> Transport<C> {
}

/// Decodes messages from an `InputBuffer`
pub fn receive<'c>(&self, input: &mut impl InputBuffer, mut context: C::Context<'c>) {
pub fn receive<'c>(&self, input: &mut impl InputBuffer, mut context: C::Context<'c>) -> Result<(), C::CommandError>
where
C::CommandError: From<ReadError>,
{
// Drive state machine forward until we either have no
// input or know we don't have enough input.
let mut data = input.data();
Expand Down Expand Up @@ -113,7 +186,7 @@ impl<C: Config> Transport<C> {
((seq + 1) & MESSAGE_SEQ_MASK) | MESSAGE_DEST,
Ordering::SeqCst,
);
let _ = self.parse_frame(frame, &mut context);
self.parse_frame(frame, &mut context)?;
}
self.encode_acknak();
}
Expand All @@ -123,15 +196,19 @@ impl<C: Config> Transport<C> {
if consumed > 0 {
input.pop(consumed);
}
Ok(())
}

fn parse_frame<'c>(
&self,
mut frame: &[u8],
context: &mut C::Context<'c>,
) -> Result<(), ReadError> {
) -> Result<(), C::CommandError>
where
C::CommandError: From<ReadError>,
{
while !frame.is_empty() {
let cmd = <u16 as Readable>::read(&mut frame)?;
let cmd = <u16 as Readable>::read(&mut frame).map_err(Into::into)?;
C::dispatch(cmd, &mut frame, context)?;
}
Ok(())
Expand Down
4 changes: 4 additions & 0 deletions anchor_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
flate2 = "1"
lazy_static = "1"
anchor_types = { path = "../anchor_types" }

[features]
shutdown-filtering = []
Loading