@@ -28,8 +28,32 @@ def get_current_time(timezone_str: str = 'UTC') -> datetime:
2828ERROR_NOT_CONNECTED = 0x407 # 1031
2929ERROR_UNSPECIFIED = 0x500 # 1280
3030
31- # Available cloud conditions from ML model
32- ALL_CLOUD_CONDITIONS = ['Clear' , 'Mostly Cloudy' , 'Overcast' , 'Rain' , 'Snow' , 'Wisps of clouds' ]
31+ # Dynamically load cloud conditions from labels file
32+ def load_labels ():
33+ """Load cloud condition labels from labels.txt file"""
34+ label_path = os .environ .get ('LABEL_PATH' , 'labels.txt' )
35+ try :
36+ if os .path .exists (label_path ):
37+ with open (label_path , 'r' ) as f :
38+ labels = []
39+ for line in f :
40+ clean_line = line .strip ()
41+ # Handle "0 Clear" format if present
42+ if " " in clean_line and clean_line .split (" " , 1 )[0 ].isdigit ():
43+ clean_line = clean_line .split (" " , 1 )[1 ]
44+ if clean_line :
45+ labels .append (clean_line )
46+ logger .info (f"Loaded { len (labels )} classes from { label_path } " )
47+ return labels
48+ except Exception as e :
49+ logger .error (f"Failed to load labels from { label_path } : { e } " )
50+
51+ # Fallback to defaults if file missing or error
52+ logger .warning ("Using fallback default cloud conditions" )
53+ return ['Clear' , 'Mostly Cloudy' , 'Overcast' , 'Rain' , 'Snow' , 'Wisps of clouds' ]
54+
55+ # Available cloud conditions from ML model (loaded dynamically from labels.txt)
56+ ALL_CLOUD_CONDITIONS = load_labels ()
3357
3458
3559@dataclass
@@ -46,7 +70,7 @@ class AlpacaConfig:
4670 update_interval : int = 30 # seconds between cloud detection updates
4771 location : str = "AllSky Camera"
4872 image_url : str = field (default_factory = lambda : os .environ .get ('IMAGE_URL' , '' ))
49- unsafe_conditions : list = field (default_factory = lambda : [ 'Rain' , 'Snow' , 'Mostly Cloudy' , 'Overcast' ] )
73+ unsafe_conditions : list = field (default_factory = lambda : ALL_CLOUD_CONDITIONS . copy () )
5074
5175 # Confidence threshold settings
5276 default_threshold : float = 50.0 # Default threshold for any class not explicitly configured
@@ -102,7 +126,33 @@ def load_settings_from_file(cls) -> dict:
102126
103127 # Filter dictionary to only include valid fields for this dataclass
104128 valid_fields = {f .name for f in cls .__dataclass_fields__ .values ()}
105- return {k : v for k , v in config_dict .items () if k in valid_fields }
129+ settings = {k : v for k , v in config_dict .items () if k in valid_fields }
130+
131+ # Sanitize loaded settings against current labels from labels.txt
132+ # Remove saved 'unsafe_conditions' that are no longer in ALL_CLOUD_CONDITIONS
133+ if 'unsafe_conditions' in settings :
134+ valid_labels = set (ALL_CLOUD_CONDITIONS )
135+ original_count = len (settings ['unsafe_conditions' ])
136+ settings ['unsafe_conditions' ] = [
137+ c for c in settings ['unsafe_conditions' ]
138+ if c in valid_labels
139+ ]
140+ removed_count = original_count - len (settings ['unsafe_conditions' ])
141+ if removed_count > 0 :
142+ logger .info (f"Removed { removed_count } obsolete unsafe condition(s) from saved configuration" )
143+
144+ # Clean up stale class thresholds
145+ if 'class_thresholds' in settings :
146+ original_count = len (settings ['class_thresholds' ])
147+ settings ['class_thresholds' ] = {
148+ k : v for k , v in settings ['class_thresholds' ].items ()
149+ if k in ALL_CLOUD_CONDITIONS
150+ }
151+ removed_count = original_count - len (settings ['class_thresholds' ])
152+ if removed_count > 0 :
153+ logger .info (f"Removed { removed_count } obsolete class threshold(s) from saved configuration" )
154+
155+ return settings
106156 except Exception as e :
107157 logger .error (f"Failed to load configuration from { filepath } : { e } " )
108158 return {}
0 commit comments