瀏覽代碼

feat: enhance error handling for I2C and MQTT services; update config for I2C debug mode

Silas Gruen 6 月之前
父節點
當前提交
531287cd5c

+ 4 - 7
config/config.yaml

@@ -10,11 +10,11 @@ mqtt:
   password: "robot"
 
 i2c:
-  debug: true
+  debug: false
   bus_number: 1
   polling_interval_ms: 100  # How often to poll devices
-  retry_count: 3  # Number of retries for failed I2C communications
-  timeout_ms: 100  # Timeout for I2C communications
+  retry_count: 3  # Number of retries for failed I2C communications # TODO implement
+  timeout_ms: 100  # Timeout for I2C communications # TODO implement
 
 http:
   debug: true
@@ -28,11 +28,8 @@ measurement:
   cycles: 3
   c_rate: 0.25
   cut_off_curr: 100
-  sample_rate_hz: 1
   min_voltage: 2.5
   max_voltage: 4.2
-  max_temperature_c: 45
-  rest_time_minutes: 30
 
 devices:
   - id: 1
@@ -43,7 +40,7 @@ devices:
   #   num_slots: 4
 
 logging:
-  level: "INFO"
+  level: "DEBUG"
   mode: "a" # a: append, w: overwrite
   max_bytes: 1000000
   file: "measure_ctrl.log"

+ 41 - 41
src/controllers/measurement_controller.py

@@ -43,50 +43,50 @@ class MeasurementController:
         while True:
             await asyncio.sleep(polling_interval)
             for device_id, device in self.devices.items():
+                try:
+                    # Read slot status via I2C
+                    new_status_list = self.i2c_service.request_status_list(device.i2c_address, len(device.slots))
+                except Exception as e:
+                    logger.error(f"Error during polling device: {device.id}:\n{str(e)}")
+                    continue
+                
+                if len(new_status_list) != len(device.slots):
+                    logger.error(f"Invalid status list length: {len(new_status_list)} != {len(device.slots)}")
+                    continue
+                
+                # Update the (change of) status for each slot and act accordingly
+                for idx, status in enumerate(new_status_list):
                     try:
-                        # Read slot status via I2C
-                        new_status_list = self.i2c_service.request_status_list(device.i2c_address, len(device.slots))
-                    except Exception as e:
-                        logger.error(f"Error during polling device: {device.id}:\n{str(e)}")
-                        continue
-                    
-                    if len(new_status_list) != len(device.slots):
-                        logger.error(f"Invalid status list length: {len(new_status_list)} != {len(device.slots)}")
-                        continue
-                    
-                    # Change the (change of) status for each slot and act accordingly
-                    for idx, status in enumerate(new_status_list):
-                        try:
-                            slot = device.slots[idx]
-                            prev_state = device.status_list[idx]
-                            device.status_list[idx] = status
+                        slot = device.slots[idx]
+                        prev_state = device.status_list[idx]
+                        device.status_list[idx] = status
 
-                            if slot.is_empty() and status is not DeviceStatus.EMPTY:
-                                logging.warning(f"Device {device.id}, Slot {slot.id} is empty, but status is {status}")
-                                continue
-                            
-                            # Check for unconfigured cell (could also be when the device resets)    
-                            cell = slot.get_cell()  
-                            if status is DeviceStatus.INSERTED and cell and not cell.limits_transmitted:
-                                self._update_cell_limits(device, slot)
-                                cell.limits_transmitted = True
-                                continue
-                            # Check for state transitions to "DONE"
-                            if prev_state is DeviceStatus.MEASURING and status is DeviceStatus.DONE and cell:
-                                self._process_done(device, slot)
-                                cell.limits_transmitted = False
-                                continue
-                            # Check for state transitions to "ERROR"
-                            if status is DeviceStatus.ERROR and prev_state is not DeviceStatus.ERROR:
-                                self._process_error(device, slot)
-                                continue
-                            if status is DeviceStatus.MEASURING:
-                                self._collect_measurement(device, slot)
-                                continue
+                        if slot.is_empty() and status is not DeviceStatus.EMPTY:
+                            logging.warning(f"Device {device.id}, Slot {slot.id} is empty, but status is {status}")
+                            continue
+                        
+                        # Check for unconfigured cell (could also be when the device resets)    
+                        cell = slot.get_cell()  
+                        if status is DeviceStatus.INSERTED and cell and not cell.limits_transmitted:
+                            self._update_cell_limits(device, slot)
+                            cell.limits_transmitted = True
+                            continue
+                        # Check for state transitions to "DONE"
+                        if prev_state is DeviceStatus.MEASURING and status is DeviceStatus.DONE and cell:
+                            self._process_done(device, slot)
+                            cell.limits_transmitted = False
+                            continue
+                        # Check for state transitions to "ERROR"
+                        if status is DeviceStatus.ERROR and prev_state is not DeviceStatus.ERROR:
+                            self._process_error(device, slot)
+                            continue
+                        if status is DeviceStatus.MEASURING:
+                            self._collect_measurement(device, slot)
+                            continue
 
-                        except Exception as e:
-                            logger.error(f"Error during processing status {status} for device {device.id}, slot {slot.id}: {str(e)}")    
-                            continue            
+                    except Exception as e:
+                        logger.error(f"Error during processing status {status} for device {device.id}, slot {slot.id}: {str(e)}")    
+                        continue            
                         
     def _update_cell_limits(self, device: Device, slot: Slot):
         """Send battery limits to the device."""

+ 4 - 1
src/services/i2c_service.py

@@ -16,7 +16,10 @@ class I2CService:
         self.bus = None
         if not self.debug:
             bus_number = config.get('i2c', {}).get('bus', 1)
-            self.bus = smbus2.SMBus(bus_number)
+            try:
+                self.bus = smbus2.SMBus(bus_number)
+            except PermissionError as e:
+                raise RuntimeError(f"Failed to access I2C bus {bus_number}. Ensure you have setup I2C properly.") from e
     
     def request_status_list(self, i2c_adress: int, num_slots: int) -> list[DeviceStatus]:
         """Request the status of a all slots."""

+ 5 - 2
src/services/mqtt_service.py

@@ -38,8 +38,11 @@ class MQTTService:
             self.client.connect("test.mosquitto.org", 1883)
             return        
         
-        self.client.connect(broker_address, port, keepalive)
-        self.client.loop_start()
+        try:
+            self.client.connect(broker_address, port, keepalive)
+            self.client.loop_start()
+        except ConnectionRefusedError as e:
+            raise ConnectionError(f"Failed to connect to MQTT broker at {broker_address}:{port}") from e
 
     def register_device(self, device_id, num_slots, callback: Optional[Callable] = None):
         """Register a new device to handle"""

+ 1 - 1
src/services/prometheus_service.py

@@ -19,4 +19,4 @@ def start_metrics_server(port: int = 8000):
         logger.info(f"Started Prometheus metrics server on port {port}")
     except Exception as e:
         logger.error(f"Failed to start metrics server: {e}")
-        raise
+        raise RuntimeError(f"Failed to start metrics server on port {port}") from e