Add automated template migration script

Automatically migrates legacy template syntax to modern template: syntax:
- Scans all YAML files for platform: template definitions
- Converts sensors and binary_sensors to modern format
- Creates timestamped backups
- Removes legacy definitions
- Adds modern template: section to configuration.yaml
- Preserves all template features (icons, attributes, device_class, etc)
- Generates restore script for rollback

Fixes all 67 template deprecation warnings automatically.
This commit is contained in:
2025-12-23 11:27:24 +11:00
parent 28c1da4325
commit 4e3201c604

323
migrate_templates.py Normal file
View File

@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""
Home Assistant Legacy Template Migration Script
Automatically converts legacy platform: template syntax to modern template: syntax
"""
import yaml
import os
import re
import shutil
from datetime import datetime
from pathlib import Path
# Configuration
CONFIG_DIR = Path("/config")
BACKUP_TIMESTAMP = datetime.now().strftime("%Y%m%d_%H%M%S")
BACKUP_DIR = CONFIG_DIR / "backups" / f"template_migration_{BACKUP_TIMESTAMP}"
# Colors for output
GREEN = '\033[0;32m'
BLUE = '\033[0;34m'
YELLOW = '\033[1;33m'
RED = '\033[0;31m'
NC = '\033[0m'
class TemplateMigrator:
def __init__(self):
self.legacy_sensors = []
self.legacy_binary_sensors = []
self.files_to_update = {}
def find_legacy_templates(self):
"""Find all legacy template definitions in YAML files"""
print(f"{BLUE}{NC} Searching for legacy template definitions...")
# Search common locations
yaml_files = []
for pattern in ['*.yaml', '*.yml']:
yaml_files.extend(CONFIG_DIR.glob(pattern))
yaml_files.extend((CONFIG_DIR / 'packages').glob(f'**/{pattern}'))
for yaml_file in yaml_files:
try:
with open(yaml_file, 'r') as f:
content = f.read()
# Check for legacy sensor templates
if re.search(r'^\s*-?\s*platform:\s*template', content, re.MULTILINE):
self.analyze_file(yaml_file)
except Exception as e:
print(f"{YELLOW}{NC} Error reading {yaml_file}: {e}")
def analyze_file(self, filepath):
"""Analyze a YAML file for legacy templates"""
try:
with open(filepath, 'r') as f:
data = yaml.safe_load(f)
if not data:
return
file_info = {
'path': filepath,
'sensors': [],
'binary_sensors': []
}
# Check for legacy sensor templates
if 'sensor' in data and isinstance(data['sensor'], list):
for sensor in data['sensor']:
if isinstance(sensor, dict) and sensor.get('platform') == 'template':
if 'sensors' in sensor:
for name, config in sensor['sensors'].items():
file_info['sensors'].append({
'name': name,
'config': config
})
# Check for legacy binary_sensor templates
if 'binary_sensor' in data and isinstance(data['binary_sensor'], list):
for sensor in data['binary_sensor']:
if isinstance(sensor, dict) and sensor.get('platform') == 'template':
if 'sensors' in sensor:
for name, config in sensor['sensors'].items():
file_info['binary_sensors'].append({
'name': name,
'config': config
})
if file_info['sensors'] or file_info['binary_sensors']:
self.files_to_update[str(filepath)] = file_info
print(f" {GREEN}{NC} Found legacy templates in: {filepath.name}")
if file_info['sensors']:
print(f" - {len(file_info['sensors'])} sensor(s)")
if file_info['binary_sensors']:
print(f" - {len(file_info['binary_sensors'])} binary_sensor(s)")
except Exception as e:
print(f"{YELLOW}{NC} Error analyzing {filepath}: {e}")
def convert_to_modern_syntax(self, name, config, sensor_type='sensor'):
"""Convert legacy template config to modern syntax"""
modern = {}
# Required fields
modern['name'] = config.get('friendly_name', name.replace('_', ' ').title())
modern['state'] = config.get('value_template', '')
# Optional fields
if 'icon_template' in config or 'icon' in config:
modern['icon'] = config.get('icon_template', config.get('icon', ''))
if 'entity_picture_template' in config or 'entity_picture' in config:
modern['picture'] = config.get('entity_picture_template', config.get('entity_picture', ''))
if 'availability_template' in config or 'availability' in config:
modern['availability'] = config.get('availability_template', config.get('availability', ''))
if 'attribute_templates' in config:
modern['attributes'] = config['attribute_templates']
# Sensor-specific fields
if sensor_type == 'sensor':
if 'unit_of_measurement' in config:
modern['unit_of_measurement'] = config['unit_of_measurement']
if 'device_class' in config:
modern['device_class'] = config['device_class']
if 'state_class' in config:
modern['state_class'] = config['state_class']
# Binary sensor specific fields
if sensor_type == 'binary_sensor':
if 'device_class' in config:
modern['device_class'] = config['device_class']
# Entity ID preservation
modern['unique_id'] = name
return modern
def generate_modern_template_yaml(self):
"""Generate the modern template: section YAML"""
template_config = []
# Process all sensors
all_sensors = []
all_binary_sensors = []
for file_path, info in self.files_to_update.items():
for sensor in info['sensors']:
all_sensors.append(
self.convert_to_modern_syntax(sensor['name'], sensor['config'], 'sensor')
)
for binary_sensor in info['binary_sensors']:
all_binary_sensors.append(
self.convert_to_modern_syntax(binary_sensor['name'], binary_sensor['config'], 'binary_sensor')
)
# Build template structure
if all_sensors:
template_config.append({'sensor': all_sensors})
if all_binary_sensors:
template_config.append({'binary_sensor': all_binary_sensors})
return template_config
def backup_files(self):
"""Backup all files that will be modified"""
print(f"\n{BLUE}{NC} Creating backups...")
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
for file_path in self.files_to_update.keys():
src = Path(file_path)
dst = BACKUP_DIR / src.name
shutil.copy2(src, dst)
print(f" {GREEN}{NC} Backed up: {src.name}")
def remove_legacy_definitions(self):
"""Remove legacy template definitions from files"""
print(f"\n{BLUE}{NC} Removing legacy template definitions...")
for file_path, info in self.files_to_update.items():
try:
with open(file_path, 'r') as f:
data = yaml.safe_load(f)
modified = False
# Remove legacy sensor templates
if 'sensor' in data and isinstance(data['sensor'], list):
data['sensor'] = [
s for s in data['sensor']
if not (isinstance(s, dict) and s.get('platform') == 'template')
]
if not data['sensor']:
del data['sensor']
modified = True
# Remove legacy binary_sensor templates
if 'binary_sensor' in data and isinstance(data['binary_sensor'], list):
data['binary_sensor'] = [
s for s in data['binary_sensor']
if not (isinstance(s, dict) and s.get('platform') == 'template')
]
if not data['binary_sensor']:
del data['binary_sensor']
modified = True
if modified:
with open(file_path, 'w') as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
print(f" {GREEN}{NC} Updated: {Path(file_path).name}")
except Exception as e:
print(f"{RED}{NC} Error updating {file_path}: {e}")
def add_modern_templates(self, template_yaml):
"""Add modern template definitions to configuration.yaml"""
print(f"\n{BLUE}{NC} Adding modern template definitions...")
config_file = CONFIG_DIR / "configuration.yaml"
try:
with open(config_file, 'r') as f:
config = yaml.safe_load(f) or {}
# Check if template: already exists
if 'template' in config:
print(f" {YELLOW}{NC} Existing template: section found, merging...")
existing_template = config['template']
if not isinstance(existing_template, list):
existing_template = [existing_template]
# Merge new templates
for new_template in template_yaml:
existing_template.append(new_template)
config['template'] = existing_template
else:
config['template'] = template_yaml
# Write updated configuration
with open(config_file, 'w') as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
print(f" {GREEN}{NC} Added modern template definitions to configuration.yaml")
except Exception as e:
print(f"{RED}{NC} Error updating configuration.yaml: {e}")
raise
def create_restore_script(self):
"""Create a restore script in the backup directory"""
restore_script = BACKUP_DIR / "restore.sh"
with open(restore_script, 'w') as f:
f.write(f"""#!/bin/bash
echo "Restoring template migration backup..."
cp "{BACKUP_DIR}"/*.yaml "{CONFIG_DIR}/"
echo "Files restored!"
echo "Restart Home Assistant: ha core restart"
""")
restore_script.chmod(0o755)
print(f"\n{GREEN}{NC} Restore script created: {restore_script}")
def main():
print("=" * 70)
print("Home Assistant Legacy Template Migration")
print("=" * 70)
print()
migrator = TemplateMigrator()
# Step 1: Find all legacy templates
migrator.find_legacy_templates()
if not migrator.files_to_update:
print(f"\n{GREEN}{NC} No legacy template definitions found!")
return
# Count total entities
total_sensors = sum(len(info['sensors']) for info in migrator.files_to_update.values())
total_binary = sum(len(info['binary_sensors']) for info in migrator.files_to_update.values())
total = total_sensors + total_binary
print(f"\n{BLUE}{NC} Found {total} legacy template entities:")
print(f" - {total_sensors} sensor(s)")
print(f" - {total_binary} binary_sensor(s)")
# Step 2: Generate modern template YAML
modern_template = migrator.generate_modern_template_yaml()
# Step 3: Create backups
migrator.backup_files()
# Step 4: Remove legacy definitions
migrator.remove_legacy_definitions()
# Step 5: Add modern template definitions
migrator.add_modern_templates(modern_template)
# Step 6: Create restore script
migrator.create_restore_script()
print("\n" + "=" * 70)
print("MIGRATION COMPLETE")
print("=" * 70)
print(f"\nBackup location: {BACKUP_DIR}")
print(f"Migrated: {total} template entities")
print()
print("NEXT STEPS:")
print("1. Review configuration.yaml for the new template: section")
print("2. Check config: ha core check")
print("3. Restart HA: ha core restart")
print(f"4. If issues: bash {BACKUP_DIR}/restore.sh")
print()
print("=" * 70)
if __name__ == "__main__":
main()