use std::fmt;
use std::num::{NonZeroU16, NonZeroU32};
use std::str::FromStr;
use anyhow::{Result, anyhow};
use serde::{Deserialize, Deserializer};
use crate::consts::NOMINAL_FREQUENCY_HZ;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum SysStream {
Time,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SvStreamId {
pub appid: u16,
pub svid: String,
pub simulated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum StreamId {
Sv {
id: SvStreamId,
},
App {
app_name: String,
stream_name: String,
},
Sys {
stream: SysStream,
},
Goose {
go_id: String,
},
}
impl StreamId {
pub const SYS_TIME: Self = Self::Sys {
stream: SysStream::Time,
};
}
impl FromStr for StreamId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
match parts.as_slice() {
["App", app_name, stream_name] => {
let app_name = app_name.to_string();
let stream_name = stream_name.to_string();
Ok(StreamId::App {
app_name,
stream_name,
})
}
["SV", appid, svid] | ["SV", appid, svid, "test"] => {
let appid = u16::from_str_radix(appid, 16)
.map_err(|_| "Invalid hex integer for appid".to_string())?;
let simulated = parts.len() > 3;
Ok(StreamId::Sv {
id: SvStreamId {
appid,
svid: svid.to_string(),
simulated,
},
})
}
["Sys", "Time"] => Ok(StreamId::Sys {
stream: SysStream::Time,
}),
["GOOSE", go_id @ ..] => Ok(StreamId::Goose {
go_id: go_id.join(":"),
}),
_ => Err(format!("Invalid format StreamId: {s}").to_string()),
}
}
}
impl fmt::Display for SysStream {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SysStream::Time => write!(f, "Time"),
}
}
}
impl fmt::Display for SvStreamId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"SV:{:x}:{}{}",
self.appid,
self.svid,
if self.simulated { ":test" } else { "" }
)
}
}
impl fmt::Display for StreamId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StreamId::App {
app_name,
stream_name,
} => {
write!(f, "App:{app_name}:{stream_name}")
}
StreamId::Sv { id } => fmt::Display::fmt(id, f),
StreamId::Sys { stream } => {
write!(f, "Sys:{stream}")
}
StreamId::Goose { go_id } => {
write!(f, "GOOSE:{go_id}")
}
}
}
}
impl<'de> Deserialize<'de> for StreamId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
StreamId::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SampleRate {
SamplesPerSecond(NonZeroU32),
SecondsPerSample(NonZeroU32),
SamplesPerPeriod(NonZeroU32),
SamplesPerCycle(NonZeroU32),
Variable(String),
}
impl SampleRate {
pub fn to_samples_per_second(&self) -> Result<NonZeroU16> {
fn to_nonzero_u16(val: u32, field: &'static str) -> Result<NonZeroU16> {
let rate = u16::try_from(val).map_err(|_| anyhow!("{field} must fit in non-zero u16"))?;
Ok(NonZeroU16::new(rate).expect("NonZeroU32 converted to u16 cannot become zero"))
}
match self {
SampleRate::SamplesPerSecond(val) => {
to_nonzero_u16(val.get(), "sample_rate.samples_per_second")
}
SampleRate::SamplesPerPeriod(val) | SampleRate::SamplesPerCycle(val) => {
let rate = val
.get()
.checked_mul(u32::from(NOMINAL_FREQUENCY_HZ.get()))
.ok_or_else(|| {
anyhow!("overflow in sample_rate.samples_per_period * nominal_frequency")
})?;
to_nonzero_u16(rate, "sample_rate.samples_per_period * nominal_frequency")
}
SampleRate::SecondsPerSample(_) => Err(anyhow!(
"sample_rate.seconds_per_sample cannot be converted to samples per second"
)),
SampleRate::Variable(_) => Err(anyhow!("sample_rate.variable has no fixed rate")),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub enum StreamValueType {
Voltage,
Current,
Energy,
Power,
Frequency,
Other,
}
#[derive(Debug, Clone, Deserialize)]
pub enum SIUnit {
Ampere,
Volt,
Radian,
Watt,
Joule,
Hz,
VArs,
SIOther,
}
#[derive(Debug, Clone, Deserialize)]
pub struct StreamLayoutValue {
pub name: String,
pub r#type: StreamValueType,
pub unit: SIUnit,
pub mag: i64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DataStreamInfo {
pub name: String,
pub sample_rate: SampleRate,
pub fields: Vec<StreamLayoutValue>,
}
impl DataStreamInfo {
pub fn find_field(&self, name: &str) -> Result<(usize, &StreamLayoutValue), std::io::Error> {
self
.fields
.iter()
.enumerate()
.find(|(_, f)| f.name == name)
.ok_or_else(|| {
std::io::Error::other(format!(
"Field '{}' not found in stream '{}'",
name, self.name
))
})
}
pub fn find_field_idx(&self, name: &str) -> Result<usize, std::io::Error> {
self.find_field(name).map(|(idx, _)| idx)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct GooseStreamInfo {
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SysStreamInfo {
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamInfo {
DataStreamInfo(DataStreamInfo),
GooseStreamInfo(GooseStreamInfo),
SysStreamInfo(SysStreamInfo),
}
impl StreamInfo {
pub fn name(&self) -> String {
match self {
Self::DataStreamInfo(info) => info.name.clone(),
Self::GooseStreamInfo(info) => info.name.clone(),
Self::SysStreamInfo(info) => info.name.clone(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct InputStream {
#[serde(rename = "$input_stream")]
pub name: StreamId,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OutputStream {
#[serde(rename = "$output_stream")]
pub name: String,
pub sample_rate: SampleRate,
pub fields: Vec<StreamLayoutValue>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OwnedTrigger {
#[serde(rename = "$trigger")]
pub id: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SendTrigger {
#[serde(rename = "$send_trigger")]
pub id: String,
}