Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rsdk 9697 support multiple nvs #376

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

npmenard
Copy link
Member

@npmenard npmenard commented Jan 8, 2025

Implements the storage traits for any type we can iterate over, we can then easily pass an array of a vec as a storage to viam server.
With this something like
let storage = vec![ NVSStorage::new("nvs").unwrap(), NVSStorage::new("nvs2").unwrap(), ];
Will allow use to store the robot data over multiple NVS

I didn't want to add an extra type to represent a collection of storage and this way the logic is driven by the underlying storage implementation.

@npmenard npmenard requested a review from a team as a code owner January 8, 2025 20:51
}

#[derive(Clone)]
pub struct NVSStorage {
// esp-idf-svc partition driver ensures that only one handle of a type can be created
// so inner mutability can be achieves safely with RefCell
nvs: Rc<RefCell<EspCustomNvs>>,
partition_name: CString,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store a copy of the partition rather than the partition name to avoid cloning a CString as we pass the Storage through different par to of the code

@@ -102,15 +103,15 @@ impl From<CertificateResponse> for TlsCertificate {
pub trait WifiCredentialStorage {
type Error: Error + Debug + Into<ServerError>;
fn has_wifi_credentials(&self) -> bool;
fn store_wifi_credentials(&self, creds: WifiCredentials) -> Result<(), Self::Error>;
fn store_wifi_credentials(&self, creds: &WifiCredentials) -> Result<(), Self::Error>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed some functions of the storage traits to accept reference instead of taking ownership. Otherwise we would have to clone each time until we can find a storage where to store the object . It's not dramatic so happy to revert that change if we feel like it doesn't make sense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like it better this way actually.

fn reset_ota_metadata(&self) -> Result<(), Self::Error>;
}

pub trait StorageDiagnostic {
fn log_space_diagnostic(&self);
}

#[derive(Error, Debug)]
#[error("empty storage collection")]
pub struct EmptyStorageCollectionError;
Copy link
Member Author

@npmenard npmenard Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a special error to handle the case where a collection is empty

@@ -158,6 +163,22 @@ struct RAMCredentialStorageInner {
#[derive(Default, Clone)]
pub struct RAMStorage(Rc<Mutex<RAMCredentialStorageInner>>);

#[derive(Error, Debug)]
pub enum RAMStorageError {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add an error type for RAMStorage to make it easier to test

self.into_iter().any(OtaMetadataStorage::has_ota_metadata)
}
fn get_ota_metadata(&self) -> Result<OtaMetadata, Self::Error> {
self.into_iter().fold(
Copy link
Member Author

@npmenard npmenard Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea here is to use fold to call get_ota_metadata until Ok is returned. If all storage in a collection return an error only the last error is reported.
We don't have an iterator that behave like try_fold but actually short-circuit on the first Ok, so we will iterate through the whole collection even after a get_ota_metadata has succeeded. The function get_ota_metadata will however not be called anymore on storage

{
type Error = Storage::Error;
fn has_ota_metadata(&self) -> bool {
self.into_iter().any(OtaMetadataStorage::has_ota_metadata)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

short-circuit on the first true

#[cfg(feature = "ota")]
impl<Iterable, Storage: OtaMetadataStorage> OtaMetadataStorage for Iterable
where
for<'a> &'a Iterable: IntoIterator<Item = &'a Storage>,
Copy link
Member Author

@npmenard npmenard Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will only work on collections that implements impl<'a, T,> IntoIterator for &'a ...

@@ -136,7 +137,7 @@ impl StorageDiagnostic for NVSStorage {
fn log_space_diagnostic(&self) {
let mut stats: nvs_stats_t = Default::default();
if let Err(err) =
esp!(unsafe { nvs_get_stats(self.partition_name.as_ptr(), &mut stats as *mut _) })
esp!(unsafe { nvs_get_stats(self.part.handle() as _, &mut stats as *mut _) })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle is the partition name

fn reset_ota_metadata(&self) -> Result<(), Self::Error> {
self.into_iter().fold(
Err::<_, Self::Error>(EmptyStorageCollectionError.into()),
|val, s| val.or(s.reset_ota_metadata()),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taking advantage of the eagerness of or might not be the best approach

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate a bit? I think I know what you are saying, but I'd rather not guess.

Copy link
Member Author

@npmenard npmenard Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As opposed to or_else or will always evaluate a function in argument if there is one (because it doesn't actually accept a closure) before considering replacing val hence the eagerness. We use it our advantage to make sure all copies of an object are erased through all storage. In theory we shouldn't need this since we use or_else elsewhere so only on copy should be in the storage array at anytime.

Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neat, LGTM, a few small comments. I'd like to think about it a bit tomorrow before it merges so I can make sure I totally understand how the various folds are working and think about whether there are any tricky edge cases.

@@ -102,15 +103,15 @@ impl From<CertificateResponse> for TlsCertificate {
pub trait WifiCredentialStorage {
type Error: Error + Debug + Into<ServerError>;
fn has_wifi_credentials(&self) -> bool;
fn store_wifi_credentials(&self, creds: WifiCredentials) -> Result<(), Self::Error>;
fn store_wifi_credentials(&self, creds: &WifiCredentials) -> Result<(), Self::Error>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like it better this way actually.

let mut inner_ref = self.0.lock().unwrap();
let _ = inner_ref.robot_creds.insert(creds);
Ok(())
}
fn get_robot_credentials(&self) -> Result<RobotCredentials, Self::Error> {
let inner_ref = self.0.lock().unwrap();
let cfg = inner_ref.robot_creds.clone().unwrap_or_default().clone();
Ok(cfg)
log::info!("get robot creds");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray log, or debug!?

As an aside, I do miss some of the logging we used to have pre #316 that gave a little more context about progression through the startup sequence, whether we were using cached credentials or configs, etc. I'm usually for "silence is golden" but in this case that's very useful information to understand how things are coming up, especially in offline mode. Would you be OK with a ticket to re-introduce some of that logging? Or did you not propagate into the refactored world because you found it too chatty?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray log i will remove it.
Yes let's make a ticket for some logging housekeeping

}
fn reset_robot_credentials(&self) -> Result<(), Self::Error> {
let mut inner_ref = self.0.lock().unwrap();
let _ = inner_ref.robot_creds.take();
log::info!("reset robot creds");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

assert!(!v.has_robot_credentials());
assert!(ram2
.store_robot_credentials(&CloudConfig {
app_address: "http://downloadramstorage.org".to_owned(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe .example.org

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that was for the joke :P

fn reset_ota_metadata(&self) -> Result<(), Self::Error> {
self.into_iter().fold(
Err::<_, Self::Error>(EmptyStorageCollectionError.into()),
|val, s| val.or(s.reset_ota_metadata()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate a bit? I think I know what you are saying, but I'd rather not guess.

@acmorrow
Copy link
Member

acmorrow commented Jan 8, 2025

How do you envision configuring the space of available NVS, for, say, callers of viam_server_start?

@npmenard
Copy link
Member Author

npmenard commented Jan 8, 2025

How do you envision configuring the space of available NVS, for, say, callers of viam_server_start?

What do you mean?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants