autocore-std 3.3.34

Standard library for AutoCore control programs - shared memory, IPC, and logging utilities
Documentation
use crate::CommandClient;
use mechutil::ipc::CommandMessage;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
    Idle,
    WaitingForResponse,
}

/// Function block for reading a file from the DataStore.
pub struct DataStoreRead {
    /// True while waiting for a response from the datastore.
    pub busy: bool,
    /// True when the read operation finishes successfully. Stays true until reset or restarted.
    pub done: bool,
    /// True if the operation failed.
    pub error: bool,
    /// Error description (empty when no error).
    pub error_message: String,
    /// The parsed JSON data from the file. `Some` after a successful read, `None` otherwise.
    pub data: Option<serde_json::Value>,

    state: State,
    start_time: Option<std::time::Instant>,
    pending_tid: Option<u32>,
}

impl DataStoreRead {
    /// Create a new DataStoreRead function block.
    pub fn new() -> Self {
        Self {
            busy: false,
            done: false,
            error: false,
            error_message: String::new(),
            data: None,
            state: State::Idle,
            start_time: None,
            pending_tid: None,
        }
    }

    /// The FB is currently awaiting a response.
    pub fn is_busy(&self) -> bool {
        self.busy
    }

    /// The last read resulted in an error.
    pub fn is_error(&self) -> bool {
        self.error
    }

    /// Start a new file read from the DataStore.
    ///
    /// # Arguments
    /// * `path` - The path to the file relative to the datastore root (e.g., `"config.json"`).
    /// * `client` - The IPC command client.
    pub fn start(&mut self, path: &str, client: &mut CommandClient) {
        self.error = false;
        self.error_message.clear();
        self.data = None;
        self.done = false;

        let msg = CommandMessage::read(&format!("datastore.{}", path));
        self.pending_tid = Some(client.send_message(msg));
        self.start_time = Some(std::time::Instant::now());
        self.busy = true;
        self.state = State::WaitingForResponse;
    }

    /// Cancel the read and return to idle.
    pub fn reset(&mut self) {
        self.state = State::Idle;
        self.busy = false;
        self.done = false;
        self.pending_tid = None;
    }

    /// Execute one scan cycle of the read state machine.
    pub fn tick(&mut self, timeout_ms: u32, client: &mut CommandClient) {
        match self.state {
            State::Idle => {}

            State::WaitingForResponse => {
                if self.check_timeout(timeout_ms) {
                    return;
                }

                if let Some(tid) = self.pending_tid {
                    if let Some(resp) = client.take_response(tid) {
                        self.pending_tid = None;
                        self.busy = false;
                        
                        if resp.success {
                            self.data = Some(resp.data);
                            self.done = true;
                            self.state = State::Idle;
                        } else {
                            self.set_error(&resp.error_message);
                        }
                    }
                }
            }
        }
    }

    fn check_timeout(&mut self, timeout_ms: u32) -> bool {
        if let Some(t) = self.start_time {
            if t.elapsed().as_millis() as u32 > timeout_ms {
                self.set_error("Datastore read timeout");
                return true;
            }
        }
        false
    }

    fn set_error(&mut self, message: &str) {
        self.state = State::Idle;
        self.busy = false;
        self.error = true;
        self.error_message = message.to_string();
        self.pending_tid = None;
    }
}

impl Default for DataStoreRead {
    fn default() -> Self {
        Self::new()
    }
}


/// Function block for writing a file to the DataStore.
pub struct DataStoreWrite {
    /// True while waiting for a response from the datastore.
    pub busy: bool,
    /// True when the write operation finishes successfully. Stays true until reset or restarted.
    pub done: bool,
    /// True if the operation failed.
    pub error: bool,
    /// Error description (empty when no error).
    pub error_message: String,

    state: State,
    start_time: Option<std::time::Instant>,
    pending_tid: Option<u32>,
}

impl DataStoreWrite {
    /// Create a new DataStoreWrite function block.
    pub fn new() -> Self {
        Self {
            busy: false,
            done: false,
            error: false,
            error_message: String::new(),
            state: State::Idle,
            start_time: None,
            pending_tid: None,
        }
    }

    /// The FB is currently awaiting a response.
    pub fn is_busy(&self) -> bool {
        self.busy
    }

    /// The last write resulted in an error.
    pub fn is_error(&self) -> bool {
        self.error
    }

    /// Start a new file write to the DataStore.
    ///
    /// # Arguments
    /// * `path` - The path to the file relative to the datastore root (e.g., `"config.json"`).
    /// * `data` - The JSON payload to save to the file.
    /// * `options` - Write options, such as `{"create_dirs": true}`.
    /// * `client` - The IPC command client.
    pub fn start(&mut self, path: &str, data: serde_json::Value, options: serde_json::Value, client: &mut CommandClient) {
        self.error = false;
        self.error_message.clear();
        self.done = false;

        let payload = serde_json::json!({
            "value": data,
            "options": options
        });

        let msg = CommandMessage::write(&format!("datastore.{}", path), payload);
        self.pending_tid = Some(client.send_message(msg));
        self.start_time = Some(std::time::Instant::now());
        self.busy = true;
        self.state = State::WaitingForResponse;
    }

    /// Cancel the write and return to idle.
    pub fn reset(&mut self) {
        self.state = State::Idle;
        self.busy = false;
        self.done = false;
        self.pending_tid = None;
    }

    /// Execute one scan cycle of the write state machine.
    pub fn tick(&mut self, timeout_ms: u32, client: &mut CommandClient) {
        match self.state {
            State::Idle => {}

            State::WaitingForResponse => {
                if self.check_timeout(timeout_ms) {
                    return;
                }

                if let Some(tid) = self.pending_tid {
                    if let Some(resp) = client.take_response(tid) {
                        self.pending_tid = None;
                        self.busy = false;
                        
                        if resp.success {
                            self.done = true;
                            self.state = State::Idle;
                        } else {
                            self.set_error(&resp.error_message);
                        }
                    }
                }
            }
        }
    }

    fn check_timeout(&mut self, timeout_ms: u32) -> bool {
        if let Some(t) = self.start_time {
            if t.elapsed().as_millis() as u32 > timeout_ms {
                self.set_error("Datastore write timeout");
                return true;
            }
        }
        false
    }

    fn set_error(&mut self, message: &str) {
        self.state = State::Idle;
        self.busy = false;
        self.error = true;
        self.error_message = message.to_string();
        self.pending_tid = None;
    }
}

impl Default for DataStoreWrite {
    fn default() -> Self {
        Self::new()
    }
}