diff --git a/src/lib.rs b/src/lib.rs index d47c0c3..76ee06d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,7 +176,18 @@ where } } - fn set_clock_source(&mut self, source: ClockSource, frequency: f64) -> Result<(), Error> { + fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error> { + // Change clock source through configuration register + self.set_clock_source(source)?; + + // Modify the master clock frequency + // Prevent redundunt call to change f_ref_clk + self.f_master_clk = frequency; + + self.set_clock_division(division) + } + + fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Error> { // Change clock source through configuration register match source { ClockSource::OSC => self.config_register.set_configurations(&mut [ @@ -190,19 +201,15 @@ where ClockSource::SMA => self.config_register.set_configurations(&mut [ (CFGMask::CLK_SEL0, 1), ]), - }?; + }.map(|_| ()) + } - // Save the new master clock frequency + fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error> { + // Update master clock frequency self.f_master_clk = frequency; - - // Calculate reference clock frequency after clock division from configuration register - let f_ref_clk = self.f_master_clk / (self.config_register.get_configuration(CFGMask::DIV) as f64); - // Update all DDS chips on reference clock frequency - for dds_channel in 0..4 { - self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?; - } - Ok(()) + // Update all DDS f_ref_clk + self.set_dds_ref_clk() } fn set_clock_division(&mut self, division: u8) -> Result<(), Error> { @@ -219,8 +226,12 @@ where _ => Err(Error::ParameterError), }?; + self.set_dds_ref_clk() + } + + fn set_dds_ref_clk(&mut self) -> Result<(), Error> { // Calculate reference clock frequency after clock division from configuration register - let f_ref_clk = self.f_master_clk / (division as f64); + let f_ref_clk = self.f_master_clk / (self.config_register.get_configuration(CFGMask::DIV) as f64); // Update all DDS chips on reference clock frequency for dds_channel in 0..4 { diff --git a/src/mqtt_mux.rs b/src/mqtt_mux.rs index 16f32c5..349996f 100644 --- a/src/mqtt_mux.rs +++ b/src/mqtt_mux.rs @@ -22,6 +22,11 @@ use crate::Error; pub enum MqttTopic { Switch(u8), Attenuation(u8), + Clock, + ClockSource, + ClockFrequency, + ClockDivision, + SystemClock(u8), Singletone(u8, u8), SingletoneFrequency(u8, u8), SingletoneAmplitude(u8, u8), @@ -35,6 +40,11 @@ pub enum MqttTopic { pub enum MqttCommand { Switch(u8, bool), Attenuation(u8, f32), + Clock(UrukulClockSource, f64, u8), + ClockSource(UrukulClockSource), + ClockFrequency(f64), + ClockDivision(u8), + SystemClock(u8, f64), Singletone(u8, u8, f64, f64, f64), SingletoneFrequency(u8, u8, f64), SingletoneAmplitude(u8, u8, f64), @@ -76,7 +86,7 @@ impl MqttMux where SPI: Transfer { singletone_frequency, singletone_amplitude, singletone_phase, - profile // Note: Put profile at the end + profile )) )(topic) } @@ -85,6 +95,11 @@ impl MqttMux where SPI: Transfer { match topic { MqttTopic::Switch(ch) => switch_message(ch, message), MqttTopic::Attenuation(ch) => attenuation_message(ch, message), + MqttTopic::Clock => clock_message(message), + MqttTopic::ClockSource => clock_source_message(message), + MqttTopic::ClockFrequency => clock_frequency_message(message), + MqttTopic::ClockDivision => clock_division_message(message), + MqttTopic::SystemClock(ch) => system_clock_message(ch, message), MqttTopic::Singletone(ch, prof) => singletone_message(ch, prof, message), MqttTopic::SingletoneFrequency(ch, prof) => singletone_frequency_message(ch, prof, message), MqttTopic::SingletoneAmplitude(ch, prof) => singletone_amplitude_message(ch, prof, message), @@ -97,6 +112,11 @@ impl MqttMux where SPI: Transfer { match command { MqttCommand::Switch(ch, state) => self.urukul.set_channel_switch(ch.into(), state), MqttCommand::Attenuation(ch, ampl) => self.urukul.set_channel_attenuation(ch, ampl), + MqttCommand::Clock(src, freq, div) => self.urukul.set_clock(src, freq, div), + MqttCommand::ClockSource(src) => self.urukul.set_clock_source(src), + MqttCommand::ClockFrequency(freq) => self.urukul.set_clock_frequency(freq), + MqttCommand::ClockDivision(div) => self.urukul.set_clock_division(div), + MqttCommand::SystemClock(ch, freq) => self.urukul.set_channel_sys_clk(ch, freq), MqttCommand::Singletone(ch, prof, freq, ampl, deg) => self.urukul.set_channel_single_tone_profile(ch, prof, freq, ampl, deg), MqttCommand::SingletoneFrequency(ch, prof, freq) => self.urukul.set_channel_single_tone_profile_frequency(ch, prof, freq), MqttCommand::SingletoneAmplitude(ch, prof, ampl) => self.urukul.set_channel_single_tone_profile_amplitude(ch, prof, ampl), @@ -152,6 +172,16 @@ fn select_profile<'a>(topic: &'a str) -> IResult<&'a str, u8> { )(topic) } +fn select_clock<'a>(topic: &'a str) -> IResult<&'a str, ()> { + value( + (), + preceded( + tag("Clock"), + topic_separator + ) + )(topic) +} + // Selection parser for Singletone // Note: This fucntion assumes singletone is not the most specific sub-topic fn select_singletone<'a>(topic: &'a str) -> IResult<&'a str, ()> { @@ -161,12 +191,6 @@ fn select_singletone<'a>(topic: &'a str) -> IResult<&'a str, ()> { )(topic) } -fn check_end_slice(message: &[u8]) -> IResult<&[u8], ()> { - not( - take(1_usize) - )(message) -} - // Read whitespace fn whitespace(message: &[u8]) -> IResult<&[u8], ()> { value((), take_while(is_space))(message) @@ -252,6 +276,175 @@ fn attenuation_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttComman )(message) } +// Parser for Clock Source Command Topic +fn clock_source<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { + all_consuming( + value( + MqttTopic::ClockSource, + preceded( + clock_source, + tag("Source") + ) + ) + )(topic) +} + +// Parser for Clock Source Command Message +fn clock_source_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + alt(( + value(MqttCommand::ClockSource(UrukulClockSource::OSC), tag_no_case("OSC")), + value(MqttCommand::ClockSource(UrukulClockSource::MMCX), tag_no_case("MMCX")), + value(MqttCommand::ClockSource(UrukulClockSource::SMA), tag_no_case("SMA")) + )) + )(message) +} + +// Parser for Clock Frequency Command Topic +fn clock_frequency<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { + all_consuming( + value( + MqttTopic::ClockFrequency, + preceded( + clock_source, + tag("Frequency") + ) + ) + )(topic) +} + +// Parser for Clock Frequency Command Message +fn clock_frequency_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + read_frequency, + |freq: f64| MqttCommand::ClockFrequency(freq) + ) + )(message) +} + +// Parser for Clock Division Command Topic +fn clock_division<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { + all_consuming( + value( + MqttTopic::ClockDivision, + preceded( + clock_source, + tag("Division") + ) + ) + )(topic) +} + +// Parser for Clock Division Command Message +fn clock_division_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + digit1, + |div: &[u8]| MqttCommand::ClockDivision( + u8::from_str_radix( + core::str::from_utf8(div).unwrap(), + 10 + ).unwrap() + ) + ) + )(message) +} + +// Parser for one-command master clock setup topic +fn clock<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { + all_consuming( + value( + MqttTopic::Clock, + tag("Clock") + ) + )(topic) +} + +// Parser for one-command master clock setup message +fn clock_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + delimited( + tag("{"), + tuple(( + preceded( + whitespace, + preceded( + tag("\"source\":"), + preceded( + whitespace, + terminated( + alt(( + value(UrukulClockSource::OSC, tag_no_case("OSC")), + value(UrukulClockSource::MMCX, tag_no_case("MMCX")), + value(UrukulClockSource::SMA, tag_no_case("SMA")) + )), + tag(",") + ) + ) + ) + ), + preceded( + whitespace, + preceded( + tag("\"frequency\":"), + preceded( + whitespace, + terminated( + read_frequency, + tag(",") + ) + ) + ) + ), + preceded( + whitespace, + preceded( + tag("\"division\":"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |div: &[u8]| u8::from_str_radix(core::str::from_utf8(div).unwrap(), 10) + ), + whitespace + ) + ) + ) + ) + )), + tag("}") + ), + |(src, freq, div): (UrukulClockSource, f64, u8)| MqttCommand::Clock(src, freq, div) + ) + )(message) +} + +// Topic parser for f_sys_clk of any channels +fn system_clock<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { + all_consuming( + map( + terminated( + select_channel, + tag("SystemClock") + ), + |channel: u8| MqttTopic::SystemClock(channel) + ) + )(topic) +} + +// Message parser for f_sys_clk of any channels +fn system_clock_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + read_frequency, + |freq: f64| MqttCommand::SystemClock(channel, freq) + ) + )(message) +} + // Parser for Singletone frequenct Command Topic fn singletone_frequency<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { all_consuming( @@ -364,28 +557,65 @@ fn singletone<'a>(topic: &'a str) -> IResult<&'a str, MqttTopic> { } // Parser for one-command singletone profile Command +// Using JSON like command structure +// Possible enhancement: further modularize parsing of all separate fields fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { all_consuming( map( tuple(( - read_frequency, preceded( - message_separator, - double - ), - preceded( - message_separator, - terminated( - double, - opt( + tag("{"), + preceded( + whitespace, + preceded( + tag("\"frequency\":"), preceded( whitespace, - tag_no_case("deg") + read_frequency + ) + ) + ) + ), + preceded( + tag(","), + preceded( + whitespace, + preceded( + tag("\"amplitude\":"), + preceded( + whitespace, + double + ) + ) + ) + ), + preceded( + tag(","), + preceded( + whitespace, + preceded( + tag("\"phase\":"), + preceded( + whitespace, + terminated( + double, + preceded( + opt( + preceded( + whitespace, + tag_no_case("deg") + ) + ), + preceded( + whitespace, + tag("}") + ) + ) + ) ) ) ) ) - )), |(freq, ampl, phase): (f64, f64, f64)| MqttCommand::Singletone(channel, profile, freq, ampl, phase) )