Murad Jabir 1 năm trước cách đây
mục cha
commit
1e47a280c3
2 tập tin đã thay đổi với 564 bổ sung và 0 xóa
  1. 171 0
      HOMEASSISTANT_SETUP.md
  2. 393 0
      homeassistant_integration.py

+ 171 - 0
HOMEASSISTANT_SETUP.md

@@ -0,0 +1,171 @@
+# Home Assistant Integration Setup Guide
+
+## 🏠 YouTube Predownloader Home Assistant Integration
+
+This integration provides real-time monitoring of your YouTube predownloader through Home Assistant using MQTT.
+
+### 📊 **Monitored Metrics:**
+
+1. **Download Queue Length** - Number of videos waiting to download
+2. **Conversion Queue Length** - Number of videos waiting for conversion  
+3. **Download Progress** - Overall download completion percentage
+4. **Conversion Progress** - Individual conversion progress
+5. **Download Speed** - Current download speed in MB/s
+6. **CPU Usage** - Process CPU utilization percentage
+7. **Memory Usage** - Process memory utilization (% and MB)
+8. **Active Downloads** - Number of currently downloading videos
+9. **Active Conversions** - Number of currently converting videos
+
+### 🔧 **Setup Instructions:**
+
+#### 1. **Enable MQTT in Home Assistant**
+
+Add to your `configuration.yaml`:
+```yaml
+mqtt:
+  broker: 192.168.1.100  # Your Home Assistant IP
+  port: 1883
+  username: mqtt_user     # Optional
+  password: mqtt_pass     # Optional
+```
+
+Or install the "Mosquitto broker" add-on from the Home Assistant Add-on Store.
+
+#### 2. **Configure MQTT Settings**
+
+Copy `config_example.py` to `config.py` and update:
+```python
+MQTT_BROKER = "192.168.1.100"  # Your Home Assistant IP
+MQTT_PORT = 1883
+MQTT_USERNAME = "mqtt_user"    # If authentication enabled
+MQTT_PASSWORD = "mqtt_pass"    # If authentication enabled
+```
+
+#### 3. **Install Dependencies**
+
+```bash
+pip install paho-mqtt psutil
+```
+
+#### 4. **Run the YouTube Predownloader**
+
+The integration will automatically start when you run:
+```bash
+python3 main.py
+```
+
+### 📱 **Home Assistant Dashboard**
+
+The sensors will automatically appear in Home Assistant. Create a dashboard with:
+
+```yaml
+type: entities
+title: YouTube Predownloader
+entities:
+  - sensor.youtube_download_queue
+  - sensor.youtube_conversion_queue
+  - sensor.youtube_download_progress
+  - sensor.youtube_conversion_progress
+  - sensor.youtube_download_speed
+  - sensor.youtube_cpu_usage
+  - sensor.youtube_memory_usage
+  - sensor.youtube_active_downloads
+  - sensor.youtube_active_conversions
+```
+
+### 📈 **Example Dashboard Cards:**
+
+#### **Progress Card:**
+```yaml
+type: gauge
+entity: sensor.youtube_download_progress
+min: 0
+max: 100
+name: Download Progress
+```
+
+#### **Queue Status:**
+```yaml
+type: horizontal-stack
+cards:
+  - type: statistic
+    entity: sensor.youtube_download_queue
+    name: Download Queue
+  - type: statistic
+    entity: sensor.youtube_conversion_queue
+    name: Conversion Queue
+```
+
+#### **System Performance:**
+```yaml
+type: history-graph
+entities:
+  - sensor.youtube_cpu_usage
+  - sensor.youtube_memory_usage
+hours_to_show: 2
+```
+
+### 🔔 **Automation Examples:**
+
+#### **Notify when downloads complete:**
+```yaml
+automation:
+  - alias: "YouTube Downloads Complete"
+    trigger:
+      platform: state
+      entity_id: sensor.youtube_download_progress
+      to: "100"
+    action:
+      service: notify.mobile_app
+      data:
+        message: "🎬 All YouTube videos downloaded!"
+```
+
+#### **Alert on high CPU usage:**
+```yaml
+automation:
+  - alias: "YouTube High CPU Alert"
+    trigger:
+      platform: numeric_state
+      entity_id: sensor.youtube_cpu_usage
+      above: 80
+      for: "00:05:00"
+    action:
+      service: notify.mobile_app
+      data:
+        message: "⚠️ YouTube predownloader using high CPU: {{ states('sensor.youtube_cpu_usage') }}%"
+```
+
+### 🐛 **Troubleshooting:**
+
+#### **Connection Issues:**
+- Verify MQTT broker is running in Home Assistant
+- Check IP address and port in config.py
+- Ensure firewall allows MQTT traffic (port 1883)
+
+#### **No Sensors Appearing:**
+- Check Home Assistant logs for MQTT discovery messages
+- Restart Home Assistant after first run
+- Verify MQTT integration is enabled
+
+#### **Permission Errors:**
+```bash
+# If psutil can't access process info:
+sudo python3 main.py
+```
+
+### 🎯 **MQTT Topics:**
+
+The integration publishes to these topics:
+- `youtube_predownloader/sensor/*/state` - Individual sensor values
+- `youtube_predownloader/status` - Combined JSON status
+- `homeassistant/sensor/youtube_predownloader_*/config` - Discovery configs
+
+### 🚀 **Features:**
+
+- ✅ **Auto-Discovery** - Sensors automatically appear in Home Assistant
+- ✅ **Real-time Updates** - 10-second update intervals
+- ✅ **Background Operation** - Continues monitoring after script completion
+- ✅ **Resource Monitoring** - CPU and memory usage tracking
+- ✅ **Queue Management** - Visual queue status and progress
+- ✅ **Error Handling** - Graceful handling of connection issues

+ 393 - 0
homeassistant_integration.py

@@ -0,0 +1,393 @@
+#!/usr/bin/env python3
+"""
+Home Assistant Integration for YouTube Predownloader
+Publishes real-time statistics via MQTT for Home Assistant monitoring
+"""
+
+import json
+import time
+import threading
+import psutil
+import os
+from datetime import datetime
+from typing import Dict, Any, Optional
+import paho.mqtt.client as mqtt
+
+# MQTT Configuration
+MQTT_BROKER = "server.local.lan"  # Change to your Home Assistant IP
+MQTT_PORT = 1883
+MQTT_USERNAME = ""  # Set if authentication required
+MQTT_PASSWORD = ""  # Set if authentication required
+MQTT_BASE_TOPIC = "youtube_predownloader"
+
+# Home Assistant Discovery Topics
+HA_DISCOVERY_PREFIX = "homeassistant"
+
+class HomeAssistantIntegration:
+    def __init__(self, mqtt_broker=MQTT_BROKER, mqtt_port=MQTT_PORT):
+        self.mqtt_broker = mqtt_broker
+        self.mqtt_port = mqtt_port
+        self.mqtt_client = None
+        self.process = psutil.Process()
+        self.stats = {
+            'download_queue_length': 0,
+            'conversion_queue_length': 0,
+            'download_progress': 0,
+            'conversion_progress': 0,
+            'download_speed': 0,
+            'cpu_usage': 0,
+            'memory_usage': 0,
+            'memory_usage_mb': 0,
+            'active_downloads': 0,
+            'active_conversions': 0,
+            'total_downloads_today': 0,
+            'total_conversions_today': 0,
+            'service_status': 'waiting',  # waiting, running, completed, error
+            'next_run_time': None,
+            'current_run_start': None,
+            'last_run_completed': None,
+            'last_updated': None
+        }
+        self.is_connected = False
+        self.update_thread = None
+        self.running = False
+        
+    def connect_mqtt(self):
+        """Connect to MQTT broker"""
+        try:
+            self.mqtt_client = mqtt.Client()
+            
+            if MQTT_USERNAME and MQTT_PASSWORD:
+                self.mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
+            
+            self.mqtt_client.on_connect = self._on_mqtt_connect
+            self.mqtt_client.on_disconnect = self._on_mqtt_disconnect
+            
+            self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, 60)
+            self.mqtt_client.loop_start()
+            
+            print(f"🏠 Connecting to Home Assistant MQTT broker at {self.mqtt_broker}:{self.mqtt_port}")
+            return True
+            
+        except Exception as e:
+            print(f"❌ Failed to connect to MQTT broker: {e}")
+            return False
+    
+    def _on_mqtt_connect(self, client, userdata, flags, rc):
+        """MQTT connection callback"""
+        if rc == 0:
+            self.is_connected = True
+            print("✅ Connected to Home Assistant MQTT broker")
+            self._publish_discovery_configs()
+        else:
+            print(f"❌ Failed to connect to MQTT broker, return code {rc}")
+    
+    def _on_mqtt_disconnect(self, client, userdata, rc):
+        """MQTT disconnection callback"""
+        self.is_connected = False
+        print("⚠️ Disconnected from MQTT broker")
+    
+    def _publish_discovery_configs(self):
+        """Publish Home Assistant MQTT Discovery configurations"""
+        sensors = [
+            {
+                'name': 'YouTube Download Queue',
+                'key': 'download_queue_length',
+                'unit': 'videos',
+                'icon': 'mdi:download-multiple',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Conversion Queue',
+                'key': 'conversion_queue_length', 
+                'unit': 'videos',
+                'icon': 'mdi:video-vintage',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Download Progress',
+                'key': 'download_progress',
+                'unit': '%',
+                'icon': 'mdi:progress-download',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Conversion Progress',
+                'key': 'conversion_progress',
+                'unit': '%', 
+                'icon': 'mdi:progress-wrench',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Download Speed',
+                'key': 'download_speed',
+                'unit': 'MB/s',
+                'icon': 'mdi:speedometer',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube CPU Usage',
+                'key': 'cpu_usage',
+                'unit': '%',
+                'icon': 'mdi:cpu-64-bit',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Memory Usage',
+                'key': 'memory_usage',
+                'unit': '%',
+                'icon': 'mdi:memory',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Memory Usage MB',
+                'key': 'memory_usage_mb',
+                'unit': 'MB',
+                'icon': 'mdi:memory',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Active Downloads',
+                'key': 'active_downloads',
+                'unit': 'downloads',
+                'icon': 'mdi:download',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Active Conversions',
+                'key': 'active_conversions',
+                'unit': 'conversions',
+                'icon': 'mdi:cog',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Service Status',
+                'key': 'service_status',
+                'unit': None,
+                'icon': 'mdi:play-circle-outline',
+                'device_class': None
+            },
+            {
+                'name': 'YouTube Next Run Time',
+                'key': 'next_run_time',
+                'unit': None,
+                'icon': 'mdi:clock-outline',
+                'device_class': 'timestamp'
+            },
+            {
+                'name': 'YouTube Current Run Start',
+                'key': 'current_run_start',
+                'unit': None,
+                'icon': 'mdi:clock-start',
+                'device_class': 'timestamp'
+            },
+            {
+                'name': 'YouTube Last Run Completed',
+                'key': 'last_run_completed',
+                'unit': None,
+                'icon': 'mdi:clock-check-outline',
+                'device_class': 'timestamp'
+            }
+        ]
+        
+        for sensor in sensors:
+            config = {
+                'name': sensor['name'],
+                'unique_id': f"{MQTT_BASE_TOPIC}_{sensor['key']}",
+                'state_topic': f"{MQTT_BASE_TOPIC}/sensor/{sensor['key']}/state",
+                'icon': sensor['icon'],
+                'device': {
+                    'identifiers': [MQTT_BASE_TOPIC],
+                    'name': 'YouTube Predownloader',
+                    'model': 'YouTube Predownloader v1.0',
+                    'manufacturer': 'Custom',
+                    'sw_version': '1.0.0'
+                }
+            }
+            
+            if sensor['unit']:
+                config['unit_of_measurement'] = sensor['unit']
+            
+            if sensor['device_class']:
+                config['device_class'] = sensor['device_class']
+            
+            discovery_topic = f"{HA_DISCOVERY_PREFIX}/sensor/{MQTT_BASE_TOPIC}_{sensor['key']}/config"
+            self.mqtt_client.publish(discovery_topic, json.dumps(config), retain=True)
+        
+        print("📡 Published Home Assistant discovery configurations")
+    
+    def update_stats(self, download_queue=0, conversion_queue=0, download_progress=0, 
+                    conversion_progress=0, download_speed=0, active_downloads=0, active_conversions=0,
+                    service_status=None, next_run_time=None, current_run_start=None, last_run_completed=None):
+        """Update statistics"""
+        # Update provided stats
+        update_dict = {
+            'download_queue_length': download_queue,
+            'conversion_queue_length': conversion_queue,
+            'download_progress': round(download_progress, 1),
+            'conversion_progress': round(conversion_progress, 1),
+            'download_speed': round(download_speed, 2),
+            'active_downloads': active_downloads,
+            'active_conversions': active_conversions,
+            'last_updated': datetime.now().isoformat()
+        }
+        
+        # Add optional scheduling stats
+        if service_status is not None:
+            update_dict['service_status'] = service_status
+        if next_run_time is not None:
+            update_dict['next_run_time'] = next_run_time
+        if current_run_start is not None:
+            update_dict['current_run_start'] = current_run_start
+        if last_run_completed is not None:
+            update_dict['last_run_completed'] = last_run_completed
+        
+        self.stats.update(update_dict)
+        
+        # Update system stats
+        try:
+            self.stats['cpu_usage'] = round(self.process.cpu_percent(), 1)
+            memory_info = self.process.memory_info()
+            memory_percent = self.process.memory_percent()
+            self.stats['memory_usage'] = round(memory_percent, 1)
+            self.stats['memory_usage_mb'] = round(memory_info.rss / 1024 / 1024, 1)
+        except (psutil.NoSuchProcess, psutil.AccessDenied):
+            pass
+        
+        # Publish to MQTT
+        if self.is_connected:
+            self._publish_stats()
+    
+    def _publish_stats(self):
+        """Publish statistics to MQTT"""
+        for key, value in self.stats.items():
+            if key != 'last_updated':
+                topic = f"{MQTT_BASE_TOPIC}/sensor/{key}/state"
+                self.mqtt_client.publish(topic, str(value))
+        
+        # Publish combined status
+        status_topic = f"{MQTT_BASE_TOPIC}/status"
+        self.mqtt_client.publish(status_topic, json.dumps(self.stats))
+    
+    def start_monitoring(self, update_interval=10):
+        """Start monitoring and publishing stats"""
+        self.running = True
+        self.update_thread = threading.Thread(target=self._monitoring_loop, args=(update_interval,), daemon=True)
+        self.update_thread.start()
+        print(f"📊 Started Home Assistant monitoring (update interval: {update_interval}s)")
+    
+    def stop_monitoring(self):
+        """Stop monitoring"""
+        self.running = False
+        if self.mqtt_client:
+            self.mqtt_client.loop_stop()
+            self.mqtt_client.disconnect()
+        print("🛑 Stopped Home Assistant monitoring")
+    
+    def _monitoring_loop(self, interval):
+        """Background monitoring loop"""
+        while self.running:
+            try:
+                # Update system stats only (other stats updated via update_stats method)
+                self.update_stats(
+                    self.stats['download_queue_length'],
+                    self.stats['conversion_queue_length'],
+                    self.stats['download_progress'],
+                    self.stats['conversion_progress'],
+                    self.stats['download_speed'],
+                    self.stats['active_downloads'],
+                    self.stats['active_conversions']
+                )
+                time.sleep(interval)
+            except Exception as e:
+                print(f"❌ Monitoring error: {e}")
+                time.sleep(interval)
+
+# Global instance
+ha_integration = None
+
+def init_homeassistant_integration(mqtt_broker=MQTT_BROKER, mqtt_port=MQTT_PORT):
+    """Initialize Home Assistant integration"""
+    """Initialize Home Assistant integration"""
+    global ha_integration
+    
+    if ha_integration is None:
+        ha_integration = HomeAssistantIntegration(mqtt_broker, mqtt_port)
+        if ha_integration.connect_mqtt():
+            ha_integration.start_monitoring()
+            return ha_integration
+        else:
+            ha_integration = None
+            return None
+    
+    return ha_integration
+
+def update_homeassistant_stats(**kwargs):
+    """Update Home Assistant statistics"""
+    global ha_integration
+    
+    if ha_integration and ha_integration.is_connected:
+        ha_integration.update_stats(**kwargs)
+
+def update_service_status(status, next_run=None, current_run_start=None, last_completed=None):
+    """Update service scheduling status"""
+    global ha_integration
+    
+    if ha_integration and ha_integration.is_connected:
+        ha_integration.update_stats(
+            service_status=status,
+            next_run_time=next_run,
+            current_run_start=current_run_start,
+            last_run_completed=last_completed
+        )
+
+def set_service_waiting(next_run_time):
+    """Set service status to waiting with next run time"""
+    update_service_status('waiting', next_run=next_run_time)
+
+def set_service_running():
+    """Set service status to running"""
+    update_service_status('running', current_run_start=datetime.now().isoformat())
+
+def set_service_completed():
+    """Set service status to completed"""
+    update_service_status('completed', last_completed=datetime.now().isoformat())
+
+def set_service_error():
+    """Set service status to error"""
+    update_service_status('error')
+
+def stop_homeassistant_integration():
+    """Stop Home Assistant integration"""
+    global ha_integration
+    
+    if ha_integration:
+        ha_integration.stop_monitoring()
+        ha_integration = None
+
+if __name__ == "__main__":
+    # Test the integration
+    print("🧪 Testing Home Assistant integration...")
+    
+    integration = init_homeassistant_integration()
+    
+    if integration:
+        print("✅ Integration started successfully")
+        
+        # Simulate some data
+        for i in range(5):
+            update_homeassistant_stats(
+                download_queue=i,
+                conversion_queue=i*2,
+                download_progress=i*20,
+                conversion_progress=i*15,
+                download_speed=i*2.5,
+                active_downloads=min(i, 2),
+                active_conversions=min(i, 2)
+            )
+            time.sleep(2)
+        
+        stop_homeassistant_integration()
+        print("✅ Test completed")
+    else:
+        print("❌ Failed to start integration")