1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//! # sameold: SAME/EAS Demodulation
//!
//! *Over-the-air weather alerts for your desktop or RPi.*
//!
//! This crate provides a digital demodulator and decoder for
//! [Specific Area Message Encoding](https://en.wikipedia.org/wiki/Specific_Area_Message_Encoding)
//! (SAME). It can detect the presence of SAME messages in an audio signal
//! and report them to the caller.
//!
//! ## Disclaimer
//!
//! This crate is dual-licensed MIT and Apache 2.0. Read these licenses
//! carefully as they may affect your rights.
//!
//! This crate has not been certified as a weather radio receiver or for any
//! other purpose. The author **strongly discourages** its use in any
//! safety-critical applications. Always have at least two methods available
//! for receiving weather alerts.
//!
//! ## Example
//!
//! A complete example may be found in our
//! [`samedec`](https://crates.io/crates/samedec) crate, which provides a
//! command-line program for decoding SAME via pipes.
//!
//! ### Demodulation and Decoding
//!
//! You will first need to recover *baseband audio* from a radio or
//! television station which broadcasts SAME signals. Obtain the
//! audio signal that you would normally listen to. You can use
//! either
//!
//! * an audio "line out" jack from a radio, scanner, or other
//! receiver; OR
//! * a software-defined radio
//!
//! In either case, obtaining the audio is beyond the scope of this
//! crate. To sample your soundcard, try
//! [cpal](https://crates.io/crates/cpal). If you have a stereo
//! signal, mix to mono first. If you are demodulating wideband FM,
//! and your demodulator offers you a choice, choose mono-only
//! demodulation.
//!
//! ```
//! use sameold::{Message, SameReceiverBuilder};
//!
//! # let some_audio_source_iterator = || std::iter::once(0.0f32);
//! // Create a SameReceiver with your audio sampling rate
//! // Sound cards typically run at 44100 Hz or 48000 Hz. Use
//! // an input rate of at least 8000 Hz.
//! let mut rx = SameReceiverBuilder::new(48000)
//! .with_agc_bandwidth(0.05) // AGC bandwidth at symbol rate, < 1.0
//! .with_agc_gain_limits(1.0/(i16::MAX as f32), 1.0/200.0) // for i16
//! .with_squelch_power(0.10, 0.05) // squelch open/close power, 0.0 < power < 1.0
//! .with_preamble_max_errors(2) // bit error limit when detecting sync sequence
//! .build();
//!
//! // let audiosrc be an iterator which outputs audio samples,
//! // such as a BufReader bound to stdin or a file, in f32
//! // format at the sampling rate (here 48000 Hz)
//! let audiosrc = some_audio_source_iterator();
//! for msg in rx.iter_messages(audiosrc) {
//! match msg {
//! Message::StartOfMessage(hdr) => {
//! println!("begin SAME voice message: {}", hdr);
//! }
//! Message::EndOfMessage => {
//! println!("end SAME voice message");
//! }
//! }
//! }
//! ```
//!
//! The digital receiver is created via a
//! [builder](crate::SameReceiverBuilder).
//!
//! The [`SameReceiver`] binds by iterator to any
//! source of `f32` PCM mono (1-channel) audio samples. If you're using `i16`
//! samples (as most sound cards do), you'll need to cast them to `f32`.
//! There is no need to scale them as long as you configure the
//! AGC properly, as above.
//!
//! The [`iter_messages()`](crate::SameReceiver::iter_messages()) iterator
//! consumes as many samples as possible until the next [`Message`] is decoded.
//!
//! ### Modem Behavior
//!
//! | # of Bursts | Decoding Strategy |
//! |-------------|------------------------------------|
//! | 1 | Fast EOM / `NNNN` only |
//! | 2 | Error detection (equality checks) |
//! | 3 | Error correction (bit voting) |
//!
//! SAME messages are always transmitted three times, in separate "bursts," for
//! redundancy. When decoding the start of message *headers* (`ZCZC`), `sameold`
//! will use all three bursts together to improve decoding—if possible.
//!
//! If one retransmission is missed, `sameold` will automatically fall back to
//! decoding with only two bursts. The decoder imposes a delay of approximately
//! **1.311 seconds** on all received headers. This delay is not usually
//! problematic as most SAME messages are prefixed with a Warning Alarm Tone that
//! is not information-bearing.
//!
//! The message *trailers* are not subject to the same error-correction process
//! and delay as the headers. The end-of-message indicator (`NNNN`) will be
//! printed just soon as it is received and decoded.
//!
//! The modem contains duplicate-suppression logic. Identical messages which
//! arrive within a window of approximately **10.86 seconds** of each other will
//! be suppressed and not emitted.
//!
//! The modem is separated into two parts:
//!
//! 1. the "*link layer*," which converts analog waveforms into framed
//! [`Burst`](crate::LinkState::Burst)s; and
//!
//! 2. the "*transport layer*," assembles individual `Bursts` into `Messages`.
//!
//! Events from both layers can be captured using the
//! [`iter_events()`](crate::SameReceiver::iter_events) method instead of
//! `iter_messages()`. The events iterator can be used to obtain raw framed
//! [bursts](crate::SameReceiverEvent::burst) without delay or error-correction.
//! Events can also report the detection of SAME carrier signals before and
//! during message decoding.
//!
//! ### Interpreting Messages
//!
//! > Message decoding and interpretation is provided by the
//! > [`sameplace`](https://docs.rs/sameplace/latest/sameplace/)
//! > crate. `sameold` re-exports the `sameplace::Message` API
//! > for ease-of-use.
//!
//! 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
//!
//! * describes the event; and
//! * provides instructions to the listener.
//!
//! This crate decodes the digital headers and trailers which summarize
//! the message. An example header, as received "off the wire" in ASCII
//! format, is:
//!
//! ```txt
//! ZCZC-WXR-RWT-012345-567890-888990+0015-0321115-KLOX/NWS-
//! ```
//!
//! If this was the header string received, then you could decode
//! `hdr` from the previous example as follows:
//!
//! ```
//! # use std::fmt;
//! # use sameold::{MessageHeader};
//! use sameold::{Phenomenon, Originator, SignificanceLevel};
//! # let hdr = MessageHeader::new(
//! # "ZCZC-WXR-RWT-012345-567890-888990+0015-0321115-KLOX/NWS-"
//! # ).expect("fail to parse");
//!
//! // what organization originated the message?
//! assert_eq!(Originator::NationalWeatherService, hdr.originator());
//!
//! // parse SAME event code `RWT`
//! let evt = hdr.event();
//!
//! // 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);
//! ```
//!
//! 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.
//! 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
//! `EndOfMessage` output for every complete SAME message.
//!
//! ## Background
//!
//! SAME is commonly used to distribute weather alerts in the United States and
//! Canada. It was originally developed for use with broadcast stations that
//! carry analog audio signals, such as:
//!
//! * [NOAA Weather Radio](https://www.weather.gov/nwr/)
//! * Commercial FM radio broadcast stations
//! * Commercial television broadcast and cable networks
//!
//! These stations participate in an emergency alerting network known as the
//! [Emergency Alert System](https://en.wikipedia.org/wiki/Emergency_Alert_System),
//! which disseminates alerts to the general public.
//!
//! SAME messages are transmitted in place of the station's normal programming
//! as an audio-only message. SAME messages include a digital header which
//! separates them from the station's normal programming. The digital header is
//! also sent in-band—encoded with an analog modulation to preserve it. SAME
//! headers are modulated using two-level frequency-shift keying (FSK) and sent
//! at a baud rate of 520.83 Hz.
//!
//! ## Crate features
//!
//! * `chrono`: Use chrono to calculate message
//! [issuance times](crate::MessageHeader#method.issue_datetime)
//! and other fields as true UTC timestamps. If enabled, `chrono`
//! becomes part of this crate's public API.
//!
//! ## MSRV Policy
//!
//! A minimum supported rust version (MSRV) increase will be treated as a minor
//! version bump.
//!
//! ## Contributing
//!
//! If you have a **recording** of a signal that you think should demodulate, but
//! doesn't, please open an new issue on
//! [github](https://github.com/cbs228/sameold). Either attach or link to your
//! recording.
//!
//! Please read our
//! [contributing guidelines](https://github.com/cbs228/sameold/blob/master/CONTRIBUTING.md)
//! before opening any issues or PRs.
// re-export sameplace's public API
pub use eventcodes;
pub use ;
pub use ;