added solar ina handling, adjusted website
This commit is contained in:
		| @@ -7,6 +7,7 @@ use chrono::{DateTime, Utc}; | ||||
| use embedded_hal::digital::OutputPin; | ||||
| use esp_idf_hal::gpio::{IOPin, Pull}; | ||||
| use esp_idf_hal::gpio::{InputOutput, PinDriver}; | ||||
| use measurements::{Current, Voltage}; | ||||
|  | ||||
| pub struct Initial<'a> { | ||||
|     pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
| @@ -123,4 +124,12 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { | ||||
|         self.esp.save_config(&self.config)?; | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|         bail!("Please configure board revision") | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -50,6 +50,7 @@ use esp_idf_hal::gpio::{ | ||||
| use esp_idf_hal::pcnt::PCNT0; | ||||
| use esp_idf_hal::prelude::Peripherals; | ||||
| use esp_idf_hal::reset::ResetReason; | ||||
| use measurements::{Current, Voltage}; | ||||
| use pca9535::StandardExpanderInterface; | ||||
|  | ||||
| //Only support for 8 right now! | ||||
| @@ -115,15 +116,18 @@ impl Default for BackupHeader { | ||||
| } | ||||
|  | ||||
| pub trait BoardInteraction<'a> { | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; | ||||
|     fn is_day(&self) -> bool; | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage>; | ||||
|     fn get_mptt_current(&mut self) -> Result<Current>; | ||||
|  | ||||
|     fn get_esp(&mut self) -> &mut ESP<'a>; | ||||
|     fn get_config(&mut self) -> &PlantControllerConfig; | ||||
|     fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>; | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; | ||||
|     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 is_day(&self) -> bool; | ||||
|     //should be multsampled | ||||
|     fn water_temperature_c(&mut self) -> Result<f32>; | ||||
|     /// return median tank sensor value in milli volt | ||||
|   | ||||
| @@ -23,6 +23,7 @@ use esp_idf_hal::pcnt::{ | ||||
|     PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, | ||||
| }; | ||||
| use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; | ||||
| use measurements::{Current, Voltage}; | ||||
| use one_wire_bus::OneWire; | ||||
| use plant_ctrl2::sipo::ShiftRegister40; | ||||
| use std::result::Result::Ok as OkStd; | ||||
| @@ -247,6 +248,26 @@ pub(crate) fn create_v3( | ||||
| } | ||||
|  | ||||
| impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { | ||||
|         Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.solar_is_day.get_level().into() | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> Result<Voltage> { | ||||
|         //if working this is the hardware set mppt voltage | ||||
|         if self.is_day() { | ||||
|             Ok(Voltage::from_volts(15_f64)) | ||||
|         } else { | ||||
|             Ok(Voltage::from_volts(0_f64)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> Result<Current> { | ||||
|         bail!("Board does not have current sensor") | ||||
|     } | ||||
|     fn get_esp(&mut self) -> &mut ESP<'a> { | ||||
|         &mut self.esp | ||||
|     } | ||||
| @@ -259,10 +280,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         &mut self.battery_monitor | ||||
|     } | ||||
|  | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> Result<()> { | ||||
|         Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?) | ||||
|     } | ||||
|  | ||||
|     fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { | ||||
|         let _ = self.shift_register.decompose()[AWAKE].set_low(); | ||||
|         deep_sleep(duration_in_ms) | ||||
| @@ -364,10 +381,6 @@ impl<'a> BoardInteraction<'a> for V3<'a> { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.solar_is_day.get_level().into() | ||||
|     } | ||||
|  | ||||
|     fn water_temperature_c(&mut self) -> Result<f32> { | ||||
|         self.one_wire_bus | ||||
|             .reset(&mut self.esp.delay) | ||||
|   | ||||
| @@ -23,12 +23,14 @@ use esp_idf_hal::pcnt::{ | ||||
|     PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, | ||||
| }; | ||||
| use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError}; | ||||
| use ina219::address::Address; | ||||
| use ina219::address::{Address, Pin}; | ||||
| use ina219::calibration::{Calibration, UnCalibrated}; | ||||
| use ina219::SyncIna219; | ||||
| use measurements::{Current, Resistance, Voltage}; | ||||
| use one_wire_bus::OneWire; | ||||
| use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; | ||||
| use std::result::Result::Ok as OkStd; | ||||
| use ina219::configuration::{Configuration, OperatingMode}; | ||||
|  | ||||
| const MS0: u8 = 1_u8; | ||||
| const MS1: u8 = 0_u8; | ||||
| @@ -37,15 +39,78 @@ const MS3: u8 = 4_u8; | ||||
| const MS4: u8 = 2_u8; | ||||
| const SENSOR_ON: u8 = 5_u8; | ||||
|  | ||||
| pub enum Charger<'a> { | ||||
|     SolarMpptV1 { | ||||
|         mppt_ina: SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>, | ||||
|         solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, | ||||
|         charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| impl<'a> Charger<'a> { | ||||
|     pub(crate) fn powersave(&mut self) { | ||||
|         match self { Charger::SolarMpptV1 { mppt_ina, .. } => { | ||||
|             let _ = mppt_ina.set_configuration(Configuration { | ||||
|                 reset: Default::default(), | ||||
|                 bus_voltage_range: Default::default(), | ||||
|                 shunt_voltage_range: Default::default(), | ||||
|                 bus_resolution: Default::default(), | ||||
|                 shunt_resolution: Default::default(), | ||||
|                 operating_mode: OperatingMode::PowerDown, | ||||
|             }).map_err(|e| { | ||||
|                 println!( | ||||
|                     "Error setting ina mppt configuration during deepsleep preparation{:?}", | ||||
|                     e | ||||
|                 ); | ||||
|             }); | ||||
|         } } | ||||
|     } | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { | ||||
|         match self { | ||||
|             Self::SolarMpptV1 { | ||||
|                 charge_indicator, .. | ||||
|             } => { | ||||
|                 charge_indicator.set_state(charging.into())?; | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         match self { | ||||
|             Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.get_level().into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> { | ||||
|         let voltage = match self { | ||||
|             Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina | ||||
|                 .bus_voltage() | ||||
|                 .map(|v| Voltage::from_millivolts(v.voltage_mv() as f64))?, | ||||
|         }; | ||||
|         Ok(voltage) | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> anyhow::Result<Current> { | ||||
|         let current = match self { | ||||
|             Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina.shunt_voltage().map(|v| { | ||||
|                 let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64); | ||||
|                 let shut_value = Resistance::from_ohms(0.05_f64); | ||||
|                 let current = shunt_voltage.as_volts() / shut_value.as_ohms(); | ||||
|                 Current::from_amperes(current) | ||||
|             })?, | ||||
|         }; | ||||
|         Ok(current) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct V4<'a> { | ||||
|     mppt_ina: SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>, | ||||
|     esp: ESP<'a>, | ||||
|     charger: Charger<'a>, | ||||
|     battery_monitor: Box<dyn BatteryInteraction + Send>, | ||||
|     config: PlantControllerConfig, | ||||
|     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>, | ||||
|     signal_counter: PcntDriver<'a>, | ||||
|     charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>, | ||||
|     light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
|     tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, | ||||
| @@ -173,7 +238,21 @@ pub(crate) fn create_v4( | ||||
|         let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin); | ||||
|     } | ||||
|  | ||||
|     let mut mppt_ina = SyncIna219::new(MutexDevice::new(&I2C_DRIVER), Address::from_byte(68)?)?; | ||||
|     //TODO error handling is not done nicely here, should not break if ina is not responsive | ||||
|     let mut mppt_ina = SyncIna219::new( | ||||
|         MutexDevice::new(&I2C_DRIVER), | ||||
|         Address::from_pins(Pin::Vcc, Pin::Gnd), | ||||
|     )?; | ||||
|  | ||||
|     mppt_ina.set_configuration(Configuration{ | ||||
|         reset: Default::default(), | ||||
|         bus_voltage_range: Default::default(), | ||||
|         shunt_voltage_range: Default::default(), | ||||
|         bus_resolution: Default::default(), | ||||
|         shunt_resolution: ina219::configuration::Resolution::Avg128, | ||||
|         operating_mode: Default::default(), | ||||
|     })?; | ||||
|     //TODO this is probably laready done untill we are ready first time?, maybee add startup time comparison on access? | ||||
|     esp.delay.delay_ms( | ||||
|         mppt_ina | ||||
|             .configuration()? | ||||
| @@ -181,18 +260,17 @@ pub(crate) fn create_v4( | ||||
|             .unwrap() | ||||
|             .as_millis() as u32, | ||||
|     ); | ||||
|     println!("Bus Voltage: {}", mppt_ina.bus_voltage()?); | ||||
|     println!("Shunt Voltage: {}", mppt_ina.shunt_voltage()?); | ||||
|     let volt = (mppt_ina.shunt_voltage()?.shunt_voltage_mv()) as f32 / 1000_f32; | ||||
|     let current = volt / 0.05; | ||||
|     println!("Shunt Current: {}", current); | ||||
|  | ||||
|     let charger = Charger::SolarMpptV1 { | ||||
|         mppt_ina, | ||||
|         solar_is_day, | ||||
|         charge_indicator, | ||||
|     }; | ||||
|  | ||||
|     let v = V4 { | ||||
|         mppt_ina, | ||||
|         esp, | ||||
|         awake, | ||||
|         tank_channel, | ||||
|         solar_is_day, | ||||
|         signal_counter, | ||||
|         light, | ||||
|         tank_power, | ||||
| @@ -202,9 +280,9 @@ pub(crate) fn create_v4( | ||||
|         general_fault, | ||||
|         pump_expander, | ||||
|         sensor_expander, | ||||
|         charge_indicator, | ||||
|         config, | ||||
|         battery_monitor, | ||||
|         charger, | ||||
|     }; | ||||
|     Ok(Box::new(v)) | ||||
| } | ||||
| @@ -223,14 +301,12 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|     } | ||||
|  | ||||
|     fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> { | ||||
|         self.charge_indicator | ||||
|             .set_state(charging.into()) | ||||
|             .expect("cannot fail"); | ||||
|         Ok(()) | ||||
|         self.charger.set_charge_indicator(charging) | ||||
|     } | ||||
|  | ||||
|     fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { | ||||
|         self.awake.set_low().unwrap(); | ||||
|         self.charger.powersave(); | ||||
|         deep_sleep(duration_in_ms); | ||||
|     } | ||||
|  | ||||
| @@ -331,7 +407,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|     } | ||||
|  | ||||
|     fn is_day(&self) -> bool { | ||||
|         self.solar_is_day.get_level().into() | ||||
|         self.charger.is_day() | ||||
|     } | ||||
|  | ||||
|     fn water_temperature_c(&mut self) -> anyhow::Result<f32> { | ||||
| @@ -568,4 +644,12 @@ impl<'a> BoardInteraction<'a> for V4<'a> { | ||||
|         self.esp.save_config(&self.config)?; | ||||
|         anyhow::Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> { | ||||
|         self.charger.get_mptt_voltage() | ||||
|     } | ||||
|  | ||||
|     fn get_mptt_current(&mut self) -> anyhow::Result<Current> { | ||||
|         self.charger.get_mptt_current() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -756,13 +756,24 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! { | ||||
|     loop { | ||||
|         unsafe { | ||||
|             let mut board = BOARD_ACCESS.lock().unwrap(); | ||||
|             if let Ok(charging) = board | ||||
|  | ||||
|             //we have mppt controller, ask it for charging current | ||||
|             if let Ok(current) = board.board_hal.get_mptt_current() { | ||||
|                 let _ = board.board_hal.set_charge_indicator(current.as_milliamperes() > 20_f64); | ||||
|             } | ||||
|             //fallback to battery controller and ask it instead | ||||
|             else if let Ok(charging) = board | ||||
|                 .board_hal | ||||
|                 .get_battery_monitor() | ||||
|                 .average_current_milli_ampere() | ||||
|             { | ||||
|                 let _ = board.board_hal.set_charge_indicator(charging > 20); | ||||
|             } | ||||
|             else { | ||||
|                 //who knows | ||||
|                 let _ = board.board_hal.set_charge_indicator(false); | ||||
|             } | ||||
|  | ||||
|             match wait_type { | ||||
|                 WaitType::MissingConfig => { | ||||
|                     // Keep existing behavior: circular filling pattern | ||||
|   | ||||
| @@ -40,6 +40,13 @@ struct Moistures { | ||||
|     moisture_b: Vec<std::string::String>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Debug)] | ||||
| struct SolarState { | ||||
|     mppt_voltage: f32, | ||||
|     mppt_current: f32, | ||||
|     is_day: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| struct SetTime<'a> { | ||||
|     time: &'a str, | ||||
| @@ -218,6 +225,18 @@ fn set_config( | ||||
|     anyhow::Ok(Some("saved".to_owned())) | ||||
| } | ||||
|  | ||||
| fn get_solar_state( | ||||
|     _request: &mut Request<&mut EspHttpConnection>, | ||||
| ) -> Result<Option<std::string::String>, anyhow::Error> { | ||||
|     let mut board = BOARD_ACCESS.lock().expect("board access"); | ||||
|     let state = SolarState { | ||||
|         mppt_voltage: board.board_hal.get_mptt_voltage()?.as_volts() as f32, | ||||
|         mppt_current: board.board_hal.get_mptt_current()?.as_amperes() as f32, | ||||
|         is_day: board.board_hal.is_day(), | ||||
|     }; | ||||
|     anyhow::Ok(Some(serde_json::to_string(&state)?)) | ||||
| } | ||||
|  | ||||
| fn get_battery_state( | ||||
|     _request: &mut Request<&mut EspHttpConnection>, | ||||
| ) -> Result<Option<std::string::String>, anyhow::Error> { | ||||
| @@ -383,6 +402,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> { | ||||
|             handle_error_to500(request, get_battery_state) | ||||
|         }) | ||||
|         .unwrap(); | ||||
|     server | ||||
|         .fn_handler("/solar", Method::Get, |request| { | ||||
|             handle_error_to500(request, get_solar_state) | ||||
|         }) | ||||
|         .unwrap(); | ||||
|     server | ||||
|         .fn_handler("/time", Method::Get, |request| { | ||||
|             handle_error_to500(request, get_time) | ||||
|   | ||||
| @@ -38,6 +38,12 @@ export interface FileList { | ||||
|   iter_error: string, | ||||
| } | ||||
|  | ||||
| export interface SolarState{ | ||||
|   mppt_voltage: number, | ||||
|   mppt_current: number, | ||||
|   is_day: boolean | ||||
| } | ||||
|  | ||||
| export interface FileInfo{ | ||||
|   filename: string, | ||||
|   size: number, | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|     <select class="boardvalue" id="hardware_board_value"> | ||||
|     </select> | ||||
| </div> | ||||
| <div class="flexcontainer" style="text-decoration-line: line-through;"> | ||||
| <div class="flexcontainer"> | ||||
|     <div class="boardkey">BatteryMonitor</div> | ||||
|     <select class="boardvalue" id="hardware_battery_value"> | ||||
|     </select> | ||||
|   | ||||
| @@ -149,6 +149,8 @@ | ||||
|     </div> | ||||
|     <div id="batteryview" class="subcontainer"> | ||||
|     </div> | ||||
|     <div id="solarview" class="subcontainer"> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="flexcontainer"> | ||||
|   | ||||
| @@ -28,8 +28,9 @@ import { | ||||
|   SetTime, SSIDList, TankInfo, | ||||
|   TestPump, | ||||
|   VersionInfo, | ||||
|   FileList | ||||
|   FileList, SolarState | ||||
| } from "./api"; | ||||
| import {SolarView} from "./solarview"; | ||||
|  | ||||
| export class Controller { | ||||
|   loadTankInfo() : Promise<void> { | ||||
| @@ -160,7 +161,7 @@ export class Controller { | ||||
|         console.log(error); | ||||
|       }); | ||||
|   } | ||||
|   updateBatteryData(): Promise<void> { | ||||
|   updateBatteryData() { | ||||
|     return fetch(PUBLIC_URL + "/battery") | ||||
|       .then(response => response.json()) | ||||
|       .then(json => json as BatteryState) | ||||
| @@ -172,6 +173,18 @@ export class Controller { | ||||
|         console.log(error); | ||||
|       }) | ||||
|   } | ||||
|   updateSolarData() { | ||||
|     return fetch(PUBLIC_URL + "/solar") | ||||
|       .then(response => response.json()) | ||||
|       .then(json => json as SolarState) | ||||
|       .then(solar => { | ||||
|         controller.solarView.update(solar) | ||||
|       }) | ||||
|       .catch(error => { | ||||
|         controller.solarView.update(null) | ||||
|         console.log(error); | ||||
|       }) | ||||
|   } | ||||
|   uploadNewFirmware(file: File) { | ||||
|     var current = 0; | ||||
|     var max = 100; | ||||
| @@ -244,6 +257,7 @@ export class Controller { | ||||
|     //load from remote to be clean | ||||
|     controller.downloadConfig() | ||||
|   } | ||||
|  | ||||
|   backupConfig(json: string, statusCallback: (status: string) => void) { | ||||
|     controller.progressview.addIndeterminate("backup_config", "Backingup Config") | ||||
|     fetch(PUBLIC_URL + "/backup_config", { | ||||
| @@ -465,6 +479,7 @@ export class Controller { | ||||
|   readonly firmWareView: OTAView; | ||||
|   readonly progressview: ProgressView; | ||||
|   readonly batteryView: BatteryView; | ||||
|   readonly solarView: SolarView; | ||||
|   readonly fileview: FileView; | ||||
|   readonly logView: LogView | ||||
|   constructor() { | ||||
| @@ -473,6 +488,7 @@ export class Controller { | ||||
|     this.networkView = new NetworkConfigView(this, PUBLIC_URL) | ||||
|     this.tankView = new TankConfigView(this) | ||||
|     this.batteryView = new BatteryView(this) | ||||
|     this.solarView = new SolarView(this) | ||||
|     this.nightLampView = new NightLampView(this) | ||||
|     this.submitView = new SubmitView(this) | ||||
|     this.firmWareView = new OTAView(this) | ||||
| @@ -489,21 +505,16 @@ export class Controller { | ||||
|       controller.exit(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   selftest() { | ||||
|  | ||||
|   } | ||||
| } | ||||
| const controller = new Controller(); | ||||
| controller.progressview.removeProgress("rebooting"); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| const tasks = [ | ||||
|   { task: controller.populateTimezones, displayString: "Populating Timezones" }, | ||||
|   { task: controller.updateRTCData, displayString: "Updating RTC Data" }, | ||||
|   { task: controller.updateBatteryData, displayString: "Updating Battery Data" }, | ||||
|   { task: controller.updateSolarData, displayString: "Updating Solar Data" }, | ||||
|   { task: controller.downloadConfig, displayString: "Downloading Configuration" }, | ||||
|   { task: controller.version, displayString: "Fetching Version Information" }, | ||||
|   { task: controller.updateFileList, displayString: "Updating File List" }, | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|   <div class="lightkey">Test Nightlight</div> | ||||
|   <input class="lightcheckbox" type="checkbox" id="night_lamp_test"> | ||||
| </div> | ||||
| <div class="flexcontainer" style="text-decoration-line: line-through;"> | ||||
| <div class="flexcontainer"> | ||||
|     <div class="lightkey">Enable Nightlight</div> | ||||
|     <input class="lightcheckbox" type="checkbox" id="night_lamp_enabled"> | ||||
| </div> | ||||
|   | ||||
| @@ -29,7 +29,7 @@ export class OTAView { | ||||
|         }; | ||||
|  | ||||
|         test.onclick = () => { | ||||
|             controller.selftest(); | ||||
|             controller.selfTest(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										29
									
								
								rust/src_webpack/src/solarview.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								rust/src_webpack/src/solarview.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <style> | ||||
|   .solarflexkey { | ||||
|     min-width: 150px; | ||||
|   } | ||||
|   .solarflexvalue { | ||||
|     text-wrap: nowrap;  | ||||
|     flex-grow: 1; | ||||
|   } | ||||
| </style> | ||||
|  | ||||
| <div class="flexcontainer"> | ||||
|     <div class="subtitle"> | ||||
|         Mppt: | ||||
|     </div> | ||||
|     <input id="solar_auto_refresh" type="checkbox">⟳ | ||||
| </div> | ||||
|  | ||||
| <div class="flexcontainer"> | ||||
|     <span class="solarflexkey">Mppt mV:</span> | ||||
|     <span class="solarflexvalue" id="solar_voltage_milli_volt"></span> | ||||
| </div> | ||||
| <div class="flexcontainer"> | ||||
|     <span class="solarflexkey">Mppt mA:</span> | ||||
|     <span class="solarflexvalue" id="solar_current_milli_ampere" ></span> | ||||
| </div> | ||||
| <div class="flexcontainer"> | ||||
|     <span class="solarflexkey">is Day:</span> | ||||
|     <span class="solarflexvalue" id="solar_is_day" ></span> | ||||
| </div> | ||||
							
								
								
									
										49
									
								
								rust/src_webpack/src/solarview.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								rust/src_webpack/src/solarview.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| import { Controller } from "./main"; | ||||
| import {BatteryState, SolarState} from "./api"; | ||||
|  | ||||
| export class SolarView{ | ||||
|   solar_voltage_milli_volt: HTMLSpanElement; | ||||
|   solar_current_milli_ampere: HTMLSpanElement; | ||||
|   solar_is_day: HTMLSpanElement; | ||||
|   solar_auto_refresh: HTMLInputElement; | ||||
|     timer: NodeJS.Timeout | undefined; | ||||
|     controller: Controller; | ||||
|  | ||||
|     constructor (controller:Controller) { | ||||
|       (document.getElementById("solarview") as HTMLElement).innerHTML = require("./solarview.html") | ||||
|         this.solar_voltage_milli_volt = document.getElementById("solar_voltage_milli_volt") as HTMLSpanElement; | ||||
|         this.solar_current_milli_ampere = document.getElementById("solar_current_milli_ampere") as HTMLSpanElement; | ||||
|         this.solar_is_day = document.getElementById("solar_is_day") as HTMLSpanElement; | ||||
|         this.solar_auto_refresh = document.getElementById("solar_auto_refresh") as HTMLInputElement; | ||||
|  | ||||
|         this.controller = controller | ||||
|         this.solar_auto_refresh.onchange = () => { | ||||
|             if(this.timer){ | ||||
|               clearTimeout(this.timer) | ||||
|             } | ||||
|             if(this.solar_auto_refresh.checked){ | ||||
|               controller.updateSolarData() | ||||
|             } | ||||
|           } | ||||
|     } | ||||
|  | ||||
|     update(solarState: SolarState|null){ | ||||
|         if (solarState == null) { | ||||
|             this.solar_voltage_milli_volt.innerText = "N/A" | ||||
|             this.solar_current_milli_ampere.innerText = "N/A" | ||||
|             this.solar_is_day.innerText = "N/A" | ||||
|         } else { | ||||
|             this.solar_voltage_milli_volt.innerText = solarState.mppt_voltage.toFixed(2) | ||||
|             this.solar_current_milli_ampere.innerText = String(+solarState.mppt_current) | ||||
|             this.solar_is_day.innerText = solarState.is_day?"🌞":"🌙" | ||||
|         } | ||||
|  | ||||
|         if(this.solar_auto_refresh.checked){ | ||||
|             this.timer = setTimeout(this.controller.updateSolarData, 1000); | ||||
|           } else { | ||||
|             if(this.timer){ | ||||
|               clearTimeout(this.timer) | ||||
|             } | ||||
|           } | ||||
|     } | ||||
|   } | ||||
| @@ -7,14 +7,31 @@ | ||||
|         word-wrap: break-word; | ||||
|         overflow: scroll; | ||||
|     } | ||||
|     .submitbutton{ | ||||
|         padding: 1em 1em; | ||||
|         background: #667eea; | ||||
|         color: white; | ||||
|         border: none; | ||||
|         border-radius: 8px; | ||||
|         font-size: 1.1em; | ||||
|         font-weight: bold; | ||||
|         cursor: pointer; | ||||
|         transition: all 0.3s ease; | ||||
|         letter-spacing: 1px; | ||||
|         margin: 1em 0; | ||||
|     } | ||||
|  | ||||
|     .submitbutton:hover { | ||||
|         background: #1c4e63; | ||||
|     } | ||||
| </style> | ||||
| <button class="submitbutton" id="submit">Submit</button> | ||||
| <br> | ||||
| <button id="showJson">Show Json</button> | ||||
| <div id="rawdata" class="flexcontainer" style="display: none;"> | ||||
|     <div class="submitarea" id="json" contenteditable="true"></div> | ||||
|     <div class="submitarea" id="backupjson">backup will be here</div>     | ||||
| </div> | ||||
| <button id="submit">Submit</button> | ||||
| <div>BackupStatus:</div> | ||||
| <div id="backuptimestamp"></div> | ||||
| <div id="backupsize"></div> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ console.log("Dev server is " + isDevServer); | ||||
| var host; | ||||
| if (isDevServer){ | ||||
|   //ensure no trailing / | ||||
|   host = 'http://192.168.71.1'; | ||||
|   host = 'http://10.23.44.186'; | ||||
| } else { | ||||
|   host = ''; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user