Skip to main content

sinusoidal_core/
stream_info.rs

1use std::fmt;
2use std::num::{NonZeroU16, NonZeroU32};
3use std::str::FromStr;
4
5use anyhow::{Result, anyhow};
6use serde::{Deserialize, Deserializer};
7
8use crate::consts::NOMINAL_FREQUENCY_HZ;
9
10/// A stream provided by the framework.
11#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
12pub enum SysStream {
13  Time,
14}
15
16/// An SV stream identifier qualified by application id.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct SvStreamId {
19  pub appid: u16,
20  pub svid: String,
21  pub simulated: bool,
22}
23
24/// Uniquely identifies a data stream within the simulator.
25#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum StreamId {
27  Sv {
28    id: SvStreamId,
29  },
30  App {
31    app_name: String,
32    stream_name: String,
33  },
34  Sys {
35    stream: SysStream,
36  },
37  Goose {
38    go_id: String,
39  },
40}
41
42impl StreamId {
43  pub const SYS_TIME: Self = Self::Sys {
44    stream: SysStream::Time,
45  };
46}
47
48impl FromStr for StreamId {
49  type Err = String;
50
51  fn from_str(s: &str) -> Result<Self, Self::Err> {
52    let parts: Vec<&str> = s.split(':').collect();
53    match parts.as_slice() {
54      ["App", app_name, stream_name] => {
55        let app_name = app_name.to_string();
56        let stream_name = stream_name.to_string();
57        Ok(StreamId::App {
58          app_name,
59          stream_name,
60        })
61      }
62      ["SV", appid, svid] | ["SV", appid, svid, "test"] => {
63        let appid = u16::from_str_radix(appid, 16)
64          .map_err(|_| "Invalid hex integer for appid".to_string())?;
65        let simulated = parts.len() > 3;
66        Ok(StreamId::Sv {
67          id: SvStreamId {
68            appid,
69            svid: svid.to_string(),
70            simulated,
71          },
72        })
73      }
74      ["Sys", "Time"] => Ok(StreamId::Sys {
75        stream: SysStream::Time,
76      }),
77      ["GOOSE", go_id @ ..] => Ok(StreamId::Goose {
78        go_id: go_id.join(":"),
79      }),
80      _ => Err(format!("Invalid format StreamId: {s}").to_string()),
81    }
82  }
83}
84
85impl fmt::Display for SysStream {
86  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87    match self {
88      SysStream::Time => write!(f, "Time"),
89    }
90  }
91}
92
93impl fmt::Display for SvStreamId {
94  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95    write!(
96      f,
97      "SV:{:x}:{}{}",
98      self.appid,
99      self.svid,
100      if self.simulated { ":test" } else { "" }
101    )
102  }
103}
104
105impl fmt::Display for StreamId {
106  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107    match self {
108      StreamId::App {
109        app_name,
110        stream_name,
111      } => {
112        write!(f, "App:{app_name}:{stream_name}")
113      }
114      StreamId::Sv { id } => fmt::Display::fmt(id, f),
115      StreamId::Sys { stream } => {
116        write!(f, "Sys:{stream}")
117      }
118      StreamId::Goose { go_id } => {
119        write!(f, "GOOSE:{go_id}")
120      }
121    }
122  }
123}
124
125impl<'de> Deserialize<'de> for StreamId {
126  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127  where
128    D: Deserializer<'de>,
129  {
130    let s: String = Deserialize::deserialize(deserializer)?;
131    StreamId::from_str(&s).map_err(serde::de::Error::custom)
132  }
133}
134
135/// Sample rate of a data stream, expressed in one of several supported units.
136#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
137#[serde(rename_all = "snake_case")]
138pub enum SampleRate {
139  SamplesPerSecond(NonZeroU32),
140  SecondsPerSample(NonZeroU32),
141  SamplesPerPeriod(NonZeroU32),
142
143  // TODO Remove
144  SamplesPerCycle(NonZeroU32),
145  Variable(String),
146}
147
148impl SampleRate {
149  /// Convert this sample rate to an integer number of samples per second.
150  ///
151  /// Returns an error if the rate cannot be expressed as a non-zero `u16`, or
152  /// if the variant has no fixed rate (e.g. `Variable` or `SecondsPerSample`).
153  pub fn to_samples_per_second(&self) -> Result<NonZeroU16> {
154    fn to_nonzero_u16(val: u32, field: &'static str) -> Result<NonZeroU16> {
155      let rate = u16::try_from(val).map_err(|_| anyhow!("{field} must fit in non-zero u16"))?;
156      Ok(NonZeroU16::new(rate).expect("NonZeroU32 converted to u16 cannot become zero"))
157    }
158
159    match self {
160      SampleRate::SamplesPerSecond(val) => {
161        to_nonzero_u16(val.get(), "sample_rate.samples_per_second")
162      }
163      // This isn't entirely correct for `SamplesPerCycle` since the actual rate depends on
164      // the actual frequency
165      SampleRate::SamplesPerPeriod(val) | SampleRate::SamplesPerCycle(val) => {
166        let rate = val
167          .get()
168          .checked_mul(u32::from(NOMINAL_FREQUENCY_HZ.get()))
169          .ok_or_else(|| {
170            anyhow!("overflow in sample_rate.samples_per_period * nominal_frequency")
171          })?;
172        to_nonzero_u16(rate, "sample_rate.samples_per_period * nominal_frequency")
173      }
174      SampleRate::SecondsPerSample(_) => Err(anyhow!(
175        "sample_rate.seconds_per_sample cannot be converted to samples per second"
176      )),
177      SampleRate::Variable(_) => Err(anyhow!("sample_rate.variable has no fixed rate")),
178    }
179  }
180}
181
182/// Physical quantity type carried by a stream field.
183#[derive(Debug, Clone, Deserialize)]
184pub enum StreamValueType {
185  Voltage,
186  Current,
187  Energy,
188  Power,
189  Frequency,
190  Other,
191}
192
193/// SI unit used by a stream field.
194#[derive(Debug, Clone, Deserialize)]
195pub enum SIUnit {
196  Ampere,
197  Volt,
198  Radian,
199  Watt,
200  Joule,
201  Hz,
202  VArs,
203  SIOther,
204}
205
206/// Describes a single field within a stream's layout.
207#[derive(Debug, Clone, Deserialize)]
208pub struct StreamLayoutValue {
209  pub name: String,
210  pub r#type: StreamValueType,
211  pub unit: SIUnit,
212  pub mag: i64,
213}
214
215/// Metadata about a Sampled Values data stream.
216#[derive(Debug, Clone, Deserialize)]
217pub struct DataStreamInfo {
218  pub name: String,
219  pub sample_rate: SampleRate,
220  pub fields: Vec<StreamLayoutValue>,
221}
222
223impl DataStreamInfo {
224  /// Find a field by name, returning its index and a reference to the field.
225  pub fn find_field(&self, name: &str) -> Result<(usize, &StreamLayoutValue), std::io::Error> {
226    self
227      .fields
228      .iter()
229      .enumerate()
230      .find(|(_, f)| f.name == name)
231      .ok_or_else(|| {
232        std::io::Error::other(format!(
233          "Field '{}' not found in stream '{}'",
234          name, self.name
235        ))
236      })
237  }
238
239  /// Find a field by name, returning its index.
240  pub fn find_field_idx(&self, name: &str) -> Result<usize, std::io::Error> {
241    self.find_field(name).map(|(idx, _)| idx)
242  }
243}
244
245/// Metadata about a GOOSE stream.
246#[derive(Debug, Clone, Deserialize)]
247pub struct GooseStreamInfo {
248  pub name: String,
249}
250
251/// Metadata about a system (framework-provided) stream.
252#[derive(Debug, Clone, Deserialize)]
253pub struct SysStreamInfo {
254  pub name: String,
255}
256
257// This is what you get from the framework when registering.
258/// Stream metadata returned by the framework upon registration.
259#[derive(Debug, Clone, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub enum StreamInfo {
262  DataStreamInfo(DataStreamInfo),
263  GooseStreamInfo(GooseStreamInfo),
264  SysStreamInfo(SysStreamInfo),
265}
266
267impl StreamInfo {
268  /// Returns the human-readable name of the stream.
269  pub fn name(&self) -> String {
270    match self {
271      Self::DataStreamInfo(info) => info.name.clone(),
272      Self::GooseStreamInfo(info) => info.name.clone(),
273      Self::SysStreamInfo(info) => info.name.clone(),
274    }
275  }
276}
277
278// This is what you put in your settings.
279/// An input stream reference used in simulator settings.
280#[derive(Debug, Clone, Deserialize)]
281pub struct InputStream {
282  #[serde(rename = "$input_stream")]
283  pub name: StreamId,
284}
285
286/// An output stream reference used in simulator settings.
287#[derive(Debug, Clone, Deserialize)]
288pub struct OutputStream {
289  #[serde(rename = "$output_stream")]
290  pub name: String,
291  pub sample_rate: SampleRate,
292  pub fields: Vec<StreamLayoutValue>,
293}
294
295#[derive(Debug, Clone, Deserialize)]
296pub struct OwnedTrigger {
297  #[serde(rename = "$trigger")]
298  pub id: String,
299}
300
301#[derive(Debug, Clone, Deserialize)]
302pub struct SendTrigger {
303  #[serde(rename = "$send_trigger")]
304  pub id: String,
305}