koprogo_api/application/ports/
mqtt_energy_port.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::domain::entities::iot_reading::{DeviceType, MetricType};
7
8/// DTO pour les messages entrants depuis MQTT (Home Assistant → Mosquitto → KoproGo).
9/// Correspond au JSON publié par Home Assistant automations.
10///
11/// Exemple de payload Home Assistant:
12/// ```json
13/// {
14///   "value": 12.47,
15///   "unit": "kWh",
16///   "ts": "2026-03-17T14:00:00Z",
17///   "device_type": "electricity_meter",
18///   "metric_type": "electricity_consumption",
19///   "quality": "good"
20/// }
21/// ```
22#[derive(Debug, Clone, Deserialize, Serialize)]
23pub struct MqttIncomingReadingDto {
24    /// Valeur mesurée
25    pub value: f64,
26    /// Unité (ex: "kWh", "°C", "%")
27    pub unit: String,
28    /// Timestamp ISO8601 émis par le capteur
29    pub ts: DateTime<Utc>,
30    /// Type d'appareil (serde snake_case)
31    pub device_type: DeviceType,
32    /// Type de métrique (serde snake_case)
33    pub metric_type: MetricType,
34    /// Qualité de la mesure ("good", "estimated", "bad")
35    pub quality: Option<String>,
36}
37
38/// Erreurs spécifiques MQTT
39#[derive(Debug, thiserror::Error)]
40pub enum MqttError {
41    #[error("Connection failed: {0}")]
42    ConnectionFailed(String),
43
44    #[error("Subscription failed on topic '{topic}': {reason}")]
45    SubscriptionFailed { topic: String, reason: String },
46
47    #[error("Payload parse error on topic '{topic}': {reason}")]
48    PayloadParseError { topic: String, reason: String },
49
50    #[error("Topic format invalid: {0}")]
51    InvalidTopic(String),
52
53    #[error("Already running")]
54    AlreadyRunning,
55
56    #[error("Not connected")]
57    NotConnected,
58}
59
60/// Port (trait) définissant le contrat pour la couche MQTT.
61/// Le domaine ne connaît pas rumqttc — seul ce trait existe dans l'application layer.
62/// L'adapter `MqttEnergyAdapter` dans infrastructure/mqtt/ implémente ce trait.
63///
64/// Flux: capteur → Home Assistant → Mosquitto (TLS:8883) → MqttEnergyAdapter
65///       → on_reading_received() → RegisterEnergyReadingFromIoTUseCase → DB
66///
67/// Topic pattern: koprogo/{copropriete_id}/energy/{unit_id}/{metric}
68#[async_trait]
69pub trait MqttEnergyPort: Send + Sync {
70    /// Démarre l'écoute MQTT (connect + subscribe).
71    /// Non-bloquant : lance un tokio::spawn en interne.
72    async fn start_listening(&self) -> Result<(), MqttError>;
73
74    /// Arrête proprement l'écoute MQTT.
75    async fn stop_listening(&self) -> Result<(), MqttError>;
76
77    /// Publie un message sortant (ex: alerte anomalie → Home Assistant).
78    /// Topic: koprogo/{copropriete_id}/alerts/{alert_type}
79    async fn publish_alert(
80        &self,
81        copropriete_id: Uuid,
82        alert_type: &str,
83        payload: &str,
84    ) -> Result<(), MqttError>;
85
86    /// Vérifie si le listener est actif.
87    async fn is_running(&self) -> bool;
88}