use crate::hardware::{flash::Flash, metadata::ApplicationMetadata, platform};
use core::fmt::Write;
use embassy_futures::block_on;
use embedded_io::Write as EioWrite;
use heapless::{String, Vec};
use miniconf::{
postcard, Leaf, Path, Tree, TreeDeserializeOwned, TreeKey, TreeSerialize,
};
use sequential_storage::{
cache::NoCache,
map::{fetch_item, store_item, SerializationError},
};
use serial_settings::{BestEffortInterface, Platform, Settings};
use smoltcp_nal::smoltcp::wire::EthernetAddress;
use stm32h7xx_hal::flash::LockedFlashBank;
#[derive(Clone, Debug, Tree)]
pub struct NetSettings {
pub broker: Leaf<String<255>>,
pub id: Leaf<String<23>>,
pub ip: Leaf<String<15>>,
#[tree(skip)]
pub mac: EthernetAddress,
}
impl NetSettings {
pub fn new(mac: EthernetAddress) -> Self {
let mut id = String::new();
write!(&mut id, "{mac}").unwrap();
Self {
broker: String::try_from("mqtt").unwrap().into(),
ip: String::try_from("0.0.0.0").unwrap().into(),
id: id.into(),
mac,
}
}
}
pub trait AppSettings {
fn new(net: NetSettings) -> Self;
fn net(&self) -> &NetSettings;
}
#[derive(
Default, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq,
)]
pub struct SettingsKey(Vec<u8, 128>);
impl sequential_storage::map::Key for SettingsKey {
fn serialize_into(
&self,
buffer: &mut [u8],
) -> Result<usize, SerializationError> {
Ok(::postcard::to_slice(self, buffer)
.map_err(|_| SerializationError::BufferTooSmall)?
.len())
}
fn deserialize_from(
buffer: &[u8],
) -> Result<(Self, usize), SerializationError> {
let original_length = buffer.len();
let (result, remainder) = ::postcard::take_from_bytes(buffer)
.map_err(|_| SerializationError::BufferTooSmall)?;
Ok((result, original_length - remainder.len()))
}
}
pub struct SerialSettingsPlatform<C> {
pub interface: BestEffortInterface<crate::hardware::SerialPort>,
pub _settings_marker: core::marker::PhantomData<C>,
pub storage: Flash,
pub metadata: &'static ApplicationMetadata,
}
impl<C> SerialSettingsPlatform<C>
where
C: TreeDeserializeOwned + TreeSerialize + TreeKey,
{
pub fn load(structure: &mut C, storage: &mut Flash) {
let mut buffer = [0u8; 512];
for path in C::nodes::<Path<String<128>, '/'>, 8>() {
let (path, _node) = path.unwrap();
let value: &[u8] = match block_on(fetch_item(
storage,
storage.range(),
&mut NoCache::new(),
&mut buffer,
&SettingsKey(path.clone().into_inner().into_bytes()),
)) {
Err(e) => {
log::warn!(
"Failed to fetch `{}` from flash: {e:?}",
path.as_str()
);
continue;
}
Ok(Some(value)) => value,
Ok(None) => continue,
};
if value.is_empty() {
continue;
}
log::info!("Loading initial `{}` from flash", path.as_str());
let flavor = ::postcard::de_flavors::Slice::new(value);
if let Err(e) = postcard::set_by_key(structure, &path, flavor) {
log::warn!(
"Failed to deserialize `{}` from flash: {e:?}",
path.as_str()
);
}
}
}
}
impl<C> Platform for SerialSettingsPlatform<C>
where
C: Settings,
{
type Interface = BestEffortInterface<crate::hardware::SerialPort>;
type Settings = C;
type Error = sequential_storage::Error<
<LockedFlashBank as embedded_storage::nor_flash::ErrorType>::Error,
>;
fn fetch<'a>(
&mut self,
buf: &'a mut [u8],
key: &[u8],
) -> Result<Option<&'a [u8]>, Self::Error> {
let range = self.storage.range();
block_on(fetch_item(
&mut self.storage,
range,
&mut NoCache::new(),
buf,
&SettingsKey(Vec::try_from(key).unwrap()),
))
.map(|v| v.filter(|v: &&[u8]| !v.is_empty()))
}
fn store(
&mut self,
buf: &mut [u8],
key: &[u8],
value: &[u8],
) -> Result<(), Self::Error> {
let range = self.storage.range();
block_on(store_item(
&mut self.storage,
range,
&mut NoCache::new(),
buf,
&SettingsKey(Vec::try_from(key).unwrap()),
&value,
))
}
fn clear(&mut self, buf: &mut [u8], key: &[u8]) -> Result<(), Self::Error> {
self.store(buf, key, b"")
}
fn cmd(&mut self, cmd: &str) {
match cmd {
"reboot" => cortex_m::peripheral::SCB::sys_reset(),
"dfu" => platform::start_dfu_reboot(),
"service" => {
writeln!(
&mut self.interface,
"{:<20}: {} [{}]",
"Version",
self.metadata.firmware_version,
self.metadata.profile,
)
.unwrap();
writeln!(
&mut self.interface,
"{:<20}: {}",
"Hardware Revision", self.metadata.hardware_version
)
.unwrap();
writeln!(
&mut self.interface,
"{:<20}: {}",
"Rustc Version", self.metadata.rust_version
)
.unwrap();
writeln!(
&mut self.interface,
"{:<20}: {}",
"Features", self.metadata.features
)
.unwrap();
writeln!(
&mut self.interface,
"{:<20}: {}",
"Panic Info", self.metadata.panic_info
)
.unwrap();
}
_ => {
writeln!(
self.interface_mut(),
"Invalid platform command: `{cmd}` not in [`dfu`, `reboot`, `service`]"
)
.ok();
}
}
}
fn interface_mut(&mut self) -> &mut Self::Interface {
&mut self.interface
}
}