Compare commits
4 Commits
25f8363e54
...
7f00e5817d
Author | SHA1 | Date |
---|---|---|
occheung | 7f00e5817d | |
occheung | 9ea56ebde5 | |
occheung | d47f9b4655 | |
occheung | 883e821794 |
|
@ -244,11 +244,13 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"minimq",
|
"minimq",
|
||||||
"nb 1.0.0",
|
"nb 1.0.0",
|
||||||
|
"nom",
|
||||||
"panic-halt",
|
"panic-halt",
|
||||||
"panic-itm",
|
"panic-itm",
|
||||||
"scpi",
|
"scpi",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
"stm32h7xx-hal",
|
"stm32h7xx-hal",
|
||||||
|
"uom 0.29.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -425,6 +427,12 @@ version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimq"
|
name = "minimq"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -458,6 +466,16 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2178127478ae4ee9be7180bc9c3bffb6354dd7238400db567102f98c413a9f35"
|
checksum = "2178127478ae4ee9be7180bc9c3bffb6354dd7238400db567102f98c413a9f35"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "5.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
|
@ -601,7 +619,7 @@ dependencies = [
|
||||||
"lexical-core",
|
"lexical-core",
|
||||||
"libm",
|
"libm",
|
||||||
"scpi_derive",
|
"scpi_derive",
|
||||||
"uom",
|
"uom 0.28.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -780,6 +798,16 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uom"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -18,6 +18,7 @@ embedded-nal = "0.1.0"
|
||||||
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
||||||
heapless = "0.5.5"
|
heapless = "0.5.5"
|
||||||
arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] }
|
arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] }
|
||||||
|
nom = { version = "5.1.2", default-features = false, features = [] }
|
||||||
|
|
||||||
# Logging and Panicking
|
# Logging and Panicking
|
||||||
panic-itm = "0.4.1"
|
panic-itm = "0.4.1"
|
||||||
|
@ -32,17 +33,10 @@ branch = "issue-4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [ "build-info", "unit-frequency", "unit-angle" ]
|
features = [ "build-info", "unit-frequency", "unit-angle" ]
|
||||||
|
|
||||||
# [dependencies.uom]
|
[dependencies.uom]
|
||||||
# version = "0.29.0"
|
version = "0.29.0"
|
||||||
# default-features = false
|
default-features = false
|
||||||
# features = [
|
features = [ "autoconvert", "f32", "f64", "si" ]
|
||||||
# "autoconvert",
|
|
||||||
# "usize", "u8", "u16", "u32", "u64",
|
|
||||||
# "isize", "i8", "i16", "i32", "i64",
|
|
||||||
# "f32", "f64",
|
|
||||||
# "si",
|
|
||||||
# "try-from"
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# Use below SCPI dependency when need to modify SCPI fork offline
|
# Use below SCPI dependency when need to modify SCPI fork offline
|
||||||
# [dependencies.scpi]
|
# [dependencies.scpi]
|
||||||
|
|
135
src/dds.rs
135
src/dds.rs
|
@ -64,7 +64,7 @@ construct_bitmask!(DDSCFRMask; u32;
|
||||||
const WRITE_MASK :u8 = 0x00;
|
const WRITE_MASK :u8 = 0x00;
|
||||||
const READ_MASK :u8 = 0x80;
|
const READ_MASK :u8 = 0x80;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum RAMDestination {
|
pub enum RAMDestination {
|
||||||
Frequency = 0,
|
Frequency = 0,
|
||||||
Phase = 1,
|
Phase = 1,
|
||||||
|
@ -355,14 +355,9 @@ where
|
||||||
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
||||||
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
||||||
|
|
||||||
let resolutions :[u64; 3] = [1 << 32, 1 << 16, 1 << 14];
|
let ftw = self.frequency_to_ftw(f_out);
|
||||||
let ftw = ((resolutions[0] as f64) * f_out / self.f_sys_clk) as u32;
|
let pow = self.degree_to_pow(phase_offset);
|
||||||
let pow = ((resolutions[1] as f64) * phase_offset / 360.0) as u16;
|
let asf = self.amplitude_to_asf(amp_scale_factor);
|
||||||
let asf :u16 = if amp_scale_factor == 1.0 {
|
|
||||||
0x3FFF
|
|
||||||
} else {
|
|
||||||
((resolutions[2] as f64) * amp_scale_factor) as u16
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup configuration registers before writing single tone register
|
// Setup configuration registers before writing single tone register
|
||||||
self.enable_single_tone_configuration()?;
|
self.enable_single_tone_configuration()?;
|
||||||
|
@ -390,9 +385,7 @@ where
|
||||||
// Setup configuration registers before writing single tone register
|
// Setup configuration registers before writing single tone register
|
||||||
self.enable_single_tone_configuration()?;
|
self.enable_single_tone_configuration()?;
|
||||||
|
|
||||||
// Calculate frequency tuning work (FTW)
|
let ftw = self.frequency_to_ftw(f_out);
|
||||||
let f_res: u64 = 1 << 32;
|
|
||||||
let ftw = ((f_res as f64) * f_out / self.f_sys_clk) as u32;
|
|
||||||
|
|
||||||
// Read existing amplitude/phase data
|
// Read existing amplitude/phase data
|
||||||
let mut register: [u8; 8] = [0; 8];
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
@ -418,9 +411,7 @@ where
|
||||||
// Setup configuration registers before writing single tone register
|
// Setup configuration registers before writing single tone register
|
||||||
self.enable_single_tone_configuration()?;
|
self.enable_single_tone_configuration()?;
|
||||||
|
|
||||||
// Calculate phase offset work (POW)
|
let pow = self.degree_to_pow(phase_offset);
|
||||||
let phase_res: u64 = 1 << 16;
|
|
||||||
let pow = ((phase_res as f64) * phase_offset / 360.0) as u16;
|
|
||||||
|
|
||||||
// Read existing amplitude/frequency data
|
// Read existing amplitude/frequency data
|
||||||
let mut register: [u8; 8] = [0; 8];
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
@ -445,12 +436,7 @@ where
|
||||||
self.enable_single_tone_configuration()?;
|
self.enable_single_tone_configuration()?;
|
||||||
|
|
||||||
// Calculate amplitude_scale_factor (ASF)
|
// Calculate amplitude_scale_factor (ASF)
|
||||||
let amp_res: u64 = 1 << 14;
|
let asf = self.amplitude_to_asf(amp_scale_factor);
|
||||||
let asf :u16 = if amp_scale_factor == 1.0 {
|
|
||||||
0x3FFF
|
|
||||||
} else {
|
|
||||||
((amp_res as f64) * amp_scale_factor) as u16
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read existing frequency/phase data
|
// Read existing frequency/phase data
|
||||||
let mut register: [u8; 8] = [0; 8];
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
@ -496,16 +482,91 @@ where
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configure a RAM mode profile, but with RAM data generated by a closure
|
||||||
|
*/
|
||||||
|
pub fn set_ram_profile_with_closure<F>(&mut self, profile: u8, start_addr: u16,
|
||||||
|
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
||||||
|
op_mode: RAMOperationMode, playback_rate: f64, f: F) -> Result<(), Error<E>>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> ArrayVec::<[f64; 2048]>
|
||||||
|
{
|
||||||
|
// Check the legality of the profile setup
|
||||||
|
assert!(profile < 7);
|
||||||
|
assert!(start_addr < 1024);
|
||||||
|
let mut vec = f();
|
||||||
|
if (ram_dst != RAMDestination::Polar && ((vec.len() as u16) + start_addr) < 1024) ||
|
||||||
|
((((vec.len()/2) as u16) + start_addr) < 1024) {
|
||||||
|
return Err(Error::DDSRAMError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Convert argument into bytes for RAM
|
||||||
|
let mut byte_vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
||||||
|
match ram_dst {
|
||||||
|
RAMDestination::Frequency => {
|
||||||
|
for freq in vec.into_iter() {
|
||||||
|
let ftw = self.frequency_to_ftw(freq);
|
||||||
|
byte_vec.push(((ftw >> 24) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((ftw >> 16) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((ftw >> 8) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((ftw >> 0) & 0xFF) as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RAMDestination::Phase => {
|
||||||
|
for deg in vec.into_iter() {
|
||||||
|
let pow = self.degree_to_pow(deg);
|
||||||
|
byte_vec.push(((pow >> 8) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((pow >> 0) & 0xFF) as u8);
|
||||||
|
byte_vec.push(0);
|
||||||
|
byte_vec.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RAMDestination::Amplitude => {
|
||||||
|
for amp in vec.into_iter() {
|
||||||
|
let asf = self.amplitude_to_asf(amp);
|
||||||
|
byte_vec.push(((asf >> 8) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((asf << 2) & 0xFC) as u8);
|
||||||
|
byte_vec.push(0);
|
||||||
|
byte_vec.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RAMDestination::Polar => {
|
||||||
|
// Alternate phase and amplitude
|
||||||
|
let mut phase = true;
|
||||||
|
for pol in vec.into_iter() {
|
||||||
|
if phase {
|
||||||
|
let pow = self.degree_to_pow(pol);
|
||||||
|
byte_vec.push(((pow >> 8) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((pow >> 0) & 0xFF) as u8);
|
||||||
|
phase = false;
|
||||||
|
} else {
|
||||||
|
let asf = self.amplitude_to_asf(pol);
|
||||||
|
byte_vec.push(((asf >> 8) & 0xFF) as u8);
|
||||||
|
byte_vec.push(((asf << 2) & 0xFC) as u8);
|
||||||
|
phase = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if phase {
|
||||||
|
return Err(Error::DDSRAMError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let data = byte_vec.as_slice();
|
||||||
|
self.set_ram_profile(profile, start_addr, start_addr + (((data.len()/4) - 1) as u16),
|
||||||
|
ram_dst, no_dwell_high, zero_crossing, op_mode, playback_rate, data)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configure a RAM mode profile
|
* Configure a RAM mode profile
|
||||||
*
|
* TODO: Possibly remove redundant end_addr parameter.
|
||||||
|
* This can be inferred by start_addr and data size.
|
||||||
*/
|
*/
|
||||||
pub fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
pub fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
||||||
op_mode: RAMOperationMode, playback_rate: f64, data: &[u8]
|
op_mode: RAMOperationMode, playback_rate: f64, data: &[u8]
|
||||||
) -> Result<(), Error<E>> {
|
) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
// Check the legality of this setup
|
// Check the legality of the profile setup
|
||||||
assert!(profile < 7);
|
assert!(profile < 7);
|
||||||
assert!(end_addr >= start_addr);
|
assert!(end_addr >= start_addr);
|
||||||
assert!(end_addr < 1024);
|
assert!(end_addr < 1024);
|
||||||
|
@ -513,8 +574,8 @@ where
|
||||||
|
|
||||||
// Calculate address step rate, and check legality
|
// Calculate address step rate, and check legality
|
||||||
let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64;
|
let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64;
|
||||||
if (step_rate == 0 || step_rate > 0xFFFF) {
|
if step_rate == 0 || step_rate > 0xFFFF {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::DDSRAMError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before setting up RAM, disable RAM_ENABLE
|
// Before setting up RAM, disable RAM_ENABLE
|
||||||
|
@ -533,7 +594,7 @@ where
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
// Temporarily disable RAM mode while accessing into RAM
|
// Temporarily disable RAM mode while accessing into RAM
|
||||||
self.disable_ram_configuration();
|
self.disable_ram_configuration()?;
|
||||||
self.write_ram(data)?;
|
self.write_ram(data)?;
|
||||||
|
|
||||||
// Properly configure start_addr and end_addr
|
// Properly configure start_addr and end_addr
|
||||||
|
@ -541,8 +602,26 @@ where
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to write data in RAM
|
// Calculate ftw (frequency tuning word)
|
||||||
// Need address range for data size check
|
fn frequency_to_ftw(&mut self, f_out: f64) -> u32 {
|
||||||
|
let f_res: u64 = 1 << 32;
|
||||||
|
((f_res as f64) * f_out / self.f_sys_clk) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate pow (Phase Offset Word)
|
||||||
|
fn degree_to_pow(&mut self, phase_offset: f64) -> u16 {
|
||||||
|
// Calculate phase offset word (POW)
|
||||||
|
let phase_res: u64 = 1 << 16;
|
||||||
|
((phase_res as f64) * phase_offset / 360.0) as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate asf (Amplitude Scale Factor)
|
||||||
|
fn amplitude_to_asf(&mut self, amplitude: f64) -> u16 {
|
||||||
|
let amp_res: u64 = 0x3FFF;
|
||||||
|
((amp_res as f64) * amplitude) as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data in RAM
|
||||||
fn write_ram(&mut self, data: &[u8]) -> Result<(), Error<E>> {
|
fn write_ram(&mut self, data: &[u8]) -> Result<(), Error<E>> {
|
||||||
let mut vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
let mut vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
||||||
vec.try_push(0x16)
|
vec.try_push(0x16)
|
||||||
|
|
57
src/lib.rs
57
src/lib.rs
|
@ -43,10 +43,11 @@ pub enum Error<E> {
|
||||||
DDSCLKError,
|
DDSCLKError,
|
||||||
DDSRAMError,
|
DDSRAMError,
|
||||||
ParameterError,
|
ParameterError,
|
||||||
|
MqttTopicError,
|
||||||
MqttCommandError,
|
MqttCommandError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ClockSource {
|
pub enum ClockSource {
|
||||||
OSC,
|
OSC,
|
||||||
SMA,
|
SMA,
|
||||||
|
@ -175,7 +176,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clock_source(&mut self, source: ClockSource, frequency: f64) -> Result<(), Error<E>> {
|
fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error<E>> {
|
||||||
|
// 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<E>> {
|
||||||
// Change clock source through configuration register
|
// Change clock source through configuration register
|
||||||
match source {
|
match source {
|
||||||
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
||||||
|
@ -189,19 +201,15 @@ where
|
||||||
ClockSource::SMA => self.config_register.set_configurations(&mut [
|
ClockSource::SMA => self.config_register.set_configurations(&mut [
|
||||||
(CFGMask::CLK_SEL0, 1),
|
(CFGMask::CLK_SEL0, 1),
|
||||||
]),
|
]),
|
||||||
}?;
|
}.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
// Save the new master clock frequency
|
fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error<E>> {
|
||||||
|
// Update master clock frequency
|
||||||
self.f_master_clk = 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
|
// Update all DDS f_ref_clk
|
||||||
for dds_channel in 0..4 {
|
self.set_dds_ref_clk()
|
||||||
self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
|
fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
|
||||||
|
@ -218,8 +226,12 @@ where
|
||||||
_ => Err(Error::ParameterError),
|
_ => Err(Error::ParameterError),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
self.set_dds_ref_clk()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dds_ref_clk(&mut self) -> Result<(), Error<E>> {
|
||||||
// Calculate reference clock frequency after clock division from configuration register
|
// 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
|
// Update all DDS chips on reference clock frequency
|
||||||
for dds_channel in 0..4 {
|
for dds_channel in 0..4 {
|
||||||
|
@ -229,28 +241,47 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
||||||
|
if channel >= 4 || attenuation < 0.0 || attenuation > 31.5 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.attenuator.set_channel_attenuation(channel, attenuation)
|
self.attenuator.set_channel_attenuation(channel, attenuation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_profile(&mut self, profile: u8) -> Result<(), Error<E>> {
|
fn set_profile(&mut self, profile: u8) -> Result<(), Error<E>> {
|
||||||
|
if profile >= 8 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.config_register.set_configurations(&mut [
|
self.config_register.set_configurations(&mut [
|
||||||
(CFGMask::PROFILE, profile.into())
|
(CFGMask::PROFILE, profile.into())
|
||||||
]).map(|_| ())
|
]).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||||
|
if channel >= 4 || profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||||
|
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
|
fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
|
||||||
|
if channel >= 4 || profile >= 8 || frequency < 0.0 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
|
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> {
|
fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> {
|
||||||
|
if channel >= 4 || profile >= 8 || phase >= 360.0 || phase < 0.0 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
|
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> {
|
fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> {
|
||||||
|
if channel >= 4 || profile >= 8 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
|
return Err(Error::ParameterError);
|
||||||
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
568
src/mqtt_mux.rs
568
src/mqtt_mux.rs
|
@ -1,4 +1,17 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use nom::IResult;
|
||||||
|
use nom::combinator::{value, map, map_res, not, opt, all_consuming};
|
||||||
|
use nom::sequence::{terminated, preceded, pair, delimited, tuple};
|
||||||
|
use nom::bytes::complete::{take, tag, tag_no_case, take_while};
|
||||||
|
use nom::character::complete::digit1;
|
||||||
|
use nom::character::is_space;
|
||||||
|
use nom::branch::alt;
|
||||||
|
use nom::number::complete::{float, double};
|
||||||
|
|
||||||
|
use uom::si::f64::Frequency;
|
||||||
|
use uom::si::frequency::{hertz, kilohertz, megahertz, gigahertz};
|
||||||
|
|
||||||
|
use arrayvec::ArrayVec;
|
||||||
|
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
|
@ -7,27 +20,41 @@ use crate::ClockSource::*;
|
||||||
use crate::Urukul;
|
use crate::Urukul;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MqttCommandType {
|
pub enum MqttTopic {
|
||||||
// Urukul/Control/Clock/Source
|
Reset,
|
||||||
ClockSource(UrukulClockSource),
|
Switch(u8),
|
||||||
// Urukul/Control/Clock/Division
|
Attenuation(u8),
|
||||||
ClockDivision(u8),
|
Clock,
|
||||||
// Urukul/Control/ChannelX/Switch
|
ClockSource,
|
||||||
Switch(u8, bool),
|
ClockFrequency,
|
||||||
// Urukul/Control/ChannelX/Attenuation
|
ClockDivision,
|
||||||
Attenuation(u8, f32),
|
SystemClock(u8),
|
||||||
// Urukul/Control/ChannelX/SystemClock
|
Singletone(u8, u8),
|
||||||
SystemClock(u8, f64),
|
SingletoneFrequency(u8, u8),
|
||||||
// Urukul/Control/ChannelX/ProfileY/Frequency
|
SingletoneAmplitude(u8, u8),
|
||||||
SingleToneFrequency(u8, u8, f64),
|
SingletonePhase(u8, u8),
|
||||||
// Urukul/Control/ChannelX/ProfileY/Amplitude
|
Profile,
|
||||||
SingleToneAmplitude(u8, u8, f64),
|
|
||||||
// Urukul/Control/ChannelX/ProfileY/Phase
|
|
||||||
SingleTonePhase(u8, u8, f64),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::mqtt_mux::MqttCommandType::*;
|
// Prossible change: Make this enum public to all comm protocol (if any)
|
||||||
|
// Such that Urukul accepts the enum directly
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MqttCommand {
|
||||||
|
Reset,
|
||||||
|
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),
|
||||||
|
SingletonePhase(u8, u8, f64),
|
||||||
|
Profile(u8)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MqttMux<SPI> {
|
pub struct MqttMux<SPI> {
|
||||||
urukul: Urukul<SPI>
|
urukul: Urukul<SPI>
|
||||||
|
@ -40,17 +67,39 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_command(&mut self, topic: &str, message: &[u8]) -> Result<(), Error<E>> {
|
pub fn process_mqtt(&mut self, topic: &str, message: &[u8]) -> Result<(), Error<E>> {
|
||||||
let command = self.parse(topic, message)?;
|
let header = self.parse_topic(topic)
|
||||||
|
.map_err(|_| Error::MqttTopicError)?;
|
||||||
|
info!("{:?}", header);
|
||||||
|
let (_, command) = self.parse_message(header, message)
|
||||||
|
.map_err(|_| Error::MqttCommandError)?;
|
||||||
|
info!("{:?}", command);
|
||||||
self.execute(command)
|
self.execute(command)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
// MQTT command are not case tolerant
|
fn parse_header<'a>(&mut self, topic: &'a str) -> IResult<&'a str, MqttTopic> {
|
||||||
// If the command differs by case, space or delimiter, it is a wrong command
|
preceded(
|
||||||
// A starting forward slash ("/") is acceptable, as per MQTT standard
|
alt((
|
||||||
// Topic should contain the appropriate command header
|
tag("Urukul/Control/"),
|
||||||
// Message should provide the parameter
|
tag("/Urukul/Control/")
|
||||||
fn parse(&mut self, topic: &str, message: &[u8]) -> Result<MqttCommandType, Error<E>> {
|
)),
|
||||||
|
alt((
|
||||||
|
switch,
|
||||||
|
attenuation,
|
||||||
|
clock,
|
||||||
|
clock_source,
|
||||||
|
clock_frequency,
|
||||||
|
clock_division,
|
||||||
|
singletone,
|
||||||
|
singletone_frequency,
|
||||||
|
singletone_amplitude,
|
||||||
|
singletone_phase,
|
||||||
|
profile
|
||||||
|
))
|
||||||
|
)(topic)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
fn parse_topic<'a>(&mut self, topic: &'a str) -> Result<MqttTopic, Error<E>> {
|
||||||
let mut assigned_channel = false;
|
let mut assigned_channel = false;
|
||||||
let mut assigned_profile = false;
|
let mut assigned_profile = false;
|
||||||
let mut channel :u8 = 0;
|
let mut channel :u8 = 0;
|
||||||
|
@ -92,7 +141,7 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
.ok_or(Error::MqttCommandError)?;
|
.ok_or(Error::MqttCommandError)?;
|
||||||
},
|
},
|
||||||
|
|
||||||
_ if header.starts_with("Profile") => {
|
_ if (header.starts_with("Profile") && assigned_channel) => {
|
||||||
// MQTT command should only mention profile once appropriately
|
// MQTT command should only mention profile once appropriately
|
||||||
if assigned_profile {
|
if assigned_profile {
|
||||||
return Err(Error::MqttCommandError);
|
return Err(Error::MqttCommandError);
|
||||||
|
@ -118,27 +167,53 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
.ok_or(Error::MqttCommandError)?;
|
.ok_or(Error::MqttCommandError)?;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Reset" => {
|
||||||
|
if assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError);
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Reset);
|
||||||
|
},
|
||||||
|
|
||||||
|
"Switch" => {
|
||||||
|
// Switch is a channel specific topic
|
||||||
|
if !(assigned_channel && !assigned_profile) {
|
||||||
|
return Err(Error::MqttCommandError);
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Switch(channel));
|
||||||
|
},
|
||||||
|
|
||||||
|
"Attenuation" => {
|
||||||
|
// Attenuation is a channel specific topic
|
||||||
|
if !(assigned_channel && !assigned_profile) {
|
||||||
|
return Err(Error::MqttCommandError);
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Attenuation(channel));
|
||||||
|
},
|
||||||
|
|
||||||
|
"Clock" => {
|
||||||
|
if assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError);
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Clock);
|
||||||
|
},
|
||||||
|
|
||||||
"Clock/Source" => {
|
"Clock/Source" => {
|
||||||
// Clock/Source refers to the Urukul clock source
|
// Clock/Source refers to the Urukul clock source
|
||||||
// It should be common for all channels and profiles
|
// It should be common for all channels and profiles
|
||||||
if assigned_channel || assigned_profile {
|
if assigned_channel || assigned_profile {
|
||||||
return Err(Error::MqttCommandError);
|
return Err(Error::MqttCommandError);
|
||||||
}
|
}
|
||||||
let source_string = core::str::from_utf8(message).unwrap();
|
return Ok(MqttTopic::ClockSource);
|
||||||
|
},
|
||||||
|
|
||||||
return match source_string {
|
"Clock/Frequency" => {
|
||||||
_ if source_string.eq_ignore_ascii_case("OSC") => {
|
// Clock/Frequency refers to the Urukul clock frequency
|
||||||
Ok(ClockSource(OSC))
|
// It should be common for all channels and profiles
|
||||||
},
|
if assigned_channel || assigned_profile {
|
||||||
_ if source_string.eq_ignore_ascii_case("SMA") => {
|
return Err(Error::MqttCommandError);
|
||||||
Ok(ClockSource(SMA))
|
}
|
||||||
},
|
return Ok(MqttTopic::ClockFrequency);
|
||||||
_ if source_string.eq_ignore_ascii_case("MMCX") => {
|
},
|
||||||
Ok(ClockSource(MMCX))
|
|
||||||
},
|
|
||||||
_ => Err(Error::MqttCommandError),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
"Clock/Division" => {
|
"Clock/Division" => {
|
||||||
// Clock/Division refers to the Urukul clock division
|
// Clock/Division refers to the Urukul clock division
|
||||||
|
@ -146,52 +221,391 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
if assigned_channel || assigned_profile {
|
if assigned_channel || assigned_profile {
|
||||||
return Err(Error::MqttCommandError);
|
return Err(Error::MqttCommandError);
|
||||||
}
|
}
|
||||||
|
return Ok(MqttTopic::ClockDivision);
|
||||||
|
},
|
||||||
|
|
||||||
let division = u8::from_str_radix(core::str::from_utf8(message).unwrap(), 10)
|
"SystemClock" => {
|
||||||
.map_or_else(
|
|
||||||
|_| Err(Error::MqttCommandError),
|
|
||||||
|div| if div == 1 || div == 2 || div == 4 {
|
|
||||||
Ok(div)
|
|
||||||
} else {
|
|
||||||
Err(Error::MqttCommandError)
|
|
||||||
})?;
|
|
||||||
return Ok(ClockDivision(division));
|
|
||||||
}
|
|
||||||
|
|
||||||
"Switch" => {
|
|
||||||
// Switch is a channel specific topic
|
|
||||||
if !(assigned_channel && !assigned_profile) {
|
if !(assigned_channel && !assigned_profile) {
|
||||||
return Err(Error::MqttCommandError);
|
return Err(Error::MqttCommandError);
|
||||||
}
|
}
|
||||||
|
return Ok(MqttTopic::SystemClock(channel));
|
||||||
|
}
|
||||||
|
|
||||||
let switch_string = core::str::from_utf8(message).unwrap();
|
"Singletone" => {
|
||||||
|
if !(assigned_channel && assigned_profile) {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Singletone(channel, profile));
|
||||||
|
}
|
||||||
|
|
||||||
return match switch_string {
|
"Singletone/Frequency" => {
|
||||||
_ if switch_string.eq_ignore_ascii_case("on") => {
|
if !(assigned_channel && assigned_profile) {
|
||||||
Ok(Switch(channel, true))
|
return Err(Error::MqttCommandError)
|
||||||
},
|
}
|
||||||
_ if switch_string.eq_ignore_ascii_case("off") => {
|
return Ok(MqttTopic::SingletoneFrequency(channel, profile));
|
||||||
Ok(Switch(channel, false))
|
}
|
||||||
},
|
|
||||||
_ => Err(Error::MqttCommandError),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: Cover all commands
|
"Singletone/Amplitude" => {
|
||||||
|
if !(assigned_channel && assigned_profile) {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::SingletoneAmplitude(channel, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
"Singletone/Phase" => {
|
||||||
|
if !(assigned_channel && assigned_profile) {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::SingletonePhase(channel, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
"Profile" => {
|
||||||
|
if assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::Profile);
|
||||||
|
}
|
||||||
|
|
||||||
_ => return Err(Error::MqttCommandError),
|
_ => return Err(Error::MqttCommandError),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
fn parse_message<'a>(&mut self, topic: MqttTopic, message: &'a [u8]) -> IResult<&'a [u8], MqttCommand> {
|
||||||
// Only need to sort the command enum
|
match topic {
|
||||||
// Obviously. This is what a MUX does
|
MqttTopic::Reset => Ok((message, MqttCommand::Reset)),
|
||||||
fn execute(&mut self, command_type: MqttCommandType) -> Result<(), Error<E>> {
|
MqttTopic::Switch(ch) => switch_message(ch, message),
|
||||||
info!("{:?}", command_type);
|
MqttTopic::Attenuation(ch) => attenuation_message(ch, message),
|
||||||
match command_type {
|
MqttTopic::Clock => clock_message(message),
|
||||||
Switch(channel, status) => self.urukul.set_channel_switch(channel as u32, status),
|
MqttTopic::ClockSource => clock_source_message(message),
|
||||||
_ => Ok(())
|
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),
|
||||||
|
MqttTopic::SingletonePhase(ch, prof) => singletone_phase_message(ch, prof, message),
|
||||||
|
MqttTopic::Profile => profile_message(message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn execute(&mut self, command: MqttCommand) -> Result<(), Error<E>> {
|
||||||
|
match command {
|
||||||
|
MqttCommand::Reset => self.urukul.reset(),
|
||||||
|
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),
|
||||||
|
MqttCommand::SingletonePhase(ch, prof, deg) => self.urukul.set_channel_single_tone_profile_phase(ch, prof, deg),
|
||||||
|
MqttCommand::Profile(prof) => self.urukul.set_profile(prof),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topic separator parser
|
||||||
|
fn topic_separator<'a>(topic: &'a str) -> IResult<&'a str, ()> {
|
||||||
|
value((), tag("/"))(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message separator parser
|
||||||
|
fn message_separator(message: &[u8]) -> IResult<&[u8], ()> {
|
||||||
|
value(
|
||||||
|
(),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
preceded(
|
||||||
|
tag("/"),
|
||||||
|
whitespace
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read whitespace
|
||||||
|
fn whitespace(message: &[u8]) -> IResult<&[u8], ()> {
|
||||||
|
value((), take_while(is_space))(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader for uom instances
|
||||||
|
fn read_frequency(message: &[u8]) -> IResult<&[u8], f64> {
|
||||||
|
map(
|
||||||
|
pair(
|
||||||
|
double,
|
||||||
|
opt(
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
alt((
|
||||||
|
value(1.0, tag_no_case("hz")),
|
||||||
|
value(1_000.0, tag_no_case("khz")),
|
||||||
|
value(1_000_000.0, tag_no_case("mhz")),
|
||||||
|
value(1_000_000_000.0, tag_no_case("ghz"))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|(freq, unit): (f64, Option<f64>)| {
|
||||||
|
freq * unit.map_or(1.0, |mul| mul)
|
||||||
|
}
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser for Switch Command Message
|
||||||
|
fn switch_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
alt((
|
||||||
|
value(true, tag("on")),
|
||||||
|
value(false, tag("off"))
|
||||||
|
)),
|
||||||
|
|switch| MqttCommand::Switch(channel, switch)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser for Attenuation Command Message
|
||||||
|
fn attenuation_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
terminated(
|
||||||
|
float,
|
||||||
|
opt(
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
tag_no_case("db")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|att: f32| MqttCommand::Attenuation(channel, att)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 frequency Command Message
|
||||||
|
fn singletone_frequency_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
read_frequency,
|
||||||
|
|freq: f64| MqttCommand::SingletoneFrequency(channel, profile, freq)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser for Singletone AMplitude Command Message
|
||||||
|
fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
double,
|
||||||
|
|ampl: f64| MqttCommand::SingletoneAmplitude(channel, profile, ampl)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser for Phase Command Message
|
||||||
|
fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
terminated(
|
||||||
|
double,
|
||||||
|
opt(
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
tag_no_case("deg")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|deg: f64| MqttCommand::SingletonePhase(channel, profile, deg)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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((
|
||||||
|
preceded(
|
||||||
|
tag("{"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
preceded(
|
||||||
|
tag("\"frequency\":"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profile_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
digit1,
|
||||||
|
|num: &[u8]| {
|
||||||
|
MqttCommand::Profile(
|
||||||
|
u8::from_str_radix(core::str::from_utf8(num).unwrap(), 10).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue