From fe14cef7b81da2f6bf98f92efc1fbb95fac17e71 Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Thu, 22 Sep 2022 17:25:10 +0300 Subject: [PATCH 01/22] Improve code clarity and save configuration for when writer is reset --- audio-logger/src/main.rs | 74 +++--------- audio-logger/src/recorder.rs | 220 +++++++++++++++++++++++------------ 2 files changed, 162 insertions(+), 132 deletions(-) diff --git a/audio-logger/src/main.rs b/audio-logger/src/main.rs index 8839314..066da9e 100644 --- a/audio-logger/src/main.rs +++ b/audio-logger/src/main.rs @@ -10,7 +10,7 @@ use recorder::{batch_recording, contiguous_recording}; use cli::*; use std::path::Path; use print_configs::*; -use std::sync::{Arc, Mutex, Condvar, atomic::{AtomicBool, Ordering}}; +use std::sync::{Condvar, atomic::{AtomicBool, Ordering}}; const DEFAULT_SAMPLE_RATE: u32 = 44100; const DEFAULT_CHANNEL_COUNT: u16 = 1; @@ -19,59 +19,6 @@ const ALLOWED_SAMPLE_RATES: &[u32] = &[44100, 48000, 88200, 96000, 176400, 19200 const MAX_CHANNEL_COUNT: u16 = 2; const MIN_BUFFER_SIZE: usize = 64; const MAX_BUFFER_SIZE: usize = 8192; -const FMT_TIME: &str = "%Y-%m-%d-%H:%M:%S.%f"; - -type StreamInterrupt = Arc<(Mutex, Condvar)>; -type BatchInterrupt= Arc; - -/// # Interrupts Handling -/// -/// The `Recorder` struct has two interrupt mechanisms: -/// -/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`. -/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`. -#[derive(Clone)] -pub struct InterruptHandles { - batch_interrupt: BatchInterrupt, - stream_interrupt: StreamInterrupt, -} - -impl InterruptHandles { - pub fn new() -> Result { - let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new())); - let stream_interrupt_cloned = stream_interrupt.clone(); - - let batch_interrupt = Arc::new(AtomicBool::new(false)); - let batch_interrupt_cloned = batch_interrupt.clone(); - - ctrlc::set_handler(move || { - // Set batch interrupt to true - batch_interrupt_cloned.store(true, Ordering::SeqCst); - - // Release the stream - let &(ref lock, ref cvar) = &*stream_interrupt_cloned; - let mut started = lock.lock().unwrap(); - *started = true; - cvar.notify_one(); - })?; - Ok(Self { - batch_interrupt, - stream_interrupt, - }) - } - - pub fn stream_wait(&self) { - let &(ref lock, ref cvar) = &*self.stream_interrupt; - let mut started = lock.lock().unwrap(); - while !*started { - started = cvar.wait(started).unwrap(); - } - } - - pub fn batch_is_running(&self) -> bool { - !self.batch_interrupt.load(Ordering::SeqCst) - } -} fn main() -> Result<(), anyhow::Error> { let args = Args::parse(); @@ -81,14 +28,27 @@ fn main() -> Result<(), anyhow::Error> { return Ok(()); } - let interrupt_handles = InterruptHandles::new()?; + let mut recorder = recorder::Recorder::init( + args.name.clone(), + match args.output.clone() { + Some(path) => path, + None => Path::new("./").to_path_buf(), + }, + match args.host { + Hosts::Alsa => cpal::HostId::Alsa, + Hosts::Jack => cpal::HostId::Jack, + }, + args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), + args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), + args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), + )?; match args.batch_recording { Some(secs) => { - batch_recording(&args, secs, interrupt_handles)?; + batch_recording(&mut recorder, secs)?; }, None => { - contiguous_recording(&args, interrupt_handles)?; + contiguous_recording(&mut recorder)?; } } diff --git a/audio-logger/src/recorder.rs b/audio-logger/src/recorder.rs index aa508f9..55a26f4 100644 --- a/audio-logger/src/recorder.rs +++ b/audio-logger/src/recorder.rs @@ -12,21 +12,42 @@ type WriteHandle = Arc>>>>; pub struct Recorder { writer: WriteHandle, - interrupt: InterruptHandles, + interrupt_handles: InterruptHandles, default_config: SupportedStreamConfig, user_config: StreamConfig, device: Device, - filename: String, + spec: hound::WavSpec, + name: String, + path: PathBuf, + current_file: String, } -/// # Stream User Config +fn get_host(host: HostId) -> Result { + Ok(cpal::host_from_id(cpal::available_hosts() + .into_iter() + .find(|id| *id == host) + .ok_or(anyhow!("Requested host device not found"))? + )?) +} + +fn get_device(host: Host) -> Result { + Ok(host.default_input_device() + .ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`", + ))?) +} + +fn get_default_config(device: &Device) -> Result { + Ok(device.default_input_config()?) +} + +/// # Get User Config /// /// Overrides certain fields of the default stream config with the user's config. /// /// sample_rate: The user's sample rate if it is supported by the device, otherwise the default sample rate. /// channels: The user's number of channels if it is supported by the device, otherwise the default number of channels. /// buffer_size: The user's buffer size if it is supported by the device, otherwise the default buffer size. -fn stream_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result { +fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result { if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) { return Err(anyhow!( "Sample rate {} is not supported. Allowed sample rates: {:?}", @@ -56,6 +77,34 @@ fn stream_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Resu }) } +fn get_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result { + Ok(hound::WavSpec { + channels: user_config.channels, + sample_rate: user_config.sample_rate.0, + bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16, + sample_format: match default_config.sample_format() { + cpal::SampleFormat::U16 => hound::SampleFormat::Int, + cpal::SampleFormat::I16 => hound::SampleFormat::Int, + cpal::SampleFormat::F32 => hound::SampleFormat::Float, + }, + }) +} + +fn get_filename(name: &str, path: &PathBuf) -> Result { + let now: DateTime = Local::now(); + let filename = format!( + "{}-{}-{}-{}-{}:{}:{}.wav", + name, + now.year(), + now.month(), + now.day(), + now.hour(), + now.minute(), + now.second(), + ); + Ok(path.join(filename).to_str().unwrap().to_string()) +} + /// # Recorder /// /// The `Recorder` struct is used to record audio. @@ -69,53 +118,50 @@ impl Recorder { sample_rate: u32, channels: u16, buffer_size: u32, - interrupt: InterruptHandles, ) -> Result { - // Select requested host - let host = cpal::host_from_id(cpal::available_hosts() - .into_iter() - .find(|id| *id == host) - .ok_or(anyhow!("Requested host device not found"))? - )?; + // Create interrupt handles to be used by the stream or batch loop. + let interrupt_handles = InterruptHandles::new()?; + + // Select requested host. + let host = get_host(host)?; // Set up the input device and stream with the default input config. - let device = host.default_input_device() - .ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`", - ))?; + let device = get_device(host)?; - let default_config = device.default_input_config()?; - let user_config = stream_user_config(sample_rate, channels, buffer_size)?; + // Get default config for the device. + let default_config = get_default_config(&device)?; - let spec = hound::WavSpec { - channels: user_config.channels as _, - sample_rate: user_config.sample_rate.0 as _, - bits_per_sample: (default_config.sample_format().sample_size() * 8) as _, - sample_format: match default_config.sample_format() { - cpal::SampleFormat::U16 => hound::SampleFormat::Int, - cpal::SampleFormat::I16 => hound::SampleFormat::Int, - cpal::SampleFormat::F32 => hound::SampleFormat::Float, - }, - }; + // Override certain fields of the default stream config with the user's config. + let user_config = get_user_config(sample_rate, channels, buffer_size)?; - // The WAV file we're recording to. - let ts: String = Utc::now().format(FMT_TIME).to_string(); - let filename: String = path.to_str().unwrap().to_owned() + &name + "-" + &ts + ".wav"; + // Get the hound WAV spec for the user's config. + let spec = get_spec(&default_config, &user_config)?; Ok(Self { - writer: Arc::new(Mutex::new(Some(hound::WavWriter::create(filename.clone(), spec)?))), - interrupt, + writer: Arc::new(Mutex::new(None)), + interrupt_handles, default_config, user_config, device, - filename, + spec, + name, + path, + current_file: "".to_string(), }) } + fn init_writer(&mut self) -> Result<(), anyhow::Error> { + let filename = get_filename(&self.name, &self.path)?; + self.current_file = filename.clone(); + *self.writer.lock().unwrap() = Some(hound::WavWriter::create(filename, self.spec)?); + Ok(()) + } + fn create_stream(&self) -> Result { let writer = self.writer.clone(); let config = self.user_config.clone(); - let err_fn = |err| { eprintln!("an error occurred on stream: {}", err); }; + let err_fn = |err| { eprintln!("An error occurred on stream: {}", err); }; let stream = match self.default_config.sample_format() { cpal::SampleFormat::F32 => self.device.build_input_stream( @@ -137,21 +183,23 @@ impl Recorder { Ok(stream) } - pub fn record(&self) -> Result<(), anyhow::Error> { + pub fn record(&mut self) -> Result<(), anyhow::Error> { + self.init_writer()?; let stream = self.create_stream()?; stream.play()?; - println!("REC: {}", self.filename); - self.interrupt.stream_wait(); + println!("REC: {}", self.current_file); + self.interrupt_handles.stream_wait(); drop(stream); self.writer.lock().unwrap().take().unwrap().finalize()?; - println!("STOP: {}", self.filename); + println!("STOP: {}", self.current_file); Ok(()) } - pub fn record_secs(&self, secs: u64) -> Result<(), anyhow::Error> { + pub fn record_secs(&mut self, secs: u64) -> Result<(), anyhow::Error> { + self.init_writer()?; let stream = self.create_stream()?; stream.play()?; - println!("REC: {}", self.filename); + println!("REC: {}", self.current_file); let now = std::time::Instant::now(); loop { std::thread::sleep(std::time::Duration::from_millis(500)); @@ -161,7 +209,7 @@ impl Recorder { } drop(stream); self.writer.lock().unwrap().take().unwrap().finalize()?; - println!("STOP: {}", self.filename); + println!("STOP: {}", self.current_file); Ok(()) } } @@ -181,44 +229,66 @@ where } } -pub fn batch_recording(args: &Args, secs: u64, interrupt_handles: InterruptHandles) -> Result<(), anyhow::Error> { - while interrupt_handles.batch_is_running() { - let recorder = recorder::Recorder::init( - args.name.clone(), - match args.output.clone() { - Some(path) => path, - None => Path::new("./").to_path_buf(), - }, - match args.host { - Hosts::Alsa => cpal::HostId::Alsa, - Hosts::Jack => cpal::HostId::Jack, - }, - args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), - args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), - args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), - interrupt_handles.clone(), - )?; - recorder.record_secs(secs)?; +pub fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), anyhow::Error> { + while rec.interrupt_handles.batch_is_running() { + rec.record_secs(secs)?; } Ok(()) } -pub fn contiguous_recording(args: &Args, interrupt_handles: InterruptHandles) -> Result<(), anyhow::Error> { - let recorder = recorder::Recorder::init( - args.name.clone(), - match args.output.clone() { - Some(path) => path, - None => Path::new("./").to_path_buf(), - }, - match args.host { - Hosts::Alsa => cpal::HostId::Alsa, - Hosts::Jack => cpal::HostId::Jack, - }, - args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), - args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), - args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), - interrupt_handles.clone(), - )?; - recorder.record()?; +pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), anyhow::Error> { + rec.record()?; Ok(()) } + +type StreamInterrupt = Arc<(Mutex, Condvar)>; +type BatchInterrupt= Arc; + +/// # Interrupts Handling +/// +/// The `Recorder` struct has two interrupt mechanisms: +/// +/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`. +/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`. +#[derive(Clone)] +pub struct InterruptHandles { + batch_interrupt: BatchInterrupt, + stream_interrupt: StreamInterrupt, +} + +impl InterruptHandles { + pub fn new() -> Result { + let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new())); + let stream_interrupt_cloned = stream_interrupt.clone(); + + let batch_interrupt = Arc::new(AtomicBool::new(false)); + let batch_interrupt_cloned = batch_interrupt.clone(); + + ctrlc::set_handler(move || { + // Set batch interrupt to true + batch_interrupt_cloned.store(true, Ordering::SeqCst); + + // Release the stream + let &(ref lock, ref cvar) = &*stream_interrupt_cloned; + let mut started = lock.lock().unwrap(); + *started = true; + cvar.notify_one(); + })?; + Ok(Self { + batch_interrupt, + stream_interrupt, + }) + } + + pub fn stream_wait(&self) { + let &(ref lock, ref cvar) = &*self.stream_interrupt; + let mut started = lock.lock().unwrap(); + while !*started { + started = cvar.wait(started).unwrap(); + } + } + + pub fn batch_is_running(&self) -> bool { + !self.batch_interrupt.load(Ordering::SeqCst) + } +} \ No newline at end of file From 3cc46118caf8129f211a5bc8800588a9a78482cb Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Thu, 22 Sep 2022 17:39:49 +0300 Subject: [PATCH 02/22] Organize codebase --- audio-logger/Cargo.toml | 2 - audio-logger/src/getters.rs | 104 ++++++++++++++++++ audio-logger/src/input_handling.rs | 54 ++++++++++ audio-logger/src/main.rs | 2 + audio-logger/src/recorder.rs | 162 ++++------------------------- 5 files changed, 181 insertions(+), 143 deletions(-) create mode 100644 audio-logger/src/getters.rs create mode 100644 audio-logger/src/input_handling.rs diff --git a/audio-logger/Cargo.toml b/audio-logger/Cargo.toml index 07a5389..b6d594e 100644 --- a/audio-logger/Cargo.toml +++ b/audio-logger/Cargo.toml @@ -3,8 +3,6 @@ name = "audio-logger" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] clap = {url = "https://github.com/clap-rs/clap", features = ["derive"]} cpal= { version = "0.13.5", features = ["jack"] } diff --git a/audio-logger/src/getters.rs b/audio-logger/src/getters.rs new file mode 100644 index 0000000..566757c --- /dev/null +++ b/audio-logger/src/getters.rs @@ -0,0 +1,104 @@ +use anyhow::anyhow; +use cpal::traits::{DeviceTrait, HostTrait}; +use cpal::*; +use std::path::PathBuf; +use chrono::prelude::*; +use super::*; +/// # Get Host +/// +/// Returns the host with the given id if it's available. +pub fn get_host(host: HostId) -> Result { + Ok(cpal::host_from_id(cpal::available_hosts() + .into_iter() + .find(|id| *id == host) + .ok_or(anyhow!("Requested host device not found"))? + )?) +} + +/// # Get Device +/// +/// Returns the default input device for the host if it's available. +pub fn get_device(host: Host) -> Result { + Ok(host.default_input_device() + .ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`", + ))?) +} + +/// # Get Default Config +/// +/// Get the default config for the given device. +pub fn get_default_config(device: &Device) -> Result { + Ok(device.default_input_config()?) +} + +/// # Get User Config +/// +/// Overrides certain fields of the default stream config with the user's config. +/// +/// sample_rate: The user's sample rate if it is supported by the device, otherwise the default sample rate. +/// channels: The user's number of channels if it is supported by the device, otherwise the default number of channels. +/// buffer_size: The user's buffer size if it is supported by the device, otherwise the default buffer size. +pub fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result { + if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) { + return Err(anyhow!( + "Sample rate {} is not supported. Allowed sample rates: {:?}", + sample_rate, + ALLOWED_SAMPLE_RATES + )); + } + if !(channels >= 1 && channels <= MAX_CHANNEL_COUNT) { + return Err(anyhow!( + "Channel count {} is not supported. Allowed channel counts: 1-{}", + channels, + MAX_CHANNEL_COUNT + )); + } + if !(buffer_size >= MIN_BUFFER_SIZE as u32 && buffer_size <= MAX_BUFFER_SIZE as u32) { + return Err(anyhow!( + "Buffer size {} is not supported. Allowed buffer sizes: {}-{}", + buffer_size, + MIN_BUFFER_SIZE, + MAX_BUFFER_SIZE + )); + } + Ok(StreamConfig { + channels, + sample_rate: SampleRate(sample_rate), + buffer_size: BufferSize::Fixed(buffer_size), + }) +} + +/// # Get WAV Spec +/// +/// Get the WAV spec for the given stream config. +pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result { + Ok(hound::WavSpec { + channels: user_config.channels, + sample_rate: user_config.sample_rate.0, + bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16, + sample_format: match default_config.sample_format() { + cpal::SampleFormat::U16 => hound::SampleFormat::Int, + cpal::SampleFormat::I16 => hound::SampleFormat::Int, + cpal::SampleFormat::F32 => hound::SampleFormat::Float, + }, + }) +} + +/// # Get Filename +/// +/// Get the filename for the current recording according to the given format, +/// the current date and time, and the name prefix. +pub fn get_filename(name: &str, path: &PathBuf) -> Result { + let now: DateTime = Local::now(); + let filename = format!( + "{}-{}-{}-{}-{}:{}:{}.wav", + name, + now.year(), + now.month(), + now.day(), + now.hour(), + now.minute(), + now.second(), + ); + Ok(path.join(filename).to_str().unwrap().to_string()) +} diff --git a/audio-logger/src/input_handling.rs b/audio-logger/src/input_handling.rs new file mode 100644 index 0000000..ab0b675 --- /dev/null +++ b/audio-logger/src/input_handling.rs @@ -0,0 +1,54 @@ +use std::sync::{Arc, Mutex}; +use super::*; + +type StreamInterrupt = Arc<(Mutex, Condvar)>; +type BatchInterrupt= Arc; + +/// # Interrupts Handling +/// +/// The `Recorder` struct has two interrupt mechanisms: +/// +/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`. +/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`. +#[derive(Clone)] +pub struct InterruptHandles { + batch_interrupt: BatchInterrupt, + stream_interrupt: StreamInterrupt, +} + +impl InterruptHandles { + pub fn new() -> Result { + let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new())); + let stream_interrupt_cloned = stream_interrupt.clone(); + + let batch_interrupt = Arc::new(AtomicBool::new(false)); + let batch_interrupt_cloned = batch_interrupt.clone(); + + ctrlc::set_handler(move || { + // Set batch interrupt to true + batch_interrupt_cloned.store(true, Ordering::SeqCst); + + // Release the stream + let &(ref lock, ref cvar) = &*stream_interrupt_cloned; + let mut started = lock.lock().unwrap(); + *started = true; + cvar.notify_one(); + })?; + Ok(Self { + batch_interrupt, + stream_interrupt, + }) + } + + pub fn stream_wait(&self) { + let &(ref lock, ref cvar) = &*self.stream_interrupt; + let mut started = lock.lock().unwrap(); + while !*started { + started = cvar.wait(started).unwrap(); + } + } + + pub fn batch_is_running(&self) -> bool { + !self.batch_interrupt.load(Ordering::SeqCst) + } +} \ No newline at end of file diff --git a/audio-logger/src/main.rs b/audio-logger/src/main.rs index 066da9e..1f50514 100644 --- a/audio-logger/src/main.rs +++ b/audio-logger/src/main.rs @@ -4,6 +4,8 @@ mod cli; mod recorder; mod print_configs; +mod getters; +mod input_handling; use clap::Parser; use recorder::{batch_recording, contiguous_recording}; diff --git a/audio-logger/src/recorder.rs b/audio-logger/src/recorder.rs index 55a26f4..1ce153f 100644 --- a/audio-logger/src/recorder.rs +++ b/audio-logger/src/recorder.rs @@ -1,12 +1,11 @@ -use anyhow::anyhow; -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::traits::{DeviceTrait, StreamTrait}; use cpal::*; use std::fs::File; use std::io::BufWriter; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use chrono::prelude::*; -use super::*; +use crate::getters::*; +use crate::input_handling::*; type WriteHandle = Arc>>>>; @@ -22,95 +21,20 @@ pub struct Recorder { current_file: String, } -fn get_host(host: HostId) -> Result { - Ok(cpal::host_from_id(cpal::available_hosts() - .into_iter() - .find(|id| *id == host) - .ok_or(anyhow!("Requested host device not found"))? - )?) -} - -fn get_device(host: Host) -> Result { - Ok(host.default_input_device() - .ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`", - ))?) -} - -fn get_default_config(device: &Device) -> Result { - Ok(device.default_input_config()?) -} - -/// # Get User Config -/// -/// Overrides certain fields of the default stream config with the user's config. -/// -/// sample_rate: The user's sample rate if it is supported by the device, otherwise the default sample rate. -/// channels: The user's number of channels if it is supported by the device, otherwise the default number of channels. -/// buffer_size: The user's buffer size if it is supported by the device, otherwise the default buffer size. -fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result { - if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) { - return Err(anyhow!( - "Sample rate {} is not supported. Allowed sample rates: {:?}", - sample_rate, - ALLOWED_SAMPLE_RATES - )); - } - if !(channels >= 1 && channels <= MAX_CHANNEL_COUNT) { - return Err(anyhow!( - "Channel count {} is not supported. Allowed channel counts: 1-{}", - channels, - MAX_CHANNEL_COUNT - )); - } - if !(buffer_size >= MIN_BUFFER_SIZE as u32 && buffer_size <= MAX_BUFFER_SIZE as u32) { - return Err(anyhow!( - "Buffer size {} is not supported. Allowed buffer sizes: {}-{}", - buffer_size, - MIN_BUFFER_SIZE, - MAX_BUFFER_SIZE - )); - } - Ok(StreamConfig { - channels, - sample_rate: SampleRate(sample_rate), - buffer_size: BufferSize::Fixed(buffer_size), - }) -} - -fn get_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result { - Ok(hound::WavSpec { - channels: user_config.channels, - sample_rate: user_config.sample_rate.0, - bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16, - sample_format: match default_config.sample_format() { - cpal::SampleFormat::U16 => hound::SampleFormat::Int, - cpal::SampleFormat::I16 => hound::SampleFormat::Int, - cpal::SampleFormat::F32 => hound::SampleFormat::Float, - }, - }) -} - -fn get_filename(name: &str, path: &PathBuf) -> Result { - let now: DateTime = Local::now(); - let filename = format!( - "{}-{}-{}-{}-{}:{}:{}.wav", - name, - now.year(), - now.month(), - now.day(), - now.hour(), - now.minute(), - now.second(), - ); - Ok(path.join(filename).to_str().unwrap().to_string()) -} - /// # Recorder /// /// The `Recorder` struct is used to record audio. +/// +/// Use `init()` to initialize the recorder, `record()` to start a continuous recording, +/// and `rec_secs()` to record for a given number of seconds. The Recorder does not +/// need to be reinitialized after a recording is stopped. Calling `record()` or +/// `rec_secs()` again will start a new recording with a new filename according to +/// the time and date. impl Recorder { - /// Initializes a new recorder. + /// # Init + /// + /// Initializes the recorder with the given host, sample rate, channel count, and buffer size. pub fn init( name: String, path: PathBuf, @@ -136,7 +60,7 @@ impl Recorder { let user_config = get_user_config(sample_rate, channels, buffer_size)?; // Get the hound WAV spec for the user's config. - let spec = get_spec(&default_config, &user_config)?; + let spec = get_wav_spec(&default_config, &user_config)?; Ok(Self { writer: Arc::new(Mutex::new(None)), @@ -183,6 +107,10 @@ impl Recorder { Ok(stream) } + /// # Record + /// + /// Start a continuous recording. The recording will be stopped when the + /// user presses `Ctrl+C`. pub fn record(&mut self) -> Result<(), anyhow::Error> { self.init_writer()?; let stream = self.create_stream()?; @@ -195,6 +123,10 @@ impl Recorder { Ok(()) } + /// # Record Seconds + /// + /// Record for a given number of seconds or until the user presses `Ctrl+C`. + /// Current batch is finished before stopping. pub fn record_secs(&mut self, secs: u64) -> Result<(), anyhow::Error> { self.init_writer()?; let stream = self.create_stream()?; @@ -240,55 +172,3 @@ pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), anyhow::Error> { rec.record()?; Ok(()) } - -type StreamInterrupt = Arc<(Mutex, Condvar)>; -type BatchInterrupt= Arc; - -/// # Interrupts Handling -/// -/// The `Recorder` struct has two interrupt mechanisms: -/// -/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`. -/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`. -#[derive(Clone)] -pub struct InterruptHandles { - batch_interrupt: BatchInterrupt, - stream_interrupt: StreamInterrupt, -} - -impl InterruptHandles { - pub fn new() -> Result { - let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new())); - let stream_interrupt_cloned = stream_interrupt.clone(); - - let batch_interrupt = Arc::new(AtomicBool::new(false)); - let batch_interrupt_cloned = batch_interrupt.clone(); - - ctrlc::set_handler(move || { - // Set batch interrupt to true - batch_interrupt_cloned.store(true, Ordering::SeqCst); - - // Release the stream - let &(ref lock, ref cvar) = &*stream_interrupt_cloned; - let mut started = lock.lock().unwrap(); - *started = true; - cvar.notify_one(); - })?; - Ok(Self { - batch_interrupt, - stream_interrupt, - }) - } - - pub fn stream_wait(&self) { - let &(ref lock, ref cvar) = &*self.stream_interrupt; - let mut started = lock.lock().unwrap(); - while !*started { - started = cvar.wait(started).unwrap(); - } - } - - pub fn batch_is_running(&self) -> bool { - !self.batch_interrupt.load(Ordering::SeqCst) - } -} \ No newline at end of file From 2384026b07d10a669a6755865114f81ce067bc05 Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Fri, 23 Sep 2022 20:03:03 +0300 Subject: [PATCH 03/22] Refactor --- audio-logger/src/getters.rs | 28 +++++++++++++--------------- audio-logger/src/main.rs | 2 +- audio-logger/src/recorder.rs | 18 +++++++++--------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/audio-logger/src/getters.rs b/audio-logger/src/getters.rs index 566757c..191ea68 100644 --- a/audio-logger/src/getters.rs +++ b/audio-logger/src/getters.rs @@ -1,13 +1,15 @@ -use anyhow::anyhow; use cpal::traits::{DeviceTrait, HostTrait}; use cpal::*; use std::path::PathBuf; +use hound::WavSpec; use chrono::prelude::*; +use anyhow::{Error, anyhow}; use super::*; + /// # Get Host /// /// Returns the host with the given id if it's available. -pub fn get_host(host: HostId) -> Result { +pub fn get_host(host: HostId) -> Result { Ok(cpal::host_from_id(cpal::available_hosts() .into_iter() .find(|id| *id == host) @@ -18,7 +20,7 @@ pub fn get_host(host: HostId) -> Result { /// # Get Device /// /// Returns the default input device for the host if it's available. -pub fn get_device(host: Host) -> Result { +pub fn get_device(host: Host) -> Result { Ok(host.default_input_device() .ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`", ))?) @@ -27,7 +29,7 @@ pub fn get_device(host: Host) -> Result { /// # Get Default Config /// /// Get the default config for the given device. -pub fn get_default_config(device: &Device) -> Result { +pub fn get_default_config(device: &Device) -> Result { Ok(device.default_input_config()?) } @@ -38,7 +40,7 @@ pub fn get_default_config(device: &Device) -> Result Result { +pub fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result { if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) { return Err(anyhow!( "Sample rate {} is not supported. Allowed sample rates: {:?}", @@ -71,8 +73,8 @@ pub fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Res /// # Get WAV Spec /// /// Get the WAV spec for the given stream config. -pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result { - Ok(hound::WavSpec { +pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result { + Ok(WavSpec { channels: user_config.channels, sample_rate: user_config.sample_rate.0, bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16, @@ -88,17 +90,13 @@ pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &Stream /// /// Get the filename for the current recording according to the given format, /// the current date and time, and the name prefix. -pub fn get_filename(name: &str, path: &PathBuf) -> Result { +pub fn get_filename(name: &str, path: &PathBuf) -> String { let now: DateTime = Local::now(); let filename = format!( "{}-{}-{}-{}-{}:{}:{}.wav", name, - now.year(), - now.month(), - now.day(), - now.hour(), - now.minute(), - now.second(), + now.year(), now.month(), now.day(), + now.hour(), now.minute(), now.second(), ); - Ok(path.join(filename).to_str().unwrap().to_string()) + path.join(filename).to_str().unwrap().to_string() } diff --git a/audio-logger/src/main.rs b/audio-logger/src/main.rs index 1f50514..fef0a94 100644 --- a/audio-logger/src/main.rs +++ b/audio-logger/src/main.rs @@ -17,7 +17,7 @@ use std::sync::{Condvar, atomic::{AtomicBool, Ordering}}; const DEFAULT_SAMPLE_RATE: u32 = 44100; const DEFAULT_CHANNEL_COUNT: u16 = 1; const DEFAULT_BUFFER_SIZE: u32 = 1024; -const ALLOWED_SAMPLE_RATES: &[u32] = &[44100, 48000, 88200, 96000, 176400, 192000]; +const ALLOWED_SAMPLE_RATES: [u32; 6] = [44100, 48000, 88200, 96000, 176400, 192000]; const MAX_CHANNEL_COUNT: u16 = 2; const MIN_BUFFER_SIZE: usize = 64; const MAX_BUFFER_SIZE: usize = 8192; diff --git a/audio-logger/src/recorder.rs b/audio-logger/src/recorder.rs index 1ce153f..3a87fe7 100644 --- a/audio-logger/src/recorder.rs +++ b/audio-logger/src/recorder.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::getters::*; use crate::input_handling::*; - +use anyhow::Error; type WriteHandle = Arc>>>>; pub struct Recorder { @@ -42,7 +42,7 @@ impl Recorder { sample_rate: u32, channels: u16, buffer_size: u32, - ) -> Result { + ) -> Result { // Create interrupt handles to be used by the stream or batch loop. let interrupt_handles = InterruptHandles::new()?; @@ -75,14 +75,14 @@ impl Recorder { }) } - fn init_writer(&mut self) -> Result<(), anyhow::Error> { - let filename = get_filename(&self.name, &self.path)?; + fn init_writer(&mut self) -> Result<(), Error> { + let filename = get_filename(&self.name, &self.path); self.current_file = filename.clone(); *self.writer.lock().unwrap() = Some(hound::WavWriter::create(filename, self.spec)?); Ok(()) } - fn create_stream(&self) -> Result { + fn create_stream(&self) -> Result { let writer = self.writer.clone(); let config = self.user_config.clone(); let err_fn = |err| { eprintln!("An error occurred on stream: {}", err); }; @@ -111,7 +111,7 @@ impl Recorder { /// /// Start a continuous recording. The recording will be stopped when the /// user presses `Ctrl+C`. - pub fn record(&mut self) -> Result<(), anyhow::Error> { + pub fn record(&mut self) -> Result<(), Error> { self.init_writer()?; let stream = self.create_stream()?; stream.play()?; @@ -127,7 +127,7 @@ impl Recorder { /// /// Record for a given number of seconds or until the user presses `Ctrl+C`. /// Current batch is finished before stopping. - pub fn record_secs(&mut self, secs: u64) -> Result<(), anyhow::Error> { + pub fn record_secs(&mut self, secs: u64) -> Result<(), Error> { self.init_writer()?; let stream = self.create_stream()?; stream.play()?; @@ -161,14 +161,14 @@ where } } -pub fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), anyhow::Error> { +pub fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), Error> { while rec.interrupt_handles.batch_is_running() { rec.record_secs(secs)?; } Ok(()) } -pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), anyhow::Error> { +pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), Error> { rec.record()?; Ok(()) } From 16a2c5c47c0a3587faf4e73e27c5077beaaa2a6f Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Fri, 23 Sep 2022 20:15:27 +0300 Subject: [PATCH 04/22] Write README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index e69de29..9346be2 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,20 @@ +# Hydrophonitor + +A software package to record audio and related metadata from a configuration +of hydrophones. + +## Overview + +Module | Description +-----------------|- +audio-logger | Receive an audio signal from the DAC and write it on disk in `.wav` format. +gps-logger | Record position and time of the device in `.csv` format. +depth-logger | Record depth of the device and save it in `.csv` format. +*lcd-display | Provide information on the device's LCD screen +*device-controls | Provide device control using physical buttons. + +## Data Formats + +Type | Format +-----|- +GPS Data | \ No newline at end of file From e81028d75137387ae23efcd45daa13a356e95d6a Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Sun, 25 Sep 2022 17:44:48 +0300 Subject: [PATCH 05/22] Implement merge and play, refactor --- audio-logger/Cargo.lock | 222 +++++++++++++++++++++++++++-- audio-logger/Cargo.toml | 3 +- audio-logger/Makefile | 9 +- audio-logger/src/cli.rs | 64 ++++++++- audio-logger/src/constants.rs | 7 + audio-logger/src/getters.rs | 39 +++-- audio-logger/src/input_handling.rs | 7 +- audio-logger/src/main.rs | 68 ++++----- audio-logger/src/merge.rs | 43 ++++++ audio-logger/src/play.rs | 14 ++ audio-logger/src/print_configs.rs | 14 ++ audio-logger/src/recorder.rs | 52 +++++-- 12 files changed, 450 insertions(+), 92 deletions(-) create mode 100644 audio-logger/src/constants.rs create mode 100644 audio-logger/src/merge.rs create mode 100644 audio-logger/src/play.rs diff --git a/audio-logger/Cargo.lock b/audio-logger/Cargo.lock index a7a1b2f..86808a2 100644 --- a/audio-logger/Cargo.lock +++ b/audio-logger/Cargo.lock @@ -51,20 +51,21 @@ dependencies = [ ] [[package]] -name = "audio-logger" +name = "audio" version = "0.1.0" dependencies = [ "alsa", "anyhow", "chrono", "clap", - "cpal", + "cpal 0.13.5", "ctrlc", "hound", "jack 0.9.2", "libc", "nix 0.23.1", "parking_lot 0.12.1", + "rodio", ] [[package]] @@ -104,6 +105,12 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.2.1" @@ -205,6 +212,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + [[package]] name = "combine" version = "4.6.6" @@ -255,7 +268,7 @@ dependencies = [ "lazy_static", "libc", "mach", - "ndk", + "ndk 0.6.0", "ndk-glue", "nix 0.23.1", "oboe", @@ -266,6 +279,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpal" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d466b47cf0ea4100186a7c12d7d0166813dda7cf648553554c9c39c6324841b" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "jni", + "js-sys", + "libc", + "mach", + "ndk 0.7.0", + "ndk-context", + "nix 0.23.1", + "oboe", + "once_cell", + "parking_lot 0.12.1", + "stdweb", + "thiserror", + "web-sys", + "windows", +] + [[package]] name = "ctrlc" version = "3.2.3" @@ -276,6 +314,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "darling" version = "0.13.4" @@ -523,6 +567,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + [[package]] name = "libc" version = "0.2.133" @@ -598,6 +653,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + [[package]] name = "ndk" version = "0.6.0" @@ -606,11 +681,25 @@ checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ "bitflags", "jni-sys", - "ndk-sys", + "ndk-sys 0.3.0", "num_enum", "thiserror", ] +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.4.0", + "num_enum", + "raw-window-handle", + "thiserror", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -626,10 +715,10 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk", + "ndk 0.6.0", "ndk-context", "ndk-macro", - "ndk-sys", + "ndk-sys 0.3.0", ] [[package]] @@ -654,6 +743,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046" +dependencies = [ + "jni-sys", +] + [[package]] name = "nix" version = "0.23.1" @@ -747,7 +845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" dependencies = [ "jni", - "ndk", + "ndk 0.6.0", "ndk-context", "num-derive", "num-traits", @@ -763,6 +861,15 @@ dependencies = [ "cc", ] +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -906,6 +1013,15 @@ dependencies = [ "proc-macro2 1.0.43", ] +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -930,6 +1046,19 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "rodio" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5" +dependencies = [ + "claxon", + "cpal 0.14.0", + "hound", + "lewton", + "minimp3", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -963,6 +1092,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach", + "winapi", +] + [[package]] name = "smallvec" version = "1.9.0" @@ -1049,6 +1189,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.9" @@ -1188,17 +1343,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -1207,26 +1375,56 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" diff --git a/audio-logger/Cargo.toml b/audio-logger/Cargo.toml index b6d594e..147f3d9 100644 --- a/audio-logger/Cargo.toml +++ b/audio-logger/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "audio-logger" +name = "audio" version = "0.1.0" edition = "2021" @@ -10,6 +10,7 @@ anyhow = "1.0.61" hound = "3.4.0" chrono = "0.4.22" ctrlc = "3.2.3" +rodio = "0.16.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies] alsa = "0.6" diff --git a/audio-logger/Makefile b/audio-logger/Makefile index 8cd27c9..38011ab 100644 --- a/audio-logger/Makefile +++ b/audio-logger/Makefile @@ -1,9 +1,10 @@ -test: all - bash run_test.sh - all: cargo build --release + +test: + cargo build --release mkdir -p recordings + bash run_test.sh clean: cargo clean @@ -14,4 +15,4 @@ fclean: clean re: fclean cargo build --release -.PHONY: all clean re test \ No newline at end of file +.PHONY: all clean fclean re test diff --git a/audio-logger/src/cli.rs b/audio-logger/src/cli.rs index d4c5e9e..1843a28 100644 --- a/audio-logger/src/cli.rs +++ b/audio-logger/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{Parser, ValueEnum}; +use clap::{Parser, ValueEnum, Subcommand}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum Hosts { @@ -6,9 +6,39 @@ pub enum Hosts { Jack, } +#[derive(Subcommand)] +#[clap(about = "A tool to record audio on Linux using the command line.")] +pub enum Commands { + /// Record audio either continously or in batches (in case a possible + /// interruption is an issue) + Rec(Rec), + /// Play audio from a .wav file + Play(Play), + /// Merge audio resulting from a batch recording into a single file + Merge(Merge), + /// Get reports of system's audio resources + Info(Info), +} + #[derive(Parser, Debug)] -#[clap(about = "A tool to record audio.")] -pub struct Args { +#[clap(about = "Merge audio resulting from a batch recording into a single + file. +")] +pub struct Merge { + /// The directory to look for files to merge. + #[clap(short, long, default_value = "./", required = true)] + pub input: std::path::PathBuf, + + /// The directory to save the merged file to. + #[clap(short, long, default_value = "./")] + pub output: std::path::PathBuf, +} + +#[derive(Parser, Debug)] +#[clap(about = "Record audio either continously or in batches (in case a + possible interruption is an issue). +")] +pub struct Rec { /// Filename will be `[NAME]-yyyy-mm-dd-H:M:S.wav` #[clap(required = true, short, long)] @@ -22,10 +52,6 @@ pub struct Args { #[clap(short, long, value_name = "SECONDS")] pub batch_recording: Option, - /// Output the available devices and their configurations - #[clap(long)] - pub print_configs: bool, - /// Host API to use #[clap(value_enum)] pub host: Hosts, @@ -42,3 +68,27 @@ pub struct Args { #[clap(long, value_name = "FRAMES")] pub buffer_size: Option, } +#[derive(Parser, Debug)] +#[clap(about = "Play audio from a .wav file. +")] +pub struct Play { + /// Path to the file to play + #[clap(value_parser, value_name = "PATH", value_hint = clap::ValueHint::FilePath)] + pub input: std::path::PathBuf, +} + +#[derive(Parser, Debug)] +#[clap(about = "Get reports of system's audio resources.")] +pub struct Info { + /// Output the available devices and their configurations + #[clap(long)] + pub print_configs: bool, +} + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +pub struct Cli { + #[clap(subcommand)] + pub command: Commands, +} \ No newline at end of file diff --git a/audio-logger/src/constants.rs b/audio-logger/src/constants.rs new file mode 100644 index 0000000..7fa1495 --- /dev/null +++ b/audio-logger/src/constants.rs @@ -0,0 +1,7 @@ +pub const DEFAULT_SAMPLE_RATE: u32 = 44100; +pub const DEFAULT_CHANNEL_COUNT: u16 = 1; +pub const DEFAULT_BUFFER_SIZE: u32 = 1024; +pub const ALLOWED_SAMPLE_RATES: [u32; 6] = [44100, 48000, 88200, 96000, 176400, 192000]; +pub const MAX_CHANNEL_COUNT: u16 = 2; +pub const MIN_BUFFER_SIZE: usize = 64; +pub const MAX_BUFFER_SIZE: usize = 8192; \ No newline at end of file diff --git a/audio-logger/src/getters.rs b/audio-logger/src/getters.rs index 191ea68..aa6ef1e 100644 --- a/audio-logger/src/getters.rs +++ b/audio-logger/src/getters.rs @@ -1,10 +1,18 @@ -use cpal::traits::{DeviceTrait, HostTrait}; -use cpal::*; -use std::path::PathBuf; -use hound::WavSpec; -use chrono::prelude::*; -use anyhow::{Error, anyhow}; use super::*; +use cpal::{ + StreamConfig, + SupportedStreamConfig, + Device, + HostId, + Host, + SampleRate, + BufferSize, + traits::{DeviceTrait, HostTrait}, +}; +use hound::WavSpec; +use std::path::PathBuf; +use chrono::*; +use anyhow::{Error, Result, anyhow}; /// # Get Host /// @@ -86,17 +94,20 @@ pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &Stream }) } +pub fn get_date_time_string() -> String { + let now: DateTime = Local::now(); + format!( + "{}-{}-{}_{}:{}:{}", + now.year(), now.month(), now.day(), + now.hour(), now.minute(), now.second(), + ) +} /// # Get Filename /// /// Get the filename for the current recording according to the given format, /// the current date and time, and the name prefix. pub fn get_filename(name: &str, path: &PathBuf) -> String { - let now: DateTime = Local::now(); - let filename = format!( - "{}-{}-{}-{}-{}:{}:{}.wav", - name, - now.year(), now.month(), now.day(), - now.hour(), now.minute(), now.second(), - ); - path.join(filename).to_str().unwrap().to_string() + let mut filename = path.clone(); + filename.push(format!("{}_{}.wav", get_date_time_string(), name)); + filename.to_str().unwrap().to_string() } diff --git a/audio-logger/src/input_handling.rs b/audio-logger/src/input_handling.rs index ab0b675..9f5b3d1 100644 --- a/audio-logger/src/input_handling.rs +++ b/audio-logger/src/input_handling.rs @@ -1,5 +1,10 @@ -use std::sync::{Arc, Mutex}; use super::*; +use std::sync::{ + Arc, + Mutex, + Condvar, + atomic::{AtomicBool, Ordering} +}; type StreamInterrupt = Arc<(Mutex, Condvar)>; type BatchInterrupt= Arc; diff --git a/audio-logger/src/main.rs b/audio-logger/src/main.rs index fef0a94..d952d5e 100644 --- a/audio-logger/src/main.rs +++ b/audio-logger/src/main.rs @@ -1,59 +1,43 @@ -//! Records a WAV file (roughly 3 seconds long) using the default input device and config. -//! -//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav". +//! # Main mod cli; mod recorder; mod print_configs; mod getters; mod input_handling; +mod merge; +mod constants; +mod play; -use clap::Parser; -use recorder::{batch_recording, contiguous_recording}; -use cli::*; -use std::path::Path; use print_configs::*; -use std::sync::{Condvar, atomic::{AtomicBool, Ordering}}; +use cli::*; +use merge::*; +use recorder::*; +use constants::*; +use getters::*; +use input_handling::*; +use play::*; -const DEFAULT_SAMPLE_RATE: u32 = 44100; -const DEFAULT_CHANNEL_COUNT: u16 = 1; -const DEFAULT_BUFFER_SIZE: u32 = 1024; -const ALLOWED_SAMPLE_RATES: [u32; 6] = [44100, 48000, 88200, 96000, 176400, 192000]; -const MAX_CHANNEL_COUNT: u16 = 2; -const MIN_BUFFER_SIZE: usize = 64; -const MAX_BUFFER_SIZE: usize = 8192; +use anyhow::{Result, Error}; +use clap::Parser; -fn main() -> Result<(), anyhow::Error> { - let args = Args::parse(); +fn main() -> Result<(), Error> { + let cli = Cli::parse(); - if args.print_configs { - print_configs()?; - return Ok(()); - } - - let mut recorder = recorder::Recorder::init( - args.name.clone(), - match args.output.clone() { - Some(path) => path, - None => Path::new("./").to_path_buf(), + match &cli.command { + Commands::Rec(args) => { + record(&args)?; }, - match args.host { - Hosts::Alsa => cpal::HostId::Alsa, - Hosts::Jack => cpal::HostId::Jack, + Commands::Play(args) => { + play(&args.input)?; }, - args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), - args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), - args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), - )?; - - match args.batch_recording { - Some(secs) => { - batch_recording(&mut recorder, secs)?; + Commands::Info(args) => { + if args.print_configs { + print_configs()?; + } }, - None => { - contiguous_recording(&mut recorder)?; + Commands::Merge(args) => { + merge_wavs(&args.input, &args.output)?; } } - Ok(()) } - diff --git a/audio-logger/src/merge.rs b/audio-logger/src/merge.rs new file mode 100644 index 0000000..bebc093 --- /dev/null +++ b/audio-logger/src/merge.rs @@ -0,0 +1,43 @@ +use hound::{WavReader, WavWriter}; +use anyhow::{Result, Error}; + +pub fn merge_wavs(input: &std::path::PathBuf, output: &std::path::PathBuf) -> Result<(), Error> { + // Read files from input directory + let mut files = std::fs::read_dir(input)? + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().ok().map(|t| t.is_file()).unwrap_or(false)) + .filter(|entry| entry.path().extension().unwrap_or_default() == "wav") + .collect::>(); + + // Sort files by name + files.sort_by(|a, b| a.file_name().cmp(&b.file_name())); + + // Use the name of the first file as the name of the output file + let output_name = files.first().unwrap().file_name(); + let output_name = output_name.to_str().unwrap(); + + // Get wav spec from file + let spec = WavReader::open(files.first().unwrap().path())?.spec(); + let mut writer = WavWriter::create(output.join(output_name), spec)?; + + match spec.sample_format { + hound::SampleFormat::Float => { + for file in files { + let mut reader = WavReader::open(file.path())?; + for sample in reader.samples::() { + writer.write_sample(sample?)?; + } + } + }, + hound::SampleFormat::Int => { + for file in files { + let mut reader = WavReader::open(file.path())?; + for sample in reader.samples::() { + writer.write_sample(sample?)?; + } + } + }, + } + writer.finalize()?; + Ok(()) +} diff --git a/audio-logger/src/play.rs b/audio-logger/src/play.rs new file mode 100644 index 0000000..0115156 --- /dev/null +++ b/audio-logger/src/play.rs @@ -0,0 +1,14 @@ +//! Play audio from a .wav file. + +use anyhow::{Error, Result}; +use rodio::{Decoder, OutputStream, Sink}; + +pub fn play(path: &std::path::PathBuf) -> Result<(), Error> { + let file = std::fs::File::open(path)?; + let source = Decoder::new(file).unwrap(); + let (_stream, stream_handle) = OutputStream::try_default()?; + let sink = Sink::try_new(&stream_handle)?; + sink.append(source); + sink.sleep_until_end(); + Ok(()) +} \ No newline at end of file diff --git a/audio-logger/src/print_configs.rs b/audio-logger/src/print_configs.rs index 6784749..45b0fb5 100644 --- a/audio-logger/src/print_configs.rs +++ b/audio-logger/src/print_configs.rs @@ -1,5 +1,19 @@ use cpal::traits::{DeviceTrait, HostTrait}; +// pub fn print_available_hosts() -> Result<(), anyhow::Error> { +// let hosts = cpal::available_hosts(); +// println!("Available hosts:"); +// for host in hosts { +// println!(" {}", host.name()); +// } +// Ok(()) +// } + +// pub fn print_supported_hosts() { +// println!("Supported hosts:"); +// println!("{:?}", cpal::ALL_HOSTS); +// } + pub fn print_configs() -> Result<(), anyhow::Error> { println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS); let available_hosts = cpal::available_hosts(); diff --git a/audio-logger/src/recorder.rs b/audio-logger/src/recorder.rs index 3a87fe7..e2d063f 100644 --- a/audio-logger/src/recorder.rs +++ b/audio-logger/src/recorder.rs @@ -1,12 +1,20 @@ -use cpal::traits::{DeviceTrait, StreamTrait}; -use cpal::*; -use std::fs::File; -use std::io::BufWriter; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use crate::getters::*; -use crate::input_handling::*; +use super::*; +use cpal::{ + StreamConfig, + SupportedStreamConfig, + Device, + HostId, + Stream, + traits::{DeviceTrait, StreamTrait}, +}; +use std::{ + fs::File, + io::BufWriter, + path::{PathBuf, Path}, + sync::{Arc, Mutex}, +}; use anyhow::Error; + type WriteHandle = Arc>>>>; pub struct Recorder { @@ -85,7 +93,7 @@ impl Recorder { fn create_stream(&self) -> Result { let writer = self.writer.clone(); let config = self.user_config.clone(); - let err_fn = |err| { eprintln!("An error occurred on stream: {}", err); }; + let err_fn = |err| { eprintln!("{}: An error occurred on stream: {}", get_date_time_string(), err); }; let stream = match self.default_config.sample_format() { cpal::SampleFormat::F32 => self.device.build_input_stream( @@ -161,14 +169,36 @@ where } } -pub fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), Error> { +fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), Error> { while rec.interrupt_handles.batch_is_running() { rec.record_secs(secs)?; } Ok(()) } -pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), Error> { +fn continuous_recording(rec: &mut Recorder) -> Result<(), Error> { rec.record()?; Ok(()) } + +pub fn record(args: &Rec) -> Result<(), Error> { + let mut recorder = Recorder::init( + args.name.clone(), + match args.output.clone() { + Some(path) => path, + None => Path::new("./").to_path_buf(), + }, + match args.host { + Hosts::Alsa => cpal::HostId::Alsa, + Hosts::Jack => cpal::HostId::Jack, + }, + args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), + args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), + args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), + )?; + + match args.batch_recording { + Some(seconds) => batch_recording(&mut recorder, seconds), + None => continuous_recording(&mut recorder), + } +} From aba5279be95d5824044facf3d33682be2200285c Mon Sep 17 00:00:00 2001 From: Julius Koskela Date: Sun, 25 Sep 2022 17:58:17 +0300 Subject: [PATCH 06/22] Clean repository, add .gitignore --- audio-logger/.gitignore | 3 +++ audio-logger/Cargo.toml | 2 +- audio-logger/run_test.sh | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 audio-logger/.gitignore diff --git a/audio-logger/.gitignore b/audio-logger/.gitignore new file mode 100644 index 0000000..0cd831f --- /dev/null +++ b/audio-logger/.gitignore @@ -0,0 +1,3 @@ +taget +*.wav +recordings \ No newline at end of file diff --git a/audio-logger/Cargo.toml b/audio-logger/Cargo.toml index 147f3d9..4b7b6ab 100644 --- a/audio-logger/Cargo.toml +++ b/audio-logger/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -clap = {url = "https://github.com/clap-rs/clap", features = ["derive"]} +clap = { url = "https://github.com/clap-rs/clap", features = ["derive"] } cpal= { version = "0.13.5", features = ["jack"] } anyhow = "1.0.61" hound = "3.4.0" diff --git a/audio-logger/run_test.sh b/audio-logger/run_test.sh index 51044ee..ebd0a94 100644 --- a/audio-logger/run_test.sh +++ b/audio-logger/run_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -target/release/audio-logger \ +target/release/audio rec \ --name test \ --output recordings/ \ --batch-recording 3 \ From 36dea6c9e29a087c95bb213f739ce8187f14a36f Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Sun, 25 Sep 2022 22:41:09 +0300 Subject: [PATCH 07/22] Add matching host platform, max seconds recording, template Makefile --- Makefile | 17 +++++++++ audio-logger/Makefile | 3 +- audio-logger/src/cli.rs | 6 +++ audio-logger/src/getters.rs | 2 +- audio-logger/src/input_handling.rs | 13 ++++++- audio-logger/src/main.rs | 2 +- audio-logger/src/recorder.rs | 59 +++++++++++++++++++++++++----- resources.md | 7 +++- 8 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1ce67e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ + + +configure: + + +build: + + +run-audio-recorder: + + +run-gps-recorder: + + +run-depth-recorder: + + diff --git a/audio-logger/Makefile b/audio-logger/Makefile index 38011ab..dc9d307 100644 --- a/audio-logger/Makefile +++ b/audio-logger/Makefile @@ -1,8 +1,7 @@ all: cargo build --release -test: - cargo build --release +test: all mkdir -p recordings bash run_test.sh diff --git a/audio-logger/src/cli.rs b/audio-logger/src/cli.rs index 1843a28..0832a28 100644 --- a/audio-logger/src/cli.rs +++ b/audio-logger/src/cli.rs @@ -4,6 +4,8 @@ use clap::{Parser, ValueEnum, Subcommand}; pub enum Hosts { Alsa, Jack, + CoreAudio, + Asio, } #[derive(Subcommand)] @@ -52,6 +54,10 @@ pub struct Rec { #[clap(short, long, value_name = "SECONDS")] pub batch_recording: Option, + /// (optional) Will record for a total of [SECONDS] + #[clap(short, long, value_name = "MAX_SECONDS")] + pub max_seconds: Option, + /// Host API to use #[clap(value_enum)] pub host: Hosts, diff --git a/audio-logger/src/getters.rs b/audio-logger/src/getters.rs index aa6ef1e..72b250c 100644 --- a/audio-logger/src/getters.rs +++ b/audio-logger/src/getters.rs @@ -97,7 +97,7 @@ pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &Stream pub fn get_date_time_string() -> String { let now: DateTime = Local::now(); format!( - "{}-{}-{}_{}:{}:{}", + "{}-{}-{}_{}:{}:{:02}", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(), ) diff --git a/audio-logger/src/input_handling.rs b/audio-logger/src/input_handling.rs index 9f5b3d1..129c3b1 100644 --- a/audio-logger/src/input_handling.rs +++ b/audio-logger/src/input_handling.rs @@ -19,10 +19,11 @@ type BatchInterrupt= Arc; pub struct InterruptHandles { batch_interrupt: BatchInterrupt, stream_interrupt: StreamInterrupt, + max_seconds: Option, } impl InterruptHandles { - pub fn new() -> Result { + pub fn new(max_seconds: Option) -> Result { let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new())); let stream_interrupt_cloned = stream_interrupt.clone(); @@ -42,13 +43,23 @@ impl InterruptHandles { Ok(Self { batch_interrupt, stream_interrupt, + max_seconds, }) } pub fn stream_wait(&self) { let &(ref lock, ref cvar) = &*self.stream_interrupt; let mut started = lock.lock().unwrap(); + let now = std::time::Instant::now(); while !*started { + match self.max_seconds { + Some(secs) => { + if now.elapsed().as_secs() >= secs { + break; + } + } + None => (), + } started = cvar.wait(started).unwrap(); } } diff --git a/audio-logger/src/main.rs b/audio-logger/src/main.rs index d952d5e..048bd68 100644 --- a/audio-logger/src/main.rs +++ b/audio-logger/src/main.rs @@ -16,8 +16,8 @@ use constants::*; use getters::*; use input_handling::*; use play::*; +use anyhow::{Error, Result, anyhow}; -use anyhow::{Result, Error}; use clap::Parser; fn main() -> Result<(), Error> { diff --git a/audio-logger/src/recorder.rs b/audio-logger/src/recorder.rs index e2d063f..adba7c7 100644 --- a/audio-logger/src/recorder.rs +++ b/audio-logger/src/recorder.rs @@ -27,6 +27,7 @@ pub struct Recorder { name: String, path: PathBuf, current_file: String, + max_seconds: Option, } /// # Recorder @@ -50,10 +51,11 @@ impl Recorder { sample_rate: u32, channels: u16, buffer_size: u32, + max_seconds: Option, ) -> Result { // Create interrupt handles to be used by the stream or batch loop. - let interrupt_handles = InterruptHandles::new()?; + let interrupt_handles = InterruptHandles::new(max_seconds)?; // Select requested host. let host = get_host(host)?; @@ -80,13 +82,14 @@ impl Recorder { name, path, current_file: "".to_string(), + max_seconds, }) } fn init_writer(&mut self) -> Result<(), Error> { let filename = get_filename(&self.name, &self.path); self.current_file = filename.clone(); - *self.writer.lock().unwrap() = Some(hound::WavWriter::create(filename, self.spec)?); + self.writer = Arc::new(Mutex::new(Some(hound::WavWriter::create(filename, self.spec)?))); Ok(()) } @@ -141,15 +144,19 @@ impl Recorder { stream.play()?; println!("REC: {}", self.current_file); let now = std::time::Instant::now(); - loop { - std::thread::sleep(std::time::Duration::from_millis(500)); + while self.interrupt_handles.batch_is_running() { + std::thread::sleep(std::time::Duration::from_millis(1)); if now.elapsed().as_secs() >= secs { break; } } drop(stream); - self.writer.lock().unwrap().take().unwrap().finalize()?; - println!("STOP: {}", self.current_file); + let writer = self.writer.clone(); + let current_file = self.current_file.clone(); + std::thread::spawn(move || { + writer.lock().unwrap().take().unwrap().finalize().unwrap(); + println!("STOP: {}", current_file); + }); Ok(()) } } @@ -170,7 +177,16 @@ where } fn batch_recording(rec: &mut Recorder, secs: u64) -> Result<(), Error> { + let now = std::time::Instant::now(); while rec.interrupt_handles.batch_is_running() { + match rec.max_seconds { + Some(max_secs) => { + if now.elapsed().as_secs() >= max_secs { + break; + } + } + None => {} + } rec.record_secs(secs)?; } Ok(()) @@ -181,6 +197,31 @@ fn continuous_recording(rec: &mut Recorder) -> Result<(), Error> { Ok(()) } +#[cfg(target_os = "linux")] +fn match_host_platform(host: Hosts) -> Result { + match host { + Hosts::Alsa => Ok(cpal::HostId::Alsa), + Hosts::Jack => Ok(cpal::HostId::Jack), + _ => Err(anyhow!("Host not supported on Linux.")), + } +} + +#[cfg(target_os = "macos")] +fn match_host_platform(host: Hosts) -> Result { + match host { + Hosts::CoreAudio => Ok(cpal::HostId::CoreAudio), + _ => Err(anyhow!("Host not supported on macOS.")), + } +} + +#[cfg(target_os = "windows")] +fn match_host_platform(host: Hosts) -> Result { + match host { + Hosts::Asio => Ok(cpal::HostId::Asio), + _ => Err(anyhow!("Host not supported on Windows.")), + } +} + pub fn record(args: &Rec) -> Result<(), Error> { let mut recorder = Recorder::init( args.name.clone(), @@ -188,13 +229,11 @@ pub fn record(args: &Rec) -> Result<(), Error> { Some(path) => path, None => Path::new("./").to_path_buf(), }, - match args.host { - Hosts::Alsa => cpal::HostId::Alsa, - Hosts::Jack => cpal::HostId::Jack, - }, + match_host_platform(args.host)?, args.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE), args.channels.unwrap_or(DEFAULT_CHANNEL_COUNT), args.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), + args.max_seconds, )?; match args.batch_recording { diff --git a/resources.md b/resources.md index cf41b0d..37e8e26 100755 --- a/resources.md +++ b/resources.md @@ -6,4 +6,9 @@ https://www.circuito.io/app?components=9443,200000,267055 # Setting up Jack on Raspberry Pi -https://wiki.linuxaudio.org/wiki/raspberrypi \ No newline at end of file +https://wiki.linuxaudio.org/wiki/raspberrypi +https://github.com/supercollider/supercollider/blob/develop/README_RASPBERRY_PI.md +https://madskjeldgaard.dk/posts/raspi4-notes/ + +# Setting up GPS + From 45cc34de3b3a61f14e27e001f19fcbeaadbe64a6 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Sun, 2 Oct 2022 15:24:26 +0300 Subject: [PATCH 08/22] Add configuration files, update README, first draft of setup documentation, add todo.md --- README.md | 17 +++++-- configuration/hydrophonitor-config.txt | 13 ++++++ configuration/ssh.txt | 0 configuration/userconf.txt | 1 + configuration/wpa_supplicant.conf | 13 ++++++ setup.md | 64 ++++++++++++++++++-------- todo.md | 23 +++++++++ 7 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 configuration/hydrophonitor-config.txt create mode 100644 configuration/ssh.txt create mode 100644 configuration/userconf.txt create mode 100644 configuration/wpa_supplicant.conf create mode 100644 todo.md diff --git a/README.md b/README.md index 9346be2..5d8140c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ depth-logger | Record depth of the device and save it in `.csv` format. ## Data Formats -Type | Format ------|- -GPS Data | \ No newline at end of file +Type | Output file format | Output file name | Output structure | Content +------------|--------------------|--------------------------------------|------------------| +Audio Data | .wav | _audio.wav | Each recorded chunk will be written to its own file in `audio` folder | Wav audio data, configuration defined in XXX +GPS Data | .csv | _gps.wav | All data written to a single file | Csv data with following fields: GPS time UTC, latitude, longitude, speed, satellites in view +Depth data | .csv | _depth.wav | All data written to a single file | Csv data with following fields: date and time, voltage of depth sensor (V), depth (m) +Log data | .txt | _log.txt | All data written to a single file | Text file where each entry contains the following: date and time, process that writes the entry, logged information + +## Output Locations + +The base location/path for the output directories is defined by a configurable value BASE_DIR_PATH. If directories along this path do not exist, they will be created. If an error occurs or the location is not writable, output will be written to the default location () instead. + + + +A recording session starts when the Raspberry Pi is turned on or booted, and ends on shutdown. Each session will have its output written in its own directory that will be named _recordings. \ No newline at end of file diff --git a/configuration/hydrophonitor-config.txt b/configuration/hydrophonitor-config.txt new file mode 100644 index 0000000..1dc69fd --- /dev/null +++ b/configuration/hydrophonitor-config.txt @@ -0,0 +1,13 @@ +# Audio + +SAMPLE_RATE=44100 +CHANNELS=2 +BITRATE=192k +BATCH_RECORD_LENGTH=60 + +# Output location + +TRY_MOUNT_SSD=true + +DEFAULT_BASE_DIR_PATH=/home/pi/recordings +BASE_DIR_PATH=/home/pi/data diff --git a/configuration/ssh.txt b/configuration/ssh.txt new file mode 100644 index 0000000..e69de29 diff --git a/configuration/userconf.txt b/configuration/userconf.txt new file mode 100644 index 0000000..19ef386 --- /dev/null +++ b/configuration/userconf.txt @@ -0,0 +1 @@ +: \ No newline at end of file diff --git a/configuration/wpa_supplicant.conf b/configuration/wpa_supplicant.conf new file mode 100644 index 0000000..11648d1 --- /dev/null +++ b/configuration/wpa_supplicant.conf @@ -0,0 +1,13 @@ +ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev +update_config=1 +country= + +network={ + scan_ssid=1 + ssid="" + psk="" + proto=RSN + key_mgmt=WPA-PSK + pairwise=CCMP + auth_alg=OPEN +} \ No newline at end of file diff --git a/setup.md b/setup.md index 5e38c38..bd3c24a 100755 --- a/setup.md +++ b/setup.md @@ -2,31 +2,59 @@ ## Components +- Raspberry Pi (tested on 2, 4B) +- MicroSD card + adapter +- card reader to access the sd card on the computer ## Raspberry OS basic setup -### 1. OS +### 1. Install the operating system and set up user, Wi-Fi, ssh access + +#### With Raspberry Pi Imager + +The easiest way to install the operating system (Raspberry Pi OS, a Linux Debian-based OS) is to use the official Raspberry Pi Imager utility which works on macOS, Ubuntu and Windows. + +Install from here: https://www.raspberrypi.com/software/ + +After installing, plug the SD card to the computer and launch Raspberry Pi Imager. + +Then the following steps: + +1. Select operating system: click Raspberry Pi OS (other) and then, depending on the Pi, either a 32-bit or 64-bit Raspberry Pi OS Lite +2. Select storage: the sd card should be listed +3. Click the cog icon to set some configurations: + - Enable SSH (use password authentication) + - Set username and password + - Configure wireless LAN: input the name and password of the wi-fi network, select Wireless LAN country + - Set locale settings: select options correct for you +4. Click Write (all existing data on the SD card will be erased and the OS installed) + +#### With some other utility + +If you do not use the Raspberry Pi Imager to set up the SD card, the following steps are required: + +1. Download the 32-bit / 64-bit Rasbperry Pi OS Lite from here: https://www.raspberrypi.com/software/operating-systems/ +2. Flash the image to the SD card with the utility of your choice (options for Mac, Linux, Windows?) +3. Fill in required details in the configuration files in configuration folder and copy them to the boot folder on the SD card (this is the folder that should open when you open the SD card volume on your computer): + - ssh.txt: this enables ssh on the Raspberry Pi, no need to edit the file (it's empty, the existence of the file in the boot folder is enough) + - userconf.txt: creates a user + - replace with the username of choice (e.g. pi) + - replace with an encrypted version of your password which can be created with the openssl command line tool: + - open Terminal, write `openssl passwd` and press Enter + - input your password and press enter (asked twice) + - as output, you will get the encrypted version of the password + - wpa_supplicant.conf: set up Wi-Fi + - replace with your country code (e.g. FI) + - replace "" with the name of your Wi-Fi network, e.g. "explorersden" + - replace "" with the Wi-Fi password, e.g. "password" + + +### 2. Installing needed packages -### 2. Users - - - -### 3. Wifi - - - -### 4. SSH access - - - -### 5. Installing needed packages - - - -## SSD +### 3. Mount SSD diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..182c6d1 --- /dev/null +++ b/todo.md @@ -0,0 +1,23 @@ +# To do list + +- [ ] Configurable, documented setup process for the Raspberry Pi + - [ ] Setup script + - [ ] Copy executables & scripts + - [ ] Required packages to install + - [ ] System configurations + - [ ] Configurable values + - [ ] Test +- [ ] Output formats & location for output data + - [ ] Automatic SSD mounting +- [ ] Audio recording + - [ ] Logging + - [ ] Test +- [ ] GPS recording + - [ ] Logging + - [ ] Test +- [ ] Depth recording + - [ ] Double check formulas to calculate depth & pressure from voltage + - [ ] Logging + - [ ] Test +- [ ] Test autonomous recording as a whole + From 06e8e063a814e875faf2c88136da60065df822fc Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Sun, 2 Oct 2022 15:24:51 +0300 Subject: [PATCH 09/22] Update scripts --- scripts/audio-setup-utils.sh | 6 +----- scripts/setup-audio.sh | 22 ++++++++++++++++++++-- scripts/setup-pressure-depth.sh | 1 + scripts/start-all.sh | 2 +- scripts/start-audio.sh | 10 ++++------ scripts/start-gps.sh | 5 ++++- scripts/start-jack.sh | 7 ------- scripts/start-pressure-depth.sh | 6 ++++++ 8 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 scripts/setup-pressure-depth.sh delete mode 100755 scripts/start-jack.sh create mode 100644 scripts/start-pressure-depth.sh diff --git a/scripts/audio-setup-utils.sh b/scripts/audio-setup-utils.sh index 60862da..d879fa2 100755 --- a/scripts/audio-setup-utils.sh +++ b/scripts/audio-setup-utils.sh @@ -8,14 +8,10 @@ GOVERNOR="performance" MAX_SPEED="0" MIN_SPEED="0" " | sudo tee -a /etc/default/cpufrequtils -# Install other useful tools -sudo apt-get install htop git perl vim - # Set CPU governor sudo sed -i 's/exit 0/sudo cpufreq-set -r -g performance/g' /etc/rc.local sudo echo "exit 0" | sudo tee -a /etc/rc.local - # Set realtime priority and memlock sudo echo " @audio nice -15 @@ -29,4 +25,4 @@ sudo echo " sudo echo " vm.swappiness = 10 fs.inotify.max_user_watches = 524288 -" | sudo tee /etc/sysctl.conf \ No newline at end of file +" | sudo tee /etc/sysctl.conf diff --git a/scripts/setup-audio.sh b/scripts/setup-audio.sh index 936a568..0aa73f1 100755 --- a/scripts/setup-audio.sh +++ b/scripts/setup-audio.sh @@ -1,5 +1,23 @@ #!/bin/sh -# Install jackd +# Get ID and number of the USB audio device -# Audio setup utils \ No newline at end of file +card_number=$(aplay -l | grep -i usb | grep -i audio | cut -d ' ' -f 2 | cut -d ':' -f 1) + +# Change default audio device + +sudo touch /etc/asound.conf + +sudo cat << EOF | sudo tee -a /etc/asound.conf +pcm.!default { + type plug + slave { + pcm "hw:$card_number,0" + } +} + +ctl.!default { + type hw + card $card_number +} +EOF diff --git a/scripts/setup-pressure-depth.sh b/scripts/setup-pressure-depth.sh new file mode 100644 index 0000000..96b4b06 --- /dev/null +++ b/scripts/setup-pressure-depth.sh @@ -0,0 +1 @@ +#!/bin/sh \ No newline at end of file diff --git a/scripts/start-all.sh b/scripts/start-all.sh index 17cbbb4..8630d40 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -1,3 +1,3 @@ #!/bin/sh -(trap 'kill 0' SIGINT; ./scripts/start-gps.sh & ./scripts/start-audio.sh) +(trap 'kill 0' SIGINT; ./scripts/start-gps.sh & ./scripts/start-audio.sh & ./scripts/start-pressure-depth.sh) diff --git a/scripts/start-audio.sh b/scripts/start-audio.sh index c82669e..cfc0680 100755 --- a/scripts/start-audio.sh +++ b/scripts/start-audio.sh @@ -1,9 +1,7 @@ #!/bin/sh -# Start jack server +AUDIO_TARGET_LOCATION="/home/shared/hydrophonitor/audio-logger/target/release" +AUDIO_TARGET_EXECUTABLE="audio" +OPTIONS="" -sh scripts/start-jack.sh - -# Start recording - -cd audio-logger && cargo run \ No newline at end of file +cd $AUDIO_TARGET_LOCATION && ./AUDIO_TARGET_EXECUTABLE $OPTIONS \ No newline at end of file diff --git a/scripts/start-gps.sh b/scripts/start-gps.sh index be7cf51..08ba09d 100755 --- a/scripts/start-gps.sh +++ b/scripts/start-gps.sh @@ -1,3 +1,6 @@ #!/usr/bin/sh -python /home/shared/logger-raspi-setup/gps-logger/record-gps.py +GPS_TARGET_LOCATION="/home/shared/hydrophonitor/gps-logger" +OPTIONS="" + +cd $GPS_TARGET_LOCATION && python record-gps.py $OPTIONS diff --git a/scripts/start-jack.sh b/scripts/start-jack.sh deleted file mode 100755 index c587dcf..0000000 --- a/scripts/start-jack.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -# Get soundcard name -soundcard=$(grep USB /proc/asound/cards | grep -oe "\[.*]" | tr -d "[] ") - -# Start jack server -/usr/bin/jackd -P75 -d alsa -d hw:${soundcard} -r 44100 -p 512 -n 3 & diff --git a/scripts/start-pressure-depth.sh b/scripts/start-pressure-depth.sh new file mode 100644 index 0000000..f7ff23a --- /dev/null +++ b/scripts/start-pressure-depth.sh @@ -0,0 +1,6 @@ +#!/usr/bin/sh + +DEPTH_TARGET_LOCATION="/home/shared/hydrophonitor/depth-logger" +OPTIONS="" + +cd $DEPTH_TARGET_LOCATION && python record-depth.py $OPTIONS From 1abe90458aff9eef98cff2cde58e897c30fb95f4 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Mon, 3 Oct 2022 19:32:32 +0300 Subject: [PATCH 10/22] Update Makefile re rule --- audio-logger/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/audio-logger/Makefile b/audio-logger/Makefile index dc9d307..aba5ce7 100644 --- a/audio-logger/Makefile +++ b/audio-logger/Makefile @@ -11,7 +11,6 @@ clean: fclean: clean rm -rf recordings -re: fclean - cargo build --release +re: fclean all .PHONY: all clean fclean re test From 4171882bd3b6bfcd965e0d49159ab2ed67fbda7d Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Mon, 3 Oct 2022 19:32:46 +0300 Subject: [PATCH 11/22] Add sample config --- configuration/hydrophonitor-config.txt | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/configuration/hydrophonitor-config.txt b/configuration/hydrophonitor-config.txt index 1dc69fd..a5fd070 100644 --- a/configuration/hydrophonitor-config.txt +++ b/configuration/hydrophonitor-config.txt @@ -1,13 +1,21 @@ # Audio -SAMPLE_RATE=44100 -CHANNELS=2 -BITRATE=192k -BATCH_RECORD_LENGTH=60 +SAMPLE_RATE="44100" +CHANNELS="2" +BITRATE="192k" +BATCH_RECORD_LENGTH="60" + +# GPS + +GPS_INTERVAL="5" + +# Depth + +DEPTH_INTERVAL="5" # Output location -TRY_MOUNT_SSD=true +TRY_MOUNT_SSD="true" -DEFAULT_BASE_DIR_PATH=/home/pi/recordings -BASE_DIR_PATH=/home/pi/data +DEFAULT_BASE_DIR_PATH="/home/pi/recordings" +BASE_DIR_PATH="/home/pi/data" From f41f6ee2ca9de4d31ab10348098d3029aabea9c4 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Mon, 3 Oct 2022 19:34:23 +0300 Subject: [PATCH 12/22] Add command line argument parsing for python scripts --- depth-logger/record-depth.py | 21 +++++++++++++++------ gps-logger/record-gps.py | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/depth-logger/record-depth.py b/depth-logger/record-depth.py index 3447583..04f1bce 100644 --- a/depth-logger/record-depth.py +++ b/depth-logger/record-depth.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 +import argparse import board import time import busio @@ -8,6 +9,12 @@ from adafruit_ads1x15.analog_in import AnalogIn from time import sleep, strftime from rpi_lcd import LCD +parser = argparse.ArgumentParser(description='GPS Logger') +parser.add_argument('-o', '--output', help='Output file', required=True) +parser.add_argument('-i', '--interval', help='Interval in seconds', required=False) + +args = parser.parse_args() + try: lcd = LCD(bus=2) except OSError: @@ -22,16 +29,15 @@ ads = ADS.ADS1015(i2c) # Create single-ended input on channel 0 # tmp36 = AnalogIn(ads, ADS.P0) -# Attempting to create a single-ended input on channel 1 +# Create a single-ended input on channel 1 depthS = AnalogIn(ads, ADS.P1) # Subtract the offset from the sensor voltage # and convert chan.voltage * (1 degree C / 0.01V) = Degrees Celcius # temperatureC = (tmp36.voltage - 0.5) / 0.01 -# Open the file to write down the results -timestr = time.strftime("%Y-%m-%dT%H-%M-%S") -filename = "/home/shared/hydrophonitor/data/depth/" + timestr + "_depth_data.csv" +# File to write down the results +filename = args.output + time.strftime("%Y-%m-%dT%H-%M-%S") + "_depth_data.csv" #depthM = ((depthS.voltage * 31.848) - 22.93) @@ -58,5 +64,8 @@ with open(filename, "w", 1) as f: lcd.text((str(roundvolts) + " V ") + (str(rounddepth) + " m"), 1) f.write(time.strftime("%Y-%m-%dT%H:%M:%S") + ",") f.write(str(roundvolts) + "," + str(rounddepth) + "\n") - - time.sleep(3) + + if args.interval: + time.sleep(int(args.interval)) + else: + time.sleep(5) diff --git a/gps-logger/record-gps.py b/gps-logger/record-gps.py index 7d944d2..4b0c5d1 100644 --- a/gps-logger/record-gps.py +++ b/gps-logger/record-gps.py @@ -1,10 +1,16 @@ #!/usr/bin/python3 from gps import * -from time import sleep, strftime +import time +import argparse -filename = "/home/shared/logger-raspi-setup/data/gps/" + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.csv" -# filename = "/mnt/myssd/GPS_Data" + timestr +".csv" +parser = argparse.ArgumentParser(description='GPS Logger') +parser.add_argument('-o', '--output', help='Output file', required=True) +parser.add_argument('-i', '--interval', help='Interval in seconds', required=False) + +args = parser.parse_args() + +filename = args.output + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.csv" with open(filename, "w", 1) as f: gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) @@ -21,8 +27,11 @@ with open(filename, "w", 1) as f: sats = str(len(gpsd.satellites)) f.write(GPStime + "," + lat +"," + lon + "," + speed + "," + sats + "\n") - - time.sleep(5) + + if args.interval: + time.sleep(int(args.interval)) + else: + time.sleep(5) except (KeyboardInterrupt, SystemExit): # when you press ctrl+c print("Done.\nExiting.") From 8f398e90de0c34d84db0f2c178f04a292d4b5f6f Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Mon, 3 Oct 2022 19:37:15 +0300 Subject: [PATCH 13/22] Update start scripts, add scripts for setup and config value exports --- configuration/setup-raspberry-pi.sh | 27 +++++++++++++++++++++++++++ scripts/export-config-values.sh | 5 +++++ scripts/start-all.sh | 12 +++++++++++- scripts/start-audio.sh | 19 +++++++++++++++---- scripts/start-gps.sh | 9 ++++++--- scripts/start-pressure-depth.sh | 8 ++++++-- 6 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 configuration/setup-raspberry-pi.sh create mode 100644 scripts/export-config-values.sh diff --git a/configuration/setup-raspberry-pi.sh b/configuration/setup-raspberry-pi.sh new file mode 100644 index 0000000..3c02223 --- /dev/null +++ b/configuration/setup-raspberry-pi.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -euo pipefail + +# Copy the files to /home/pi +cd /home/pi +cp -R /boot/hydrophonitor . +cd hydrophonitor + +# Install the Rust toolchain +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# Setup audio +sh scripts/setup-audio.sh + +# Setup GPS +sh scripts/setup-gps.sh + +# Setup depth sensor +sh scripts/setup-pressure-depth.sh + +# Setup cron job to start the recordings at boot +CRON_FILE=/etc/crontab +echo "@reboot root /home/pi/hydrophonitor/scripts/start-all.sh" >> $CRON_FILE + +# Reboot +sudo reboot diff --git a/scripts/export-config-values.sh b/scripts/export-config-values.sh new file mode 100644 index 0000000..483f3dd --- /dev/null +++ b/scripts/export-config-values.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +CONFIG_FILE=/boot/hydrophonitor/hydrophonitor-config.txt + +export $(grep -v '^#' $CONFIG_FILE | xargs -d '\n') diff --git a/scripts/start-all.sh b/scripts/start-all.sh index 8630d40..f642cba 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -1,3 +1,13 @@ #!/bin/sh -(trap 'kill 0' SIGINT; ./scripts/start-gps.sh & ./scripts/start-audio.sh & ./scripts/start-pressure-depth.sh) +set -euo pipefail + +# Export the configuration values +. /home/pi/hydrophonitor/scripts/export-config-values.sh + +# Create output directory +OUTPUT_DIR=$BASE_DIR_PATH/$(date +"%Y-%m-%d_%H-%M-%S_output") + +mkdir -p $OUTPUT_DIR + +(export OUTPUT_DIR=$OUTPUT_DIR; trap 'kill 0' SIGINT; ./scripts/start-gps.sh & ./scripts/start-audio.sh & ./scripts/start-pressure-depth.sh) diff --git a/scripts/start-audio.sh b/scripts/start-audio.sh index cfc0680..6a1b825 100755 --- a/scripts/start-audio.sh +++ b/scripts/start-audio.sh @@ -1,7 +1,18 @@ #!/bin/sh -AUDIO_TARGET_LOCATION="/home/shared/hydrophonitor/audio-logger/target/release" -AUDIO_TARGET_EXECUTABLE="audio" -OPTIONS="" +# Export the configuration values +. /home/pi/hydrophonitor/scripts/export-config-values.sh -cd $AUDIO_TARGET_LOCATION && ./AUDIO_TARGET_EXECUTABLE $OPTIONS \ No newline at end of file +AUDIO_TARGET_LOCATION="/home/pi/hydrophonitor/audio-logger/target/release" +AUDIO_TARGET_EXECUTABLE="audio" + +OPTIONS="rec \ +--name audio_data \ +--output $OUTPUT_DIR \ +--batch-recording $BATCH_RECORD_LENGTH \ +--sample-rate $SAMPLE_RATE \ +--channels $CHANNELS \ +--buffer-size 1024 \ +alsa" + +cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS \ No newline at end of file diff --git a/scripts/start-gps.sh b/scripts/start-gps.sh index 08ba09d..7b1bf5e 100755 --- a/scripts/start-gps.sh +++ b/scripts/start-gps.sh @@ -1,6 +1,9 @@ #!/usr/bin/sh -GPS_TARGET_LOCATION="/home/shared/hydrophonitor/gps-logger" -OPTIONS="" +# Export the configuration values +. /home/pi/hydrophonitor/scripts/export-config-values.sh -cd $GPS_TARGET_LOCATION && python record-gps.py $OPTIONS +GPS_EXECUTABLE_LOCATION="/home/pi/hydrophonitor/gps-logger" +OPTIONS="--output $OUTPUT_DIR --interval $GPS_INTERVAL" + +cd $GPS_EXECUTABLE_LOCATION && python record-gps.py $OPTIONS diff --git a/scripts/start-pressure-depth.sh b/scripts/start-pressure-depth.sh index f7ff23a..ac344c9 100644 --- a/scripts/start-pressure-depth.sh +++ b/scripts/start-pressure-depth.sh @@ -1,6 +1,10 @@ #!/usr/bin/sh -DEPTH_TARGET_LOCATION="/home/shared/hydrophonitor/depth-logger" -OPTIONS="" +# Export the configuration values +. /home/pi/hydrophonitor/scripts/export-config-values.sh + +DEPTH_EXECUTABLE_LOCATION="/home/pi/hydrophonitor/depth-logger" + +OPTIONS="--output $OUTPUT_DIR --interval $DEPTH_INTERVAL" cd $DEPTH_TARGET_LOCATION && python record-depth.py $OPTIONS From a8ebf389fbdb1081900dbd4ba2483e95bc23e22c Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 4 Oct 2022 21:12:00 +0300 Subject: [PATCH 14/22] Fix configuration and scripts --- configuration/hydrophonitor-config.txt | 14 +++++++------- configuration/setup-raspberry-pi.sh | 4 +--- gps-logger/record-gps.py | 2 +- scripts/export-config-values.sh | 2 +- scripts/setup-audio.sh | 5 +++-- scripts/start-all.sh | 12 ++++++++---- scripts/start-audio.sh | 6 ++++-- scripts/start-gps.sh | 2 ++ 8 files changed, 27 insertions(+), 20 deletions(-) diff --git a/configuration/hydrophonitor-config.txt b/configuration/hydrophonitor-config.txt index a5fd070..008aa7f 100644 --- a/configuration/hydrophonitor-config.txt +++ b/configuration/hydrophonitor-config.txt @@ -1,21 +1,21 @@ # Audio -SAMPLE_RATE="44100" -CHANNELS="2" +SAMPLE_RATE=44100 +CHANNELS=2 BITRATE="192k" -BATCH_RECORD_LENGTH="60" +BATCH_RECORD_LENGTH=60 # GPS -GPS_INTERVAL="5" +GPS_INTERVAL=5 # Depth -DEPTH_INTERVAL="5" +DEPTH_INTERVAL=5 # Output location TRY_MOUNT_SSD="true" -DEFAULT_BASE_DIR_PATH="/home/pi/recordings" -BASE_DIR_PATH="/home/pi/data" +DEFAULT_BASE_DIR_PATH=/home/pi/recordings +BASE_DIR_PATH=/home/pi/data diff --git a/configuration/setup-raspberry-pi.sh b/configuration/setup-raspberry-pi.sh index 3c02223..a6e82f7 100644 --- a/configuration/setup-raspberry-pi.sh +++ b/configuration/setup-raspberry-pi.sh @@ -1,7 +1,5 @@ #!/bin/sh -set -euo pipefail - # Copy the files to /home/pi cd /home/pi cp -R /boot/hydrophonitor . @@ -21,7 +19,7 @@ sh scripts/setup-pressure-depth.sh # Setup cron job to start the recordings at boot CRON_FILE=/etc/crontab -echo "@reboot root /home/pi/hydrophonitor/scripts/start-all.sh" >> $CRON_FILE +echo "@reboot root /home/pi/hydrophonitor/scripts/start-all.sh" | sudo tee -a $CRON_FILE # Reboot sudo reboot diff --git a/gps-logger/record-gps.py b/gps-logger/record-gps.py index 4b0c5d1..1b1d629 100644 --- a/gps-logger/record-gps.py +++ b/gps-logger/record-gps.py @@ -10,7 +10,7 @@ parser.add_argument('-i', '--interval', help='Interval in seconds', required=Fal args = parser.parse_args() -filename = args.output + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.csv" +filename = args.output + "/" + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.csv" with open(filename, "w", 1) as f: gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) diff --git a/scripts/export-config-values.sh b/scripts/export-config-values.sh index 483f3dd..d85f8c8 100644 --- a/scripts/export-config-values.sh +++ b/scripts/export-config-values.sh @@ -1,5 +1,5 @@ #!/bin/sh -CONFIG_FILE=/boot/hydrophonitor/hydrophonitor-config.txt +CONFIG_FILE=/boot/hydrophonitor/configuration/hydrophonitor-config.txt export $(grep -v '^#' $CONFIG_FILE | xargs -d '\n') diff --git a/scripts/setup-audio.sh b/scripts/setup-audio.sh index 0aa73f1..1b1e3b7 100755 --- a/scripts/setup-audio.sh +++ b/scripts/setup-audio.sh @@ -1,11 +1,12 @@ #!/bin/sh -# Get ID and number of the USB audio device +# Install packages +sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-dev jackd2 +# Get ID and number of the USB audio device card_number=$(aplay -l | grep -i usb | grep -i audio | cut -d ' ' -f 2 | cut -d ':' -f 1) # Change default audio device - sudo touch /etc/asound.conf sudo cat << EOF | sudo tee -a /etc/asound.conf diff --git a/scripts/start-all.sh b/scripts/start-all.sh index f642cba..04a5905 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -1,13 +1,17 @@ #!/bin/sh -set -euo pipefail - # Export the configuration values . /home/pi/hydrophonitor/scripts/export-config-values.sh # Create output directory OUTPUT_DIR=$BASE_DIR_PATH/$(date +"%Y-%m-%d_%H-%M-%S_output") -mkdir -p $OUTPUT_DIR +mkdir -p $OUTPUT_DIR/audio -(export OUTPUT_DIR=$OUTPUT_DIR; trap 'kill 0' SIGINT; ./scripts/start-gps.sh & ./scripts/start-audio.sh & ./scripts/start-pressure-depth.sh) +sleep 10 + +# (export OUTPUT_DIR=$OUTPUT_DIR; trap 'kill 0' SIGINT; /home/pi/hydrophonitor/scripts/scripts/start-gps.sh & /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-pressure-depth.sh) + +echo "(export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh)" >> $OUTPUT_DIR/log.txt 2>&1 + +(export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh) >> $OUTPUT_DIR/log.txt 2>&1 diff --git a/scripts/start-audio.sh b/scripts/start-audio.sh index 6a1b825..4107bc0 100755 --- a/scripts/start-audio.sh +++ b/scripts/start-audio.sh @@ -8,11 +8,13 @@ AUDIO_TARGET_EXECUTABLE="audio" OPTIONS="rec \ --name audio_data \ ---output $OUTPUT_DIR \ +--output $OUTPUT_DIR/audio \ --batch-recording $BATCH_RECORD_LENGTH \ --sample-rate $SAMPLE_RATE \ --channels $CHANNELS \ --buffer-size 1024 \ alsa" -cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS \ No newline at end of file +echo "cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS" + +cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS diff --git a/scripts/start-gps.sh b/scripts/start-gps.sh index 7b1bf5e..e392daf 100755 --- a/scripts/start-gps.sh +++ b/scripts/start-gps.sh @@ -6,4 +6,6 @@ GPS_EXECUTABLE_LOCATION="/home/pi/hydrophonitor/gps-logger" OPTIONS="--output $OUTPUT_DIR --interval $GPS_INTERVAL" +echo "cd $GPS_EXECUTABLE_LOCATION && python record-gps.py $OPTIONS" + cd $GPS_EXECUTABLE_LOCATION && python record-gps.py $OPTIONS From 31df098fa8f48d9ce7813ecac7c01ec2d161ef76 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 4 Oct 2022 21:24:42 +0300 Subject: [PATCH 15/22] Clean up --- configuration/hydrophonitor-config.txt | 2 +- data/depth/2022-09-03T17-55-58_depth_data.csv | 43 ---------------- data/depth/2022-09-03T18-01-18_depth_data.csv | 49 ------------------- data/depth/2022-09-04T11-50-21_depth_data.csv | 17 ------- data/depth/2022-09-04T11-56-09_depth_data.csv | 7 --- data/gps/2022-08-20T12-23-39_GPS_data.csv | 11 ----- scripts/start-all.sh | 4 +- scripts/start-audio.sh | 6 +-- scripts/start-gps.sh | 7 +-- scripts/start-pressure-depth.sh | 4 +- 10 files changed, 11 insertions(+), 139 deletions(-) delete mode 100644 data/depth/2022-09-03T17-55-58_depth_data.csv delete mode 100644 data/depth/2022-09-03T18-01-18_depth_data.csv delete mode 100644 data/depth/2022-09-04T11-50-21_depth_data.csv delete mode 100644 data/depth/2022-09-04T11-56-09_depth_data.csv delete mode 100644 data/gps/2022-08-20T12-23-39_GPS_data.csv diff --git a/configuration/hydrophonitor-config.txt b/configuration/hydrophonitor-config.txt index 008aa7f..96eba0f 100644 --- a/configuration/hydrophonitor-config.txt +++ b/configuration/hydrophonitor-config.txt @@ -15,7 +15,7 @@ DEPTH_INTERVAL=5 # Output location -TRY_MOUNT_SSD="true" +TRY_MOUNT_SSD=true DEFAULT_BASE_DIR_PATH=/home/pi/recordings BASE_DIR_PATH=/home/pi/data diff --git a/data/depth/2022-09-03T17-55-58_depth_data.csv b/data/depth/2022-09-03T17-55-58_depth_data.csv deleted file mode 100644 index 51e0d0c..0000000 --- a/data/depth/2022-09-03T17-55-58_depth_data.csv +++ /dev/null @@ -1,43 +0,0 @@ -time and date, Voltage of depth sensor (V), Depth (m) -2022-09-03T17:55:58,0.714,-0.13 -2022-09-03T17:56:01,0.714,-0.13 -2022-09-03T17:56:04,0.714,-0.19 -2022-09-03T17:56:07,0.714,-0.19 -2022-09-03T17:56:10,0.714,-0.19 -2022-09-03T17:56:13,0.714,-0.13 -2022-09-03T17:56:16,0.714,-0.19 -2022-09-03T17:56:19,0.716,-0.19 -2022-09-03T17:56:22,0.714,-0.13 -2022-09-03T17:56:25,0.714,-0.19 -2022-09-03T17:56:28,0.714,-0.19 -2022-09-03T17:56:31,0.714,-0.19 -2022-09-03T17:56:34,0.714,-0.19 -2022-09-03T17:56:37,0.714,-0.13 -2022-09-03T17:56:40,0.714,-0.13 -2022-09-03T17:56:43,0.714,-0.19 -2022-09-03T17:56:46,0.716,-0.13 -2022-09-03T17:56:49,0.714,-0.13 -2022-09-03T17:56:52,0.714,-0.13 -2022-09-03T17:56:55,0.716,-0.19 -2022-09-03T17:56:59,0.714,-0.19 -2022-09-03T17:57:02,0.714,-0.19 -2022-09-03T17:57:05,0.716,-0.13 -2022-09-03T17:57:08,0.714,-0.13 -2022-09-03T17:57:11,0.714,-0.19 -2022-09-03T17:57:14,0.714,-0.13 -2022-09-03T17:57:17,0.894,5.48 -2022-09-03T17:57:20,0.986,8.54 -2022-09-03T17:57:23,0.988,8.47 -2022-09-03T17:57:26,1.368,20.58 -2022-09-03T17:57:29,1.354,20.26 -2022-09-03T17:57:32,0.714,-0.19 -2022-09-03T17:57:35,0.716,-0.13 -2022-09-03T17:57:38,0.714,-0.19 -2022-09-03T17:57:41,0.714,-0.19 -2022-09-03T17:57:44,0.714,-0.19 -2022-09-03T17:57:47,0.716,-0.19 -2022-09-03T17:57:50,0.714,-0.19 -2022-09-03T17:57:53,0.714,-0.13 -2022-09-03T17:57:56,0.714,-0.13 -2022-09-03T17:57:59,0.714,-0.19 -2022-09-03T17:58:02,0.714,-0.13 diff --git a/data/depth/2022-09-03T18-01-18_depth_data.csv b/data/depth/2022-09-03T18-01-18_depth_data.csv deleted file mode 100644 index 60e093b..0000000 --- a/data/depth/2022-09-03T18-01-18_depth_data.csv +++ /dev/null @@ -1,49 +0,0 @@ -time and date, Voltage of depth sensor (V), Depth (m) -2022-09-03T18:01:18,0.716,-0.13 -2022-09-03T18:01:21,0.716,-0.13 -2022-09-03T18:01:24,0.714,-0.19 -2022-09-03T18:01:27,0.716,-0.13 -2022-09-03T18:01:30,0.714,-0.19 -2022-09-03T18:01:33,0.716,-0.13 -2022-09-03T18:01:36,0.716,-0.13 -2022-09-03T18:01:39,0.716,-0.13 -2022-09-03T18:01:42,0.716,-0.13 -2022-09-03T18:01:45,0.714,-0.19 -2022-09-03T18:01:48,0.714,-0.19 -2022-09-03T18:01:51,0.714,-0.19 -2022-09-03T18:01:54,0.714,-0.19 -2022-09-03T18:01:57,0.716,-0.13 -2022-09-03T18:02:00,0.716,-0.13 -2022-09-03T18:02:03,0.714,-0.19 -2022-09-03T18:02:06,0.714,-0.19 -2022-09-03T18:02:09,0.714,-0.19 -2022-09-03T18:02:12,0.714,-0.19 -2022-09-03T18:02:15,0.714,-0.19 -2022-09-03T18:02:18,0.716,-0.13 -2022-09-03T18:02:21,0.716,-0.13 -2022-09-03T18:02:24,0.714,-0.19 -2022-09-03T18:02:27,0.714,-0.19 -2022-09-03T18:02:30,0.714,-0.19 -2022-09-03T18:02:33,0.714,-0.19 -2022-09-03T18:02:36,0.714,-0.19 -2022-09-03T18:02:39,0.716,-0.13 -2022-09-03T18:02:42,0.714,-0.19 -2022-09-03T18:02:45,0.714,-0.19 -2022-09-03T18:02:48,0.714,-0.19 -2022-09-03T18:02:51,0.714,-0.19 -2022-09-03T18:02:54,0.714,-0.19 -2022-09-03T18:02:57,0.714,-0.19 -2022-09-03T18:03:00,0.714,-0.19 -2022-09-03T18:03:03,0.714,-0.19 -2022-09-03T18:03:06,0.714,-0.19 -2022-09-03T18:03:09,0.714,-0.19 -2022-09-03T18:03:12,0.714,-0.19 -2022-09-03T18:03:15,0.714,-0.19 -2022-09-03T18:03:18,0.716,-0.13 -2022-09-03T18:03:21,0.716,-0.13 -2022-09-03T18:03:24,0.714,-0.19 -2022-09-03T18:03:27,0.716,-0.13 -2022-09-03T18:03:30,0.714,-0.19 -2022-09-03T18:03:33,0.714,-0.19 -2022-09-03T18:03:36,0.714,-0.19 -2022-09-03T18:03:39,0.714,-0.19 diff --git a/data/depth/2022-09-04T11-50-21_depth_data.csv b/data/depth/2022-09-04T11-50-21_depth_data.csv deleted file mode 100644 index 7998f67..0000000 --- a/data/depth/2022-09-04T11-50-21_depth_data.csv +++ /dev/null @@ -1,17 +0,0 @@ -time and date, Voltage of depth sensor (V), Depth (m) -2022-09-04T11:50:21,0.716,-0.13 -2022-09-04T11:50:24,0.716,-0.13 -2022-09-04T11:50:27,0.716,-0.13 -2022-09-04T11:50:31,1.12,12.74 -2022-09-04T11:50:34,0.716,-0.13 -2022-09-04T11:50:37,1.074,11.28 -2022-09-04T11:50:40,1.154,13.82 -2022-09-04T11:50:43,0.716,-0.13 -2022-09-04T11:50:46,1.034,10.0 -2022-09-04T11:50:49,1.234,16.37 -2022-09-04T11:50:52,1.212,15.67 -2022-09-04T11:50:55,0.714,-0.19 -2022-09-04T11:50:58,0.716,-0.13 -2022-09-04T11:51:01,0.716,-0.13 -2022-09-04T11:51:04,0.716,-0.13 -2022-09-04T11:51:07,0.716,-0.13 diff --git a/data/depth/2022-09-04T11-56-09_depth_data.csv b/data/depth/2022-09-04T11-56-09_depth_data.csv deleted file mode 100644 index f021fa0..0000000 --- a/data/depth/2022-09-04T11-56-09_depth_data.csv +++ /dev/null @@ -1,7 +0,0 @@ -time and date, Voltage of depth sensor (V), Depth (m) -2022-09-04T11:56:09,0.716,-0.13 -2022-09-04T11:56:12,0.716,-0.13 -2022-09-04T11:56:15,0.716,-0.13 -2022-09-04T11:56:18,0.716,-0.13 -2022-09-04T11:56:21,0.716,-0.13 -2022-09-04T11:56:24,0.716,-0.13 diff --git a/data/gps/2022-08-20T12-23-39_GPS_data.csv b/data/gps/2022-08-20T12-23-39_GPS_data.csv deleted file mode 100644 index 525378d..0000000 --- a/data/gps/2022-08-20T12-23-39_GPS_data.csv +++ /dev/null @@ -1,11 +0,0 @@ -GPStime utc,latitude,longitude,speed,sats in view -2022-08-20T09:23:40.000Z,60.196451148,24.960462239,0.582,0 -2022-08-20T09:23:41.000Z,60.196446687,24.960462239,0.374,0 -2022-08-20T09:23:42.000Z,60.196437765,24.960462239,0.257,7 -2022-08-20T09:23:43.000Z,60.196441539,24.960486191,0.192,7 -2022-08-20T09:23:44.000Z,60.196441539,24.960486191,0.121,7 -2022-08-20T09:23:45.000Z,60.196437078,24.960486191,0.279,7 -2022-08-20T09:23:46.000Z,60.196439678,24.960493798,0.233,7 -2022-08-20T09:23:47.000Z,60.196439678,24.960493798,0.059,7 -2022-08-20T09:23:48.000Z,60.196439678,24.960493798,0.124,7 -2022-08-20T09:23:49.000Z,60.196446739,24.960501406,0.369,7 diff --git a/scripts/start-all.sh b/scripts/start-all.sh index 04a5905..905e206 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -5,13 +5,11 @@ # Create output directory OUTPUT_DIR=$BASE_DIR_PATH/$(date +"%Y-%m-%d_%H-%M-%S_output") - mkdir -p $OUTPUT_DIR/audio +# Sleep for a little to wait for GPS and sound card to be ready sleep 10 -# (export OUTPUT_DIR=$OUTPUT_DIR; trap 'kill 0' SIGINT; /home/pi/hydrophonitor/scripts/scripts/start-gps.sh & /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-pressure-depth.sh) - echo "(export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh)" >> $OUTPUT_DIR/log.txt 2>&1 (export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh) >> $OUTPUT_DIR/log.txt 2>&1 diff --git a/scripts/start-audio.sh b/scripts/start-audio.sh index 4107bc0..858a3b3 100755 --- a/scripts/start-audio.sh +++ b/scripts/start-audio.sh @@ -3,7 +3,7 @@ # Export the configuration values . /home/pi/hydrophonitor/scripts/export-config-values.sh -AUDIO_TARGET_LOCATION="/home/pi/hydrophonitor/audio-logger/target/release" +AUDIO_TARGET_PATH="/home/pi/hydrophonitor/audio-logger/target/release" AUDIO_TARGET_EXECUTABLE="audio" OPTIONS="rec \ @@ -15,6 +15,6 @@ OPTIONS="rec \ --buffer-size 1024 \ alsa" -echo "cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS" +echo "cd $AUDIO_TARGET_PATH && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS" -cd $AUDIO_TARGET_LOCATION && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS +cd $AUDIO_TARGET_PATH && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS diff --git a/scripts/start-gps.sh b/scripts/start-gps.sh index e392daf..0afdc86 100755 --- a/scripts/start-gps.sh +++ b/scripts/start-gps.sh @@ -3,9 +3,10 @@ # Export the configuration values . /home/pi/hydrophonitor/scripts/export-config-values.sh -GPS_EXECUTABLE_LOCATION="/home/pi/hydrophonitor/gps-logger" +GPS_EXECUTABLE_PATH="/home/pi/hydrophonitor/gps-logger" + OPTIONS="--output $OUTPUT_DIR --interval $GPS_INTERVAL" -echo "cd $GPS_EXECUTABLE_LOCATION && python record-gps.py $OPTIONS" +echo "cd $GPS_EXECUTABLE_PATH && python record-gps.py $OPTIONS" -cd $GPS_EXECUTABLE_LOCATION && python record-gps.py $OPTIONS +cd $GPS_EXECUTABLE_PATH && python record-gps.py $OPTIONS diff --git a/scripts/start-pressure-depth.sh b/scripts/start-pressure-depth.sh index ac344c9..c2071b3 100644 --- a/scripts/start-pressure-depth.sh +++ b/scripts/start-pressure-depth.sh @@ -3,8 +3,8 @@ # Export the configuration values . /home/pi/hydrophonitor/scripts/export-config-values.sh -DEPTH_EXECUTABLE_LOCATION="/home/pi/hydrophonitor/depth-logger" +DEPTH_EXECUTABLE_PATH="/home/pi/hydrophonitor/depth-logger" OPTIONS="--output $OUTPUT_DIR --interval $DEPTH_INTERVAL" -cd $DEPTH_TARGET_LOCATION && python record-depth.py $OPTIONS +cd $DEPTH_EXECUTABLE_PATH && python record-depth.py $OPTIONS From 2af755c4fc0a3dc44df55aaa8817d6e6abce5c87 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 09:16:24 +0300 Subject: [PATCH 16/22] Update setup.md --- setup.md | 91 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/setup.md b/setup.md index bd3c24a..3267c0b 100755 --- a/setup.md +++ b/setup.md @@ -2,16 +2,22 @@ ## Components -- Raspberry Pi (tested on 2, 4B) +- Raspberry Pi (tested on 4B) - MicroSD card + adapter -- card reader to access the sd card on the computer +- Card reader to access the sd card on the computer +- Audio card connected via USB and a microphone/hydrophone attached to the audio card +- USB GPS receiver +- Depth recording components: + - Pressure sensor + - Adafruit ADS1015 ADC + - breadboard, resistors, jumper wires, 12V battery ## Raspberry OS basic setup ### 1. Install the operating system and set up user, Wi-Fi, ssh access -#### With Raspberry Pi Imager +#### 1.1 With Raspberry Pi Imager The easiest way to install the operating system (Raspberry Pi OS, a Linux Debian-based OS) is to use the official Raspberry Pi Imager utility which works on macOS, Ubuntu and Windows. @@ -30,7 +36,7 @@ Then the following steps: - Set locale settings: select options correct for you 4. Click Write (all existing data on the SD card will be erased and the OS installed) -#### With some other utility +#### 1.2 With some other utility If you do not use the Raspberry Pi Imager to set up the SD card, the following steps are required: @@ -50,11 +56,70 @@ If you do not use the Raspberry Pi Imager to set up the SD card, the following s - replace "" with the Wi-Fi password, e.g. "password" -### 2. Installing needed packages +### 2. Setting up the recording programs on the Raspberry Pi + +After flashing the operating system to the SD card, it should show up as volume called "boot". + +To install all the needed components and to configure the Raspberry Pi to start the recordings when it is turned on, four steps are needed: copying the needed files to the SD card, putting the SD card in the Raspberry Pi and connecting to it on the command line over SSH, running an installer script on the command line, and finally letting it restart and verify that everything works as intended. + +#### 2.1 Copy files to the SD card + +Copy the entire `hydrophonitor` folder to the SD card (simple Ctrl+C and Ctrl+V works) + +#### 2.2 Plug the SD card in and connect to the Raspberry Pi over SSH + +Plug the SD card in the Raspberry Pi. Connect the audio card and the GPS receiver over USB to the Raspberry Pi, and plug the power cable. It will take some time for the Raspberry Pi to be ready to accept SSH connections. + +To figure out what IP address the Raspberry Pi has been assigned in the local network, a tool called `nmap` is needed. + +To check whether nmap is already installed on the system, open a terminal, run the following command (write it to the terminal and press Enter): + +``` +nmap --version +``` + +If this prints out version information about nmap (e.g. Nmap version 7.93 ( https://nmap.org)), it is installed. Otherwise, installation instructions can be found here: https://nmap.org/download.html + +After installing, run the following command (it will ask for password, write it and press Enter) to find all devices connected to the local network: + +``` +sudo nmap -sn 192.168.1.0/24 +``` + +The result will contain a series of discovered devices (hosts) with the following information for each device: + +``` +Nmap scan report for 192.168.1.108 +Host is up (0.18s latency). +MAC Address: E4:5F:01:B3:65:DE (Raspberry Pi Trading) +``` + +The Raspberry Pi should show up with its IP address (here, 192.168.1.108), MAC address and a name after the MAC address that should help identifying it (here, it's Raspberry Pi Trading). + +Now, this IP address can be used to connect to the Raspberry Pi over SSH on the command line. Connect by running the command `ssh @`, which with a user called `pi` and an IP address of 192.168.1.108 would be + +``` +ssh pi@192.168.1.108 +``` + +When asked `Are you sure you want to continue connecting (yes/no/[fingerprint])?`, type `yes` and press Enter. Then, write the user's password when asked and press Enter. + +After successfully connecting, your prompt should change to `@raspberrypi:~` or something similar. + +#### 2.3 Run the installer script + +After establishing the SSH connection to the Raspberry Pi, change the current directory to the location of the installer script and run it: + +``` +cd /boot/hydrophonitor/configuration +./setup-raspberry-pi.sh +``` -### 3. Mount SSD +### 3. Configuration options + +### 4. Mount SSD @@ -62,20 +127,6 @@ If you do not use the Raspberry Pi Imager to set up the SD card, the following s -## Set up recording - -### 1. Audio - - - -### 2. GPS - - - -### 3. Water pressure - - - ## Test & run From 1e7048717a13cc193b831f3918c51471bccdcb2f Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 09:17:13 +0300 Subject: [PATCH 17/22] Update setup scripts --- configuration/setup-raspberry-pi.sh | 65 ++++++++++++++++++++++++----- scripts/setup-audio.sh | 10 +++-- scripts/setup-gps.sh | 5 ++- scripts/setup-pressure-depth.sh | 11 ++++- 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/configuration/setup-raspberry-pi.sh b/configuration/setup-raspberry-pi.sh index a6e82f7..85e71f6 100644 --- a/configuration/setup-raspberry-pi.sh +++ b/configuration/setup-raspberry-pi.sh @@ -1,25 +1,68 @@ -#!/bin/sh +#!/bin/bash -# Copy the files to /home/pi -cd /home/pi -cp -R /boot/hydrophonitor . -cd hydrophonitor +set -ex + +DIR_PATH=$HOME +BOOT_DIR_PATH=/boot/hydrophonitor + +# Copy the files to DIR_PATH +echo +echo "### Copy files to $DIR_PATH" +echo + +mkdir -p "$DIR_PATH" +cd "$DIR_PATH" +cp -R $BOOT_DIR_PATH/ . # Install the Rust toolchain +echo +echo "### Install the Rust toolchain" +echo + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "$HOME/.cargo/env" + +# Install some developer tools +echo +echo "### Install some developer tools" +echo + +sudo apt-get update && sudo apt-get install -y build-essential # Setup audio -sh scripts/setup-audio.sh +echo +echo "### Setup audio" +echo + +cd "$DIR_PATH" && sh hydrophonitor/scripts/setup-audio.sh # Setup GPS -sh scripts/setup-gps.sh +echo +echo "### Setup GPS" +echo + +cd "$DIR_PATH" && sh hydrophonitor/scripts/setup-gps.sh # Setup depth sensor -sh scripts/setup-pressure-depth.sh +echo +echo "### Setup depth recording" +echo -# Setup cron job to start the recordings at boot +cd "$DIR_PATH" && sh hydrophonitor/scripts/setup-pressure-depth.sh + +# Set up cron job to start the recordings at boot +echo +echo "### Set up a cron job to start the recordings at boot" +echo + +# USER=$(whoami) CRON_FILE=/etc/crontab -echo "@reboot root /home/pi/hydrophonitor/scripts/start-all.sh" | sudo tee -a $CRON_FILE +CRON_COMMAND="@reboot root $DIR_PATH/hydrophonitor/scripts/start-all.sh 2>&1 >> $BOOT_DIR_PATH/log.txt" + +# Append command to cron file only if it's not there yet +sudo grep -qxF "$CRON_COMMAND" $CRON_FILE || echo "$CRON_COMMAND" | sudo tee -a $CRON_FILE # Reboot -sudo reboot +echo +echo "### Setup ready, run 'sudo reboot' to apply all changes" +echo diff --git a/scripts/setup-audio.sh b/scripts/setup-audio.sh index 1b1e3b7..7fea15c 100755 --- a/scripts/setup-audio.sh +++ b/scripts/setup-audio.sh @@ -1,7 +1,9 @@ -#!/bin/sh +#!/bin/bash + +echo "Setting up audio recording" # Install packages -sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-dev jackd2 +sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-dev # Get ID and number of the USB audio device card_number=$(aplay -l | grep -i usb | grep -i audio | cut -d ' ' -f 2 | cut -d ':' -f 1) @@ -9,7 +11,7 @@ card_number=$(aplay -l | grep -i usb | grep -i audio | cut -d ' ' -f 2 | cut -d # Change default audio device sudo touch /etc/asound.conf -sudo cat << EOF | sudo tee -a /etc/asound.conf +sudo cat << EOF | sudo tee /etc/asound.conf pcm.!default { type plug slave { @@ -22,3 +24,5 @@ ctl.!default { card $card_number } EOF + +cd hydrophonitor/audio-logger && cargo build --release diff --git a/scripts/setup-gps.sh b/scripts/setup-gps.sh index 051d067..50babcf 100755 --- a/scripts/setup-gps.sh +++ b/scripts/setup-gps.sh @@ -1,5 +1,7 @@ #!/bin/sh +echo "Setting up GPS recording" + sudo apt-get update && sudo apt-get install -y \ gpsd gpsd-clients @@ -12,5 +14,4 @@ sudo gpsd ${device} -F /var/run/gpsd.sock sudo sed -i "s|DEVICES=\"\"|DEVICES=\"${device}\"|g" /etc/default/gpsd -echo "START_DAEMON=\"true\"" | sudo tee -a /etc/default/gpsd - +sudo grep -qxF "START_DAEMON=\"true\"" /etc/default/gpsd || echo "START_DAEMON=\"true\"" | sudo tee -a /etc/default/gpsd diff --git a/scripts/setup-pressure-depth.sh b/scripts/setup-pressure-depth.sh index 96b4b06..d5fba10 100644 --- a/scripts/setup-pressure-depth.sh +++ b/scripts/setup-pressure-depth.sh @@ -1 +1,10 @@ -#!/bin/sh \ No newline at end of file +#!/bin/sh + +echo "Setting up depth recording" + +# Enable i2c bus on Raspberry Pi +sudo raspi-config nonint do_i2c 0 + +# Install packages +sudo apt-get update && sudo apt-get install -y i2c-tools python3-pip +sudo pip3 install Adafruit-Blinka adafruit-circuitpython-ads1x15 From 163e75ccccc6d7eacb62cfe38eb81c965390d47b Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 09:17:48 +0300 Subject: [PATCH 18/22] Add log prints and clean python recording scripts --- depth-logger/record-depth.py | 58 ++++++++++++++++++------------------ gps-logger/record-gps.py | 14 ++++----- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/depth-logger/record-depth.py b/depth-logger/record-depth.py index 04f1bce..bdfb9da 100644 --- a/depth-logger/record-depth.py +++ b/depth-logger/record-depth.py @@ -6,19 +6,18 @@ import time import busio import adafruit_ads1x15.ads1015 as ADS from adafruit_ads1x15.analog_in import AnalogIn -from time import sleep, strftime -from rpi_lcd import LCD +# from rpi_lcd import LCD parser = argparse.ArgumentParser(description='GPS Logger') -parser.add_argument('-o', '--output', help='Output file', required=True) +parser.add_argument('-o', '--output', help='Output directory', required=True) parser.add_argument('-i', '--interval', help='Interval in seconds', required=False) args = parser.parse_args() -try: - lcd = LCD(bus=2) -except OSError: - lcd = None +# try: +# lcd = LCD(bus=2) +# except OSError: +# lcd = None # Create the I2C bus i2c = busio.I2C(board.SCL, board.SDA) @@ -26,46 +25,47 @@ i2c = busio.I2C(board.SCL, board.SDA) # Create the ADC object using the I2C bus ads = ADS.ADS1015(i2c) -# Create single-ended input on channel 0 -# tmp36 = AnalogIn(ads, ADS.P0) - # Create a single-ended input on channel 1 depthS = AnalogIn(ads, ADS.P1) +# Create single-ended input on channel 0 +# tmp36 = AnalogIn(ads, ADS.P0) + # Subtract the offset from the sensor voltage # and convert chan.voltage * (1 degree C / 0.01V) = Degrees Celcius # temperatureC = (tmp36.voltage - 0.5) / 0.01 # File to write down the results -filename = args.output + time.strftime("%Y-%m-%dT%H-%M-%S") + "_depth_data.csv" +filename = args.output + "/" + time.strftime("%Y-%m-%dT%H-%M-%S") + "_depth_data.csv" -#depthM = ((depthS.voltage * 31.848) - 22.93) +interval = int(args.interval) if args.interval else 5 #Attempting to round the figure to a more intelligible figure #rounddepth = round(depthM, ndigits) #psi = depthS.voltage * 104.1666667 - 75 - #bar = psi * 14.503773800722 with open(filename, "w", 1) as f: + print(f"Writing pressure/depth output to {filename}, interval {interval} seconds") f.write("time and date, Voltage of depth sensor (V), Depth (m)\n") - while True: - voltage = depthS.voltage - depthM = ((voltage * 31.848) - 22.93) - rounddepth = round(depthM, 2) - # roundtemp = round(temperatureC, 2) - roundvolts = round(voltage, 3) + try: + while True: + voltage = depthS.voltage + depthM = ((voltage * 31.848) - 22.93) + rounddepth = round(depthM, 2) + roundvolts = round(voltage, 3) + # roundtemp = round(temperatureC, 2) - print((str(voltage) + " V ") + (str(depthM) + " m ") + (str(roundvolts) + " V ") + (str(rounddepth) + " m")) + print((str(voltage) + " V ") + (str(depthM) + " m ") + (str(roundvolts) + " V ") + (str(rounddepth) + " m")) - if lcd: - lcd.clear() - lcd.text((str(roundvolts) + " V ") + (str(rounddepth) + " m"), 1) - f.write(time.strftime("%Y-%m-%dT%H:%M:%S") + ",") - f.write(str(roundvolts) + "," + str(rounddepth) + "\n") + # if lcd: + # lcd.clear() + # lcd.text((str(roundvolts) + " V ") + (str(rounddepth) + " m"), 1) + + f.write(time.strftime("%Y-%m-%dT%H:%M:%S") + "," + str(roundvolts) + "," + str(rounddepth) + "\n") - if args.interval: - time.sleep(int(args.interval)) - else: - time.sleep(5) + time.sleep(interval) + + except (KeyboardInterrupt, SystemExit): # when you press ctrl+c + print("Exiting depth recording.") diff --git a/gps-logger/record-gps.py b/gps-logger/record-gps.py index 1b1d629..e7b179d 100644 --- a/gps-logger/record-gps.py +++ b/gps-logger/record-gps.py @@ -5,15 +5,19 @@ import time import argparse parser = argparse.ArgumentParser(description='GPS Logger') -parser.add_argument('-o', '--output', help='Output file', required=True) +parser.add_argument('-o', '--output', help='Output directory', required=True) parser.add_argument('-i', '--interval', help='Interval in seconds', required=False) args = parser.parse_args() filename = args.output + "/" + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.csv" +interval = int(args.interval) if args.interval else 5 + with open(filename, "w", 1) as f: gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) + + print(f"Writing GPS output to {filename}, interval {interval} seconds") f.write("GPStime utc,latitude,longitude,speed,sats in view\n") try: @@ -28,11 +32,7 @@ with open(filename, "w", 1) as f: f.write(GPStime + "," + lat +"," + lon + "," + speed + "," + sats + "\n") - if args.interval: - time.sleep(int(args.interval)) - else: - time.sleep(5) + time.sleep(interval) except (KeyboardInterrupt, SystemExit): # when you press ctrl+c - print("Done.\nExiting.") - f.close() + print("Exiting GPS recording.") From ef5c7d03cc879546c9a4ea88a0187b6391b66ee7 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 09:19:01 +0300 Subject: [PATCH 19/22] Switch start scripts to use bash, clean up --- scripts/start-all.sh | 16 ++++++++++------ scripts/start-audio.sh | 9 +++------ scripts/start-gps.sh | 10 +++------- scripts/start-pressure-depth.sh | 8 +++----- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/scripts/start-all.sh b/scripts/start-all.sh index 905e206..d6c4c21 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -1,15 +1,19 @@ -#!/bin/sh +#!/bin/bash + +# Print all commands to standard output +set -x + +SCRIPT_PATH=/home/pi/hydrophonitor/scripts # Export the configuration values -. /home/pi/hydrophonitor/scripts/export-config-values.sh +$SCRIPT_PATH/export-config-values.sh # Create output directory OUTPUT_DIR=$BASE_DIR_PATH/$(date +"%Y-%m-%d_%H-%M-%S_output") -mkdir -p $OUTPUT_DIR/audio +echo "Create output directory $OUTPUT_DIR" +mkdir -p "$OUTPUT_DIR"/audio # Sleep for a little to wait for GPS and sound card to be ready sleep 10 -echo "(export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh)" >> $OUTPUT_DIR/log.txt 2>&1 - -(export OUTPUT_DIR=$OUTPUT_DIR; /home/pi/hydrophonitor/scripts/start-audio.sh & /home/pi/hydrophonitor/scripts/start-gps.sh) >> $OUTPUT_DIR/log.txt 2>&1 +(export OUTPUT_DIR=$OUTPUT_DIR; $SCRIPT_PATH/start-audio.sh & $SCRIPT_PATH/start-gps.sh & $SCRIPT_PATH/start-pressure-depth.sh) >> "$OUTPUT_DIR"/log.txt 2>&1 diff --git a/scripts/start-audio.sh b/scripts/start-audio.sh index 858a3b3..4af6078 100755 --- a/scripts/start-audio.sh +++ b/scripts/start-audio.sh @@ -1,9 +1,8 @@ -#!/bin/sh +#!/bin/bash # Export the configuration values -. /home/pi/hydrophonitor/scripts/export-config-values.sh +/home/pi/hydrophonitor/scripts/export-config-values.sh -AUDIO_TARGET_PATH="/home/pi/hydrophonitor/audio-logger/target/release" AUDIO_TARGET_EXECUTABLE="audio" OPTIONS="rec \ @@ -15,6 +14,4 @@ OPTIONS="rec \ --buffer-size 1024 \ alsa" -echo "cd $AUDIO_TARGET_PATH && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS" - -cd $AUDIO_TARGET_PATH && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS +cd /home/pi/hydrophonitor/audio-logger/target/release && ./$AUDIO_TARGET_EXECUTABLE $OPTIONS diff --git a/scripts/start-gps.sh b/scripts/start-gps.sh index 0afdc86..f8a4a19 100755 --- a/scripts/start-gps.sh +++ b/scripts/start-gps.sh @@ -1,12 +1,8 @@ -#!/usr/bin/sh +#!/bin/bash # Export the configuration values -. /home/pi/hydrophonitor/scripts/export-config-values.sh - -GPS_EXECUTABLE_PATH="/home/pi/hydrophonitor/gps-logger" +/home/pi/hydrophonitor/scripts/export-config-values.sh OPTIONS="--output $OUTPUT_DIR --interval $GPS_INTERVAL" -echo "cd $GPS_EXECUTABLE_PATH && python record-gps.py $OPTIONS" - -cd $GPS_EXECUTABLE_PATH && python record-gps.py $OPTIONS +cd /home/pi/hydrophonitor/gps-logger && python record-gps.py $OPTIONS diff --git a/scripts/start-pressure-depth.sh b/scripts/start-pressure-depth.sh index c2071b3..5c29960 100644 --- a/scripts/start-pressure-depth.sh +++ b/scripts/start-pressure-depth.sh @@ -1,10 +1,8 @@ -#!/usr/bin/sh +#!/bin/bash # Export the configuration values -. /home/pi/hydrophonitor/scripts/export-config-values.sh - -DEPTH_EXECUTABLE_PATH="/home/pi/hydrophonitor/depth-logger" +/home/pi/hydrophonitor/scripts/export-config-values.sh OPTIONS="--output $OUTPUT_DIR --interval $DEPTH_INTERVAL" -cd $DEPTH_EXECUTABLE_PATH && python record-depth.py $OPTIONS +cd /home/pi/hydrophonitor/depth-logger && python record-depth.py $OPTIONS From 89701ca893867144cd6520900b589d8c96fc86e2 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 09:19:50 +0300 Subject: [PATCH 20/22] Switch config export script to get config file as argument --- scripts/export-config-values.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/export-config-values.sh b/scripts/export-config-values.sh index d85f8c8..8b397a8 100644 --- a/scripts/export-config-values.sh +++ b/scripts/export-config-values.sh @@ -1,5 +1,5 @@ -#!/bin/sh +#!/bin/bash -CONFIG_FILE=/boot/hydrophonitor/configuration/hydrophonitor-config.txt +CONFIG_FILE=$1 -export $(grep -v '^#' $CONFIG_FILE | xargs -d '\n') +export $(grep -v '^#' $CONFIG_FILE | tr -d '[:space:]' | xargs -d '\n') From 1a7bd8c21bad5591eae3cbf01b37a9ac583f1421 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 22:06:50 +0300 Subject: [PATCH 21/22] Switch python to use gpsd-pip3 module, flush print outputs immediately, add requirements.txt --- depth-logger/record-depth.py | 6 ++--- depth-logger/requirements.txt | 2 ++ gps-logger/record-gps.py | 41 +++++++++++++++++---------------- gps-logger/requirements.txt | 1 + scripts/setup-gps.sh | 2 ++ scripts/setup-pressure-depth.sh | 3 ++- 6 files changed, 31 insertions(+), 24 deletions(-) create mode 100644 depth-logger/requirements.txt create mode 100644 gps-logger/requirements.txt diff --git a/depth-logger/record-depth.py b/depth-logger/record-depth.py index bdfb9da..b801707 100644 --- a/depth-logger/record-depth.py +++ b/depth-logger/record-depth.py @@ -46,7 +46,7 @@ interval = int(args.interval) if args.interval else 5 #bar = psi * 14.503773800722 with open(filename, "w", 1) as f: - print(f"Writing pressure/depth output to {filename}, interval {interval} seconds") + print(f"Writing pressure/depth output to {filename}, interval {interval} seconds", flush=True) f.write("time and date, Voltage of depth sensor (V), Depth (m)\n") try: @@ -57,7 +57,7 @@ with open(filename, "w", 1) as f: roundvolts = round(voltage, 3) # roundtemp = round(temperatureC, 2) - print((str(voltage) + " V ") + (str(depthM) + " m ") + (str(roundvolts) + " V ") + (str(rounddepth) + " m")) + print((str(voltage) + " V ") + (str(depthM) + " m ") + (str(roundvolts) + " V ") + (str(rounddepth) + " m"), flush=True) # if lcd: # lcd.clear() @@ -68,4 +68,4 @@ with open(filename, "w", 1) as f: time.sleep(interval) except (KeyboardInterrupt, SystemExit): # when you press ctrl+c - print("Exiting depth recording.") + print("Exiting depth recording.", flush=True) diff --git a/depth-logger/requirements.txt b/depth-logger/requirements.txt new file mode 100644 index 0000000..1a18997 --- /dev/null +++ b/depth-logger/requirements.txt @@ -0,0 +1,2 @@ +Adafruit-Blinka==8.5.0 +adafruit-circuitpython-ads1x15==2.2.21 \ No newline at end of file diff --git a/gps-logger/record-gps.py b/gps-logger/record-gps.py index e7b179d..97b2a54 100644 --- a/gps-logger/record-gps.py +++ b/gps-logger/record-gps.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from gps import * +import gpsd import time import argparse @@ -15,24 +15,25 @@ filename = args.output + "/" + time.strftime("%Y-%m-%dT%H-%M-%S") + "_GPS_data.c interval = int(args.interval) if args.interval else 5 with open(filename, "w", 1) as f: - gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) + gpsd.connect() - print(f"Writing GPS output to {filename}, interval {interval} seconds") - f.write("GPStime utc,latitude,longitude,speed,sats in view\n") + print(f"Writing GPS output to {filename}, interval {interval} seconds", flush=True) + f.write("system_time,gps_time_utc,latitude,longitude,speed,sats_in_view\n") - try: - while True: - report = gpsd.next() - if report["class"] == "TPV": - GPStime = str(getattr(report,"time","")) - lat = str(getattr(report,"lat",0.0)) - lon = str(getattr(report,"lon",0.0)) - speed = str(getattr(report,"speed","nan")) - sats = str(len(gpsd.satellites)) - - f.write(GPStime + "," + lat +"," + lon + "," + speed + "," + sats + "\n") - - time.sleep(interval) - - except (KeyboardInterrupt, SystemExit): # when you press ctrl+c - print("Exiting GPS recording.") + while True: + try: + packet = gpsd.get_current() + gps_time_utc = str(packet.time) if packet.mode >= 2 else "-" + lat = str(packet.lat) if packet.mode >= 2 else "0.0" + lon = str(packet.lon) if packet.mode >= 2 else "0.0" + speed = str(packet.hspeed) if packet.mode >= 2 else "0.0" + sats = str(packet.sats) + system_time = time.strftime("%Y-%m-%dT%H-%M-%S") + f.write(f"{system_time},{gps_time_utc},{lat},{lon},{speed},{sats}\n") + except (KeyboardInterrupt, SystemExit): # when you press ctrl+c + print("Exiting GPS recording.", flush=True) + break + except Exception as e: + print(f"GPS error: {e}", flush=True) + + time.sleep(interval) diff --git a/gps-logger/requirements.txt b/gps-logger/requirements.txt new file mode 100644 index 0000000..2a628b2 --- /dev/null +++ b/gps-logger/requirements.txt @@ -0,0 +1 @@ +gpsd-py3==0.3.0 \ No newline at end of file diff --git a/scripts/setup-gps.sh b/scripts/setup-gps.sh index 50babcf..81258d9 100755 --- a/scripts/setup-gps.sh +++ b/scripts/setup-gps.sh @@ -5,6 +5,8 @@ echo "Setting up GPS recording" sudo apt-get update && sudo apt-get install -y \ gpsd gpsd-clients +sudo pip install -r /home/pi/hydrophonitor/gps-logger/requirements.txt + sudo systemctl stop gpsd.socket sudo systemctl disable gpsd.socket diff --git a/scripts/setup-pressure-depth.sh b/scripts/setup-pressure-depth.sh index d5fba10..eeacb8e 100644 --- a/scripts/setup-pressure-depth.sh +++ b/scripts/setup-pressure-depth.sh @@ -7,4 +7,5 @@ sudo raspi-config nonint do_i2c 0 # Install packages sudo apt-get update && sudo apt-get install -y i2c-tools python3-pip -sudo pip3 install Adafruit-Blinka adafruit-circuitpython-ads1x15 + +sudo pip install -r /home/pi/hydrophonitor/depth-logger/requirements.txt From d5ebffaa89f558ddad5a1f037d8f741405c620b6 Mon Sep 17 00:00:00 2001 From: Satu Koskinen Date: Tue, 11 Oct 2022 22:06:56 +0300 Subject: [PATCH 22/22] Update setup.md --- setup.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/setup.md b/setup.md index 3267c0b..d6ac351 100755 --- a/setup.md +++ b/setup.md @@ -62,17 +62,17 @@ After flashing the operating system to the SD card, it should show up as volume To install all the needed components and to configure the Raspberry Pi to start the recordings when it is turned on, four steps are needed: copying the needed files to the SD card, putting the SD card in the Raspberry Pi and connecting to it on the command line over SSH, running an installer script on the command line, and finally letting it restart and verify that everything works as intended. -#### 2.1 Copy files to the SD card +#### 2.1 Copy files to the SD card, set configuration values + +First, set the configuration values in the file hydrophonitor/configuration/hydrophonitor-config.txt. Then, copy the entire `hydrophonitor` folder to the SD card (simple Ctrl+C and Ctrl+V works). -Copy the entire `hydrophonitor` folder to the SD card (simple Ctrl+C and Ctrl+V works) - #### 2.2 Plug the SD card in and connect to the Raspberry Pi over SSH Plug the SD card in the Raspberry Pi. Connect the audio card and the GPS receiver over USB to the Raspberry Pi, and plug the power cable. It will take some time for the Raspberry Pi to be ready to accept SSH connections. To figure out what IP address the Raspberry Pi has been assigned in the local network, a tool called `nmap` is needed. -To check whether nmap is already installed on the system, open a terminal, run the following command (write it to the terminal and press Enter): +To check whether nmap is already installed on the system, open a terminal and run the following command (write it to the terminal and press Enter): ``` nmap --version @@ -80,7 +80,7 @@ nmap --version If this prints out version information about nmap (e.g. Nmap version 7.93 ( https://nmap.org)), it is installed. Otherwise, installation instructions can be found here: https://nmap.org/download.html -After installing, run the following command (it will ask for password, write it and press Enter) to find all devices connected to the local network: +After installing, run the following command (it will ask for your user password, write it and press Enter) to find all devices connected to the local network: ``` sudo nmap -sn 192.168.1.0/24 @@ -102,7 +102,7 @@ Now, this IP address can be used to connect to the Raspberry Pi over SSH on the ssh pi@192.168.1.108 ``` -When asked `Are you sure you want to continue connecting (yes/no/[fingerprint])?`, type `yes` and press Enter. Then, write the user's password when asked and press Enter. +When asked `Are you sure you want to continue connecting (yes/no/[fingerprint])?`, type `yes` and press Enter. Then, write the Raspberry Pi user's password when asked and press Enter. After successfully connecting, your prompt should change to `@raspberrypi:~` or something similar. @@ -115,18 +115,22 @@ cd /boot/hydrophonitor/configuration ./setup-raspberry-pi.sh ``` +At the end of successful configuration, the script should print "### Setup ready, run 'sudo reboot' to apply all changes". Run the command and input the Raspberry Pi user's password if requested: +``` +sudo reboot +``` + +This will restart the Raspberry Pi and apply the changes made in the setup. On startup, it should now start recording audio, GPS and depth data. ### 3. Configuration options +todo + ### 4. Mount SSD - - -## Real time clock (RTC) module - - +todo ## Test & run - +todo