#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "README.md" ) ) ]
use core::{fmt, num};
use std::io::Write;
use std::panic::PanicInfo;
use std::{env, mem, str::FromStr, sync::Arc, time::Duration};
use colored::Colorize as _;
#[cfg(target_os = "linux")]
use thread_rt::{RTParams, Scheduling};
pub use log::LevelFilter;
pub use roboplc_derive::DataPolicy;
pub use parking_lot_rt as locking;
#[cfg(feature = "metrics")]
pub use metrics;
pub mod buf;
pub mod comm;
#[cfg(target_os = "linux")]
pub mod controller;
pub mod hub;
pub mod hub_async;
pub mod io;
pub mod pchannel;
pub mod pchannel_async;
pub mod pdeque;
pub mod semaphore;
#[cfg(target_os = "linux")]
pub mod supervisor;
#[cfg(target_os = "linux")]
pub mod thread_rt;
pub mod time;
pub mod ttlcell;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("channel full")]
ChannelFull,
#[error("channel message skipped")]
ChannelSkipped,
#[error("channel closed")]
ChannelClosed,
#[error("channel empty")]
ChannelEmpty,
#[error("hub send error {0}")]
HubSend(Box<Error>),
#[error("hub client already registered: {0}")]
HubAlreadyRegistered(Arc<str>),
#[error("timed out")]
Timeout,
#[error("I/O error: {0}")]
IO(String),
#[error("API error {0}: {1}")]
API(String, i64),
#[error("RT SYS_gettid {0}")]
RTGetTId(libc::c_int),
#[error("RT sched_setaffinity {0}")]
RTSchedSetAffinity(libc::c_int),
#[error("RT sched_setscheduler {0}")]
RTSchedSetSchduler(libc::c_int),
#[error("Task name must be specified when spawning by a supervisor")]
SupervisorNameNotSpecified,
#[error("Task already registered: `{0}`")]
SupervisorDuplicateTask(String),
#[error("Task not found")]
SupervisorTaskNotFound,
#[error("Invalid data")]
InvalidData(String),
#[error("binrw {0}")]
BinRw(String),
#[error("not implemented")]
Unimplemented,
#[error("never happens")]
Infallible(#[from] std::convert::Infallible),
#[error("operation failed: {0}")]
Failed(String),
}
macro_rules! impl_error {
($t: ty, $key: ident) => {
impl From<$t> for Error {
fn from(err: $t) -> Self {
Error::$key(err.to_string())
}
}
};
}
impl_error!(std::io::Error, IO);
#[cfg(feature = "modbus")]
impl_error!(rmodbus::ErrorKind, IO);
impl_error!(oneshot::RecvError, IO);
impl_error!(num::ParseIntError, InvalidData);
impl_error!(num::ParseFloatError, InvalidData);
impl_error!(binrw::Error, BinRw);
impl Error {
pub fn is_data_skipped(&self) -> bool {
matches!(self, Error::ChannelSkipped)
}
pub fn invalid_data<S: fmt::Display>(msg: S) -> Self {
Error::InvalidData(msg.to_string())
}
pub fn io<S: fmt::Display>(msg: S) -> Self {
Error::IO(msg.to_string())
}
pub fn failed<S: fmt::Display>(msg: S) -> Self {
Error::Failed(msg.to_string())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum DeliveryPolicy {
#[default]
Always,
Latest,
Optional,
Single,
SingleOptional,
}
impl FromStr for DeliveryPolicy {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"always" => Ok(DeliveryPolicy::Always),
"optional" => Ok(DeliveryPolicy::Optional),
"single" => Ok(DeliveryPolicy::Single),
"single-optional" => Ok(DeliveryPolicy::SingleOptional),
_ => Err(Error::invalid_data(s)),
}
}
}
impl fmt::Display for DeliveryPolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
DeliveryPolicy::Always => "always",
DeliveryPolicy::Latest => "latest",
DeliveryPolicy::Optional => "optional",
DeliveryPolicy::Single => "single",
DeliveryPolicy::SingleOptional => "single-optional",
}
)
}
}
pub trait DataDeliveryPolicy
where
Self: Sized,
{
fn delivery_policy(&self) -> DeliveryPolicy {
DeliveryPolicy::Always
}
fn priority(&self) -> usize {
100
}
fn eq_kind(&self, other: &Self) -> bool {
mem::discriminant(self) == mem::discriminant(other)
}
fn is_expired(&self) -> bool {
false
}
#[doc(hidden)]
fn is_delivery_policy_single(&self) -> bool {
let dp = self.delivery_policy();
dp == DeliveryPolicy::Single || dp == DeliveryPolicy::SingleOptional
}
#[doc(hidden)]
fn is_delivery_policy_optional(&self) -> bool {
let dp = self.delivery_policy();
dp == DeliveryPolicy::Optional || dp == DeliveryPolicy::SingleOptional
}
}
#[cfg(target_os = "linux")]
pub fn critical(msg: &str) -> ! {
eprintln!("{}", msg.red().bold());
thread_rt::suicide_myself(Duration::from_secs(0), false);
std::process::exit(1);
}
#[cfg(target_os = "linux")]
pub fn suicide(delay: Duration, warn: bool) {
let mut builder = thread_rt::Builder::new().name("suicide").rt_params(
RTParams::new()
.set_priority(99)
.set_scheduling(Scheduling::FIFO)
.set_cpu_ids(&[0]),
);
builder.park_on_errors = true;
let res = builder.spawn(move || {
thread_rt::suicide_myself(delay, warn);
});
if res.is_err() {
std::thread::spawn(move || {
thread_rt::suicide_myself(delay, warn);
});
};
}
#[cfg(feature = "metrics")]
pub fn metrics_exporter() -> metrics_exporter_prometheus::PrometheusBuilder {
metrics_exporter_prometheus::PrometheusBuilder::new()
}
#[cfg(target_os = "linux")]
pub fn setup_panic() {
std::panic::set_hook(Box::new(move |info: &PanicInfo| {
panic(info);
}));
}
#[cfg(target_os = "linux")]
fn panic(info: &PanicInfo) -> ! {
eprintln!("{}", info.to_string().red().bold());
thread_rt::suicide_myself(Duration::from_secs(0), false);
loop {
std::thread::sleep(Duration::from_secs(1));
}
}
impl DataDeliveryPolicy for () {}
impl DataDeliveryPolicy for usize {}
impl DataDeliveryPolicy for String {}
impl<T> DataDeliveryPolicy for Vec<T> {}
pub fn is_production() -> bool {
env::var("INVOCATION_ID").map_or(false, |v| !v.is_empty())
}
pub fn configure_logger(filter: LevelFilter) {
let mut builder = env_logger::Builder::new();
builder.target(env_logger::Target::Stdout);
builder.filter_level(filter);
if is_production() {
builder.format(|buf, record| writeln!(buf, "{} {}", record.level(), record.args()));
}
builder.init();
}
pub mod prelude {
#[cfg(target_os = "linux")]
pub use super::suicide;
#[cfg(target_os = "linux")]
pub use crate::controller::*;
pub use crate::hub::prelude::*;
pub use crate::io::prelude::*;
#[cfg(target_os = "linux")]
pub use crate::supervisor::prelude::*;
pub use crate::time::DurationRT;
pub use crate::ttlcell::TtlCell;
pub use bma_ts::{Monotonic, Timestamp};
pub use roboplc_derive::DataPolicy;
pub use std::time::Duration;
}