use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};

use crate::log::LogMessage;
use ds323x::{DateTimeAccess, Ds323x};
use esp_ota::mark_app_valid;

use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr};
use embedded_hal_bus::i2c::MutexDevice;
use embedded_svc::wifi::{
    AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
};

use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution};
use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError};
use esp_idf_hal::units::FromValueType;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::ipv4::IpInfo;
use esp_idf_svc::mqtt::client::QoS::AtLeastOnce;
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
use esp_idf_svc::wifi::EspWifi;
use esp_idf_sys::esp_restart;
use esp_idf_sys::{
    esp_deep_sleep, esp_sleep_enable_ext1_wakeup,
    esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, esp_spiffs_info,
};
use measurements::Temperature;
use once_cell::sync::Lazy;
use plant_ctrl2::sipo::ShiftRegister40;

use anyhow::{anyhow, Context};
use anyhow::{bail, Ok, Result};
use serde::{Deserialize, Serialize};
use std::ffi::CString;
use std::fs::{self, File};
use std::path::Path;

use chrono::{DateTime, Utc};
use ds18b20::Ds18b20;
use std::result::Result::Ok as OkStd;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use embedded_hal::digital::OutputPin;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull};
use esp_idf_hal::pcnt::{
    PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
};
use esp_idf_hal::prelude::Peripherals;
use esp_idf_hal::reset::ResetReason;
use esp_idf_svc::sntp::{self, SyncStatus};
use esp_idf_svc::systime::EspSystemTime;
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
use one_wire_bus::OneWire;

use crate::config::PlantControllerConfig;
use crate::log::log;
use crate::plant_hal::BoardHal::V3;
use crate::{plant_hal, to_string, STAY_ALIVE};

//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
const REPEAT_MOIST_MEASURE: usize = 1;

const SPIFFS_PARTITION_NAME: &str = "storage";
const CONFIG_FILE: &str = "/spiffs/config.cfg";
const BASE_PATH: &str = "/spiffs";

const TANK_MULTI_SAMPLE: usize = 11;

const PUMP8_BIT: usize = 0;
const PUMP1_BIT: usize = 1;
const PUMP2_BIT: usize = 2;
const PUMP3_BIT: usize = 3;
const PUMP4_BIT: usize = 4;
const PUMP5_BIT: usize = 5;
const PUMP6_BIT: usize = 6;
const PUMP7_BIT: usize = 7;

const MS_0: usize = 8;
const MS_4: usize = 9;
const MS_2: usize = 10;
const MS_3: usize = 11;
const SENSOR_ON: usize = 12;
const MS_1: usize = 13;
const CHARGING: usize = 14;
const AWAKE: usize = 15;

const FAULT_3: usize = 16;
const FAULT_8: usize = 17;
const FAULT_7: usize = 18;
const FAULT_6: usize = 19;
const FAULT_5: usize = 20;
const FAULT_4: usize = 21;
const FAULT_1: usize = 22;
const FAULT_2: usize = 23;

const SENSOR_A_1: u8 = 7;
const SENSOR_A_2: u8 = 6;
const SENSOR_A_3: u8 = 5;
const SENSOR_A_4: u8 = 4;
const SENSOR_A_5: u8 = 3;
const SENSOR_A_6: u8 = 2;
const SENSOR_A_7: u8 = 1;
const SENSOR_A_8: u8 = 0;

const SENSOR_B_1: u8 = 8;
const SENSOR_B_2: u8 = 9;
const SENSOR_B_3: u8 = 10;
const SENSOR_B_4: u8 = 11;
const SENSOR_B_5: u8 = 12;
const SENSOR_B_6: u8 = 13;
const SENSOR_B_7: u8 = 14;
const SENSOR_B_8: u8 = 15;

const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);

#[link_section = ".rtc.data"]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"]
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"]
static mut LOW_VOLTAGE_DETECTED: bool = false;

#[link_section = ".rtc.data"]
static mut RESTART_TO_CONF: bool = false;

pub struct FileSystemSizeInfo {
    pub total_size: usize,
    pub used_size: usize,
    pub free_size: usize,
}

#[derive(strum::Display)]
pub enum StartMode {
    AP,
    Normal,
}

#[derive(Debug, PartialEq)]
pub enum Sensor {
    A,
    B,
}

pub struct PlantHal {}

pub struct ESP<'a> {
    mqtt_client: Option<EspMqttClient<'a>>,
    signal_counter: PcntDriver<'a>,
    wifi_driver: EspWifi<'a>,
    boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
    one_wire_bus: OneWire<PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>>,
}

pub struct HAL<'a>{
    pub board_hal: BoardHal<'a>,
    pub esp: ESP<'a>,
    pub battery_monitor: BatteryMonitor<'a>
}

pub enum BatteryMonitor<'a> {
    Disabled {

    },
    BQ34Z100G1 {
        battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>
    },
    WchI2cSlave {

    }
}

pub enum BoardHal<'a>{
    Initial {

    },
    V3 {
        shift_register: ShiftRegister40<
            PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
            PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
            PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        >,
        shift_register_enable_invert:
            PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>,
        tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
        solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
        light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,

        rtc:
            Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
        eeprom: Eeprom24x<
            MutexDevice<'a, I2cDriver<'a>>,
            eeprom24x::page_size::B32,
            eeprom24x::addr_size::TwoBytes,
            eeprom24x::unique_serial::No,
        >,
    },
    V4 {
        tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
        solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
        boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
        signal_counter: PcntDriver<'a>,
        light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
        wifi_driver: EspWifi<'a>,
        one_wire_bus: OneWire<PinDriver<'a, Gpio18, InputOutput>>,
        battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
        rtc:
            Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
        eeprom: Eeprom24x<
            MutexDevice<'a, I2cDriver<'a>>,
            eeprom24x::page_size::B32,
            eeprom24x::addr_size::TwoBytes,
            eeprom24x::unique_serial::No,
        >,
    }
}

#[derive(Serialize, Debug)]
pub struct FileInfo {
    filename: String,
    size: usize,
}

#[derive(Serialize, Debug)]
pub struct FileList {
    total: usize,
    used: usize,
    files: Vec<FileInfo>,
    file_system_corrupt: Option<String>,
    iter_error: Option<String>,
}

#[derive(Serialize)]
pub struct BatteryState {
    voltage_milli_volt: String,
    current_milli_ampere: String,
    cycle_count: String,
    design_milli_ampere: String,
    remaining_milli_ampere: String,
    state_of_charge: String,
    state_of_health: String,
    temperature: String,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct BackupHeader {
    pub timestamp: i64,
    crc16: u16,
    pub size: usize,
}

impl BatteryInteraction for BatteryMonitor<'_> {
    fn state_charge_percent(&mut self) -> Result<u8> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.state_of_charge() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading SoC {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            }
            BatteryMonitor::Disabled {} => {
                bail!("Battery monitor is disabled")
            }
        }
    }

    fn remaining_milli_ampere_hour(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.remaining_capacity(){
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading remaining_milli_ampere_hour {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn max_milli_ampere_hour(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.full_charge_capacity() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading max_milli_ampere_hour {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn design_milli_ampere_hour(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.design_capacity() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading design_milli_ampere_hour {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn voltage_milli_volt(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.voltage() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading voltage_milli_volt {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn average_current_milli_ampere(&mut self) -> Result<i16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.average_current() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading average_current_milli_ampere {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn cycle_count(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.cycle_count() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading cycle_count {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn state_health_percent(&mut self) -> Result<u8> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.state_of_health() {
                    OkStd(r) => Ok(r as u8),
                    Err(err) => bail!("Error reading state_health_percent {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
    fn bat_temperature(&mut self) -> Result<u16> {
        match self {
            BatteryMonitor::BQ34Z100G1 { battery_driver} => {
                match battery_driver.temperature() {
                    OkStd(r) => Ok(r),
                    Err(err) => bail!("Error reading bat_temperature {:?}", err),
                }
            },
            BatteryMonitor::WchI2cSlave { .. } => {
                bail!("Not implemented")
            },
            &mut BatteryMonitor::Disabled {  } => {
                bail!("Battery monitor is disabled")
            }
        }
    }
}

impl BoardInteraction for HAL<'_> {
    fn set_charge_indicator(&mut self, charging: bool) {
        match &mut self.board_hal {
            BoardHal::V3 { shift_register, .. } => {
                shift_register.decompose()[CHARGING]
                    .set_state(charging.into())
                    .unwrap();
            }
            BoardHal::V4 { .. } => {},
            &mut plant_hal::BoardHal::Initial {  } => {

            }
        }
    }
    fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
        match &mut self.board_hal {
            BoardHal::V3 { shift_register, .. } => {
                shift_register.decompose()[AWAKE].set_low().unwrap();
            }
            BoardHal::V4 { .. } => {},
            &mut plant_hal::BoardHal::Initial {  } => {

            }
        }

        unsafe {
            //if we don't do this here, we might just revert newly flashed firmware
            mark_app_valid();
            //allow early wakeup by pressing the boot button
            if duration_in_ms == 0 {
                esp_restart();
            } else {
                //configure gpio 1 to wakeup on low, reused boot button for this
                esp_sleep_enable_ext1_wakeup(
                    0b10u64,
                    esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
                );
                esp_deep_sleep(duration_in_ms);
            }
        };
    }
    fn get_backup_info(&mut self) -> Result<BackupHeader> {
        let eeprom = match &mut self.board_hal {
            BoardHal::V3 {  eeprom, .. } => {eeprom}
            BoardHal::V4 { eeprom, .. } => {eeprom },
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };
        let dummy = BackupHeader {
            timestamp: 0,
            crc16: 0,
            size: 0,
        };
        let store = bincode::serialize(&dummy)?.len();
        let mut header_page_buffer = vec![0_u8; store];

        match eeprom.read_data(0, &mut header_page_buffer) {
            OkStd(_) => {}
            Err(err) => bail!("Error reading eeprom header {:?}", err),
        };
        println!("Raw header is {:?} with size {}", header_page_buffer, store);
        let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
        Ok(header)
    }
    fn get_backup_config(&mut self) -> Result<Vec<u8>> {
        let eeprom = match &mut self.board_hal {
            BoardHal::V3 {  eeprom, .. } => {eeprom}
            BoardHal::V4 { eeprom, .. } => {eeprom }
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };

        let dummy = BackupHeader {
            timestamp: 0,
            crc16: 0,
            size: 0,
        };
        let store = bincode::serialize(&dummy)?.len();
        let mut header_page_buffer = vec![0_u8; store];

        match eeprom.read_data(0, &mut header_page_buffer) {
            OkStd(_) => {}
            Err(err) => bail!("Error reading eeprom header {:?}", err),
        };
        let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;

        //skip page 0, used by the header
        let data_start_address = eeprom.page_size() as u32;
        let mut data_buffer = vec![0_u8; header.size];
        match eeprom.read_data(data_start_address, &mut data_buffer) {
            OkStd(_) => {}
            Err(err) => bail!("Error reading eeprom data {:?}", err),
        };

        let checksum = X25.checksum(&data_buffer);
        if checksum != header.crc16 {
            bail!(
                "Invalid checksum, got {} but expected {}",
                checksum,
                header.crc16
            );
        }

        Ok(data_buffer)
    }
    fn backup_config(&mut self, bytes: &[u8]) -> Result<()> {
        let time = self.get_rtc_time()?.timestamp_millis();
        let eeprom = match &mut self.board_hal {
            V3 {  eeprom, .. } => {
                eeprom
            }
            BoardHal::V4 { eeprom, .. } => { eeprom },
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };

        let delay = Delay::new_default();

        let checksum = X25.checksum(bytes);
        let page_size = eeprom.page_size();

        let header = BackupHeader {
            crc16: checksum,
            timestamp: time,
            size: bytes.len(),
        };

        let encoded = bincode::serialize(&header)?;
        if encoded.len() > page_size {
            bail!(
                "Size limit reached header is {}, but firest page is only {}",
                encoded.len(),
                page_size
            )
        }
        let as_u8: &[u8] = &encoded;

        match eeprom.write_page(0, as_u8) {
            OkStd(_) => {}
            Err(err) => bail!("Error writing eeprom {:?}", err),
        };
        delay.delay_ms(5);

        let to_write = bytes.chunks(page_size);

        let mut lastiter = 0;
        let mut current_page = 1;
        for chunk in to_write {
            let address = current_page * page_size as u32;
            let eeprom = match &mut self.board_hal {
                V3 {  eeprom, .. } => {
                    eeprom
                }
                BoardHal::V4 { eeprom, .. } => { eeprom }
                &mut plant_hal::BoardHal::Initial {  } => {
                    bail!("Board not configured yet")
                }
            };
            match eeprom.write_page(address, chunk) {
                OkStd(_) => {}
                Err(err) => bail!("Error writing eeprom {:?}", err),
            };
            current_page += 1;

            let iter = (current_page % 8) as usize;
            if iter != lastiter {
                for i in 0..PLANT_COUNT {
                    self.fault(i, iter == i);
                }
                lastiter = iter;
            }

            //update led here?
            delay.delay_ms(5);
        }
        Ok(())
    }
    fn get_battery_state(&mut self) -> String {
        let bat = BatteryState {
            voltage_milli_volt: to_string(self.battery_monitor.voltage_milli_volt()),
            current_milli_ampere: to_string(self.battery_monitor.average_current_milli_ampere()),
            cycle_count: to_string(self.battery_monitor.cycle_count()),
            design_milli_ampere: to_string(self.battery_monitor.design_milli_ampere_hour()),
            remaining_milli_ampere: to_string(self.battery_monitor.remaining_milli_ampere_hour()),
            state_of_charge: to_string(self.battery_monitor.state_charge_percent()),
            state_of_health: to_string(self.battery_monitor.state_health_percent()),
            temperature: to_string(self.battery_monitor.bat_temperature()),
        };

        match serde_json::to_string(&bat) {
            std::prelude::rust_2015::Ok(state) => {
                state
            }
            Err(err) => {
                format!("{:?}", err).to_owned()
            }
        }
    }
    fn list_files(&self) -> FileList {
        let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();

        let mut file_system_corrupt = None;

        let mut iter_error = None;
        let mut result = Vec::new();

        let filepath = Path::new(BASE_PATH);
        let read_dir = fs::read_dir(filepath);
        match read_dir {
            OkStd(read_dir) => {
                for item in read_dir {
                    match item {
                        OkStd(file) => {
                            let f = FileInfo {
                                filename: file.file_name().into_string().unwrap(),
                                size: file
                                    .metadata()
                                    .and_then(|it| Result::Ok(it.len()))
                                    .unwrap_or_default()
                                    as usize,
                            };
                            result.push(f);
                        }
                        Err(err) => {
                            iter_error = Some(format!("{err:?}"));
                            break;
                        }
                    }
                }
            }
            Err(err) => {
                file_system_corrupt = Some(format!("{err:?}"));
            }
        }
        let mut total: usize = 0;
        let mut used: usize = 0;
        unsafe {
            esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
        }

        FileList {
            total,
            used,
            file_system_corrupt,
            files: result,
            iter_error,
        }
    }
    fn delete_file(&self, filename: &str) -> Result<()> {
        let filepath = Path::new(BASE_PATH).join(Path::new(filename));
        match fs::remove_file(filepath) {
            OkStd(_) => Ok(()),
            Err(err) => {
                bail!(format!("{err:?}"))
            }
        }
    }
    fn get_file_handle(&self, filename: &str, write: bool) -> Result<File> {
        let filepath = Path::new(BASE_PATH).join(Path::new(filename));
        Ok(if write {
            File::create(filepath)?
        } else {
            File::open(filepath)?
        })
    }
    fn is_day(& self) -> bool {
        match & self.board_hal {
            BoardHal::V3 {  solar_is_day, .. } => {solar_is_day.get_level().into()}
            BoardHal::V4 { solar_is_day, .. } => {solar_is_day.get_level().into() }
            plant_hal::BoardHal::Initial {} => {
                false
            }
        }
    }
    //should be multsampled
    fn water_temperature_c(&mut self) -> Result<f32> {
        let mut delay = Delay::new_default();

        self.esp.one_wire_bus
            .reset(&mut delay)
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
        let first = self.esp.one_wire_bus.devices(false, &mut delay).next();
        if first.is_none() {
            bail!("Not found any one wire  Ds18b20");
        }
        let device_address = first
            .unwrap()
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;

        let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;

        water_temp_sensor
            .start_temp_measurement(&mut self.esp.one_wire_bus, &mut delay)
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
        ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut delay);
        let sensor_data = water_temp_sensor
            .read_data(&mut self.esp.one_wire_bus, &mut delay)
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
        if sensor_data.temperature == 85_f32 {
            bail!("Ds18b20 dummy temperature returned");
        }
        Ok(sensor_data.temperature / 10_f32)
    }
    /// return median tank sensor value in milli volt
    fn tank_sensor_voltage(&mut self) -> Result<f32> {
        let (tank_power, tank_channel) = match &mut self.board_hal {
            BoardHal::V3 {  tank_power, tank_channel, .. } => {(tank_power,tank_channel)}
            BoardHal::V4 { tank_power, tank_channel, .. } => {(tank_power,tank_channel) }
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };


        let delay = Delay::new_default();
        tank_power.set_high()?;
        //let stabilize
        delay.delay_ms(100);

        let mut store = [0_u16; TANK_MULTI_SAMPLE];
        for multisample in 0..TANK_MULTI_SAMPLE {
            let value = tank_channel.read()?;
            store[multisample] = value;
        }
        tank_power.set_low()?;

        store.sort();
        let median_mv = store[6] as f32 / 1000_f32;
        Ok(median_mv)
    }
    fn set_low_voltage_in_cycle(&mut self) {
        unsafe {
            LOW_VOLTAGE_DETECTED = true;
        }
    }
    fn clear_low_voltage_in_cycle(&mut self) {
        unsafe {
            LOW_VOLTAGE_DETECTED = false;
        }
    }
    fn light(&mut self, enable: bool) -> Result<()> {
        let light = match &mut self.board_hal {
            BoardHal::V3 { light, .. } => light,
            BoardHal::V4 { light, .. } => light,
                &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };

        unsafe { gpio_hold_dis(light.pin()) };
        light.set_state(enable.into())?;
        unsafe { gpio_hold_en(light.pin()) };
        Ok(())
    }
    fn pump(& self, plant: usize, enable: bool) -> Result<()> {
        match & self.board_hal {
            BoardHal::V3 { shift_register, .. } => {
                let index = match plant {
                    0 => PUMP1_BIT,
                    1 => PUMP2_BIT,
                    2 => PUMP3_BIT,
                    3 => PUMP4_BIT,
                    4 => PUMP5_BIT,
                    5 => PUMP6_BIT,
                    6 => PUMP7_BIT,
                    7 => PUMP8_BIT,
                    _ => bail!("Invalid pump {plant}",),
                };
                //currently infallible error, keep for future as result anyway
                shift_register.decompose()[index].set_state(enable.into())?;
            }
            BoardHal::V4 { .. } => {
                bail!("Not yet implemented")
            },
            &plant_hal::BoardHal::Initial {} => {
                bail!("Board not configured yet")
            }
        }

        Ok(())
    }
    fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
        let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
        DateTime::from_timestamp_millis(ts)
    }
    fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
        unsafe {
            LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
        }
    }
    fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
        unsafe {
            CONSECUTIVE_WATERING_PLANT[plant] = count;
        }
    }
    fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
        unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
    }
    fn fault(& self, plant: usize, enable: bool) -> Result<()>{
        match & self.board_hal {
            V3 { shift_register, .. } => {
                let index = match plant {
                    0 => FAULT_1,
                    1 => FAULT_2,
                    2 => FAULT_3,
                    3 => FAULT_4,
                    4 => FAULT_5,
                    5 => FAULT_6,
                    6 => FAULT_7,
                    7 => FAULT_8,
                    _ => panic!("Invalid plant id {}", plant),
                };
                shift_register.decompose()[index]
                    .set_state(enable.into())?;
                Ok(())

            }
            BoardHal::V4 { .. } => {
                bail!("Not yet implemented")
            }
            &plant_hal::BoardHal::Initial {} => {
                bail!("Board not configured yet")
            }
        }

    }
    fn low_voltage_in_cycle(&mut self) -> bool {
        unsafe { LOW_VOLTAGE_DETECTED }
    }
    fn any_pump(&mut self, enable: bool) -> Result<()> {

        match &mut self.board_hal {
            BoardHal::V3 { main_pump, .. } => {
                main_pump.set_state(enable.into())?;
            }
            BoardHal::V4 { main_pump, .. } => {
                main_pump.set_state(enable.into())?;
            }
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        }
        Ok(())

    }
    fn time(&mut self) -> Result<DateTime<Utc>> {
        let time = EspSystemTime {}.now().as_millis();
        let smaller_time = time as i64;
        let local_time = DateTime::from_timestamp_millis(smaller_time)
            .ok_or(anyhow!("could not convert timestamp"))?;
        Ok(local_time)
    }
    fn sntp(&mut self, max_wait_ms: u32) -> Result<DateTime<Utc>> {
        let sntp = sntp::EspSntp::new_default()?;
        let mut counter = 0;
        while sntp.get_sync_status() != SyncStatus::Completed {
            let delay = Delay::new_default();
            delay.delay_ms(100);
            counter += 100;
            if counter > max_wait_ms {
                bail!("Reached sntp timeout, aborting")
            }
        }

        self.time()
    }
    fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32> {
        match &mut self.board_hal {
             V3{  shift_register, .. } => {
                let mut results = [0_f32; REPEAT_MOIST_MEASURE];
                for repeat in 0..REPEAT_MOIST_MEASURE {
                    self.esp.signal_counter.counter_pause()?;
                    self.esp.signal_counter.counter_clear()?;
                    //Disable all
                    shift_register.decompose()[MS_4].set_high()?;

                    let sensor_channel = match sensor {
                        Sensor::A => match plant {
                            0 => SENSOR_A_1,
                            1 => SENSOR_A_2,
                            2 => SENSOR_A_3,
                            3 => SENSOR_A_4,
                            4 => SENSOR_A_5,
                            5 => SENSOR_A_6,
                            6 => SENSOR_A_7,
                            7 => SENSOR_A_8,
                            _ => bail!("Invalid plant id {}", plant),
                        },
                        Sensor::B => match plant {
                            0 => SENSOR_B_1,
                            1 => SENSOR_B_2,
                            2 => SENSOR_B_3,
                            3 => SENSOR_B_4,
                            4 => SENSOR_B_5,
                            5 => SENSOR_B_6,
                            6 => SENSOR_B_7,
                            7 => SENSOR_B_8,
                            _ => bail!("Invalid plant id {}", plant),
                        },
                    };


                    let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
                    let pin_0 = &mut shift_register.decompose()[MS_0];
                    let pin_1 = &mut shift_register.decompose()[MS_1];
                    let pin_2 = &mut shift_register.decompose()[MS_2];
                    let pin_3 = &mut shift_register.decompose()[MS_3];
                    if is_bit_set(0) {
                        pin_0.set_high()?;
                    } else {
                        pin_0.set_low()?;
                    }
                    if is_bit_set(1) {
                        pin_1.set_high()?;
                    } else {
                        pin_1.set_low()?;
                    }
                    if is_bit_set(2) {
                        pin_2.set_high()?;
                    } else {
                        pin_2.set_low()?;
                    }
                    if is_bit_set(3) {
                        pin_3.set_high()?;
                    } else {
                        pin_3.set_low()?;
                    }

                    shift_register.decompose()[MS_4].set_low()?;
                    shift_register.decompose()[SENSOR_ON].set_high()?;

                    let delay = Delay::new_default();
                    let measurement = 100; // TODO what is this scaling factor? what is its purpose?
                    let factor = 1000f32 / measurement as f32;

                    //give some time to stabilize
                    delay.delay_ms(10);
                    self.esp.signal_counter.counter_resume()?;
                    delay.delay_ms(measurement);
                    self.esp.signal_counter.counter_pause()?;
                    shift_register.decompose()[MS_4].set_high()?;
                    shift_register.decompose()[SENSOR_ON].set_low()?;
                    delay.delay_ms(10);
                    let unscaled = self.esp.signal_counter.get_counter_value()? as i32;
                    let hz = unscaled as f32 * factor;
                    log(
                        LogMessage::RawMeasure,
                        unscaled as u32,
                        hz as u32,
                        &plant.to_string(),
                        &format!("{sensor:?}"),
                    );
                    results[repeat] = hz;
                }
                results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord

                let mid = results.len() / 2;
                let median = results[mid];
                Ok(median)
            },
            _ => {
                bail!("Not implemented for this board")
            }
        }

    }
    fn general_fault(&mut self, enable: bool) {
        let general_fault = match &mut self.board_hal {
            BoardHal::V3 {  general_fault, .. } => {general_fault}
            BoardHal::V4 { general_fault,  .. } => {general_fault},
            &mut plant_hal::BoardHal::Initial {  } => {
                return
            }
        };
        unsafe { gpio_hold_dis(general_fault.pin()) };
        general_fault.set_state(enable.into()).unwrap();
        unsafe { gpio_hold_en(general_fault.pin()) };
    }
    fn wifi_ap(&mut self, ap_ssid: Option<heapless::String<32>>) -> Result<()> {
        let ssid =
            ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap());
        let apconfig = AccessPointConfiguration {
            ssid,
            auth_method: AuthMethod::None,
            ssid_hidden: false,
            ..Default::default()
        };
        self.esp.wifi_driver
            .set_configuration(&Configuration::AccessPoint(apconfig))?;
        self.esp.wifi_driver.start()?;
        Ok(())
    }
    fn wifi(
        &mut self,
        ssid: heapless::String<32>,
        password: Option<heapless::String<64>>,
        max_wait: u32,
    ) -> Result<IpInfo> {
        match password {
            Some(pw) => {
                //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
                self.esp.wifi_driver.set_configuration(&Configuration::Client(
                    ClientConfiguration {
                        ssid,
                        password: pw,
                        ..Default::default()
                    },
                ))?;
            }
            None => {
                self.esp.wifi_driver.set_configuration(&Configuration::Client(
                    ClientConfiguration {
                        ssid,
                        auth_method: AuthMethod::None,
                        ..Default::default()
                    },
                ))?;
            }
        }

        self.esp.wifi_driver.start()?;
        self.esp.wifi_driver.connect()?;

        let delay = Delay::new_default();
        let mut counter = 0_u32;
        while !self.esp.wifi_driver.is_connected()? {
            delay.delay_ms(250);
            counter += 250;
            if counter > max_wait {
                //ignore these errors, Wi-Fi will not be used this
                self.esp.wifi_driver.disconnect().unwrap_or(());
                self.esp.wifi_driver.stop().unwrap_or(());
                bail!("Did not manage wifi connection within timeout");
            }
        }
        println!("Should be connected now");

        while !self.esp.wifi_driver.is_up()? {
            delay.delay_ms(250);
            counter += 250;
            if counter > max_wait {
                //ignore these errors, Wi-Fi will not be used this
                self.esp.wifi_driver.disconnect().unwrap_or(());
                self.esp.wifi_driver.stop().unwrap_or(());
                bail!("Did not manage wifi connection within timeout");
            }
        }
        //update freertos registers ;)
        let address = self.esp.wifi_driver.sta_netif().get_ip_info()?;
        log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
        Ok(address)
    }
    fn mount_file_system(&mut self) -> Result<()> {
        let base_path = CString::new("/spiffs")?;
        let storage = CString::new(SPIFFS_PARTITION_NAME)?;
        let conf = esp_idf_sys::esp_vfs_spiffs_conf_t {
            base_path: base_path.as_ptr(),
            partition_label: storage.as_ptr(),
            max_files: 5,
            format_if_mount_failed: true,
        };

        //TODO check fielsystem esp_spiffs_check

        unsafe {
            esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?;
            Ok(())
        }
    }
    fn file_system_size(&mut self) -> Result<FileSystemSizeInfo> {
        let storage = CString::new(SPIFFS_PARTITION_NAME)?;
        let mut total_size = 0;
        let mut used_size = 0;
        unsafe {
            esp_idf_sys::esp!(esp_spiffs_info(
                storage.as_ptr(),
                &mut total_size,
                &mut used_size
            ))?;
        }
        Ok(FileSystemSizeInfo {
            total_size,
            used_size,
            free_size: total_size - used_size,
        })
    }
    fn mode_override_pressed(&mut self) -> bool {
        self.esp.boot_button.get_level() == Level::Low
    }
    fn factory_reset(&mut self) -> Result<()> {
        println!("factory resetting");
        let config = Path::new(CONFIG_FILE);
        if config.exists() {
            println!("Removing config");
            fs::remove_file(config)?;
        }

        //destroy backup header
        let dummy: [u8; 0] = [];
        self.backup_config(&dummy)?;

        Ok(())
    }
    fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
        let rtc = match &mut self.board_hal {
            BoardHal::V3 { rtc, .. } => {rtc}
            BoardHal::V4 { rtc, .. } => {rtc},
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };
        match rtc.datetime() {
            OkStd(rtc_time) => Ok(rtc_time.and_utc()),
            Err(err) => {
                bail!("Error getting rtc time {:?}", err)
            }
        }
    }
    fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()> {
        let rtc = match &mut self.board_hal {
            BoardHal::V3 { rtc, .. } => {rtc}
            BoardHal::V4 { rtc, .. } => {rtc}
            &mut plant_hal::BoardHal::Initial {  } => {
                bail!("Board not configured yet")
            }
        };
        let naive_time = time.naive_utc();
        match rtc.set_datetime(&naive_time) {
            OkStd(_) => Ok(()),
            Err(err) => {
                bail!("Error getting rtc time {:?}", err)
            }
        }
    }
    fn get_config(&mut self) -> Result<PlantControllerConfig> {
        let cfg = File::open(CONFIG_FILE)?;
        let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
        Ok(config)
    }
    fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> {
        let mut cfg = File::create(CONFIG_FILE)?;
        serde_json::to_writer(&mut cfg, &config)?;
        println!("Wrote config config {:?}", config);
        Ok(())
    }
    fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>> {
        self.esp.wifi_driver.start_scan(
            &ScanConfig {
                scan_type: ScanType::Passive(Duration::from_secs(5)),
                show_hidden: false,
                ..Default::default()
            },
            true,
        )?;
        Ok(self.esp.wifi_driver.get_scan_result()?)
    }
    fn test_pump(&mut self, plant: usize) -> Result<()> {
        self.any_pump(true)?;
        self.pump(plant, true)?;
        unsafe { vTaskDelay(30000) };
        self.pump(plant, false)?;
        self.any_pump(false)?;
        Ok(())
    }
    fn test(&mut self) -> Result<()> {
        self.general_fault(true);
        unsafe { vTaskDelay(100) };
        self.general_fault(false);
        unsafe { vTaskDelay(100) };
        self.any_pump(true)?;
        unsafe { vTaskDelay(500) };
        self.any_pump(false)?;
        unsafe { vTaskDelay(500) };
        self.light(true)?;
        unsafe { vTaskDelay(500) };
        self.light(false)?;
        unsafe { vTaskDelay(500) };
        for i in 0..PLANT_COUNT {
            self.fault(i, true)?;
            unsafe { vTaskDelay(500) };
            self.fault(i, false)?;
            unsafe { vTaskDelay(500) };
        }
        for i in 0..PLANT_COUNT {
            self.pump(i, true)?;
            unsafe { vTaskDelay(100) };
            self.pump(i, false)?;
            unsafe { vTaskDelay(100) };
        }
        for plant in 0..PLANT_COUNT {
            let a = self.measure_moisture_hz(plant, Sensor::A);
            let b = self.measure_moisture_hz(plant, Sensor::B);
            let aa = match a {
                OkStd(a) => a as u32,
                Err(_) => u32::MAX,
            };
            let bb = match b {
                OkStd(b) => b as u32,
                Err(_) => u32::MAX,
            };
            log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
        }
        Delay::new_default().delay_ms(10);
        Ok(())
    }
    fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> {
        let base_topic = config
            .network
            .base_topic
            .as_ref()
            .context("missing base topic")?;
        if base_topic.is_empty() {
            bail!("Mqtt base_topic was empty")
        }
        let mqtt_url = config
            .network
            .mqtt_url
            .as_ref()
            .context("missing mqtt url")?;
        if mqtt_url.is_empty() {
            bail!("Mqtt url was empty")
        }

        let last_will_topic = format!("{}/state", base_topic);
        let mqtt_client_config = MqttClientConfiguration {
            lwt: Some(LwtConfiguration {
                topic: &last_will_topic,
                payload: "lost".as_bytes(),
                qos: AtLeastOnce,
                retain: true,
            }),
            client_id: Some("plantctrl"),
            keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)),
            //room for improvement
            ..Default::default()
        };

        let mqtt_connected_event_received = Arc::new(AtomicBool::new(false));
        let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false));

        let round_trip_ok = Arc::new(AtomicBool::new(false));
        let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
        let stay_alive_topic = format!("{}/stay_alive", base_topic);
        log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic);

        let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone();
        let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone();
        let stay_alive_topic_copy = stay_alive_topic.clone();
        let round_trip_topic_copy = round_trip_topic.clone();
        let round_trip_ok_copy = round_trip_ok.clone();
        let client_id = mqtt_client_config.client_id.unwrap_or("not set");
        log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url);
        let mut client = EspMqttClient::new_cb(mqtt_url, &mqtt_client_config, move |event| {
            let payload = event.payload();
            match payload {
                embedded_svc::mqtt::client::EventPayload::Received {
                    id: _,
                    topic,
                    data,
                    details: _,
                } => {
                    let data = String::from_utf8_lossy(data);
                    if let Some(topic) = topic {
                        //todo use enums
                        if topic.eq(round_trip_topic_copy.as_str()) {
                            round_trip_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
                        } else if topic.eq(stay_alive_topic_copy.as_str()) {
                            let value =
                                data.eq_ignore_ascii_case("true") || data.eq_ignore_ascii_case("1");
                            log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
                            STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
                        } else {
                            log(LogMessage::UnknownTopic, 0, 0, "", topic);
                        }
                    }
                }
                esp_idf_svc::mqtt::client::EventPayload::Connected(_) => {
                    mqtt_connected_event_received_copy
                        .store(true, std::sync::atomic::Ordering::Relaxed);
                    mqtt_connected_event_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
                    println!("Mqtt connected");
                }
                esp_idf_svc::mqtt::client::EventPayload::Disconnected => {
                    mqtt_connected_event_received_copy
                        .store(true, std::sync::atomic::Ordering::Relaxed);
                    mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
                    println!("Mqtt disconnected");
                }
                esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => {
                    println!("EspMqttError reported {:?}", esp_error);
                    mqtt_connected_event_received_copy
                        .store(true, std::sync::atomic::Ordering::Relaxed);
                    mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
                    println!("Mqtt error");
                }
                esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => {
                    println!("Mqtt before connect")
                }
                esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => {
                    println!("Mqtt subscribed")
                }
                esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => {
                    println!("Mqtt unsubscribed")
                }
                esp_idf_svc::mqtt::client::EventPayload::Published(_) => {
                    println!("Mqtt published")
                }
                esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => {
                    println!("Mqtt deleted")
                }
            }
        })?;

        let mut wait_for_connections_event = 0;
        while wait_for_connections_event < 100 {
            wait_for_connections_event += 1;
            match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
                true => {
                    println!("Mqtt connection callback received, progressing");
                    match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) {
                        true => {
                            println!("Mqtt did callback as connected, testing with roundtrip now");
                            //subscribe to roundtrip
                            client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
                            client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
                            //publish to roundtrip
                            client.publish(
                                round_trip_topic.as_str(),
                                ExactlyOnce,
                                false,
                                "online_test".as_bytes(),
                            )?;

                            let mut wait_for_roundtrip = 0;
                            while wait_for_roundtrip < 100 {
                                wait_for_roundtrip += 1;
                                match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
                                    true => {
                                        println!("Round trip registered, proceeding");
                                        self.esp.mqtt_client = Some(client);
                                        return Ok(());
                                    }
                                    false => {
                                        unsafe { vTaskDelay(10) };
                                    }
                                }
                            }
                            bail!("Mqtt did not complete roundtrip in time");
                        }
                        false => {
                            bail!("Mqtt did respond but with failure")
                        }
                    }
                }
                false => {
                    unsafe { vTaskDelay(10) };
                }
            }
        }
        bail!("Mqtt did not fire connection callback in time");
    }
    fn mqtt_publish(
        &mut self,
        config: &PlantControllerConfig,
        subtopic: &str,
        message: &[u8],
    ) -> Result<()> {
        if self.esp.mqtt_client.is_none() {
            return Ok(());
        }
        if !subtopic.starts_with("/") {
            println!("Subtopic without / at start {}", subtopic);
            bail!("Subtopic without / at start {}", subtopic);
        }
        if subtopic.len() > 192 {
            println!("Subtopic exceeds 192 chars {}", subtopic);
            bail!("Subtopic exceeds 192 chars {}", subtopic);
        }
        let client = self.esp.mqtt_client.as_mut().unwrap();
        let mut full_topic: heapless::String<256> = heapless::String::new();
        if full_topic
            .push_str(config.network.base_topic.as_ref().unwrap())
            .is_err()
        {
            println!("Some error assembling full_topic 1");
            bail!("Some error assembling full_topic 1")
        };
        if full_topic.push_str(subtopic).is_err() {
            println!("Some error assembling full_topic 2");
            bail!("Some error assembling full_topic 2")
        };
        let publish = client.publish(&full_topic, ExactlyOnce, true, message);
        Delay::new(10).delay_ms(50);
        match publish {
            OkStd(message_id) => {
                println!(
                    "Published mqtt topic {} with message {:#?} msgid is {:?}",
                    full_topic,
                    String::from_utf8_lossy(message),
                    message_id
                );
                Ok(())
            }
            Err(err) => {
                println!(
                    "Error during mqtt send on topic {} with message {:#?} error is {:?}",
                    full_topic,
                    String::from_utf8_lossy(message),
                    err
                );
                Err(err)?
            }
        }
    }
    fn get_restart_to_conf(&mut self) -> bool {
        unsafe { RESTART_TO_CONF }
    }
    fn set_restart_to_conf(&mut self, to_conf: bool) {
        unsafe {
            RESTART_TO_CONF = to_conf;
        }
    }
}

pub trait BatteryInteraction {
    fn state_charge_percent(&mut self) -> Result<u8>;
    fn remaining_milli_ampere_hour(&mut self) -> Result<u16>;
    fn max_milli_ampere_hour(&mut self) -> Result<u16>;
    fn design_milli_ampere_hour(&mut self) -> Result<u16>;
    fn voltage_milli_volt(&mut self) -> Result<u16>;
    fn average_current_milli_ampere(&mut self) -> Result<i16>;
    fn cycle_count(&mut self) -> Result<u16>;
    fn state_health_percent(&mut self) -> Result<u8>;
    fn bat_temperature(&mut self) -> Result<u16>;
}

pub trait BoardInteraction {
    fn set_charge_indicator(&mut self, charging: bool);
    fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
    fn get_backup_info(&mut self) -> Result<BackupHeader>;
    fn get_backup_config(&mut self) -> Result<Vec<u8>>;
    fn backup_config(&mut self, bytes: &[u8]) -> Result<()>;
    fn get_battery_state(&mut self) -> String;
    fn list_files(&self) -> FileList;
    fn delete_file(&self, filename: &str) -> Result<()>;
    fn get_file_handle(&self, filename: &str, write: bool) -> Result<File>;
    fn is_day(&self) -> bool;
    //should be multsampled
    fn water_temperature_c(&mut self) -> Result<f32>;
    /// return median tank sensor value in milli volt
    fn tank_sensor_voltage(&mut self) -> Result<f32>;
    fn set_low_voltage_in_cycle(&mut self);
    fn clear_low_voltage_in_cycle(&mut self);
    fn light(&mut self, enable: bool) -> Result<()>;
    fn pump(&self, plant: usize, enable: bool) -> Result<()>;
    fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>>;
    fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>);
    fn store_consecutive_pump_count(&mut self, plant: usize, count: u32);
    fn consecutive_pump_count(&mut self, plant: usize) -> u32;
    fn fault(&self, plant: usize, enable: bool) -> Result<()>;
    fn low_voltage_in_cycle(&mut self) -> bool;
    fn any_pump(&mut self, enable: bool) -> Result<()>;
    fn time(&mut self) -> Result<DateTime<Utc>>;
    fn sntp(&mut self, max_wait_ms: u32) -> Result<DateTime<Utc>>;
    fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
    fn general_fault(&mut self, enable: bool);
    fn wifi_ap(&mut self, ap_ssid: Option<heapless::String<32>>) -> Result<()>;
    fn wifi(
        &mut self,
        ssid: heapless::String<32>,
        password: Option<heapless::String<64>>,
        max_wait: u32,
    ) -> Result<IpInfo>;
    fn mount_file_system(&mut self) -> Result<()>;
    fn file_system_size(&mut self) -> Result<FileSystemSizeInfo>;
    fn mode_override_pressed(&mut self) -> bool;
    fn factory_reset(&mut self) -> Result<()>;
    fn get_rtc_time(&mut self) -> Result<DateTime<Utc>>;
    fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()>;
    fn get_config(&mut self) -> Result<PlantControllerConfig>;
    fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()>;
    fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>>;
    fn test_pump(&mut self, plant: usize) -> Result<()>;
    fn test(&mut self) -> Result<()>;
    fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()>;
    fn mqtt_publish(
        &mut self,
        config: &PlantControllerConfig,
        subtopic: &str,
        message: &[u8],
    ) -> Result<()>;
    fn get_restart_to_conf(&mut self) -> bool;
    fn set_restart_to_conf(&mut self, to_conf: bool);

}

fn print_battery(
    battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>,
) -> Result<(), Bq34Z100Error<I2cError>> {
    println!("Try communicating with battery");
    let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
        println!("Firmware {:?}", e);
        0
    });
    println!("fw version is {}", fwversion);

    let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| {
        println!("Design capacity {:?}", e);
        0
    });
    println!("Design Capacity {}", design_capacity);
    if design_capacity == 1000 {
        println!("Still stock configuring battery, readouts are likely to be wrong!");
    }

    let flags = battery_driver.get_flags_decoded()?;
    println!("Flags {:?}", flags);

    let chem_id = battery_driver.chem_id().unwrap_or_else(|e| {
        println!("Chemid {:?}", e);
        0
    });

    let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| {
        println!("Bat Temp {:?}", e);
        0
    });
    let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius();
    let voltage = battery_driver.voltage().unwrap_or_else(|e| {
        println!("Bat volt {:?}", e);
        0
    });
    let current = battery_driver.current().unwrap_or_else(|e| {
        println!("Bat current {:?}", e);
        0
    });
    let state = battery_driver.state_of_charge().unwrap_or_else(|e| {
        println!("Bat Soc {:?}", e);
        0
    });
    let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| {
        println!("Bat Charge Volt {:?}", e);
        0
    });
    let charge_current = battery_driver.charge_current().unwrap_or_else(|e| {
        println!("Bat Charge Current {:?}", e);
        0
    });
    println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
    let _ = battery_driver.unsealed();
    let _ = battery_driver.it_enable();
    Result::Ok(())
}

pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c);
impl PlantHal {
    fn create_i2c() -> Mutex<I2cDriver<'static>> {
        let peripherals = unsafe { Peripherals::new() };

        let config = I2cConfig::new()
            .scl_enable_pullup(true)
            .sda_enable_pullup(true)
            .baudrate(100_u32.kHz().into())
            .timeout(APBTickType::from(Duration::from_millis(100)));

        let i2c = peripherals.i2c0;
        let scl = peripherals.pins.gpio19.downgrade();
        let sda = peripherals.pins.gpio20.downgrade();

        Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
    }
    pub fn create_v3() -> Result<Mutex<HAL<'static>>> {
        let peripherals = Peripherals::take()?;

        let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?;
        clock.set_pull(Pull::Floating)?;
        let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?;
        latch.set_pull(Pull::Floating)?;
        let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?;
        data.set_pull(Pull::Floating)?;
        let shift_register = ShiftRegister40::new(clock, latch, data);
        //disable all
        for mut pin in shift_register.decompose() {
            pin.set_low()?;
        }

        let awake = &mut shift_register.decompose()[AWAKE];
        awake.set_high()?;

        let charging = &mut shift_register.decompose()[CHARGING];
        charging.set_high()?;

        let ms0 = &mut shift_register.decompose()[MS_0];
        ms0.set_low()?;
        let ms1 = &mut shift_register.decompose()[MS_1];
        ms1.set_low()?;
        let ms2 = &mut shift_register.decompose()[MS_2];
        ms2.set_low()?;
        let ms3 = &mut shift_register.decompose()[MS_3];
        ms3.set_low()?;

        let ms4 = &mut shift_register.decompose()[MS_4];
        ms4.set_high()?;

        println!("Init battery driver");
        let mut battery_driver = Bq34z100g1Driver {
            i2c: MutexDevice::new(&I2C_DRIVER),
            delay: Delay::new(0),
            flash_block_data: [0; 32],
        };

        println!("Init rtc driver");
        let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));

        println!("Init rtc eeprom driver");
        let mut eeprom = {
            Eeprom24x::new_24x32(
                MutexDevice::new(&I2C_DRIVER),
                SlaveAddr::Alternative(true, true, true),
            )
        };

        let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18.downgrade())?;
        one_wire_pin.set_pull(Pull::Floating)?;

        let rtc_time = rtc.datetime();
        match rtc_time {
            OkStd(tt) => {
                println!("Rtc Module reports time at UTC {}", tt);
            }
            Err(err) => {
                println!("Rtc Module could not be read {:?}", err);
            }
        }
        match eeprom.read_byte(0) {
            OkStd(byte) => {
                println!("Read first byte with status {}", byte);
            }
            Err(err) => {
                println!("Eeprom could not read first byte {:?}", err);
            }
        }

        //init,reset rtc memory depending on cause
        let mut init_rtc_store: bool = false;
        let mut to_config_mode: bool = false;
        let reasons = ResetReason::get();
        match reasons {
            ResetReason::Software => {}
            ResetReason::ExternalPin => {}
            ResetReason::Watchdog => {
                init_rtc_store = true;
            }
            ResetReason::Sdio => init_rtc_store = true,
            ResetReason::Panic => init_rtc_store = true,
            ResetReason::InterruptWatchdog => init_rtc_store = true,
            ResetReason::PowerOn => init_rtc_store = true,
            ResetReason::Unknown => init_rtc_store = true,
            ResetReason::Brownout => init_rtc_store = true,
            ResetReason::TaskWatchdog => init_rtc_store = true,
            ResetReason::DeepSleep => {}
            ResetReason::USBPeripheral => {
                init_rtc_store = true;
                to_config_mode = true;
            }
            ResetReason::JTAG => init_rtc_store = true,
        };
        log(
            LogMessage::ResetReason,
            init_rtc_store as u32,
            to_config_mode as u32,
            "",
            &format!("{reasons:?}"),
        );
        if init_rtc_store {
            unsafe {
                LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT];
                CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT];
                LOW_VOLTAGE_DETECTED = false;
                crate::log::init();
                RESTART_TO_CONF = to_config_mode;
            };
        } else {
            unsafe {
                if to_config_mode {
                    RESTART_TO_CONF = true;
                }
                log(
                    LogMessage::RestartToConfig,
                    RESTART_TO_CONF as u32,
                    0,
                    "",
                    "",
                );
                log(
                    LogMessage::LowVoltage,
                    LOW_VOLTAGE_DETECTED as u32,
                    0,
                    "",
                    "",
                );
                for i in 0..PLANT_COUNT {
                    println!(
                        "LAST_WATERING_TIMESTAMP[{}] = UTC {}",
                        i, LAST_WATERING_TIMESTAMP[i]
                    );
                }
                for i in 0..PLANT_COUNT {
                    println!(
                        "CONSECUTIVE_WATERING_PLANT[{}] = {}",
                        i, CONSECUTIVE_WATERING_PLANT[i]
                    );
                }
            }
        }

        let mut counter_unit1 = PcntDriver::new(
            peripherals.pcnt0,
            Some(peripherals.pins.gpio22),
            Option::<AnyInputPin>::None,
            Option::<AnyInputPin>::None,
            Option::<AnyInputPin>::None,
        )?;

        counter_unit1.channel_config(
            PcntChannel::Channel0,
            PinIndex::Pin0,
            PinIndex::Pin1,
            &PcntChannelConfig {
                lctrl_mode: PcntControlMode::Keep,
                hctrl_mode: PcntControlMode::Keep,
                pos_mode: PcntCountMode::Increment,
                neg_mode: PcntCountMode::Hold,
                counter_h_lim: i16::MAX,
                counter_l_lim: 0,
            },
        )?;

        let sys_loop = EspSystemEventLoop::take()?;
        let nvs = EspDefaultNvsPartition::take()?;
        let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;

        let adc_config = AdcChannelConfig {
            attenuation: attenuation::DB_11,
            resolution: Resolution::Resolution12Bit,
            calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
        };
        let tank_driver = AdcDriver::new(peripherals.adc1)?;
        let tank_channel: AdcChannelDriver<Gpio5, AdcDriver<esp_idf_hal::adc::ADC1>> =
            AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?;

        let mut solar_is_day = PinDriver::input(peripherals.pins.gpio7.downgrade())?;
        solar_is_day.set_pull(Pull::Floating)?;

        let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?;
        boot_button.set_pull(Pull::Floating)?;

        let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?;
        light.set_pull(Pull::Floating)?;

        let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?;
        main_pump.set_pull(Pull::Floating)?;
        main_pump.set_low()?;
        let mut tank_power = PinDriver::input_output(peripherals.pins.gpio11.downgrade())?;
        tank_power.set_pull(Pull::Floating)?;
        let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?;
        general_fault.set_pull(Pull::Floating)?;
        general_fault.set_low()?;

        let one_wire_bus = OneWire::new(one_wire_pin)
            .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;

        let status = print_battery(&mut battery_driver);
        match status {
            OkStd(_) => {}
            Err(err) => {
                log(
                    LogMessage::BatteryCommunicationError,
                    0u32,
                    0,
                    "",
                    &format!("{err:?})"),
                );
            }
        }
        let mut shift_register_enable_invert = PinDriver::output(peripherals.pins.gpio21.downgrade())?;


        unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) };
        shift_register_enable_invert.set_low()?;
        unsafe { gpio_hold_en(shift_register_enable_invert.pin()) };

        let rv = Mutex::new(HAL {
            board_hal: BoardHal::V3 {
                shift_register,
                shift_register_enable_invert,
                tank_channel,
                solar_is_day,
                light,
                main_pump,
                tank_power,
                general_fault,
                rtc,
                eeprom,
            },
            esp: ESP {
                mqtt_client: None,
                signal_counter: counter_unit1,
                wifi_driver,
                boot_button,
                one_wire_bus,
            },
            battery_monitor: BatteryMonitor::BQ34Z100G1 {
                battery_driver
            },
        });
        Ok(rv)
    }
}
