Skip to content

Commit

Permalink
Merge branch 'feature/new_events' into develop
Browse files Browse the repository at this point in the history
sameold,samedec: New EventCode API

Previous versions of the `EventCode` API had two problems:

1. The conversion from `MessageHeader` → `EventCode` was fallible,
   even though messages with unrecognized event codes should still
   be played.

2. Since each SAME event code was its own `EventCode`, it was not
   possible to separate the phenomenon from the severity.

This PR is a major overhaul of all enums used to decode SAME
event codes.

Originator:

* Improved detection of National Weather Service vs Environment
  Canada. The `WeatherService` enum variant is dropped.

* Rename `Originator::as_str()` to `Originator::as_code_str()`
  for clarity

* Support the "alternate" format code.

* Originator is no longer `From<&str>`. Instead, it should be
  constructed via `Originator::from_org_and_call()`.

SignificanceLevel:

* `SignificanceLevel::from()` constructs directly from
   significance code str. The conversion is now infallible.

  * A new `SignificanceLevel::Unknown` variant is returned
    when a significance level cannot be determined. It sorts
    higher than `Warning`.

  * The UnknownSignificanceLevel is no longer required and
    is removed.

* Renamed `SignificanceLevel::as_str()` →
  `SignificanceLevel::as_code_str()`. This method returns a
  one-character significance code.

* The following messages are upgraded to `Statement`:

  * ADR Administrative Message
  * NMN Network Message Notification

Phenomenon:

* The enum of all the SAME events is renamed to `Phenomenon`.

* Phenomenon previously de-normalized into significance levels,
  like `TornadoWatch` and `TornadoWarning`, are represented as
  a single enum entry like `Tornado`.

* Add Phenomenon from the 2022 revision of NWSI 10-1712 and more
  Canadian codes:

  * EAN: Renamed to National Emergency Message
  * NAT: National Audible Test
  * NST: National Silent Test
  * FSW: Flash Freeze Warning
  * FZW: Freeze Warning
  * HLS: Hurricane Local Statement
  * SQW: Snow Squall Warning

* Add basic categories of Phenomenon

* Fix bad code entry for ADR (Administrative Message)

EventCode:

* The `EventCode` is now a struct which represents the
  combination of a `Phenomenon` and a `SignificanceLevel`.

* `EventCode` no longer convert directly to static str. Formatting
   requires either `Display` or `to_string()`. The human-readable
   string output for each SAME event code is largely unchanged.
   Remove `EventCode::as_display_str()` and replace with
   `to_display_string()`.

* The alternate formatter for `EventCode` now displays only the
  phenomenon, like `Tornado`, without the significance level

* `EventCode` are now Ord by their significance levels

* `EventCode` no longer convert back to their original SAME event
   code string. Use `MessageHeader::event_str()` instead.

This is an API-BREAKING change for sameold but preserves the
samedec CLI.
  • Loading branch information
cbs228 committed Feb 23, 2024
2 parents 98b4491 + ec9f691 commit c5292b4
Show file tree
Hide file tree
Showing 15 changed files with 1,536 additions and 832 deletions.
64 changes: 64 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions crates/samedec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,15 @@ The child process receives the following additional environment variables:

* `SAMEDEC_EVT`: the three-character SAME event code, like "`RWT`"

* `SAMEDEC_EVENT`: human-readable event name: "`Required Weekly Test`." If the
event code is not known, and it its significance level is also unknown, then
this string will be "`Unrecognized`."
* `SAMEDEC_EVENT`: human-readable event description, including its significance
level: "`Required Weekly Test`." If the event code is not known, and it its
significance level is also unknown, then this string will be
"`Unrecognized Warning`."

* `SAMEDEC_SIGNIFICANCE`: one-character significance level. This variable will
be empty if the significance level could not be determined (i.e., because
the event code is unknown).

* `T`: Test
* `M`: Message
* `S`: Statement
Expand Down
4 changes: 2 additions & 2 deletions crates/samedec/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ mod tests {
use super::*;

use chrono::{Duration, TimeZone, Utc};
use sameold::EventCode;
use sameold::Phenomenon;

#[test]
fn test_make_demo_message() {
Expand All @@ -270,7 +270,7 @@ mod tests {
Message::StartOfMessage(hdr) => hdr,
_ => unreachable!(),
};
assert_eq!(msg.event().unwrap(), EventCode::PracticeDemoWarning);
assert_eq!(msg.event().phenomenon(), Phenomenon::PracticeDemoWarning);
assert_eq!(msg.issue_datetime(&tm).unwrap(), tm);
assert_eq!(msg.valid_duration(), Duration::minutes(15));
}
Expand Down
35 changes: 6 additions & 29 deletions crates/samedec/src/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::io;
use std::process::{Child, Command, Stdio};

use chrono::{DateTime, Utc};
use sameold::{MessageHeader, UnrecognizedEventCode};
use sameold::MessageHeader;

/// Spawn a child process to handle the given message
///
Expand Down Expand Up @@ -54,8 +54,11 @@ where
header.originator().as_display_str(),
)
.env(childenv::SAMEDEC_EVT, header.event_str())
.env(childenv::SAMEDEC_EVENT, msg_to_event(&header))
.env(childenv::SAMEDEC_SIGNIFICANCE, msg_to_significance(header))
.env(childenv::SAMEDEC_EVENT, header.event().to_string())
.env(
childenv::SAMEDEC_SIGNIFICANCE,
header.event().significance().as_code_str(),
)
.env(childenv::SAMEDEC_LOCATIONS, locations.join(" "))
.env(childenv::SAMEDEC_ISSUETIME, issue_ts)
.env(childenv::SAMEDEC_PURGETIME, purge_ts)
Expand Down Expand Up @@ -143,23 +146,6 @@ mod childenv {
pub const SAMEDEC_PURGETIME: &str = "SAMEDEC_PURGETIME";
}

// convert message event code to string
fn msg_to_event(msg: &MessageHeader) -> String {
match msg.event() {
Ok(evt) => evt.as_display_str().to_owned(),
Err(err) => format!("{}", err),
}
}

// convert message event code to significance
fn msg_to_significance(msg: &MessageHeader) -> &'static str {
match msg.event() {
Ok(evt) => evt.to_significance_level().as_str(),
Err(UnrecognizedEventCode::WithSignificance(sl)) => sl.as_str(),
Err(UnrecognizedEventCode::Unrecognized) => "",
}
}

// convert DateTime to UTC unix timestamp in seconds, as string
fn time_to_unix_str(tm: DateTime<Utc>) -> String {
format!("{}", tm.format("%s"))
Expand All @@ -169,15 +155,6 @@ fn time_to_unix_str(tm: DateTime<Utc>) -> String {
mod tests {
use super::*;

use sameold::MessageHeader;

#[test]
fn test_msg_to_significance() {
const MSG: &str = "ZCZC-WXR-RWT-012345-567890-888990+0351-3662322-NOCALL-";
let msg = MessageHeader::new(MSG).unwrap();
assert_eq!("T", msg_to_significance(&msg));
}

#[test]
fn test_time_to_unix_str() {
let dt: DateTime<Utc> = DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT")
Expand Down
1 change: 1 addition & 0 deletions crates/sameold/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ log = "0.4"
nalgebra = "^0.32.2"
num-complex = "^0.3.1"
num-traits = "^0.2"
phf = {version = "^0.11", features = ["macros"]}
regex = "^1.5.5"
slice-ring-buffer = "^0.3"
strum = "^0.21"
Expand Down
34 changes: 20 additions & 14 deletions crates/sameold/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ carrier signals before and during message decoding.

### Interpreting Messages

The [`Message`] type marks the start or end of a SAME message. The
actual "message" part of a SAME message is the audio itself, which
should contain a voice message that
The [`Message`](https://docs.rs/sameold/latest/sameold/enum.Message.html) type
marks the start or end of a SAME message. The actual "message" part of a SAME
message is the audio itself, which should contain a voice message that

* describes the event; and
* provides instructions to the listener.
Expand All @@ -148,21 +148,25 @@ If this was the header string received, then you could decode
`hdr` from the previous example as follows:

```rust
use sameold::{EventCode, Originator, SignificanceLevel};
use sameold::{Phenomenon, Originator, SignificanceLevel};

// what organization originated the message?
assert_eq!(Originator::NationalWeatherService, hdr.originator());

// event code
// in actual implementations, handle this error gracefully!
let evt = hdr.event().expect("unknown event code");
assert_eq!(EventCode::RequiredWeeklyTest, evt);
// parse SAME event code `RWT`
let evt = hdr.event();

// events have a "significance level" which describes how
// urgent or actual they are
assert_eq!(SignificanceLevel::Test, evt.to_significance_level());
// the Phenomenon describes what is occurring
assert_eq!(Phenomenon::RequiredWeeklyTest, evt.phenomenon());

// the SignificanceLevel indicates the overall severity and/or
// how intrusive or noisy the alert should be
assert_eq!(SignificanceLevel::Test, evt.significance());
assert!(SignificanceLevel::Test < SignificanceLevel::Warning);

// Display to the user
assert_eq!("Required Weekly Test", &format!("{}", evt));

// location codes are accessed by iterator
let first_location = hdr.location_str_iter().next();
assert_eq!(Some("012345"), first_location);
Expand All @@ -171,10 +175,12 @@ assert_eq!(Some("012345"), first_location);
SAME messages are always transmitted three times for redundancy.
When decoding the message header, `sameold` will use all three
transmissions together to improve decoding. Only one
[`Message::StartOfMessage`] is output for all three header transmissions.
[`Message::StartOfMessage`](https://docs.rs/sameold/latest/sameold/enum.Message.html#variant.StartOfMessage)
is output for all three header transmissions.
The trailers which denote the end of the message are **not** subject to
this error-correction process. One [`Message::EndOfMessage`] is
output for every trailer received. There may be up to three
this error-correction process. One
[`Message::EndOfMessage`](https://docs.rs/sameold/latest/sameold/enum.Message.html#variant.EndOfMessage)
is output for every trailer received. There may be up to three
`EndOfMessage` output for every complete SAME message.

## Background
Expand Down
Loading

0 comments on commit c5292b4

Please sign in to comment.