Repository: HaoboGu/rmk (language: rust)

Issue:
Clear storage automatically after flashing a new firmware



Discussion / hints:

Fix patch:
diff --git a/rmk/CHANGELOG.md b/rmk/CHANGELOG.md
index 7c05772a..d2476a67 100644
--- a/rmk/CHANGELOG.md
+++ b/rmk/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Clear the storage by checking build hash after flashing a new firmware
+
 ## [0.5.2] - 2025-01-22
 
 ### Added
diff --git a/rmk/Cargo.toml b/rmk/Cargo.toml
index a6ab5c04..03e251f0 100644
--- a/rmk/Cargo.toml
+++ b/rmk/Cargo.toml
@@ -68,6 +68,10 @@ esp-idf-svc = { version = "0.51", default-features = false, optional = true }
 # Document feature
 document-features = "0.2"
 
+[build-dependencies]
+chrono = "0.4"
+crc32fast = "1.3"
+
 [package.metadata.docs.rs]
 features = ["split"]
 
diff --git a/rmk/build.rs b/rmk/build.rs
new file mode 100644
index 00000000..5d3f95ed
--- /dev/null
+++ b/rmk/build.rs
@@ -0,0 +1,43 @@
+use std::env;
+use std::fs;
+use std::path::Path;
+use std::process::Command;
+
+fn main() {
+    // Ensure build.rs is re-run if files change
+    // println!("cargo:rerun-if-changed=NEVER_EXISTS");
+    println!("cargo:rerun-if-changed=.git/HEAD");
+    println!("cargo:rerun-if-changed=build.rs");
+
+    // Get the short hash of the latest Git commit. If it fails, use "unknown"
+    let commit_id = Command::new("git")
+        .args(["rev-parse", "--short", "HEAD"])
+        .output()
+        .ok()
+        .and_then(|output| {
+            if output.status.success() {
+                Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| "unknown".to_string());
+
+    // Get the current local time and format it
+    let now = chrono::Local::now();
+
+    // Combine data and compute CRC32
+    let combined = format!("{}_{}", commit_id, now);
+    let mut hasher = crc32fast::Hasher::new();
+    hasher.update(combined.as_bytes());
+    let build_hash = hasher.finalize();
+
+    // Generate file contents
+    let contents = format!("pub const BUILD_HASH: u32 = {:#010x};\n", build_hash);
+
+    // Write to constants.rs in the OUT_DIR
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let dest_path = Path::new(&out_dir).join("constants.rs");
+    fs::write(&dest_path, contents).expect("Failed to write build identifier");
+
+}
\ No newline at end of file
diff --git a/rmk/src/lib.rs b/rmk/src/lib.rs
index 3405744c..6989d4ef 100644
--- a/rmk/src/lib.rs
+++ b/rmk/src/lib.rs
@@ -11,6 +11,9 @@
 // Enable std for espidf and test
 #![cfg_attr(not(test), no_std)]
 
+// Include generated constants
+include!(concat!(env!("OUT_DIR"), "/constants.rs"));
+
 // This mod MUST go first, so that the others see its macros.
 pub(crate) mod fmt;
 
diff --git a/rmk/src/storage/mod.rs b/rmk/src/storage/mod.rs
index 9c1e1d68..9b71ba21 100644
--- a/rmk/src/storage/mod.rs
+++ b/rmk/src/storage/mod.rs
@@ -4,13 +4,13 @@ pub mod nor_flash;
 use crate::{
     combo::{Combo, COMBO_MAX_LENGTH},
     config::StorageConfig,
+    BUILD_HASH,
 };
 use byteorder::{BigEndian, ByteOrder};
 use core::fmt::Debug;
 use core::ops::Range;
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::channel::Channel;
-use embedded_storage::nor_flash::NorFlash;
 use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
 use heapless::Vec;
 use sequential_storage::{
@@ -145,7 +145,9 @@ impl Value<'_> for StorageData {
                 } else {
                     buffer[1] = 1;
                 }
-                Ok(4)
+                // Save build_hash
+                BigEndian::write_u32(&mut buffer[2..6], c.build_hash);
+                Ok(6)
             }
             StorageData::LayoutConfig(c) => {
                 buffer[0] = StorageKeys::LayoutConfig as u8;
@@ -226,14 +228,21 @@ impl Value<'_> for StorageData {
         if let Some(key_type) = StorageKeys::from_u8(buffer[0]) {
             match key_type {
                 StorageKeys::StorageConfig => {
+                    if buffer.len() < 6 {
+                        return Err(SerializationError::BufferTooSmall);
+                    }
                     // 1 is the initial state of flash, so it means storage is NOT initialized
                     if buffer[1] == 1 {
                         Ok(StorageData::StorageConfig(LocalStorageConfig {
                             enable: false,
+                            build_hash: BUILD_HASH,
                         }))
                     } else {
+                        // Enabled, read build hash
+                        let build_hash = BigEndian::read_u32(&buffer[2..6]);
                         Ok(StorageData::StorageConfig(LocalStorageConfig {
                             enable: true,
+                            build_hash,
                         }))
                     }
                 }
@@ -332,6 +341,7 @@ impl StorageData {
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub(crate) struct LocalStorageConfig {
     enable: bool,
+    build_hash: u32,
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -444,15 +454,14 @@ impl<F: AsyncNorFlash, const ROW: usize, const COL: usize, const NUM_LAYER: usiz
             buffer: [0; get_buffer_size()],
         };
 
-        if config.clear_storage {
-            // Clear storage
+        // Check whether keymap and configs have been storaged in flash
+        if !storage.check_enable().await {
+            // Clear storage first
+            debug!("Clearing storage!");
             let _ =
                 sequential_storage::erase_all(&mut storage.flash, storage.storage_range.clone())
                     .await;
-        }
 
-        // Check whether keymap and configs have been storaged in flash
-        if !storage.check_enable().await {
             // Initialize storage from keymap and config
             if storage
                 .initialize_storage_with_config(keymap)
@@ -466,7 +475,10 @@ impl<F: AsyncNorFlash, const ROW: usize, const COL: usize, const NUM_LAYER: usiz
                     &mut NoCache::new(),
                     &mut storage.buffer,
                     &(StorageKeys::StorageConfig as u32),
-                    &StorageData::StorageConfig(LocalStorageConfig { enable: false }),
+                    &StorageData::StorageConfig(LocalStorageConfig {
+                        enable: false,
+                        build_hash: BUILD_HASH,
+                    }),
                 )
                 .await
                 .ok();
@@ -476,11 +488,6 @@ impl<F: AsyncNorFlash, const ROW: usize, const COL: usize, const NUM_LAYER: usiz
         storage
     }
 
-    // TODO: Is there a way to convert `NorFlash` trait object to `F: AsyncNorFlash`?
-    pub(crate) async fn new_from_blocking<BF: NorFlash>(_flash: BF) {
-        // Self { flash }
-    }
-
     pub(crate) async fn run(&mut self) {
         let mut storage_cache = NoCache::new();
         loop {
@@ -709,7 +716,10 @@ impl<F: AsyncNorFlash, const ROW: usize, const COL: usize, const NUM_LAYER: usiz
     ) -> Result<(), ()> {
         let mut cache = NoCache::new();
         // Save storage config
-        let storage_config = StorageData::StorageConfig(LocalStorageConfig { enable: true });
+        let storage_config = StorageData::StorageConfig(LocalStorageConfig {
+            enable: true,
+            build_hash: BUILD_HASH,
+        });
         store_item(
             &mut self.flash,
             self.storage_range.clone(),
@@ -776,10 +786,11 @@ impl<F: AsyncNorFlash, const ROW: usize, const COL: usize, const NUM_LAYER: usiz
         )
         .await
         {
-            config.enable
-        } else {
-            false
+            if config.enable && config.build_hash == BUILD_HASH {
+                return true;
+            }
         }
+        false
     }
 }
 


Test patch: