Adding initial streaming implementation
This commit is contained in:
parent
d8cc3c74d9
commit
21ca8e1c8f
|
@ -548,6 +548,23 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66acf3cf8ab62785852a0f67ae3bfcd38324da6561e52e7f4a049ca555c6d55e"
|
||||||
|
dependencies = [
|
||||||
|
"heapless 0.6.1",
|
||||||
|
"postcard-cobs",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard-cobs"
|
||||||
|
version = "0.1.5-pre"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
@ -772,6 +789,7 @@ dependencies = [
|
||||||
"nb 1.0.0",
|
"nb 1.0.0",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"paste",
|
"paste",
|
||||||
|
"postcard",
|
||||||
"rtt-logger",
|
"rtt-logger",
|
||||||
"rtt-target",
|
"rtt-target",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -45,6 +45,7 @@ ad9959 = { path = "ad9959" }
|
||||||
miniconf = "0.1.0"
|
miniconf = "0.1.0"
|
||||||
shared-bus = {version = "0.2.2", features = ["cortex-m"] }
|
shared-bus = {version = "0.2.2", features = ["cortex-m"] }
|
||||||
serde-json-core = "0.3"
|
serde-json-core = "0.3"
|
||||||
|
postcard = "0.6"
|
||||||
|
|
||||||
[dependencies.rtt-logger]
|
[dependencies.rtt-logger]
|
||||||
git = "https://github.com/quartiq/rtt-logger.git"
|
git = "https://github.com/quartiq/rtt-logger.git"
|
||||||
|
|
|
@ -13,7 +13,9 @@ use hardware::{
|
||||||
DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1,
|
DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1,
|
||||||
};
|
};
|
||||||
|
|
||||||
use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState};
|
use net::{
|
||||||
|
BlockGenerator, NetworkUsers, Telemetry, TelemetryBuffer, UpdateState,
|
||||||
|
};
|
||||||
|
|
||||||
const SCALE: f32 = i16::MAX as _;
|
const SCALE: f32 = i16::MAX as _;
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ const APP: () = {
|
||||||
adcs: (Adc0Input, Adc1Input),
|
adcs: (Adc0Input, Adc1Input),
|
||||||
dacs: (Dac0Output, Dac1Output),
|
dacs: (Dac0Output, Dac1Output),
|
||||||
network: NetworkUsers<Settings, Telemetry>,
|
network: NetworkUsers<Settings, Telemetry>,
|
||||||
|
generator: BlockGenerator,
|
||||||
|
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
telemetry: TelemetryBuffer,
|
telemetry: TelemetryBuffer,
|
||||||
|
@ -71,7 +74,7 @@ const APP: () = {
|
||||||
// Configure the microcontroller
|
// Configure the microcontroller
|
||||||
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
||||||
|
|
||||||
let network = NetworkUsers::new(
|
let mut network = NetworkUsers::new(
|
||||||
stabilizer.net.stack,
|
stabilizer.net.stack,
|
||||||
stabilizer.net.phy,
|
stabilizer.net.phy,
|
||||||
stabilizer.cycle_counter,
|
stabilizer.cycle_counter,
|
||||||
|
@ -79,6 +82,11 @@ const APP: () = {
|
||||||
stabilizer.net.mac_address,
|
stabilizer.net.mac_address,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: Remove unwrap.
|
||||||
|
let remote: smoltcp_nal::embedded_nal::SocketAddr =
|
||||||
|
"10.35.16.10:1111".parse().unwrap();
|
||||||
|
let generator = network.enable_streaming(remote.into());
|
||||||
|
|
||||||
// Spawn a settings update for default settings.
|
// Spawn a settings update for default settings.
|
||||||
c.spawn.settings_update().unwrap();
|
c.spawn.settings_update().unwrap();
|
||||||
c.spawn.telemetry().unwrap();
|
c.spawn.telemetry().unwrap();
|
||||||
|
@ -96,6 +104,7 @@ const APP: () = {
|
||||||
afes: stabilizer.afes,
|
afes: stabilizer.afes,
|
||||||
adcs: stabilizer.adcs,
|
adcs: stabilizer.adcs,
|
||||||
dacs: stabilizer.dacs,
|
dacs: stabilizer.dacs,
|
||||||
|
generator,
|
||||||
network,
|
network,
|
||||||
digital_inputs: stabilizer.digital_inputs,
|
digital_inputs: stabilizer.digital_inputs,
|
||||||
telemetry: net::TelemetryBuffer::default(),
|
telemetry: net::TelemetryBuffer::default(),
|
||||||
|
@ -119,7 +128,7 @@ const APP: () = {
|
||||||
///
|
///
|
||||||
/// Because the ADC and DAC operate at the same rate, these two constraints actually implement
|
/// Because the ADC and DAC operate at the same rate, these two constraints actually implement
|
||||||
/// the same time bounds, meeting one also means the other is also met.
|
/// the same time bounds, meeting one also means the other is also met.
|
||||||
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry, generator], priority=2)]
|
||||||
fn process(c: process::Context) {
|
fn process(c: process::Context) {
|
||||||
let adc_samples = [
|
let adc_samples = [
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
c.resources.adcs.0.acquire_buffer(),
|
||||||
|
@ -157,6 +166,9 @@ const APP: () = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stream the data.
|
||||||
|
c.resources.generator.send(&adc_samples, &dac_samples);
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
c.resources.telemetry.adcs =
|
||||||
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
use core::borrow::BorrowMut;
|
||||||
|
use heapless::{
|
||||||
|
spsc::{Consumer, Producer, Queue},
|
||||||
|
Vec,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use smoltcp_nal::embedded_nal::{Mode, SocketAddr, TcpStack};
|
||||||
|
|
||||||
|
use super::NetworkReference;
|
||||||
|
use crate::hardware::design_parameters::SAMPLE_BUFFER_SIZE;
|
||||||
|
|
||||||
|
// The number of data blocks that we will buffer in the queue.
|
||||||
|
type BlockBufferSize = heapless::consts::U10;
|
||||||
|
|
||||||
|
pub fn setup_streaming(
|
||||||
|
stack: NetworkReference,
|
||||||
|
) -> (BlockGenerator, DataStream) {
|
||||||
|
let queue = cortex_m::singleton!(: Queue<AdcDacData, BlockBufferSize> = Queue::new()).unwrap();
|
||||||
|
|
||||||
|
let (producer, consumer) = queue.split();
|
||||||
|
|
||||||
|
let generator = BlockGenerator::new(producer);
|
||||||
|
|
||||||
|
let stream = DataStream::new(stack, consumer);
|
||||||
|
|
||||||
|
(generator, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AdcDacData {
|
||||||
|
block_id: u32,
|
||||||
|
adcs: [[u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
dacs: [[u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BlockGenerator {
|
||||||
|
queue: Producer<'static, AdcDacData, BlockBufferSize>,
|
||||||
|
current_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockGenerator {
|
||||||
|
pub fn new(queue: Producer<'static, AdcDacData, BlockBufferSize>) -> Self {
|
||||||
|
Self {
|
||||||
|
queue,
|
||||||
|
current_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(
|
||||||
|
&mut self,
|
||||||
|
adcs: &[&[u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
dacs: &[&mut [u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
) {
|
||||||
|
let block = AdcDacData {
|
||||||
|
block_id: self.current_id,
|
||||||
|
adcs: [*adcs[0], *adcs[1]],
|
||||||
|
dacs: [*dacs[0], *dacs[1]],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.current_id = self.current_id.wrapping_add(1);
|
||||||
|
|
||||||
|
// We perform best-effort enqueueing of the data block.
|
||||||
|
self.queue.enqueue(block).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataStream {
|
||||||
|
stack: NetworkReference,
|
||||||
|
socket: Option<<NetworkReference as TcpStack>::TcpSocket>,
|
||||||
|
queue: Consumer<'static, AdcDacData, BlockBufferSize>,
|
||||||
|
current_index: u32,
|
||||||
|
remote: Option<SocketAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct DataBlock {
|
||||||
|
block_id: u32,
|
||||||
|
block_size: usize,
|
||||||
|
adcs: [[u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
dacs: [[u16; SAMPLE_BUFFER_SIZE]; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataStream {
|
||||||
|
pub fn new(
|
||||||
|
stack: NetworkReference,
|
||||||
|
consumer: Consumer<'static, AdcDacData, BlockBufferSize>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
stack,
|
||||||
|
socket: None,
|
||||||
|
current_index: 0,
|
||||||
|
remote: None,
|
||||||
|
queue: consumer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&mut self, remote: SocketAddr) -> Result<(), ()> {
|
||||||
|
if self.socket.is_some() {
|
||||||
|
// Note(unwrap): We guarantee that the socket is available above.
|
||||||
|
let socket = self.socket.take().unwrap();
|
||||||
|
self.stack.close(socket).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let socket =
|
||||||
|
self.stack
|
||||||
|
.open(Mode::NonBlocking)
|
||||||
|
.map_err(|err| match err {
|
||||||
|
<NetworkReference as TcpStack>::Error::NoIpAddress => (),
|
||||||
|
other => {
|
||||||
|
log::info!("Network Error: {:?}", other);
|
||||||
|
()
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// TODO: How should we handle a connection failure?
|
||||||
|
let socket = self.stack.connect(socket, remote).unwrap();
|
||||||
|
|
||||||
|
// Note(unwrap): The socket will be empty before we replace it.
|
||||||
|
self.socket.replace(socket);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_remote(&mut self, remote: SocketAddr) {
|
||||||
|
// If the remote is identical to what we already have, do nothing.
|
||||||
|
if let Some(current_remote) = self.remote {
|
||||||
|
if current_remote == remote {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the new remote connection.
|
||||||
|
self.open(remote).ok();
|
||||||
|
self.remote = Some(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self) {
|
||||||
|
while let Some(data) = self.queue.dequeue() {
|
||||||
|
// If there's no socket available, try to connect to our remote.
|
||||||
|
if self.socket.is_none() && self.remote.is_some() {
|
||||||
|
// If we still can't open the remote, continue.
|
||||||
|
if self.open(self.remote.unwrap()).is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = DataBlock {
|
||||||
|
adcs: data.adcs,
|
||||||
|
dacs: data.dacs,
|
||||||
|
block_id: data.block_id,
|
||||||
|
block_size: SAMPLE_BUFFER_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increment the current block index.
|
||||||
|
self.current_index = self.current_index.wrapping_add(1);
|
||||||
|
|
||||||
|
// Serialize the datablock.
|
||||||
|
// TODO: Do we want to packetize the data block as well?
|
||||||
|
let data: Vec<u8, heapless::consts::U256> =
|
||||||
|
postcard::to_vec(&block).unwrap();
|
||||||
|
|
||||||
|
let mut socket = self.socket.borrow_mut().unwrap();
|
||||||
|
|
||||||
|
// Transmit the data block.
|
||||||
|
// TODO: How should we handle partial packet transmission?
|
||||||
|
match self.stack.write(&mut socket, &data) {
|
||||||
|
Ok(len) => assert!(len == data.len()),
|
||||||
|
_ => info!("Dropping packet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,18 +11,23 @@ use serde::Serialize;
|
||||||
|
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
mod data_stream;
|
||||||
mod messages;
|
mod messages;
|
||||||
mod miniconf_client;
|
mod miniconf_client;
|
||||||
mod network_processor;
|
mod network_processor;
|
||||||
mod shared;
|
mod shared;
|
||||||
mod telemetry;
|
mod telemetry;
|
||||||
|
|
||||||
|
pub use data_stream::BlockGenerator;
|
||||||
|
|
||||||
use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack};
|
use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack};
|
||||||
|
use data_stream::DataStream;
|
||||||
use messages::{MqttMessage, SettingsResponse};
|
use messages::{MqttMessage, SettingsResponse};
|
||||||
|
|
||||||
pub use miniconf_client::MiniconfClient;
|
pub use miniconf_client::MiniconfClient;
|
||||||
pub use network_processor::NetworkProcessor;
|
pub use network_processor::NetworkProcessor;
|
||||||
pub use shared::NetworkManager;
|
pub use shared::NetworkManager;
|
||||||
|
use smoltcp_nal::embedded_nal::SocketAddr;
|
||||||
pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient};
|
pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient};
|
||||||
|
|
||||||
pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>;
|
pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>;
|
||||||
|
@ -36,7 +41,9 @@ pub enum UpdateState {
|
||||||
/// A structure of Stabilizer's default network users.
|
/// A structure of Stabilizer's default network users.
|
||||||
pub struct NetworkUsers<S: Default + Clone + Miniconf, T: Serialize> {
|
pub struct NetworkUsers<S: Default + Clone + Miniconf, T: Serialize> {
|
||||||
pub miniconf: MiniconfClient<S>,
|
pub miniconf: MiniconfClient<S>,
|
||||||
pub processor: NetworkProcessor,
|
processor: NetworkProcessor,
|
||||||
|
stream: DataStream,
|
||||||
|
generator: Option<BlockGenerator>,
|
||||||
pub telemetry: TelemetryClient<T>,
|
pub telemetry: TelemetryClient<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +94,27 @@ where
|
||||||
&prefix,
|
&prefix,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (generator, stream) =
|
||||||
|
data_stream::setup_streaming(stack_manager.acquire_stack());
|
||||||
|
|
||||||
NetworkUsers {
|
NetworkUsers {
|
||||||
miniconf: settings,
|
miniconf: settings,
|
||||||
processor,
|
processor,
|
||||||
telemetry,
|
telemetry,
|
||||||
|
stream,
|
||||||
|
generator: Some(generator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable live data streaming.
|
||||||
|
pub fn enable_streaming(&mut self, remote: SocketAddr) -> BlockGenerator {
|
||||||
|
self.stream.set_remote(remote);
|
||||||
|
self.generator.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn direct_stream(&mut self, remote: SocketAddr) {
|
||||||
|
if self.generator.is_none() {
|
||||||
|
self.stream.set_remote(remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +129,11 @@ where
|
||||||
// Update the MQTT clients.
|
// Update the MQTT clients.
|
||||||
self.telemetry.update();
|
self.telemetry.update();
|
||||||
|
|
||||||
|
// Update the data stream.
|
||||||
|
if self.generator.is_none() {
|
||||||
|
self.stream.process();
|
||||||
|
}
|
||||||
|
|
||||||
match self.miniconf.update() {
|
match self.miniconf.update() {
|
||||||
UpdateState::Updated => UpdateState::Updated,
|
UpdateState::Updated => UpdateState::Updated,
|
||||||
UpdateState::NoChange => poll_result,
|
UpdateState::NoChange => poll_result,
|
||||||
|
|
Loading…
Reference in New Issue