twinleaf-tools 2.3.0

Tools for the Twinleaf I/O protocol for reading data from Twinleaf quantum sensors.
Documentation
use twinleaf::{
    device::{RpcList, RpcRegistry},
    tio::proto::{identifiers::SessionId, ProxyStatus},
};

use crate::tui::rpc_palette::RpcPaletteStatus;

enum RouteRpcPhase {
    Empty,
    Fetching,
    Ready {
        registry: RpcRegistry,
    },
    Refreshing {
        registry: RpcRegistry,
    },
    Disconnected {
        registry: Option<RpcRegistry>,
    },
    Failed {
        registry: Option<RpcRegistry>,
        error: String,
    },
}

pub struct RouteRpcState {
    phase: RouteRpcPhase,
    last_session_id: Option<SessionId>,
}

impl Default for RouteRpcState {
    fn default() -> Self {
        Self {
            phase: RouteRpcPhase::Empty,
            last_session_id: None,
        }
    }
}

impl RouteRpcState {
    pub fn registry(&self) -> Option<&RpcRegistry> {
        match &self.phase {
            RouteRpcPhase::Ready { registry } | RouteRpcPhase::Refreshing { registry, .. } => {
                Some(registry)
            }
            RouteRpcPhase::Disconnected {
                registry: Some(registry),
            }
            | RouteRpcPhase::Failed {
                registry: Some(registry),
                ..
            } => Some(registry),
            _ => None,
        }
    }

    pub fn palette_status(&self) -> RpcPaletteStatus {
        match &self.phase {
            RouteRpcPhase::Empty => RpcPaletteStatus::WaitingForRpc,
            RouteRpcPhase::Fetching => RpcPaletteStatus::Fetching,
            RouteRpcPhase::Ready { .. } => RpcPaletteStatus::Ready,
            RouteRpcPhase::Refreshing { .. } => RpcPaletteStatus::Refreshing,
            RouteRpcPhase::Disconnected { .. } => RpcPaletteStatus::Disconnected,
            RouteRpcPhase::Failed { registry, .. } => {
                if registry.is_some() {
                    RpcPaletteStatus::FailedUsingPrevious
                } else {
                    RpcPaletteStatus::Failed
                }
            }
        }
    }

    pub fn last_error(&self) -> Option<&str> {
        match &self.phase {
            RouteRpcPhase::Failed { error, .. } => Some(error.as_str()),
            _ => None,
        }
    }

    pub fn on_route_discovered(&mut self) -> bool {
        match &self.phase {
            RouteRpcPhase::Empty
            | RouteRpcPhase::Disconnected { registry: None }
            | RouteRpcPhase::Failed { registry: None, .. } => {
                self.phase = RouteRpcPhase::Fetching;
                true
            }
            _ => false,
        }
    }

    pub fn on_status(&mut self, status: ProxyStatus) -> bool {
        match status {
            ProxyStatus::SensorDisconnected
            | ProxyStatus::FailedToConnect
            | ProxyStatus::FailedToReconnect => {
                let registry = self.take_registry();
                self.phase = RouteRpcPhase::Disconnected { registry };
                false
            }
            ProxyStatus::SensorReconnected => self.begin_fetch(),
            ProxyStatus::Unknown(_) => false,
        }
    }

    pub fn on_heartbeat(&mut self, session_id: Option<SessionId>) -> bool {
        let changed = match (self.last_session_id, session_id) {
            (Some(old), Some(new)) => old != new,
            _ => false,
        };
        if let Some(new) = session_id {
            self.last_session_id = Some(new);
        }

        if changed {
            return self.begin_fetch();
        }

        match &self.phase {
            RouteRpcPhase::Empty | RouteRpcPhase::Failed { registry: None, .. } => {
                self.begin_fetch()
            }
            _ => false,
        }
    }

    pub fn on_new_hash(&mut self, hash: Option<u32>) -> bool {
        match hash {
            Some(hash) => {
                if self.registry().and_then(|reg| reg.hash) == Some(hash) {
                    if let Some(registry) = self.take_registry() {
                        self.phase = RouteRpcPhase::Ready { registry };
                    }
                    false
                } else {
                    self.begin_fetch()
                }
            }
            None => self.begin_fetch(),
        }
    }

    pub fn on_fetch_success(&mut self, list: &RpcList) {
        let registry = RpcRegistry::from(list);
        self.phase = RouteRpcPhase::Ready { registry };
    }

    pub fn on_fetch_error(&mut self, error: String) {
        let registry = self.take_registry();
        self.phase = RouteRpcPhase::Failed { registry, error };
    }

    fn begin_fetch(&mut self) -> bool {
        match &self.phase {
            RouteRpcPhase::Fetching | RouteRpcPhase::Refreshing { .. } => false,
            _ => {
                if let Some(registry) = self.take_registry() {
                    self.phase = RouteRpcPhase::Refreshing { registry };
                } else {
                    self.phase = RouteRpcPhase::Fetching;
                }
                true
            }
        }
    }

    fn take_registry(&mut self) -> Option<RpcRegistry> {
        let phase = std::mem::replace(&mut self.phase, RouteRpcPhase::Empty);
        match phase {
            RouteRpcPhase::Ready { registry } | RouteRpcPhase::Refreshing { registry, .. } => {
                Some(registry)
            }
            RouteRpcPhase::Disconnected { registry } | RouteRpcPhase::Failed { registry, .. } => {
                registry
            }
            RouteRpcPhase::Empty | RouteRpcPhase::Fetching => None,
        }
    }
}