|
|
@@ -2,11 +2,11 @@ import asyncio
|
|
|
import logging
|
|
|
from typing import Dict
|
|
|
import json
|
|
|
-from models.cell import Cell, MeasureValues
|
|
|
+from models.cell import Cell, CellLimits
|
|
|
from models.device import Device, DeviceStatus, Slot
|
|
|
from services.i2c_service import I2CService
|
|
|
from services.http_service import HTTPService
|
|
|
-from services.mqtt_service import MQTTService
|
|
|
+from services.mqtt_service import MQTTService, InsertedCell
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@@ -27,12 +27,7 @@ class MeasurementController:
|
|
|
|
|
|
self.subscribe_prefix = self.config['mqtt']['subscribe_prefix']
|
|
|
for device in self.devices:
|
|
|
- self.setup_mqtt_subscription(device.id)
|
|
|
-
|
|
|
- def setup_mqtt_subscription(self, device_id):
|
|
|
- """Setup MQTT subscriptions for each device."""
|
|
|
- topic = f"{self.subscribe_prefix}/device_{device_id}"
|
|
|
- self.mqtt_service.add_message_handler(topic, lambda client, userdata, msg, dev_id=device_id: self._handle_cell_insertion(client, userdata, msg, dev_id))
|
|
|
+ self.mqtt_service.register_device(device.id, len(device.slots), self._update_inserted_cell)
|
|
|
|
|
|
async def start_polling(self):
|
|
|
"""Start the polling tasks."""
|
|
|
@@ -49,72 +44,68 @@ class MeasurementController:
|
|
|
|
|
|
while True:
|
|
|
await asyncio.sleep(polling_interval)
|
|
|
- try:
|
|
|
- for device in self.devices:
|
|
|
- # Read slot status via I2C
|
|
|
- new_status_list = await self.i2c_service.request_status_list(device.i2c_address)
|
|
|
- if len(device.status_list) != len(device.slots):
|
|
|
- raise IndexError(f"Invalid status list length: {len(device.status_list)} != {len(device.slots)}")
|
|
|
+ for device in self.devices:
|
|
|
+ try:
|
|
|
+ # Read slot status via I2C
|
|
|
+ new_status_list = self.i2c_service.request_status_list(device.i2c_address)
|
|
|
+ if len(device.status_list) != len(device.slots):
|
|
|
+ raise IndexError(f"Invalid status list length: {len(device.status_list)} != {len(device.slots)}")
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error during polling device: {device.id}:\n{str(e)}")
|
|
|
+ continue
|
|
|
|
|
|
# Change the (change of) status for each slot and act accordingly
|
|
|
for idx, status in enumerate(new_status_list):
|
|
|
- slot = device.slots[idx]
|
|
|
- prev_state = device.status_list[idx]
|
|
|
- device.status_list[idx] = status
|
|
|
-
|
|
|
- # Check for state transitions to "INSERTED"
|
|
|
- if status is DeviceStatus.INSERTED and prev_state is not DeviceStatus.INSERTED:
|
|
|
- self._update_cell_limits(device, slot)
|
|
|
- continue
|
|
|
- # Check for state transitions to "DONE"
|
|
|
- if prev_state is DeviceStatus.MEASURING and status is DeviceStatus.DONE:
|
|
|
- self._process_done(device, slot)
|
|
|
- continue
|
|
|
- # Check for state transitions to "ERROR"
|
|
|
- if status is DeviceStatus.ERROR and prev_state is not DeviceStatus.ERROR:
|
|
|
- logger.error(f"Error detected for device {device.id}, slot {slot.id}")
|
|
|
- continue
|
|
|
- if status is DeviceStatus.MEASURING:
|
|
|
- self._collect_measurement(device, slot)
|
|
|
- continue
|
|
|
+ try:
|
|
|
+ slot = device.slots[idx]
|
|
|
+ prev_state = device.status_list[idx]
|
|
|
+ device.status_list[idx] = status
|
|
|
+
|
|
|
+ # Check for unconfigured cell
|
|
|
+ if status is DeviceStatus.INSERTED and slot.get_cell() and not slot.get_cell().limits_transmitted:
|
|
|
+ self._update_cell_limits(device, slot)
|
|
|
+ continue
|
|
|
+ # Check for state transitions to "DONE"
|
|
|
+ if prev_state is DeviceStatus.MEASURING and status is DeviceStatus.DONE:
|
|
|
+ self._process_done(device, slot)
|
|
|
+ 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 device polling: {str(e)}")
|
|
|
-
|
|
|
- async def _update_cell_limits(self, device: Device, slot: Slot):
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"Error during processing device: {device.id}), slot: {slot.id}:\n{str(e)}")
|
|
|
+ continue
|
|
|
+
|
|
|
+ def _update_cell_limits(self, device: Device, slot: Slot):
|
|
|
"""Send battery limits to the device."""
|
|
|
- cell_id = slot.get_cell().id
|
|
|
- limits = self.http_service.fetch_cell_info(cell_id)
|
|
|
- self.i2c_service.send_cell_limits(device.i2c_address, slot, limits)
|
|
|
+ cell = slot.get_cell()
|
|
|
+ if cell is None:
|
|
|
+ raise ValueError(f"No cell inserted in device {device.id}, slot {slot.id}")
|
|
|
+ self.i2c_service.send_cell_limits(device.i2c_address, slot.id, cell.limits)
|
|
|
+ cell.limits_transmitted = True
|
|
|
|
|
|
- async def _collect_measurement(self, device: Device, slot: Slot):
|
|
|
+ def _collect_measurement(self, device: Device, slot: Slot):
|
|
|
"""Collect measurement data from active slots."""
|
|
|
- try:
|
|
|
- measure_values = await self.i2c_service.request_measure_values(device.i2c_address, slot.id)
|
|
|
- slot.get_cell().add_measurement(measure_values)
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"Error collecting measurement data: {str(e)}")
|
|
|
-
|
|
|
- def _handle_cell_insertion(self, client, userdata, message: str, device_id: int):
|
|
|
- """Handle MQTT message for cell insertion."""
|
|
|
- try:
|
|
|
- data = json.loads(message.payload)
|
|
|
- slot = data.get('slot')
|
|
|
- cell_id = data.get('cell_id')
|
|
|
-
|
|
|
- if slot is None or cell_id is None:
|
|
|
- logger.error(f"Invalid message format: {message.payload}")
|
|
|
- return
|
|
|
+ measure_values = self.i2c_service.request_measure_values(device.i2c_address, slot.id)
|
|
|
+ cell = slot.get_cell()
|
|
|
+ if cell is None:
|
|
|
+ raise ValueError(f"No cell inserted in device {device.id}, slot {slot.id}")
|
|
|
+ cell.add_measurement(measure_values)
|
|
|
|
|
|
- # Create and schedule the measurement task
|
|
|
- self._start_measurement(device_id, slot, cell_id)
|
|
|
- logger.info(f"Initiated measurement for device {device_id}, slot {slot}, cell {cell_id}")
|
|
|
-
|
|
|
- except json.JSONDecodeError:
|
|
|
- logger.error(f"Invalid JSON in MQTT message: {message}")
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"Error handling cell insertion: {str(e)}")
|
|
|
+ def _update_inserted_cell(self, insertion_info: InsertedCell):
|
|
|
+ """Update the inserted cell id for a device."""
|
|
|
+ cell_info = self.http_service.fetch_cell_info(insertion_info.cell_id)
|
|
|
+ min_volt = max(cell_info['cell_type']['min_voltage'], self.config['measurement']['min_voltage'])
|
|
|
+ max_volt = min(cell_info['cell_type']['max_voltage'], self.config['measurement']['max_voltage'])
|
|
|
+ max_current = cell_info['cell_type']['capacity'] * self.config['measurement']['c_rate']
|
|
|
+ limits = CellLimits(min_volt, max_volt, max_current)
|
|
|
+ nom_capacity = cell_info['cell_type']['capacity']
|
|
|
+ self.devices[insertion_info.device_id].slots[insertion_info.slot_id].insert_cell(Cell(insertion_info.cell_id, limits, nom_capacity))
|
|
|
|
|
|
def _process_done(self, device: Device, slot: Slot):
|
|
|
"""Execute measurement cycles for a Cell."""
|
|
|
@@ -124,15 +115,12 @@ class MeasurementController:
|
|
|
estimated_capacity = cell.estimate_capacity()
|
|
|
|
|
|
logger.info(f"Measurement complete for cell {cell.id}. Estimated capacity: {estimated_capacity}%")
|
|
|
+ self.mqtt_service.cell_finished(device.id, slot.id, cell.id, estimated_capacity, DeviceStatus.DONE)
|
|
|
+ slot.remove_cell()
|
|
|
|
|
|
- # Publish completion message
|
|
|
- topic = f"{self.config['mqtt']['measurement_done_topic']}/device_{device.id}/slot_{slot}"
|
|
|
- self.mqtt_service.publish(topic, json.dumps({
|
|
|
- "cell_id": cell.id,
|
|
|
- "device_id": device.id,
|
|
|
- "slot_id": slot.id,
|
|
|
- "capacity": estimated_capacity,
|
|
|
- "status": DeviceStatus.DONE.name
|
|
|
- }))
|
|
|
+ def _process_error(self, device: Device, slot: Slot):
|
|
|
+ """Handle errors during measurement."""
|
|
|
+ logger.error(f"Error detected for device {device.id}, slot {slot.id}")
|
|
|
+ self.mqtt_service.cell_finished(device.id, slot.id, slot.get_cell().id, 0.0, DeviceStatus.ERROR)
|
|
|
slot.remove_cell()
|
|
|
|