Skip to content

Commit

Permalink
implement report exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
rkusa committed Jan 10, 2019
1 parent 567a809 commit b7996d5
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 77 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.7.0] - 2019-01-10
### Changed
- Added radio information to the initial SRS sync message #10
- All ATIS reports are now exported into the DCS saved games directory into `Logs\atis-reports.json`

### Added
- Log a warning if there are no ATIS stations found
Expand Down
7 changes: 5 additions & 2 deletions datis/src/datis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::srs::AtisSrsClient;
use crate::station::*;
use crate::tts::VoiceKind;
use crate::weather::*;
use crate::export::ReportExporter;
use hlua51::{Lua, LuaFunction, LuaTable};
use regex::{Regex, RegexBuilder};

Expand All @@ -14,7 +15,7 @@ pub struct Datis {
}

impl Datis {
pub fn create(mut lua: Lua<'_>) -> Result<Self, Error> {
pub fn create(mut lua: Lua<'_>, log_dir: String) -> Result<Self, Error> {
debug!("Extracting ATIS stations from Mission Situation");

// read gcloud access key option
Expand Down Expand Up @@ -274,10 +275,12 @@ impl Datis {
warn!("No ATIS stations found ...");
}

let export = ReportExporter::new(log_dir + "atis-reports.json");

Ok(Datis {
clients: stations
.into_iter()
.map(|station| AtisSrsClient::new(station, gcloud_key.clone()))
.map(|station| AtisSrsClient::new(station, export.clone(), gcloud_key.clone()))
.collect(),
})
}
Expand Down
37 changes: 37 additions & 0 deletions datis/src/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::fs::File;

#[derive(Clone)]
pub struct ReportExporter(Arc<Mutex<ReportExporterInner>>);

pub struct ReportExporterInner {
path: String,
reports: HashMap<String, String>,
}

impl ReportExporter {
pub fn new(path: String) -> Self {
ReportExporter(Arc::new(Mutex::new(ReportExporterInner {
path,
reports: HashMap::new(),
})))
}

pub fn export(&self, name: &str, report: String) {
let mut inner = self.0.lock().unwrap();
inner.reports.insert(name.to_string(), report);

let mut file = match File::create(&inner.path) {
Ok(f) => f,
Err(err) => {
error!("Error opening export file {}: {}", inner.path, err);
return;
}
};

if let Err(err) = serde_json::to_writer_pretty(&mut file, &inner.reports) {
error!("Error exporting report: {}", err);
}
}
}
52 changes: 28 additions & 24 deletions datis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extern crate serde_derive;
mod macros;
mod datis;
mod error;
mod export;
mod srs;
mod station;
mod tts;
Expand All @@ -33,14 +34,7 @@ use lua51_sys as ffi;
static mut INITIALIZED: bool = false;
static mut DATIS: Option<Datis> = None;

pub fn init(lua: &mut Lua<'_>) -> Result<(), Error> {
unsafe {
if INITIALIZED {
return Ok(());
}
INITIALIZED = true;
}

pub fn init(lua: &mut Lua<'_>) -> Result<String, Error> {
// init logging
use log::LevelFilter;
use log4rs::append::file::FileAppender;
Expand All @@ -49,22 +43,29 @@ pub fn init(lua: &mut Lua<'_>) -> Result<(), Error> {
let mut lfs: LuaTable<_> = get!(lua, "lfs")?;
let mut writedir: LuaFunction<_> = get!(lfs, "writedir")?;
let writedir: String = writedir.call()?;
let log_file = writedir + "Logs\\DATIS.log";

let requests = FileAppender::builder()
.append(false)
.build(log_file)
.unwrap();
if unsafe { !INITIALIZED } {
let log_file = writedir.clone() + "Logs\\DATIS.log";

let requests = FileAppender::builder()
.append(false)
.build(log_file)
.unwrap();

let config = Config::builder()
.appender(Appender::builder().build("file", Box::new(requests)))
.logger(Logger::builder().build("datis", LevelFilter::Info))
.build(Root::builder().appender("file").build(LevelFilter::Off))
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("file", Box::new(requests)))
.logger(Logger::builder().build("datis", LevelFilter::Info))
.build(Root::builder().appender("file").build(LevelFilter::Off))
.unwrap();

log4rs::init_config(config).unwrap();
log4rs::init_config(config).unwrap();
}

Ok(())
unsafe {
INITIALIZED = true;
}

Ok(writedir + "Logs\\")
}

#[no_mangle]
Expand All @@ -73,13 +74,16 @@ pub extern "C" fn start(state: *mut ffi::lua_State) -> c_int {
if DATIS.is_none() {
let mut lua = Lua::from_existing_state(state, false);

if let Err(err) = init(&mut lua) {
return report_error(state, &err.to_string());
}
let log_dir = match init(&mut lua) {
Ok(p) => p,
Err(err) => {
return report_error(state, &err.to_string());
}
};

info!("Starting DATIS version {} ...", env!("CARGO_PKG_VERSION"));

match Datis::create(lua) {
match Datis::create(lua, log_dir) {
Ok(mut datis) => {
for client in datis.clients.iter_mut() {
if let Err(err) = client.start() {
Expand Down
16 changes: 12 additions & 4 deletions datis/src/srs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ use crate::worker::{Context, Worker};
use byteorder::{LittleEndian, WriteBytesExt};
use ogg::reading::PacketReader;
use uuid::Uuid;
use crate::export::ReportExporter;

const MAX_FRAME_LENGTH: usize = 1024;

pub struct AtisSrsClient {
sguid: String,
gcloud_key: String,
station: Station,
exporter: ReportExporter,
worker: Vec<Worker<()>>,
}

impl AtisSrsClient {
pub fn new(station: Station, gcloud_key: String) -> Self {
pub fn new(station: Station, exporter: ReportExporter, gcloud_key: String) -> Self {
let sguid = Uuid::new_v4();
let sguid = base64::encode_config(sguid.as_bytes(), base64::URL_SAFE_NO_PAD);
assert_eq!(sguid.len(), 22);
Expand All @@ -30,6 +32,7 @@ impl AtisSrsClient {
sguid,
gcloud_key,
station,
exporter,
worker: Vec::new(),
}
}
Expand Down Expand Up @@ -92,8 +95,9 @@ impl AtisSrsClient {
let sguid = self.sguid.clone();
let gcloud_key = self.gcloud_key.clone();
let station = self.station.clone();
let exporter = self.exporter.clone();
self.worker.push(Worker::new(move |ctx| {
if let Err(err) = audio_broadcast(ctx, sguid, gcloud_key, station) {
if let Err(err) = audio_broadcast(ctx, sguid, gcloud_key, station, exporter) {
error!("Error starting SRS broadcast: {}", err);
}
}));
Expand Down Expand Up @@ -197,18 +201,22 @@ fn audio_broadcast(
sguid: String,
gloud_key: String,
station: Station,
exporter: ReportExporter,
) -> Result<(), Error> {
let interval = Duration::from_secs(60 * 60); // 60min
let mut interval_start;
let mut report_ix = 0;
loop {
interval_start = Instant::now();

// TODO: unwrap
let report = station.generate_report(report_ix)?;
let report = station.generate_report(report_ix, true)?;
let report_textual = station.generate_report(report_ix, false)?;
exporter.export(&station.name, report_textual);

report_ix += 1;
debug!("Report: {}", report);


let data = text_to_speech(&gloud_key, &report, station.voice)?;
let mut data = Cursor::new(data);

Expand Down
75 changes: 50 additions & 25 deletions datis/src/station.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,70 +37,92 @@ pub(crate) const BREAK: &str = "<break time=\"500ms\"/>\n";
pub(crate) const BREAK: &str = "| ";

impl Station {
pub fn generate_report(&self, report_nr: usize) -> Result<String, Error> {
pub fn generate_report(&self, report_nr: usize, spoken: bool) -> Result<String, Error> {
#[cfg(not(test))]
let _break = if spoken {
"<break time=\"500ms\"/>\n"
} else {
""
};
#[cfg(test)]
let _break = if spoken {
"| "
} else {
""
};

let information_letter = PHONETIC_ALPHABET[report_nr % PHONETIC_ALPHABET.len()];

let weather = self.get_current_weather()?;
let mut report = format!(
"<speak>\nThis is {} information {}. {}",
self.name, information_letter, BREAK
let mut report = if spoken { "<speak>\n" } else { "" }.to_string();

report += &format!(
"This is {} information {}. {}",
self.name, information_letter, _break
);

if let Some(rwy) = self.get_active_runway(weather.wind_dir) {
let rwy = pronounce_number(rwy);
report += &format!("Runway in use is {}. {}", rwy, BREAK);
let rwy = pronounce_number(rwy, spoken);
report += &format!("Runway in use is {}. {}", rwy, _break);
} else {
error!("Could not find active runway for {}", self.name);
}

let wind_dir = format!("{:0>3}", weather.wind_dir.round().to_string());
report += &format!(
"Wind {} at {} knots. {}",
pronounce_number(wind_dir),
pronounce_number(weather.wind_speed.round()),
BREAK,
pronounce_number(wind_dir, spoken),
pronounce_number(weather.wind_speed.round(), spoken),
_break,
);

if self.weather_kind == WeatherKind::Static {
report += &self.static_weather.get_clouds_report();
report += &format!("{}. {}", self.static_weather.get_visibility_report(spoken), _break);
if let Some(clouds_report) = self.static_weather.get_clouds_report(spoken) {
report += &format!("{}. {}", clouds_report, _break);
}
}

report += &format!(
"Temperature {} celcius. {}",
pronounce_number(round(weather.temperature, 1)),
BREAK,
pronounce_number(round(weather.temperature, 1), spoken),
_break,
);

report += &format!(
"ALTIMETER {}. {}",
// inHg, but using 0.02953 instead of 0.0002953 since we don't want to speak the
// DECIMAL here
pronounce_number((weather.pressure_qnh * 0.02953).round()),
BREAK,
pronounce_number((weather.pressure_qnh * 0.02953).round(), spoken),
_break,
);

if let Some(traffic_freq) = self.traffic_freq {
report += &format!(
"Traffic frequency {}. {}",
pronounce_number(round(traffic_freq as f64 / 1_000_000.0, 3)),
BREAK
pronounce_number(round(traffic_freq as f64 / 1_000_000.0, 3), spoken),
_break
);
}

report += &format!("REMARKS. {}", BREAK,);
report += &format!("REMARKS. {}", _break,);
report += &format!(
"{} hectopascal. {}",
pronounce_number((weather.pressure_qnh / 100.0).round()), // to hPA
BREAK,
pronounce_number((weather.pressure_qnh / 100.0).round(), spoken), // to hPA
_break,
);
report += &format!(
"QFE {} or {}. {}",
pronounce_number((weather.pressure_qfe * 0.02953).round()), // to inHg
pronounce_number((weather.pressure_qfe / 100.0).round()), // to hPA
BREAK,
pronounce_number((weather.pressure_qfe * 0.02953).round(), spoken), // to inHg
pronounce_number((weather.pressure_qfe / 100.0).round(), spoken), // to hPA
_break,
);

report += &format!("End information {}.\n</speak>", information_letter);
report += &format!("End information {}.", information_letter);

if spoken {
report += "\n</speak>";
}

Ok(report)
}
Expand Down Expand Up @@ -211,7 +233,10 @@ mod test {
dynamic_weather: DynamicWeather::create("").unwrap(),
};

let report = station.generate_report(26).unwrap();
assert_eq!(report, "<speak>\nThis is Kutaisi information Alpha. | Runway in use is ZERO 4. | Wind ZERO ZERO 6 at 1 ZERO knots. | Visibility ZERO. | Temperature 2 2 celcius. | ALTIMETER 2 NINER NINER 7. | Traffic frequency 2 4 NINER DECIMAL 5. | REMARKS. | 1 ZERO 1 5 hectopascal. | QFE 2 NINER NINER 7 or 1 ZERO 1 5. | End information Alpha.\n</speak>");
let report = station.generate_report(26, true).unwrap();
assert_eq!(report, "<speak>\nThis is Kutaisi information Alpha. | Runway in use is ZERO 4. | Wind ZERO ZERO 6 at 5 knots. | Visibility ZERO. | Temperature 2 2 celcius. | ALTIMETER 2 NINER NINER 7. | Traffic frequency 2 4 NINER DECIMAL 5. | REMARKS. | 1 ZERO 1 5 hectopascal. | QFE 2 NINER NINER 7 or 1 ZERO 1 5. | End information Alpha.\n</speak>");

let report = station.generate_report(26, false).unwrap();
assert_eq!(report, "This is Kutaisi information Alpha. Runway in use is 04. Wind 006 at 5 knots. Visibility 0. Temperature 22 celcius. ALTIMETER 2997. Traffic frequency 249.5. REMARKS. 1015 hectopascal. QFE 2997 or 1015. End information Alpha.");
}
}
6 changes: 5 additions & 1 deletion datis/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ pub fn round(n: f64, max_decimal_places: i32) -> f64 {
static PHONETIC_NUMBERS: &'static [&str] =
&["ZERO", "1", "2", "3", "4", "5", "6", "7", "8", "NINER"];

pub fn pronounce_number<S>(n: S) -> String
pub fn pronounce_number<S>(n: S, pronounce: bool) -> String
where
S: ToString,
{
if !pronounce {
return n.to_string();
}

n.to_string()
.chars()
.filter_map(|c| match c {
Expand Down
Loading

0 comments on commit b7996d5

Please sign in to comment.