From ed82c3444e3243cf73c13daca63fb73b0836fba2 Mon Sep 17 00:00:00 2001
From: Alex Janka <alex@alexjanka.com>
Date: Fri, 10 Jan 2025 12:03:38 +1100
Subject: [PATCH] ccs: abstraction for data storage

---
 .../src/controller.rs                         | 10 ++--
 charge-controller-supervisor/src/main.rs      | 12 +++--
 charge-controller-supervisor/src/storage.rs   | 47 +++++++++++++++++++
 charge-controller-supervisor/src/web.rs       | 36 +++++++-------
 4 files changed, 78 insertions(+), 27 deletions(-)
 create mode 100644 charge-controller-supervisor/src/storage.rs

diff --git a/charge-controller-supervisor/src/controller.rs b/charge-controller-supervisor/src/controller.rs
index 106c84a..345fa90 100644
--- a/charge-controller-supervisor/src/controller.rs
+++ b/charge-controller-supervisor/src/controller.rs
@@ -1,3 +1,5 @@
+use crate::storage::ControllerData;
+
 mod pl;
 mod tristar;
 
@@ -5,7 +7,7 @@ pub struct Controller {
     name: String,
     interval: std::time::Duration,
     inner: ControllerInner,
-    data: std::sync::Arc<tokio::sync::RwLock<Option<ControllerState>>>,
+    data: std::sync::Arc<ControllerData>,
     voltage_rx: Option<tokio::sync::mpsc::UnboundedReceiver<VoltageCommand>>,
     voltage_tx: Option<MultiTx>,
 }
@@ -69,7 +71,7 @@ impl Controller {
             },
         };
 
-        let data = std::sync::Arc::new(tokio::sync::RwLock::new(None));
+        let data = std::sync::Arc::new(ControllerData::new());
 
         let (voltage_tx, voltage_rx) = if config.follow_primary {
             let (a, b) = tokio::sync::mpsc::unbounded_channel();
@@ -91,7 +93,7 @@ impl Controller {
         ))
     }
 
-    pub fn get_data_ptr(&self) -> std::sync::Arc<tokio::sync::RwLock<Option<ControllerState>>> {
+    pub fn get_data_ptr(&self) -> std::sync::Arc<ControllerData> {
         self.data.clone()
     }
 
@@ -114,7 +116,7 @@ impl Controller {
             }
         }
 
-        *self.data.write().await = Some(data);
+        *self.data.write_state().await = Some(data);
 
         Ok(())
     }
diff --git a/charge-controller-supervisor/src/main.rs b/charge-controller-supervisor/src/main.rs
index da2e361..4b894e6 100644
--- a/charge-controller-supervisor/src/main.rs
+++ b/charge-controller-supervisor/src/main.rs
@@ -26,6 +26,8 @@ enum Commands {
 mod controller;
 mod gauges;
 
+mod storage;
+
 mod web;
 
 pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
@@ -92,7 +94,7 @@ async fn run() -> eyre::Result<()> {
 
 async fn watch(args: Args) -> eyre::Result<()> {
     let _w = config::init_config(&args.config);
-    let (map, follow_voltage_tx, mut controller_tasks) = {
+    let (storage, follow_voltage_tx, mut controller_tasks) = {
         let config = config::access_config().await;
         if config
             .charge_controllers
@@ -138,12 +140,16 @@ async fn watch(args: Args) -> eyre::Result<()> {
             controller_tasks.push(run_loop(controller));
         }
 
-        (map, follow_voltage_tx, controller_tasks)
+        (
+            storage::AllControllers::new(map),
+            follow_voltage_tx,
+            controller_tasks,
+        )
     };
 
     let server = web::rocket(web::ServerState::new(
         &config::access_config().await.primary_charge_controller,
-        map,
+        storage,
         follow_voltage_tx,
     ));
     let server_task = tokio::task::spawn(server.launch());
diff --git a/charge-controller-supervisor/src/storage.rs b/charge-controller-supervisor/src/storage.rs
new file mode 100644
index 0000000..48eb84a
--- /dev/null
+++ b/charge-controller-supervisor/src/storage.rs
@@ -0,0 +1,47 @@
+pub struct AllControllers {
+    map: std::collections::HashMap<String, std::sync::Arc<ControllerData>>,
+}
+
+impl AllControllers {
+    pub const fn new(
+        map: std::collections::HashMap<String, std::sync::Arc<ControllerData>>,
+    ) -> Self {
+        Self { map }
+    }
+
+    pub fn controller_names(&self) -> impl Iterator<Item = &String> {
+        self.map.keys()
+    }
+
+    pub fn get(&self, name: &str) -> Option<&std::sync::Arc<ControllerData>> {
+        self.map.get(name)
+    }
+
+    pub fn all_data(&self) -> impl Iterator<Item = (&String, &std::sync::Arc<ControllerData>)> {
+        self.map.iter()
+    }
+}
+
+pub struct ControllerData {
+    state: tokio::sync::RwLock<Option<crate::controller::ControllerState>>,
+}
+
+impl ControllerData {
+    pub const fn new() -> Self {
+        Self {
+            state: tokio::sync::RwLock::const_new(None),
+        }
+    }
+
+    pub async fn write_state(
+        &self,
+    ) -> tokio::sync::RwLockWriteGuard<Option<crate::controller::ControllerState>> {
+        self.state.write().await
+    }
+
+    pub async fn read_state(
+        &self,
+    ) -> tokio::sync::RwLockReadGuard<Option<crate::controller::ControllerState>> {
+        self.state.read().await
+    }
+}
diff --git a/charge-controller-supervisor/src/web.rs b/charge-controller-supervisor/src/web.rs
index 57d55f5..30add36 100644
--- a/charge-controller-supervisor/src/web.rs
+++ b/charge-controller-supervisor/src/web.rs
@@ -1,29 +1,25 @@
 use rocket::{get, post, routes, serde::json::Json, State};
 
+use crate::storage::AllControllers;
+
 mod static_handler;
 
 pub struct ServerState {
     primary_name: String,
-    map: std::collections::HashMap<
-        String,
-        std::sync::Arc<tokio::sync::RwLock<Option<crate::controller::ControllerState>>>,
-    >,
+    data: AllControllers,
     tx_to_controllers: crate::controller::MultiTx,
 }
 
 impl ServerState {
     pub fn new(
         primary_name: &impl ToString,
-        map: std::collections::HashMap<
-            String,
-            std::sync::Arc<tokio::sync::RwLock<Option<crate::controller::ControllerState>>>,
-        >,
+        data: AllControllers,
         tx_to_controllers: crate::controller::MultiTx,
     ) -> Self {
         let primary_name = primary_name.to_string();
         Self {
             primary_name,
-            map,
+            data,
             tx_to_controllers,
         }
     }
@@ -65,7 +61,7 @@ pub fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
 
 #[get("/interfaces")]
 fn interfaces(state: &State<ServerState>) -> Json<Vec<String>> {
-    Json(state.map.keys().cloned().collect())
+    Json(state.data.controller_names().cloned().collect())
 }
 
 #[get("/interfaces/primary")]
@@ -73,10 +69,10 @@ async fn primary_interface(
     state: &State<ServerState>,
 ) -> Result<Json<crate::controller::CommonData>, ServerError> {
     let s = state
-        .map
+        .data
         .get(&state.primary_name)
         .ok_or(ServerError::InvalidPrimaryName)?
-        .read()
+        .read_state()
         .await;
 
     Ok(Json(s.as_ref().ok_or(ServerError::NoData)?.common()))
@@ -88,8 +84,8 @@ async fn all_interfaces(
 ) -> Json<Vec<(String, crate::controller::CommonData)>> {
     let mut data = Vec::new();
 
-    for (k, v) in &state.map {
-        let v = v.read().await;
+    for (k, v) in state.data.all_data() {
+        let v = v.read_state().await;
         if let Some(v) = v.as_ref() {
             data.push((k.clone(), v.common().clone()));
         }
@@ -104,8 +100,8 @@ async fn all_interfaces_full(
 ) -> Json<Vec<(String, crate::controller::ControllerState)>> {
     let mut data = Vec::new();
 
-    for (k, v) in &state.map {
-        let v = v.read().await;
+    for (k, v) in state.data.all_data() {
+        let v = v.read_state().await;
         if let Some(v) = v.as_ref() {
             data.push((k.clone(), v.clone()));
         }
@@ -120,10 +116,10 @@ async fn interface(
     state: &State<ServerState>,
 ) -> Result<Json<crate::controller::CommonData>, ServerError> {
     let data = state
-        .map
+        .data
         .get(name)
         .ok_or(ServerError::NotFound)?
-        .read()
+        .read_state()
         .await;
     Ok(Json(data.as_ref().ok_or(ServerError::NoData)?.common()))
 }
@@ -134,10 +130,10 @@ async fn interface_full(
     state: &State<ServerState>,
 ) -> Result<Json<crate::controller::ControllerState>, ServerError> {
     let data = state
-        .map
+        .data
         .get(name)
         .ok_or(ServerError::NotFound)?
-        .read()
+        .read_state()
         .await;
     Ok(Json(data.as_ref().ok_or(ServerError::NoData)?.clone()))
 }