clippy happier

This commit is contained in:
Empire Phoenix 2025-05-07 00:00:21 +02:00
parent a401d4de7b
commit 26da6b39cc
17 changed files with 234 additions and 236 deletions

7
rust/.idea/dictionaries/project.xml generated Normal file
View File

@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>sntp</w>
</words>
</dictionary>
</component>

View File

@ -65,7 +65,7 @@ struct LightState {
active: bool,
/// led should not be on at this time of day
out_of_work_hour: bool,
/// battery is low so do not use led
/// the battery is low so do not use led
battery_low: bool,
/// the sun is up
is_day: bool,
@ -79,8 +79,8 @@ enum SensorError {
OpenCircuit { hz: f32, min: f32 },
}
fn safe_main() -> anyhow::Result<()> {
// It is necessary to call this function once. Otherwise some patches to the runtime
fn safe_main() -> Result<()> {
// It is necessary to call this function once. Otherwise, some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
@ -100,7 +100,7 @@ fn safe_main() -> anyhow::Result<()> {
let version = get_version();
println!(
"Version useing git has {} build on {}",
"Version using git has {} build on {}",
version.git_hash, version.build_time
);
@ -129,16 +129,16 @@ fn safe_main() -> anyhow::Result<()> {
&format!("unknown {ota_state}")
}
};
log(log::LogMessage::PartitionState, 0, 0, "", ota_state_string);
log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap();
board.general_fault(false);
log(log::LogMessage::MountingFilesystem, 0, 0, "", "");
log(LogMessage::MountingFilesystem, 0, 0, "", "");
board.mount_file_system()?;
let free_space = board.file_system_size()?;
log(
log::LogMessage::FilesystemMount,
LogMessage::FilesystemMount,
free_space.free_size as u32,
free_space.total_size as u32,
&free_space.used_size.to_string(),
@ -157,17 +157,17 @@ fn safe_main() -> anyhow::Result<()> {
})
.unwrap();
//check if we know the time current > 2020 (plausibility check, this code is newer than 2020)
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 {
to_config = true;
log(log::LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
}
println!("cur is {}", cur);
board.update_charge_indicator();
if board.get_restart_to_conf() {
log(log::LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
for _i in 0..2 {
board.general_fault(true);
Delay::new_default().delay_ms(100);
@ -177,9 +177,9 @@ fn safe_main() -> anyhow::Result<()> {
to_config = true;
board.general_fault(true);
board.set_restart_to_conf(false);
} else if board.is_mode_override() {
} else if board.mode_override_pressed() {
board.general_fault(true);
log(log::LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
for _i in 0..5 {
board.general_fault(true);
Delay::new_default().delay_ms(100);
@ -187,7 +187,7 @@ fn safe_main() -> anyhow::Result<()> {
Delay::new_default().delay_ms(100);
}
if board.is_mode_override() {
if board.mode_override_pressed() {
board.general_fault(true);
to_config = true;
} else {
@ -195,27 +195,26 @@ fn safe_main() -> anyhow::Result<()> {
}
}
let config: PlantControllerConfig;
match board.get_config() {
let config: PlantControllerConfig = match board.get_config() {
Ok(valid) => {
config = valid;
valid
}
Err(err) => {
log(
log::LogMessage::ConfigModeMissingConfig,
LogMessage::ConfigModeMissingConfig,
0,
0,
"",
&err.to_string(),
);
//config upload will trigger reboot!
let _ = board.wifi_ap(Option::None);
let _ = board.wifi_ap(None);
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
}
}
};
let mut wifi = false;
let mut mqtt = false;
@ -273,7 +272,7 @@ fn safe_main() -> anyhow::Result<()> {
}
Err(err) => println!(
"Could not start config override ap mode due to {}",
err.to_string()
err
),
}
}
@ -321,7 +320,7 @@ fn safe_main() -> anyhow::Result<()> {
}
log(
log::LogMessage::StartupInfo,
LogMessage::StartupInfo,
wifi as u32,
sntp as u32,
&mqtt.to_string(),
@ -329,7 +328,7 @@ fn safe_main() -> anyhow::Result<()> {
);
if to_config {
//check if client or ap mode and init wifi
//check if client or ap mode and init Wi-Fi
println!("executing config mode override");
//config upload will trigger reboot!
drop(board);
@ -337,7 +336,7 @@ fn safe_main() -> anyhow::Result<()> {
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::ConfigButton, reboot_now.clone());
} else {
log(log::LogMessage::NormalRun, 0, 0, "", "");
log(LogMessage::NormalRun, 0, 0, "", "");
}
let dry_run = false;
@ -367,10 +366,10 @@ fn safe_main() -> anyhow::Result<()> {
0,
0,
"",
&format!("{}", &err.to_string()),
&err.to_string()
),
}
// disabled can not trigger this because of wrapping if is_enabled
// disabled cannot trigger this because of wrapping if is_enabled
board.general_fault(true);
} else if tank_state.warn_level(&config.tank).is_ok_and(|warn| warn) {
log(LogMessage::TankWaterLevelLow, 0, 0, "", "");
@ -432,15 +431,15 @@ fn safe_main() -> anyhow::Result<()> {
let pump_required = plantstate
.iter()
.zip(&config.plants)
.any(|(it, conf)| it.needs_to_be_watered(&conf, &timezone_time))
.any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time))
&& !water_frozen;
if pump_required {
log(log::LogMessage::EnableMain, dry_run as u32, 0, "", "");
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
if !dry_run {
board.any_pump(true)?; // what does this do? Does it need to be reset?
}
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() {
if state.needs_to_be_watered(&plant_config, &timezone_time) {
if state.needs_to_be_watered(plant_config, &timezone_time) {
let pump_count = board.consecutive_pump_count(plant_id) + 1;
board.store_consecutive_pump_count(plant_id, pump_count);
//TODO(judge) where to put this?
@ -456,7 +455,7 @@ fn safe_main() -> anyhow::Result<()> {
// board.fault(plant, true);
//}
log(
log::LogMessage::PumpPlant,
LogMessage::PumpPlant,
(plant_id + 1) as u32,
plant_config.pump_time_s as u32,
&dry_run.to_string(),
@ -470,7 +469,7 @@ fn safe_main() -> anyhow::Result<()> {
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
board.pump(plant_id, false)?;
}
} else if !state.pump_in_timeout(&plant_config, &timezone_time) {
} else if !state.pump_in_timeout(plant_config, &timezone_time) {
// plant does not need to be watered and is not in timeout
// -> reset consecutive pump count
board.store_consecutive_pump_count(plant_id, 0);
@ -504,23 +503,21 @@ fn safe_main() -> anyhow::Result<()> {
if config.night_lamp.night_lamp_only_when_dark {
if !light_state.is_day {
if light_state.battery_low {
board.light(false).unwrap();
board.light(false)?;
} else {
light_state.active = true;
board.light(true).unwrap();
board.light(true)?;
}
}
} else {
if light_state.battery_low {
board.light(false).unwrap();
} else if light_state.battery_low {
board.light(false)?;
} else {
light_state.active = true;
board.light(true).unwrap();
}
board.light(true)?;
}
} else {
light_state.active = false;
board.light(false).unwrap();
board.light(false)?;
}
println!("Lightstate is {:?}", light_state);
@ -547,10 +544,6 @@ fn safe_main() -> anyhow::Result<()> {
};
let _ = board.mqtt_publish(&config, "/state", "sleep".as_bytes());
//determine next event
//is light out of work trigger soon?
//is battery low ??
//is deep sleep
mark_app_valid();
let stay_alive_mqtt = STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed);
@ -576,7 +569,7 @@ fn publish_battery_state(
let bat = board.get_battery_state();
match serde_json::to_string(&bat) {
Ok(state) => {
let _ = board.mqtt_publish(&config, "/battery", state.as_bytes());
let _ = board.mqtt_publish(config, "/battery", state.as_bytes());
}
Err(err) => {
println!("Error publishing battery_state {}", err);
@ -631,11 +624,9 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
drop(lock);
vTaskDelay(delay);
if wait_type == WaitType::MqttConfig {
if !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed) {
if wait_type == WaitType::MqttConfig && !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed) {
reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
//ensure clean http answer
Delay::new_default().delay_ms(500);
@ -655,7 +646,7 @@ fn main() {
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false);
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
}
// if safe_main exists with error, rollback to known good ota version
// if safe_main exists with an error, rollback to a known good ota version
Err(err) => {
println!("Failed main {}", err);
let _rollback_successful = rollback_and_reboot();
@ -665,22 +656,22 @@ fn main() {
}
fn to_string<T: Display>(value: Result<T>) -> String {
return match value {
match value {
Ok(v) => v.to_string(),
Err(err) => {
format!("{:?}", err)
}
};
}
}
pub fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool {
let curhour = cur.hour() as u8;
//eg 10-14
if start < end {
return curhour > start && curhour < end;
curhour > start && curhour < end
} else {
//eg 20-05
return curhour > start || curhour < end;
curhour > start || curhour < end
}
}
@ -695,11 +686,11 @@ fn get_version() -> VersionInfo {
} else {
"ota_0 @ "
};
return VersionInfo {
git_hash: (branch + "@" + hash),
VersionInfo {
git_hash: branch + "@" + hash,
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
partition: partition.to_owned() + &address.to_string(),
};
}
}
#[derive(Serialize, Debug)]

View File

@ -60,9 +60,9 @@ 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::{self, PlantControllerConfig};
use crate::config::PlantControllerConfig;
use crate::log::log;
use crate::{plant_hal, to_string, STAY_ALIVE};
use crate::{to_string, STAY_ALIVE};
//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
@ -168,7 +168,7 @@ pub struct PlantCtrlBoard<'a> {
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, esp_idf_hal::gpio::InputOutput>>,
one_wire_bus: OneWire<PinDriver<'a, Gpio18, InputOutput>>,
mqtt_client: Option<EspMqttClient<'a>>,
battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
rtc:
@ -229,9 +229,9 @@ impl PlantCtrlBoard<'_> {
pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.shift_register.decompose()[AWAKE].set_low().unwrap();
unsafe {
//if we dont do this here, we might just revert a newly flashed firmeware
//if we don't do this here, we might just revert newly flashed firmware
mark_app_valid();
//allow early wakup by pressing the boot button
//allow early wakeup by pressing the boot button
if duration_in_ms == 0 {
esp_restart();
} else {
@ -278,7 +278,8 @@ impl PlantCtrlBoard<'_> {
};
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
let data_start_address = 1 * self.eeprom.page_size() as u32;
//skip page 0, used by the header
let data_start_address = self.eeprom.page_size() as u32;
let mut data_buffer = vec![0_u8; header.size];
match self.eeprom.read_data(data_start_address, &mut data_buffer) {
OkStd(_) => {}
@ -337,9 +338,9 @@ impl PlantCtrlBoard<'_> {
OkStd(_) => {}
Err(err) => bail!("Error writing eeprom {:?}", err),
};
current_page = current_page + 1;
current_page += 1;
let iter = ((current_page / 1) % 8) as usize;
let iter = (current_page % 8) as usize;
if iter != lastiter {
for i in 0..PLANT_COUNT {
self.fault(i, iter == i);
@ -350,11 +351,11 @@ impl PlantCtrlBoard<'_> {
//update led here?
delay.delay_ms(5);
}
return Ok(());
Ok(())
}
pub fn get_battery_state(&mut self) -> BatteryState {
let bat = BatteryState {
BatteryState {
voltage_milli_volt: to_string(self.voltage_milli_volt()),
current_milli_ampere: to_string(self.average_current_milli_ampere()),
cycle_count: to_string(self.cycle_count()),
@ -363,14 +364,13 @@ impl PlantCtrlBoard<'_> {
state_of_charge: to_string(self.state_charge_percent()),
state_of_health: to_string(self.state_health_percent()),
temperature: to_string(self.bat_temperature()),
};
return bat;
}
}
pub fn list_files(&self) -> FileList {
let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
let mut file_system_corrupt = Option::None;
let mut file_system_corrupt = None;
let mut iter_error = None;
let mut result = Vec::new();
@ -386,7 +386,7 @@ impl PlantCtrlBoard<'_> {
filename: file.file_name().into_string().unwrap(),
size: file
.metadata()
.and_then(|it| core::result::Result::Ok(it.len()))
.and_then(|it| Result::Ok(it.len()))
.unwrap_or_default()
as usize,
};
@ -409,13 +409,13 @@ impl PlantCtrlBoard<'_> {
esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
}
return FileList {
FileList {
total,
used,
file_system_corrupt,
files: result,
iter_error,
};
}
}
pub fn delete_file(&self, filename: &str) -> Result<()> {
@ -430,11 +430,11 @@ impl PlantCtrlBoard<'_> {
pub fn get_file_handle(&self, filename: &str, write: bool) -> Result<File> {
let filepath = Path::new(BASE_PATH).join(Path::new(filename));
return Ok(if write {
Ok(if write {
File::create(filepath)?
} else {
File::open(filepath)?
});
})
}
pub fn is_day(&self) -> bool {
@ -522,17 +522,17 @@ impl PlantCtrlBoard<'_> {
7 => PUMP8_BIT,
_ => bail!("Invalid pump {plant}",),
};
//currently infailable error, keep for future as result anyway
//currently infallible error, keep for future as result anyway
self.shift_register.decompose()[index].set_state(enable.into())?;
Ok(())
}
pub fn last_pump_time(&self, plant: usize) -> Option<chrono::DateTime<Utc>> {
pub fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
return Some(DateTime::from_timestamp_millis(ts)?);
DateTime::from_timestamp_millis(ts)
}
pub fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
pub fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
unsafe {
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
}
@ -546,7 +546,7 @@ impl PlantCtrlBoard<'_> {
pub fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
unsafe {
return CONSECUTIVE_WATERING_PLANT[plant];
CONSECUTIVE_WATERING_PLANT[plant]
}
}
@ -569,18 +569,18 @@ impl PlantCtrlBoard<'_> {
pub fn low_voltage_in_cycle(&mut self) -> bool {
unsafe {
return LOW_VOLTAGE_DETECTED;
LOW_VOLTAGE_DETECTED
}
}
pub fn any_pump(&mut self, enable: bool) -> Result<()> {
{
self.main_pump.set_state(enable.into()).unwrap();
self.main_pump.set_state(enable.into())?;
Ok(())
}
}
pub fn time(&mut self) -> Result<chrono::DateTime<Utc>> {
pub 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)
@ -588,7 +588,7 @@ impl PlantCtrlBoard<'_> {
Ok(local_time)
}
pub fn sntp(&mut self, max_wait_ms: u32) -> Result<chrono::DateTime<Utc>> {
pub 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 {
@ -634,28 +634,26 @@ impl PlantCtrlBoard<'_> {
self.signal_counter.counter_pause()?;
self.signal_counter.counter_clear()?;
//Disable all
self.shift_register.decompose()[MS_4].set_high().unwrap();
self.shift_register.decompose()[MS_4].set_high()?;
self.sensor_multiplexer(sensor_channel)?;
self.shift_register.decompose()[MS_4].set_low().unwrap();
self.shift_register.decompose()[MS_4].set_low()?;
self.shift_register.decompose()[SENSOR_ON]
.set_high()
.unwrap();
.set_high()?;
let delay = Delay::new_default();
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
let factor = 1000 as f32 / measurement as f32;
let factor = 1000f32 / measurement as f32;
//give some time to stabilize
delay.delay_ms(10);
self.signal_counter.counter_resume()?;
delay.delay_ms(measurement);
self.signal_counter.counter_pause()?;
self.shift_register.decompose()[MS_4].set_high().unwrap();
self.shift_register.decompose()[MS_4].set_high()?;
self.shift_register.decompose()[SENSOR_ON]
.set_low()
.unwrap();
.set_low()?;
delay.delay_ms(10);
let unscaled = self.signal_counter.get_counter_value()? as i32;
let hz = unscaled as f32 * factor;
@ -707,7 +705,7 @@ impl PlantCtrlBoard<'_> {
//TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
self.wifi_driver.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid: ssid,
ssid,
password: pw,
..Default::default()
},
@ -716,7 +714,7 @@ impl PlantCtrlBoard<'_> {
None => {
self.wifi_driver.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid: ssid,
ssid,
auth_method: AuthMethod::None,
..Default::default()
},
@ -733,7 +731,7 @@ impl PlantCtrlBoard<'_> {
delay.delay_ms(250);
counter += 250;
if counter > max_wait {
//ignore these errors, wifi will not be used this
//ignore these errors, Wi-Fi will not be used this
self.wifi_driver.disconnect().unwrap_or(());
self.wifi_driver.stop().unwrap_or(());
bail!("Did not manage wifi connection within timeout");
@ -745,7 +743,7 @@ impl PlantCtrlBoard<'_> {
delay.delay_ms(250);
counter += 250;
if counter > max_wait {
//ignore these errors, wifi will not be used this
//ignore these errors, Wi-Fi will not be used this
self.wifi_driver.disconnect().unwrap_or(());
self.wifi_driver.stop().unwrap_or(());
bail!("Did not manage wifi connection within timeout");
@ -780,7 +778,7 @@ impl PlantCtrlBoard<'_> {
let mut total_size = 0;
let mut used_size = 0;
unsafe {
esp_idf_sys::esp!(esp_idf_sys::esp_spiffs_info(
esp_idf_sys::esp!(esp_spiffs_info(
storage.as_ptr(),
&mut total_size,
&mut used_size
@ -793,7 +791,7 @@ impl PlantCtrlBoard<'_> {
})
}
pub fn is_mode_override(&mut self) -> bool {
pub fn mode_override_pressed(&mut self) -> bool {
self.boot_button.get_level() == Level::Low
}
@ -802,16 +800,20 @@ impl PlantCtrlBoard<'_> {
let config = Path::new(CONFIG_FILE);
if config.exists() {
println!("Removing config");
std::fs::remove_file(config)?;
fs::remove_file(config)?;
}
//TODO clear eeprom
//destroy backup header
let dummy : [u8;0] = [];
self.backup_config(&dummy)?;
Ok(())
}
pub fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
match self.rtc.datetime() {
OkStd(rtc_time) => {
return Ok(rtc_time.and_utc());
Ok(rtc_time.and_utc())
}
Err(err) => {
bail!("Error getting rtc time {:?}", err)
@ -829,7 +831,7 @@ impl PlantCtrlBoard<'_> {
}
}
pub fn get_config(&mut self) -> Result<config::PlantControllerConfig> {
pub fn get_config(&mut self) -> Result<PlantControllerConfig> {
let cfg = File::open(CONFIG_FILE)?;
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
Ok(config)
@ -889,8 +891,8 @@ impl PlantCtrlBoard<'_> {
unsafe { vTaskDelay(100) };
}
for plant in 0..PLANT_COUNT {
let a = self.measure_moisture_hz(plant, plant_hal::Sensor::A);
let b = self.measure_moisture_hz(plant, plant_hal::Sensor::B);
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,
@ -905,11 +907,6 @@ impl PlantCtrlBoard<'_> {
Ok(())
}
pub fn is_wifi_config_file_existant(&mut self) -> bool {
let config = Path::new(CONFIG_FILE);
config.exists()
}
pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> {
let base_topic = config
.network
@ -956,8 +953,8 @@ impl PlantCtrlBoard<'_> {
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| {
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 {
@ -977,7 +974,7 @@ impl PlantCtrlBoard<'_> {
log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
} else {
log(LogMessage::UnknownTopic, 0, 0, "", &topic);
log(LogMessage::UnknownTopic, 0, 0, "", topic);
}
}
}
@ -1018,8 +1015,9 @@ impl PlantCtrlBoard<'_> {
}
})?;
let wait_for_connections_event = 0;
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");
@ -1037,8 +1035,9 @@ impl PlantCtrlBoard<'_> {
"online_test".as_bytes(),
)?;
let wait_for_roundtrip = 0;
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");
@ -1085,7 +1084,7 @@ impl PlantCtrlBoard<'_> {
let client = self.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())
.push_str(config.network.base_topic.as_ref().unwrap())
.is_err()
{
println!("Some error assembling full_topic 1");
@ -1097,7 +1096,7 @@ impl PlantCtrlBoard<'_> {
};
let publish = client.publish(
&full_topic,
embedded_svc::mqtt::client::QoS::ExactlyOnce,
ExactlyOnce,
true,
message,
);
@ -1110,7 +1109,7 @@ impl PlantCtrlBoard<'_> {
String::from_utf8_lossy(message),
message_id
);
return Ok(());
Ok(())
}
Err(err) => {
println!(
@ -1119,13 +1118,13 @@ impl PlantCtrlBoard<'_> {
String::from_utf8_lossy(message),
err
);
return Err(err)?;
Err(err)?
}
}
};
}
pub fn get_restart_to_conf(&mut self) -> bool {
return unsafe { RESTART_TO_CONF };
unsafe { RESTART_TO_CONF }
}
pub fn set_restart_to_conf(&mut self, to_conf: bool) {
@ -1192,7 +1191,7 @@ impl PlantCtrlBoard<'_> {
pub fn bat_temperature(&mut self) -> Result<u16> {
match self.battery_driver.temperature() {
OkStd(r) => Ok(r as u16),
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Temperature {:?}", err),
}
}
@ -1241,7 +1240,7 @@ fn print_battery(
) -> Result<(), Bq34Z100Error<I2cError>> {
println!("Try communicating with battery");
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
println!("Firmeware {:?}", e);
println!("Firmware {:?}", e);
0
});
println!("fw version is {}", fwversion);
@ -1291,10 +1290,10 @@ fn print_battery(
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();
return Result::Ok(());
Result::Ok(())
}
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(|| PlantHal::create_i2c());
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() };
@ -1315,15 +1314,15 @@ impl PlantHal {
let peripherals = Peripherals::take()?;
let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?;
clock.set_pull(Pull::Floating).unwrap();
clock.set_pull(Pull::Floating)?;
let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?;
latch.set_pull(Pull::Floating).unwrap();
latch.set_pull(Pull::Floating)?;
let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?;
data.set_pull(Pull::Floating).unwrap();
let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into());
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().unwrap();
pin.set_low()?;
}
let awake = &mut shift_register.decompose()[AWAKE];
@ -1363,7 +1362,7 @@ impl PlantHal {
};
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?;
one_wire_pin.set_pull(Pull::Floating).unwrap();
one_wire_pin.set_pull(Pull::Floating)?;
let rtc_time = rtc.datetime();
match rtc_time {
@ -1498,7 +1497,7 @@ impl PlantHal {
boot_button.set_pull(Pull::Floating)?;
let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?;
light.set_pull(Pull::Floating).unwrap();
light.set_pull(Pull::Floating)?;
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?;
main_pump.set_pull(Pull::Floating)?;
@ -1518,7 +1517,7 @@ impl PlantHal {
Err(err) => {
log(
LogMessage::BatteryCommunicationError,
0 as u32,
0u32,
0,
"",
&format!("{err:?})"),
@ -1541,16 +1540,16 @@ impl PlantHal {
signal_counter: counter_unit1,
wifi_driver,
mqtt_client: None,
battery_driver: battery_driver,
rtc: rtc,
eeprom: eeprom,
battery_driver,
rtc,
eeprom,
});
let _ = rv.lock().is_ok_and(|mut board| {
unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) };
board.shift_register_enable_invert.set_low().unwrap();
unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) };
return true;
true
});
Ok(rv)

View File

@ -3,12 +3,12 @@ use chrono_tz::Tz;
use serde::{Deserialize, Serialize};
use crate::{
config::{self, PlantConfig},
config::PlantConfig,
in_time_range, plant_hal,
};
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 6500.; // 60kHz (500Hz margin)
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, think like cactus levels
#[derive(Debug, PartialEq, Serialize)]
pub enum MoistureSensorError {
@ -117,7 +117,7 @@ impl PlantState {
pub fn read_hardware_state(
plant_id: usize,
board: &mut plant_hal::PlantCtrlBoard,
config: &config::PlantConfig,
config: &PlantConfig,
) -> Self {
let sensor_a = if config.sensor_a {
match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) {
@ -225,8 +225,7 @@ impl PlantState {
if let Some(moisture_percent) = moisture_percent {
if self.pump_in_timeout(plant_conf, current_time) {
false
} else {
if moisture_percent < plant_conf.target_moisture {
} else if moisture_percent < plant_conf.target_moisture {
in_time_range(
current_time,
plant_conf.pump_hour_start,
@ -235,18 +234,13 @@ impl PlantState {
} else {
false
}
}
} else {
// in case no moisture can be determined do not water plant
return false;
// in case no moisture can be determined, do not water the plant
false
}
}
PlantWateringMode::TimerOnly => {
if self.pump_in_timeout(plant_conf, current_time) {
false
} else {
true
}
!self.pump_in_timeout(plant_conf, current_time)
}
}
}
@ -299,9 +293,9 @@ pub struct PlantInfo<'a> {
sensor_b: &'a MoistureSensorState,
/// configured plant watering mode
mode: PlantWateringMode,
/// plant needs to be watered
/// the plant needs to be watered
do_water: bool,
/// is plant considerd to be dry according to settings
/// plant is considered to be dry according to settings
dry: bool,
/// plant irrigation cooldown is active
cooldown: bool,
@ -310,7 +304,7 @@ pub struct PlantInfo<'a> {
/// how often has the pump been watered without reaching target moisture
consecutive_pump_count: u32,
pump_error: Option<PumpError>,
/// last time when pump was active
/// last time when the pump was active
last_pump: Option<DateTime<Tz>>,
/// next time when pump should activate
next_pump: Option<DateTime<Tz>>,

View File

@ -17,12 +17,12 @@ pub enum TankError {
}
pub enum TankState {
TankSensorPresent(f32),
TankSensorError(TankError),
TankSensorDisabled,
Present(f32),
Error(TankError),
Disabled,
}
fn raw_volatge_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
if raw_value_mv > OPEN_TANK_VOLTAGE {
return Err(TankError::SensorMissing(raw_value_mv));
}
@ -37,7 +37,7 @@ fn raw_voltage_to_tank_fill_percent(
raw_value_mv: f32,
config: &TankConfig,
) -> Result<f32, TankError> {
let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?;
let divider_percent = raw_voltage_to_divider_percent(raw_value_mv)?;
if divider_percent < config.tank_empty_percent.into()
|| divider_percent > config.tank_full_percent.into()
{
@ -56,9 +56,9 @@ fn raw_voltage_to_tank_fill_percent(
impl TankState {
pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.)
}
@ -66,9 +66,9 @@ impl TankState {
}
pub fn enough_water(&self, config: &TankConfig) -> Result<bool, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
if tank_fill_percent > config.tank_empty_percent.into() {
Ok(true)
@ -80,14 +80,14 @@ impl TankState {
}
pub fn is_enabled(&self) -> bool {
matches!(self, TankState::TankSensorDisabled)
matches!(self, TankState::Disabled)
}
pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config);
match tank_fill_percent {
Ok(value) => {
@ -108,11 +108,11 @@ impl TankState {
pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
match self {
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Present(raw_value_mv) => {
raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
}
TankState::TankSensorError(err) => Some(err.clone()),
TankState::TankSensorDisabled => Some(TankError::SensorDisabled),
TankState::Error(err) => Some(err.clone()),
TankState::Disabled => Some(TankError::SensorDisabled),
}
}
@ -130,10 +130,10 @@ impl TankState {
Ok(left_ml) => Some(left_ml),
};
let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water?
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should no warn level be triggered if there is an error?
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should warn level be triggered if there is an error?
let raw = match self {
TankState::TankSensorDisabled | TankState::TankSensorError(_) => None,
TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv),
TankState::Disabled | TankState::Error(_) => None,
TankState::Present(raw_value_mv) => Some(*raw_value_mv),
};
let percent = match raw {
@ -163,30 +163,30 @@ pub fn determine_tank_state(
) -> TankState {
if config.tank.tank_sensor_enabled {
match board.tank_sensor_voltage() {
Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv),
Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())),
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
}
} else {
TankState::TankSensorDisabled
TankState::Disabled
}
}
#[derive(Debug, Serialize)]
/// Information structure send to mqtt for monitoring purposes
pub struct TankInfo {
/// is there enough water in the tank
/// there is enough water in the tank
enough_water: bool,
/// warning that water needs to be refilled soon
warn_level: bool,
/// estimation how many ml are still in tank
/// estimation how many ml are still in the tank
left_ml: Option<f32>,
/// if there is was an issue with the water level sensor
/// if there is an issue with the water level sensor
sensor_error: Option<TankError>,
/// raw water sensor value
raw: Option<f32>,
/// percent value
percent: Option<f32>,
/// water in tank might be frozen
/// water in the tank might be frozen
water_frozen: bool,
/// water temperature
water_temp: Option<f32>,

View File

@ -1,9 +1,10 @@
pub trait LimitPrecision {
fn to_precision(self, presision: i32) -> Self;
fn to_precision(self, precision: i32) -> Self;
}
impl LimitPrecision for f32 {
fn to_precision(self, precision: i32) -> Self {
(self * (10_f32).powi(precision)).round() / (10_f32).powi(precision)
let factor = 10_f32.powi(precision);
(self * factor).round() / factor
}
}

View File

@ -82,12 +82,10 @@ fn get_time(
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let native = board
.time()
.and_then(|t| Ok(t.to_rfc3339()))
.time().map(|t| t.to_rfc3339())
.unwrap_or("error".to_string());
let rtc = board
.get_rtc_time()
.and_then(|t| Ok(t.to_rfc3339()))
.get_rtc_time().map(|t| t.to_rfc3339())
.unwrap_or("error".to_string());
let data = LoadData {
@ -166,7 +164,7 @@ fn get_backup_config(
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let json = match board.get_backup_config() {
Ok(config) => std::str::from_utf8(&config)?.to_owned(),
Ok(config) => from_utf8(&config)?.to_owned(),
Err(err) => {
println!("Error get backup config {:?}", err);
err.to_string()
@ -290,7 +288,7 @@ fn list_files(
let board = BOARD_ACCESS.lock().unwrap();
let result = board.list_files();
let file_list_json = serde_json::to_string(&result)?;
return anyhow::Ok(Some(file_list_json));
anyhow::Ok(Some(file_list_json))
}
fn ota(
@ -333,7 +331,7 @@ fn ota(
board.set_restart_to_conf(true);
drop(board);
finalizer.set_as_boot_partition()?;
return anyhow::Ok(None);
anyhow::Ok(None)
}
fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
@ -372,7 +370,7 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
}
println!("Finished flashing file {line} lines processed");
board.general_fault(false);
return anyhow::Ok(());
anyhow::Ok(())
}
@ -380,12 +378,12 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
println!("{uri} get {param_name}");
let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
let value = parsed.query_pairs().filter(|it| it.0 == param_name).next();
let value = parsed.query_pairs().find(|it| it.0 == param_name);
match value {
Some(found) => {
return Some(found.1.into_owned());
Some(found.1.into_owned())
}
None => return None,
None => None,
}
}
@ -694,7 +692,7 @@ fn cors_response(
let mut response = request.into_response(status, None, &headers)?;
response.write(body.as_bytes())?;
response.flush()?;
return anyhow::Ok(());
anyhow::Ok(())
}
type AnyhowHandler =
@ -719,7 +717,7 @@ fn handle_error_to500(
cors_response(request, 500, &error_text)?;
}
}
return anyhow::Ok(());
anyhow::Ok(())
}
fn read_up_to_bytes_from_request(

View File

@ -1,4 +1,4 @@
import { Controller } from "./main";
import {Controller} from "./main";
const regex = /[^a-zA-Z0-9_.]/g;
@ -24,7 +24,7 @@ export class FileView {
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
fileuploadfile.onchange = () => {
var selectedFile = fileuploadfile.files?.[0];
const selectedFile = fileuploadfile.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
@ -42,7 +42,7 @@ export class FileView {
}
fileuploadbtn.onclick = () => {
var selectedFile = fileuploadfile.files?.[0];
const selectedFile = fileuploadfile.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
@ -77,8 +77,7 @@ class FileEntry {
this.view.classList.add("fileentryouter")
const template = require('./fileviewentry.html') as string;
const fileRaw = template.replaceAll("${fileid}", String(fileid));
this.view.innerHTML = fileRaw
this.view.innerHTML = template.replaceAll("${fileid}", String(fileid))
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;

View File

@ -45,7 +45,7 @@
display: inline-block;
height: 100%;
animation: indeterminateAnimation 1s infinite linear;
transform-origin: 0% 50%;
transform-origin: 0 50%;
}

View File

@ -1,4 +1,3 @@
import { deepEqual } from 'fast-equals';
declare var PUBLIC_URL: string;
@ -8,7 +7,7 @@ document.body.innerHTML = require('./main.html') as string;
import { TimeView } from "./timeview";
import { PlantView, PlantViews } from "./plant";
import { PlantViews } from "./plant";
import { NetworkConfigView } from "./network";
import { NightLampView } from "./nightlightview";
import { TankConfigView } from "./tankview";
@ -261,7 +260,6 @@ export class Controller {
configChanged() {
const current = controller.getConfig();
var pretty = JSON.stringify(current, undefined, 0);
var initial = JSON.stringify(this.initialConfig, undefined, 0);
controller.submitView.setJson(pretty);
if (deepEqual(current, controller.initialConfig)) {
@ -307,7 +305,7 @@ export class Controller {
})
.then(response => response.text())
.then(
text => {
_ => {
clearTimeout(timerId);
controller.progressview.removeProgress("test_pump");
}
@ -507,3 +505,16 @@ controller.populateTimezones().then(_ => {
controller.progressview.removeProgress("rebooting");
window.addEventListener("beforeunload", (event) => {
const currentConfig = controller.getConfig();
// Check if the current state differs from the initial configuration
if (!deepEqual(currentConfig, controller.initialConfig)) {
const confirmationMessage = "You have unsaved changes. Are you sure you want to leave this page?";
// Standard behavior for displaying the confirmation dialog
event.preventDefault();
event.returnValue = confirmationMessage; // This will trigger the browser's default dialog
return confirmationMessage;
}
});

View File

@ -3,8 +3,8 @@ import { Controller } from "./main";
export class NetworkConfigView {
setScanResult(ssidList: SSIDList) {
this.ssidlist.innerHTML = ''
for (var ssid of ssidList.ssids) {
var wi = document.createElement("option");
for (const ssid of ssidList.ssids) {
const wi = document.createElement("option");
wi.value = ssid;
this.ssidlist.appendChild(wi);
}

View File

@ -17,7 +17,7 @@ export class OTAView {
const file = document.getElementById("firmware_file") as HTMLInputElement;
this.file1Upload = file
this.file1Upload.onchange = () => {
var selectedFile = file.files?.[0];
const selectedFile = file.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return

View File

@ -14,7 +14,7 @@
}
.plantcheckbox{
min-width: 20px;
margin: 0px;
margin: 0;
}
</style>
@ -59,7 +59,7 @@
</div>
<div class="flexcontainer">
<div class="plantkey">Warn Pump Count:</div>
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" max="50" ,
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" max="50"
placeholder="10">
</div>
<div class="flexcontainer">

View File

@ -1,7 +1,7 @@
const PLANT_COUNT = 8;
import { Controller } from "./main";
import {Controller} from "./main";
export class PlantViews {
private readonly measure_moisture: HTMLButtonElement;
@ -61,12 +61,10 @@ export class PlantView {
constructor(plantId: number, parent:HTMLDivElement, controller:Controller) {
const dummy = this;
this.plantId = plantId;
this.plantDiv = document.createElement("div")! as HTMLDivElement
const template = require('./plant.html') as string;
const plantRaw = template.replaceAll("${plantId}", String(plantId));
this.plantDiv.innerHTML = plantRaw
this.plantDiv.innerHTML = template.replaceAll("${plantId}", String(plantId))
this.plantDiv.classList.add("plantcontainer")
parent.appendChild(this.plantDiv)
@ -184,7 +182,7 @@ export class PlantView {
}
getConfig(): PlantConfig {
const rv: PlantConfig = {
return {
mode: this.mode.value,
target_moisture: this.targetMoisture.valueAsNumber,
pump_time_s: this.pumpTimeS.valueAsNumber,
@ -196,14 +194,5 @@ export class PlantView {
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || undefined,
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || undefined,
};
return rv;
}
setMoistureA(a: number) {
this.moistureA.innerText = String(a);
}
setMoistureB(b: number) {
this.moistureB.innerText = String(b);
}
}

View File

@ -1,14 +1,14 @@
<style>
.tankcheckbox {
min-width: 20px;
margin: 0px;
margin: 0;
}
.tankkey{
min-width: 250px;
}
.tankvalue{
flex-grow: 1;
margin: 0px;
margin: 0;
}
.hidden {
display: none;

View File

@ -38,7 +38,7 @@ export class TimeView {
update(native: string, rtc: string) {
this.esp_time.innerText = native;
this.rtc_time.innerText = rtc;
var date = new Date();
const date = new Date();
this.browser_time.innerText = date.toISOString();
if(this.auto_refresh.checked){
this.timer = setTimeout(this.controller.updateRTCData, 1000);

9
website/.idea/website.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>