Skip to main content

PkCommand

Struct PkCommand 

Source
pub struct PkCommand<VA, MA, Instant>
where VA: PkVariableAccessor, MA: PkMethodAccessor, Instant: PkInstant + Add<Duration, Output = Instant> + PartialOrd + Copy,
{ /* private fields */ }
Expand description

The main state machine for handling the PK Command protocol.

It manages the lifecycle of a transaction, including:

  • Parsing incoming raw bytes into commands.
  • Generating response commands (ACKs, data slices, etc.).
  • Handling timeouts and retransmissions.
  • Managing data slicing for large transfers.

This struct is generic over:

§Usage Pattern

  1. Feed received data into incoming_command().
  2. Regularly call poll() to progress the state machine and check for commands to send.
  3. If poll() returns Some(Command), serialize it with to_bytes() and send it over your transport.

§Host vs Device

They are not actual device types, but rather roles in a transaction.

  • Host is the one who calls perform() to initiate a transaction (e.g., SENDV, INVOK).
  • Device is the one who reacts against the transaction and automatically responds to incoming root commands using the provided accessors.

§Example

use pk_command::{PkCommand, PkCommandConfig, PkHashmapMethod, PkHashmapVariable};

let config = PkCommandConfig::default(64);
let vars = PkHashmapVariable::new(vec![]);
let methods = PkHashmapMethod::new(vec![]);
let pk = PkCommand::<_, _, std::time::Instant>::new(config, vars, methods);
loop {
    // 1. Receive data from transport...
    if let Some(received_bytes) = transport.recv() {
        pk.incoming_command(received_bytes);
    }

    // 2. Drive the state machine
    if let Some(cmd) = pk.poll() {
        let bytes = cmd.to_bytes();
        transport.send(bytes);
    }
    std::thread::sleep(std::time::Duration::from_millis(10));
}

Implementations§

Source§

impl<VA: PkVariableAccessor, MA: PkMethodAccessor, Instant: PkInstant + Add<Duration, Output = Instant> + PartialOrd + Copy> PkCommand<VA, MA, Instant>

Source

pub fn incoming_command( &self, command_bytes: Vec<u8>, ) -> Result<(), &'static str>

Ingests a raw command received from the other party.

This should be called whenever new bytes arrive on your transport layer. The state machine will parse the bytes and update its internal buffers for the next poll() cycle.

§Arguments
  • command_bytes: The raw bytes of the received command.
§Returns

Ok(()) if the command was successfully parsed and buffered. Err(&'static str) if parsing failed (e.g., invalid format, unknown operation).

Source

pub fn poll(&self) -> Option<Command>

Polls the state machine for progress and pending actions.

See PkCommand for more details.

This method must be called frequently in your main loop. It handles:

  1. Processing: Consuming commands received via incoming_command.
  2. Execution: Running device-side logic (variable access, method polling).
  3. Protocol Flow: Automatically generating ACKs, data slices, and ENDTRs.
  4. Reliability: Handling timeouts and retransmitting lost packets.
§Returns
  • Some(Command): A command that needs to be sent to the peer. Serialize it with to_bytes() and transmit it.
  • None: No action required at this time.
Source

pub fn perform( &self, operation: Operation, object: Option<String>, data: Option<Vec<u8>>, ) -> Result<(), &'static str>

Initiates a new root operation from the Host side.

This starts a new transaction chain. It can only be called when the state machine is Idle. The actual protocol exchange (beginning with a START packet) is driven by subsequent poll() calls.

§Arguments
  • operation: The root operation to perform (SENDV, REQUV, INVOK, or PKVER).
  • object: The target name (e.g., variable name for REQUV, method name for INVOK).
  • data: Optional parameter data (e.g., the value to set for SENDV).
§Returns
  • Ok(()): The transaction was successfully queued.
  • Err(&'static str): The request was invalid (e.g., already in a transaction, not a root op).
Source

pub fn is_complete(&self) -> bool

Returns true if the state machine is currently Idle (no active transaction).

Source

pub fn get_return_data(&self) -> Option<Vec<u8>>

Retrieves the return data from a finished transaction and resets the transaction state.

This should be called by the Host after is_complete() returns true for a root operation that expects return data (e.g., REQUV or INVOK).

§Returns
  • Some(Vec<u8>): The returned payload.
  • None: If there was no data or the state machine is not in a completed host state.
Source

pub fn wait_for_complete_and<F>(&self, callback: F) -> bool
where F: FnOnce(Option<Vec<u8>>),

Checks for transaction completion and executes a callback with the resulting data.

This is a convenience method for polling for completion on the Host side. If the transaction is complete, it calls the callback with the return data (if any) and returns true.

§Note

This function is also poll-based. You should also call it regularly (e.g., in your main loop), and when the transaction completes, it would call the provided function.

§Returns

true if the transaction was complete and the callback was executed.

§Example
use pk_command::{PkCommand, PkCommandConfig, PkHashmapVariable, PkHashmapMethod};

let config = PkCommandConfig::default(64);
let vars = PkHashmapVariable::new(vec![]);
let methods = PkHashmapMethod::new(vec![]);
let pk = PkCommand::<_, _, std::time::Instant>::new(config, vars, methods);

loop {
    // 1. Receive data from transport...
    if let Some(received_data) = transport.recv() {
        pk.incoming_command(received_data);
    }

    // 3. perform some operation
    if some_condition && pk.is_complete() {
        pk.perform(operation, object, data).unwrap();
    }

    // 4. poll
    let cmd=pk.poll();

    // 5. check for completion and handle return data
    //    We can see that this function is poll-based as well, and should be called regularly. (typically
    //    right after calling `poll()`) When the transaction completes, it would call the provided function
    //    with the return data (if any).
    let mut should_break=false;
    pk.wait_for_complete_and(|data_opt| {
        println!("Transaction complete! Return data: {:?}", data_opt);
        should_break=true;
    });

    // 6. Send cmd back via transport...
    if let Some(cmd_to_send) = cmd {
        transport.send(cmd_to_send.to_bytes());
    }

    // 7. break if needed
    if should_break {
        break;
    }
}
Source

pub fn new( config: PkCommandConfig, variable_accessor: VA, method_accessor: MA, ) -> Self

Creates a new PkCommand state machine.

§Arguments
  • config: Configuration defining timeouts and packet limits.
  • variable_accessor: Provider for reading/writing variables.
  • method_accessor: Provider for invoking methods.
§Note
§The Instant Type Parameter

The Instant type parameter must implement PkInstant, Copy, PartialOrd, and Add<Duration>. We provide a default implementation for std::time::Instant in std environments, so that could be used directly.

For no_std environments, users can implement their own PkInstant and use it here.

§The VA, MA Type Parameters

In std environments, the library provides two convenient implementations for variable and method accessors: PkHashmapVariable and PkHashmapMethod, which use HashMaps internally. You can use them directly or implement your own if you have different storage needs.

In no_std environments, you must provide your own implementations of VA, MA, and Instant.

§For Embassy Users

The library provides no_std utilities, based on the Embassy ecosystem, to help you integrate PK Command into a Embassy-based application. They are:

Unfortunately still, you may need to provide your own implementation of PkVariableAccessor.

§Example
use pk_command::{PkCommand, PkCommandConfig, PkHashmapVariable, PkHashmapMethod};

// The third type parameter here is the PkInstant type. This can't be usually inferred so you must
// specify it explicitly. If you are using std, just use std::time::Instant. If you are using no_std,
//  implement your own PkInstant and specify it here.
let pk = PkCommand::<_, _, std::time::Instant>::new(
    PkCommandConfig::default(64),
    PkHashmapVariable::new(vec![]),
    PkHashmapMethod::new(vec![]),
);

Auto Trait Implementations§

§

impl<VA, MA, Instant> !Freeze for PkCommand<VA, MA, Instant>

§

impl<VA, MA, Instant> !RefUnwindSafe for PkCommand<VA, MA, Instant>

§

impl<VA, MA, Instant> !Send for PkCommand<VA, MA, Instant>

§

impl<VA, MA, Instant> !Sync for PkCommand<VA, MA, Instant>

§

impl<VA, MA, Instant> Unpin for PkCommand<VA, MA, Instant>
where VA: Unpin, MA: Unpin, Instant: Unpin,

§

impl<VA, MA, Instant> !UnwindSafe for PkCommand<VA, MA, Instant>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.