diff --git a/.DS_Store b/.DS_Store index 4a50b42..e31bb61 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/PlantDashboard/.DS_Store b/PlantDashboard/.DS_Store index b81a9ba..ce5dd34 100644 Binary files a/PlantDashboard/.DS_Store and b/PlantDashboard/.DS_Store differ diff --git a/PlantDashboard/.cache.sqlite b/PlantDashboard/.cache.sqlite new file mode 100644 index 0000000..c710cb9 Binary files /dev/null and b/PlantDashboard/.cache.sqlite differ diff --git a/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc b/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc index 9566346..6ee3eb6 100644 Binary files a/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc and b/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc differ diff --git a/PlantDashboard/__pycache__/plant_meteo.cpython-313.pyc b/PlantDashboard/__pycache__/plant_meteo.cpython-313.pyc new file mode 100644 index 0000000..2830156 Binary files /dev/null and b/PlantDashboard/__pycache__/plant_meteo.cpython-313.pyc differ diff --git a/PlantDashboard/main_dashboard.py b/PlantDashboard/main_dashboard.py index fb446ff..ad6476a 100644 --- a/PlantDashboard/main_dashboard.py +++ b/PlantDashboard/main_dashboard.py @@ -6,7 +6,8 @@ import os from datetime import datetime, date from plant_model import PlantGrowthModel from data_handler import DataHandler -from tkcalendar import DateEntry +from tkcalendar import DateEntry, Calendar +from plant_meteo import HappyMeteo class PlantGrowthDashboard: def __init__(self, root): @@ -15,7 +16,11 @@ class PlantGrowthDashboard: self.root.geometry("1000x800") # More square dimensions self.root.configure(bg='#f0f0f0') - image = Image.open("public/transparentLogo.png") + image = Image.open("public/logoTransparent.png") + + desired_size = (128, 128) + image = image.resize(desired_size, Image.Resampling.LANCZOS) + # Convert to PhotoImage icon = ImageTk.PhotoImage(image) # Set as window icon @@ -24,6 +29,7 @@ class PlantGrowthDashboard: # Initialize components self.plant_model = PlantGrowthModel() self.data_handler = DataHandler() + self.happyMeteo = HappyMeteo() # Variables - fixed plant type self.current_plant = "tomato" # Fixed plant type @@ -143,6 +149,8 @@ class PlantGrowthDashboard: command=lambda x, p=param: self.on_param_change(p)) scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(4, 4)) + setattr(self, f"{param}_scale", scale) + # Value label value_label = ttk.Label(param_frame, text=f"{self.env_params[param].get():.1f}", width=5, font=('Arial', 8)) value_label.pack(side=tk.RIGHT) @@ -163,28 +171,33 @@ class PlantGrowthDashboard: spacer.grid(row=row, column=0, columnspan=2, pady=10) # Add this after the parameters section - ttk.Label(control_frame, text="Final date of growth:", + ttk.Label(control_frame, text="Final date of growth (choose a date):", font=('Arial', 9, 'bold')).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=(10, 4)) row += 1 # Compact date entry with calendar popup - # To get the selected dat simply call self.date_entry.get_date() - self.date_entry = DateEntry(control_frame, - width=12, - background='darkblue', - foreground='white', - borderwidth=2, - font=('Arial', 8), - date_pattern='dd/mm/yyyy', - state='readonly', # Add this - cursor='hand2') # Add this - self.date_entry.grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=2) + # To get the selected dat simply call self.calendar.get_date() + # self.date_entry + self.calendar = Calendar(control_frame, selectmode='day', + year=2025, month=8, day=1, + font=('Arial', 8)) + self.calendar.grid(row=row, column=0, columnspan=2, pady=2) row += 1 control_frame.columnconfigure(0, weight=1) control_frame.columnconfigure(1, weight=1) - + + def disable_parameter(self, param): + scale = getattr(self, f"{param}_scale", None) + if scale: + scale.configure(state='disabled') + + def enable_parameter(self, param): + scale = getattr(self, f"{param}_scale", None) + if scale: + scale.configure(state='normal') + def setup_visualization_panel(self, parent): viz_frame = ttk.LabelFrame(parent, text="Plant Visualization", padding="6") viz_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=3) @@ -325,14 +338,18 @@ class PlantGrowthDashboard: if current_mode == "controlled": print("Switched to Controlled mode") # Enable all parameter controls + self.enable_parameter("humidity") + self.enable_parameter("brightness") + self.enable_parameter("temperature") + # No need to call the meteo api elif current_mode == "open": print("Switched to Open mode") # Disable most parameter controls (temp, humidity, light) - - # Call the meteo api to retrieve all the parameters, set variable meteo_values to retrieve when submitiing all - # Inside the retrieving of all the data check if the mode is one, select which data to use + self.disable_parameter("humidity") + self.disable_parameter("brightness") + self.disable_parameter("temperature") def on_param_change(self, param): value = self.env_params[param].get() @@ -354,7 +371,7 @@ class PlantGrowthDashboard: self.ambient_mode.set("controlled") - self.date_entry.set_date(date.today()) + self.calendar.set_date(date.today()) def update_parameter_label(self, param, value): """Update the value label for a specific parameter""" @@ -387,10 +404,24 @@ class PlantGrowthDashboard: def submit_plant_data(self): """Submit plant information and photo""" try: - # Get current parameters + start_date = datetime.now().date() + end_date = self.calendar.get_date() + + time_lapse = end_date - start_date + days_difference = time_lapse.days + params = {param: var.get() for param, var in self.env_params.items()} params['plant_type'] = self.current_plant params['ambient_mode'] = self.ambient_mode.get() + + current_mode = self.ambient_mode.get() + happy_data = 0 + if current_mode == "open": + happy_data = self.happyMeteo.openMeteoCall(days_difference) + + excluded_params = {"humidity", "temperature", "brightness"} + params = {param: var.get() for param, var in self.env_params.items() + if param not in excluded_params} # Create submission data submission_data = { @@ -399,9 +430,12 @@ class PlantGrowthDashboard: 'baseline_image_path': self.baseline_image_path, 'plant_info': self.plant_info_text.get(1.0, tk.END), 'start date': datetime.now().date().isoformat(), - 'end_date': self.date_entry.get_date().isoformat() + 'end_date': self.calendar.get_date().isoformat() } + if current_mode == "open": + submission_data['meteoForecast'] = happy_data + #Remove plant_info_text self.plant_info_text.delete(1.0, tk.END) diff --git a/PlantDashboard/plant_meteo.py b/PlantDashboard/plant_meteo.py new file mode 100644 index 0000000..fbb42a5 --- /dev/null +++ b/PlantDashboard/plant_meteo.py @@ -0,0 +1,80 @@ +import openmeteo_requests + +import pandas as pd +import requests_cache +from retry_requests import retry +import geocoder + + +class HappyMeteo: + def __init__(self): + # Setup the Open-Meteo API client with cache and retry on error + cache_session = requests_cache.CachedSession('.cache', expire_after = 3600) + retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2) + self.openmeteo = openmeteo_requests.Client(session = retry_session) + + def get_current_location(self): + """Get current location using IP geolocation""" + try: + g = geocoder.ip('me') + if g.ok: + latitude = g.latlng[0] + longitude = g.latlng[1] + print(f"Latitude: {latitude}") + print(f"Longitude: {longitude}") + print(f"Address: {g.address}") + return latitude, longitude + else: + print("Could not determine location") + return None, None + except Exception as e: + print(f"Error getting location: {e}") + return None, None + +def openMeteoCall(self, timeLapse): + lat, lon = self.get_current_location() + + # Make sure all required weather variables are listed here + # The order of variables in hourly or daily is important to assign them correctly below + url = "https://api.open-meteo.com/v1/forecast" + params = { + "latitude": lat, + "longitude": lon, + "daily": ["weather_code", "temperature_2m_mean", "rain_sum", "showers_sum", "precipitation_sum", "daylight_duration", "relative_humidity_2m_mean"], + "timezone": "auto", + "forecast_days": timeLapse + } + responses = self.openmeteo.weather_api(url, params=params) + + # Process first location. Add a for-loop for multiple locations or weather models + response = responses[0] + + # Process daily data. The order of variables needs to be the same as requested. + daily = response.Daily() + daily_weather_code = daily.Variables(0).ValuesAsNumpy() + daily_temperature_2m_mean = daily.Variables(1).ValuesAsNumpy() + daily_rain_sum = daily.Variables(2).ValuesAsNumpy() + daily_showers_sum = daily.Variables(3).ValuesAsNumpy() + daily_precipitation_sum = daily.Variables(4).ValuesAsNumpy() + daily_daylight_duration = daily.Variables(5).ValuesAsNumpy() + daily_relative_humidity_2m_mean = daily.Variables(6).ValuesAsNumpy() + + # Return comprehensive data structure + return { + "daily_data": { + "weather_code": daily_weather_code.tolist(), + "temperature_2m_mean": daily_temperature_2m_mean.tolist(), + "rain_sum": daily_rain_sum.tolist(), + "showers_sum": daily_showers_sum.tolist(), + "precipitation_sum": daily_precipitation_sum.tolist(), + "daylight_duration": daily_daylight_duration.tolist(), + "relative_humidity_2m_mean": daily_relative_humidity_2m_mean.tolist() + }, + "summary": { + "avg_temperature": float(daily_temperature_2m_mean.mean()), + "total_precipitation": float(daily_precipitation_sum.sum()), + "avg_humidity": float(daily_relative_humidity_2m_mean.mean()), + "total_daylight_hours": float(daily_daylight_duration.sum() / 3600) # Convert seconds to hours + } + } + diff --git a/PlantDashboard/public/.DS_Store b/PlantDashboard/public/.DS_Store index 61bd1cc..bd81130 100644 Binary files a/PlantDashboard/public/.DS_Store and b/PlantDashboard/public/.DS_Store differ diff --git a/PlantDashboard/public/logo.jpg b/PlantDashboard/public/logo.jpg deleted file mode 100644 index 332b356..0000000 Binary files a/PlantDashboard/public/logo.jpg and /dev/null differ diff --git a/PlantDashboard/public/logo.png b/PlantDashboard/public/logo.png new file mode 100644 index 0000000..7517897 Binary files /dev/null and b/PlantDashboard/public/logo.png differ diff --git a/PlantDashboard/public/logoTransparent.png b/PlantDashboard/public/logoTransparent.png new file mode 100644 index 0000000..6586849 Binary files /dev/null and b/PlantDashboard/public/logoTransparent.png differ diff --git a/PlantDashboard/public/transparentLogo.png b/PlantDashboard/public/transparentLogo.png deleted file mode 100644 index bd04425..0000000 Binary files a/PlantDashboard/public/transparentLogo.png and /dev/null differ diff --git a/PlantDashboard/requirements.txt b/PlantDashboard/requirements.txt index 90880d9..4e360cb 100644 --- a/PlantDashboard/requirements.txt +++ b/PlantDashboard/requirements.txt @@ -30,6 +30,10 @@ memory-profiler>=0.60.0 tkcalendar +openmeteo-requests +requests-cache +retry-requests +geocoder # Note: tkinter comes pre-installed with most Python distributions # If tkinter is not available, install it using your system package manager: # Ubuntu/Debian: sudo apt-get install python3-tk diff --git a/__pycache__/script.cpython-311.pyc b/__pycache__/script.cpython-311.pyc new file mode 100644 index 0000000..36213a5 Binary files /dev/null and b/__pycache__/script.cpython-311.pyc differ diff --git a/basilico.jpg b/basilico.jpg new file mode 100644 index 0000000..d84b39d Binary files /dev/null and b/basilico.jpg differ diff --git a/forDashboardIntegration.py b/forDashboardIntegration.py new file mode 100644 index 0000000..0e07774 --- /dev/null +++ b/forDashboardIntegration.py @@ -0,0 +1,42 @@ +from datetime import datetime +from script import PlantPredictor + +def dashboard_plant_prediction(image_path, start_date, end_date, additional_notes=""): + """ + Simple function for dashboard integration + """ + try: + # Calculate days + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + end_dt = datetime.strptime(end_date, "%Y-%m-%d") + days = (end_dt - start_dt).days + + if days <= 0: + return {"success": False, "error": "Invalid date range"} + + # Create predictor and run + predictor = PlantPredictor() + result = predictor.dashboard_plant_prediction(image_path, days, additional_notes) + + if result: + return {"success": True, "result": result} + else: + return {"success": False, "error": "No result"} + + except Exception as e: + return {"success": False, "error": str(e)} + + +# Test +if __name__ == "__main__": + result = dashboard_plant_prediction( + "./basilico.jpg", + "2024-08-01", + "2024-08-08", + "Test plant" + ) + + if result["success"]: + print(" SUCCESS!") + else: + print(f" ERROR: {result['error']}") \ No newline at end of file diff --git a/path.txt b/path.txt index ad13395..a3b4c99 100644 --- a/path.txt +++ b/path.txt @@ -1,18 +1,5 @@ ROADMAP - - -differences between the three modes: - Open mode: - -the temp, humidity, light, water parameters are setted by the meteo api, the rest is setted by the user - -all the parameters are controlled by the user - SemiControlled mode: - -the user choose how to set the parameters - -all parameters free - - Controlled mode: - -all the values are set by the user - -the user choose which parameters are free and which are controlled by the meteo api - - -make the calendar widget working + -format the data that will came to the user -final updates of README.md, hackathon page, forgejo, github page, small design adjustments. diff --git a/script.py b/script.py new file mode 100644 index 0000000..3c28508 --- /dev/null +++ b/script.py @@ -0,0 +1,60 @@ +from datetime import datetime +import sys +import os + +class PlantPredictor: + def dashboard_plant_prediction( + image_path: str, + start_date: str, + end_date: str, + additional_notes: str = "" + ) -> dict: + try: + # Calcola giorni + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + end_dt = datetime.strptime(end_date, "%Y-%m-%d") + days = (end_dt - start_dt).days + if days <= 0: + return {"success": False, "error": "End date must be after start date", "days": days} + + # Log + print(f"Dashboard prediction request: {start_date} to {end_date} ({days} days) image={image_path}") + if additional_notes: + print(f"Notes: {additional_notes}") + + # Inizializza il predictor e chiama il metodo + predictor = PlantPredictor() + result = predictor.predict_plant_growth(image_path, days, additional_notes) + + # Unwrap risultato tuple + if isinstance(result, tuple) and len(result) == 5: + _img, conditions, weather_df, plant_type, plant_health = result + return { + "success": True, + "plant_analysis": {"plant_type": plant_type, "plant_health": plant_health}, + "weather_conditions": conditions, + "weather_data_shape": weather_df.shape, + "parameters_used": {"start_date": start_date, "end_date": end_date, "days": days, "notes": additional_notes, "image": image_path}, + "prediction_summary": { + "temperature_range": f"{conditions['avg_temp_min']}–{conditions['avg_temp_max']}°C", + "total_rain": f"{conditions['total_rain']}mm", + "sunshine_hours": f"{conditions['total_sunshine_hours']}h" + } + } + else: + return {"success": False, "error": "Invalid result from PlantPredictor", "result": result} + + except ValueError as e: + return {"success": False, "error": f"Date format error: {e}"} + except Exception as e: + return {"success": False, "error": f"Unexpected error: {e}"} + +# Esempio di test +if __name__ == '__main__': + res = dashboard_plant_prediction( + image_path='./basilico.jpg', + start_date='2024-08-01', + end_date='2024-08-08', + additional_notes='Indoor day 3' + ) + print(res)