diff --git a/rust/src/main.rs b/rust/src/main.rs
index 8387cb7..7293148 100644
--- a/rust/src/main.rs
+++ b/rust/src/main.rs
@@ -26,6 +26,9 @@ use crate::{config::PlantControllerConfig, webserver::webserver::httpd};
 mod config;
 mod log;
 pub mod plant_hal;
+mod tank;
+
+use tank::*;
 
 const TIME_ZONE: Tz = Berlin;
 
@@ -125,34 +128,6 @@ enum SensorError {
     OpenCircuit { hz: f32, min: f32 },
 }
 
-#[derive(Debug, PartialEq, Default)]
-/// State data for water tank
-///
-/// TODO unify with TankStateMQTT
-struct TankState {
-    /// is there enough water in the tank
-    enough_water: bool,
-    /// warning that water needs to be refilled soon
-    warn_level: bool,
-    /// estimation how many ml are still in tank
-    left_ml: u32,
-    /// if there is was an issue with the water level sensor
-    /// TODO merge with left_ml as Result<u32, error_type>
-    sensor_error: bool,
-    /// raw water sensor value
-    raw: u16,
-}
-
-#[derive(Serialize)]
-struct TankStateMQTT {
-    enough_water: bool,
-    warn_level: bool,
-    left_ml: u32,
-    sensor_error: bool,
-    raw: u16,
-    water_frozen: String,
-}
-
 #[derive(Serialize)]
 struct PlantStateMQTT<'a> {
     a: &'a str,
@@ -631,74 +606,12 @@ fn publish_battery_state(
     };
 }
 
-fn determine_tank_state(
-    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
-    config: &PlantControllerConfig,
-) -> TankState {
-    if config.tank.tank_sensor_enabled {
-        let mut rv: TankState = TankState {
-            ..Default::default()
-        };
-        let success = board
-            .tank_sensor_percent()
-            .and_then(|raw| {
-                rv.raw = raw;
-                return map_range(
-                    (
-                        config.tank.tank_empty_percent as f32,
-                        config.tank.tank_full_percent as f32,
-                    ),
-                    raw as f32,
-                );
-            })
-            .and_then(|percent| {
-                rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32;
-                println!(
-                    "Tank sensor returned mv {} as {}% leaving {} ml useable",
-                    rv.raw, percent as u8, rv.left_ml
-                );
-                if config.tank.tank_warn_percent > percent as u8 {
-                    board.general_fault(true);
-                    println!(
-                        "Low water, current percent is {}, minimum warn level is {}",
-                        percent as u8, config.tank.tank_warn_percent
-                    );
-                    rv.warn_level = true;
-                }
-                if config.tank.tank_empty_percent < percent as u8 {
-                    println!(
-                        "Enough water, current percent is {}, minimum empty level is {}",
-                        percent as u8, config.tank.tank_empty_percent
-                    );
-                    rv.enough_water = true;
-                }
-                return Ok(());
-            });
-        match success {
-            Err(err) => {
-                println!("Could not determine tank value due to {}", err);
-                board.general_fault(true);
-                rv.sensor_error = true;
-            }
-            Ok(_) => {}
-        }
-        return rv;
-    }
-    return TankState {
-        warn_level: false,
-        enough_water: true,
-        left_ml: 1337,
-        sensor_error: false,
-        raw: 0,
-    };
-}
-
 fn determine_state_target_moisture_for_plant(
     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
     plant: usize,
     state: &mut PlantState,
     config: &PlantControllerConfig,
-    tank_state: &TankState,
+    tank_state: &TankInfo,
     cur: DateTime<Tz>,
 ) {
     let plant_config = &config.plants[plant];
@@ -788,7 +701,7 @@ fn determine_state_timer_only_for_plant(
     plant: usize,
     state: &mut PlantState,
     config: &PlantControllerConfig,
-    tank_state: &TankState,
+    tank_state: &TankInfo,
     cur: DateTime<Tz>,
 ) {
     let plant_config = &config.plants[plant];
@@ -826,7 +739,7 @@ fn determine_state_timer_and_deadzone_for_plant(
     plant: usize,
     state: &mut PlantState,
     config: &PlantControllerConfig,
-    tank_state: &TankState,
+    tank_state: &TankInfo,
     cur: DateTime<Tz>,
 ) {
     let plant_config = &config.plants[plant];
@@ -870,7 +783,7 @@ fn determine_state_timer_and_deadzone_for_plant(
 fn determine_plant_state(
     plantstate: &mut [PlantState; PLANT_COUNT],
     cur: DateTime<Tz>,
-    tank_state: &TankState,
+    tank_state: &TankInfo,
     config: &PlantControllerConfig,
     board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 ) {
@@ -1051,24 +964,6 @@ fn to_string<T: Display>(value: Result<T>) -> String {
     };
 }
 
-fn map_range(from_range: (f32, f32), s: f32) -> anyhow::Result<f32> {
-    if s < from_range.0 {
-        anyhow::bail!(
-            "Value out of range, min {} but current is {}",
-            from_range.0,
-            s
-        );
-    }
-    if s > from_range.1 {
-        anyhow::bail!(
-            "Value out of range, max {} but current is {}",
-            from_range.1,
-            s
-        );
-    }
-    return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0));
-}
-
 fn map_range_moisture(s: f32) -> Result<u8, SensorError> {
     if s < FROM.0 {
         return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 });
diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs
index c6dc2b5..a65c91d 100644
--- a/rust/src/plant_hal.rs
+++ b/rust/src/plant_hal.rs
@@ -471,44 +471,23 @@ impl PlantCtrlBoard<'_> {
         Ok(sensor_data.temperature / 10_f32)
     }
 
-    pub fn tank_sensor_percent(&mut self) -> Result<u16> {
+    /// return median tank sensor value in milli volt
+    pub fn tank_sensor_voltage(&mut self) -> Result<u16> {
         let delay = Delay::new_default();
         self.tank_power.set_high()?;
         //let stabilize
         delay.delay_ms(100);
-        unsafe {
-            vTaskDelay(100);
-        }
 
         let mut store = [0_u16; TANK_MULTI_SAMPLE];
         for multisample in 0..TANK_MULTI_SAMPLE {
             let value = self.tank_channel.read()?;
             store[multisample] = value;
         }
+        self.tank_power.set_low()?;
+
         store.sort();
-        let median = store[6] as f32 / 1000_f32;
-        let config_open_voltage_mv = 3.0;
-        if config_open_voltage_mv < median {
-            self.tank_power.set_low()?;
-            bail!(
-                "Tank sensor missing, open loop voltage {} on tank sensor input {}",
-                config_open_voltage_mv,
-                median
-            );
-        }
-
-        let r2 = median * 50.0 / (3.3 - median);
-        let mut percent = r2 / 190_f32 * 100_f32;
-        percent = percent.clamp(0.0, 100.0);
-        log(
-            LogMessage::SensorTankRaw,
-            median as u32,
-            percent as u32,
-            "",
-            "",
-        );
-
-        return Ok(percent as u16);
+        let median_mv = store[6] as f32 / 1000_f32;
+        Ok(median_mv)
     }
 
     pub fn set_low_voltage_in_cycle(&mut self) {
diff --git a/rust/src/tank.rs b/rust/src/tank.rs
new file mode 100644
index 0000000..05f0374
--- /dev/null
+++ b/rust/src/tank.rs
@@ -0,0 +1,152 @@
+use crate::config::TankConfig;
+
+const OPEN_TANK_VOLTAGE: f32 = 3.0;
+
+#[derive(Debug, PartialEq, Default)]
+/// State data for water tank
+///
+/// TODO unify with TankStateMQTT
+pub struct TankInfo {
+    /// is there enough water in the tank
+    enough_water: bool,
+    /// warning that water needs to be refilled soon
+    warn_level: bool,
+    /// estimation how many ml are still in tank
+    left_ml: u32,
+    /// if there is was an issue with the water level sensor
+    /// TODO merge with left_ml as Result<u32, error_type>
+    sensor_error: bool,
+    /// raw water sensor value
+    raw: u16,
+}
+
+pub enum TankError {
+    SensorDisabled,
+    SensorMissing(f32),
+    SensorValueError { value: f32, min: f32, max: f32 },
+}
+
+pub enum TankState {
+    TankSensorPresent(u16),
+    TankSensorDisabled,
+}
+
+fn raw_volatge_to_divider_percent(raw_value_mv: u16) -> Result<f32, TankError> {
+    if raw_value_mv > OPEN_TANK_VOLTAGE {
+        return Err(TankError::SensorMissing(raw_value_mv));
+    }
+
+    let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv);
+    let mut percent = r2 / 190_f32 * 100_f32;
+    percent = percent.clamp(0.0, 100.0);
+    // TODO(judge) move this to a sensible place
+    //log(
+    //LogMessage::SensorTankRaw,
+    //raw_value_mv as u32,
+    //percent as u32,
+    //"",
+    //"",
+    //);
+    Ok(percent)
+}
+
+fn raw_voltage_to_tank_fill_percent(
+    raw_value_mv: u16,
+    config: &TankConfig,
+) -> Result<f32, TankError> {
+    let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?;
+    if s < config.tank_empty_percent || s > config.tank_full_percent {
+        return Err(TankError::SensorValueError {
+            value: divider_percent,
+            min: config.tank_empty_percent,
+            max: config.tank_full_percent,
+        });
+    }
+    Ok((divider_percent - config.tank_empty_percent) * 100
+        / (config.tank_full_percent - config.tank_empty_percent))
+}
+
+
+impl TankState {
+    pub fn left_ml(&self, config: &TankConfig) -> Result<u32, TankError> {
+        match self {
+            TankState::TankSensorDisabled => Err(TankError::SensorMissing),
+            TankState::TankSensorPresent(raw_value_mv) => {
+                let tank_fill_percent = raw_voltage_to_tank_fill_percent(raw_value_mv, config);
+                todo!()
+            }
+        }
+    }
+}
+
+#[derive(Serialize)]
+pub struct TankStateMQTT {
+    enough_water: bool,
+    warn_level: bool,
+    left_ml: u32,
+    sensor_error: bool,
+    raw: u16,
+    water_frozen: String,
+}
+
+pub fn determine_tank_state(
+    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
+    config: &PlantControllerConfig,
+) -> TankInfo {
+    if config.tank.tank_sensor_enabled {
+        let mut rv: TankInfo = TankInfo {
+            ..Default::default()
+        };
+        let success = board
+            .tank_sensor_percent()
+            .and_then(|raw| {
+                rv.raw = raw;
+                return map_range(
+                    (
+                        config.tank.tank_empty_percent as f32,
+                        config.tank.tank_full_percent as f32,
+                    ),
+                    raw as f32,
+                );
+            })
+            .and_then(|percent| {
+                rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32;
+                println!(
+                    "Tank sensor returned mv {} as {}% leaving {} ml useable",
+                    rv.raw, percent as u8, rv.left_ml
+                );
+                if config.tank.tank_warn_percent > percent as u8 {
+                    board.general_fault(true);
+                    println!(
+                        "Low water, current percent is {}, minimum warn level is {}",
+                        percent as u8, config.tank.tank_warn_percent
+                    );
+                    rv.warn_level = true;
+                }
+                if config.tank.tank_empty_percent < percent as u8 {
+                    println!(
+                        "Enough water, current percent is {}, minimum empty level is {}",
+                        percent as u8, config.tank.tank_empty_percent
+                    );
+                    rv.enough_water = true;
+                }
+                return Ok(());
+            });
+        match success {
+            Err(err) => {
+                println!("Could not determine tank value due to {}", err);
+                board.general_fault(true);
+                rv.sensor_error = true;
+            }
+            Ok(_) => {}
+        }
+        return rv;
+    }
+    return TankInfo {
+        warn_level: false,
+        enough_water: true,
+        left_ml: 1337,
+        sensor_error: false,
+        raw: 0,
+    };
+}