Initial commit
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tahoma Module
|
||||
Enthält NUR Tahoma-spezifische Geräte-Discovery Logik
|
||||
KEINE Datenbank-Operationen!
|
||||
"""
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
import re
|
||||
import logging
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from modules.base_module import BaseModule
|
||||
|
||||
# SSL-Warnungen deaktivieren
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TahomaAPI:
|
||||
"""Original TahomaAPI Klasse - unverändert"""
|
||||
|
||||
def __init__(self, gateway_ip: str, api_token: str):
|
||||
self.base_url = f"https://{gateway_ip}:8443/enduser-mobile-web/1/enduserAPI"
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {api_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def get_setup(self) -> Optional[Dict]:
|
||||
try:
|
||||
url = f"{self.base_url}/setup"
|
||||
response = requests.get(url, headers=self.headers, verify=False, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Fehler beim Abrufen der Setup-Daten: {e}")
|
||||
return None
|
||||
|
||||
def get_devices(self) -> List[Dict]:
|
||||
setup = self.get_setup()
|
||||
if not setup:
|
||||
return []
|
||||
devices = setup.get('devices', [])
|
||||
logger.info(f"{len(devices)} Tahoma-Geräte gefunden")
|
||||
return devices
|
||||
|
||||
|
||||
class DeviceClassifier:
|
||||
"""Original DeviceClassifier - unverändert"""
|
||||
|
||||
ACTOR_TYPES = {
|
||||
'RollerShutter', 'ExteriorScreen', 'Awning', 'Blind',
|
||||
'GarageDoor', 'Window', 'Light', 'OnOff', 'DimmableLight',
|
||||
'HeatingSystem', 'Valve', 'Switch', 'Door', 'Curtain',
|
||||
'VenetianBlind', 'PergolaScreen'
|
||||
}
|
||||
|
||||
SENSOR_TYPES = {
|
||||
'TemperatureSensor', 'LightSensor', 'HumiditySensor',
|
||||
'ContactSensor', 'OccupancySensor', 'SmokeSensor',
|
||||
'WaterDetectionSensor', 'WindowHandle', 'MotionSensor',
|
||||
'SunSensor', 'WindSensor', 'RainSensor', 'ConsumptionSensor'
|
||||
}
|
||||
|
||||
# Tahoma Commands mit Parametern
|
||||
TAHOMA_COMMANDS = {
|
||||
"setClosure": [{"name": "position", "type": "integer", "min": 0, "max": 100}],
|
||||
"setClosureAndOrientation": [
|
||||
{"name": "position", "type": "integer", "min": 0, "max": 100},
|
||||
{"name": "neigung", "type": "integer", "min": 0, "max": 100}
|
||||
],
|
||||
"setOrientation": [{"name": "neigung", "type": "integer", "min": 0, "max": 100}],
|
||||
"up": [], "down": [], "my": [], "stop": [], "refresh": [], "wink":[],
|
||||
"setMyPosition": [{"name": "position", "type": "integer", "min": 0, "max": 100}],
|
||||
"on": [], "off": [], "toggle": [],
|
||||
"setIntensity": [{"name": "helligkeit", "type": "integer", "min": 0, "max": 100}],
|
||||
"setColor": [
|
||||
{"name": "farbton", "type": "integer", "min": 0, "max": 360},
|
||||
{"name": "sättigung", "type": "integer", "min": 0, "max": 100}
|
||||
],
|
||||
"setColorTemperature": [{"name": "farbtemperatur", "type": "integer", "min": 2000, "max": 6500}],
|
||||
"setTargetTemperature": [{"name": "temperatur", "type": "float", "min": 5.0, "max": 30.0}],
|
||||
"setMode": [{"name": "betriebsart", "type": "string"}],
|
||||
"pulse": [{"name": "impuls_dauer", "type": "integer", "min": 1, "max": 3600}],
|
||||
"setLevel": [{"name": "ausgangs_level", "type": "integer", "min": 0, "max": 100}],
|
||||
"trigger": [],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def is_actor(cls, device: Dict) -> bool:
|
||||
"""Prüft ob Gerät ein Aktor ist"""
|
||||
device_type = device.get('controllableName', device.get('uiClass', ''))
|
||||
|
||||
if device_type in cls.ACTOR_TYPES:
|
||||
return True
|
||||
|
||||
commands = device.get('definition', {}).get('commands', [])
|
||||
if commands:
|
||||
command_names = [cmd.get('commandName', '') for cmd in commands]
|
||||
actor_commands = {'open', 'close', 'on', 'off', 'up', 'down', 'setPosition', 'dim'}
|
||||
if any(cmd in actor_commands for cmd in command_names):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_sensor(cls, device: Dict) -> bool:
|
||||
"""Prüft ob Gerät ein Sensor ist"""
|
||||
device_type = device.get('controllableName', device.get('uiClass', ''))
|
||||
|
||||
if device_type in cls.SENSOR_TYPES:
|
||||
return True
|
||||
|
||||
states = device.get('states', [])
|
||||
commands = device.get('definition', {}).get('commands', [])
|
||||
|
||||
if states and len(states) > 0 and len(commands) <= 1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def extract_actor_data(cls, device: Dict) -> tuple:
|
||||
"""Extrahiert Commands und States aus Aktor"""
|
||||
commands = []
|
||||
states = []
|
||||
|
||||
cmd_definitions = device.get('definition', {}).get('commands', [])
|
||||
|
||||
for cmd in cmd_definitions:
|
||||
command_name = cmd.get('commandName', '')
|
||||
cmd_params = cls.TAHOMA_COMMANDS.get(command_name, "Not in List")
|
||||
if(cmd_params != "Not in List"): #only append command, if it is on of the listed commands, to prevent flooding the DB with bullshit.
|
||||
command_entry = {
|
||||
'command': command_name,
|
||||
'parameters': []
|
||||
}
|
||||
|
||||
for cmd_param in cmd_params:
|
||||
param_detail = {'name': cmd_param.get('name', '')}
|
||||
|
||||
if 'type' in cmd_param:
|
||||
param_detail['type'] = cmd_param['type']
|
||||
if 'min' in cmd_param:
|
||||
param_detail['min'] = cmd_param['min']
|
||||
if 'max' in cmd_param:
|
||||
param_detail['max'] = cmd_param['max']
|
||||
if 'values' in cmd_param:
|
||||
param_detail['values'] = cmd_param['values']
|
||||
|
||||
if param_detail['name']:
|
||||
command_entry['parameters'].append(param_detail)
|
||||
|
||||
commands.append(command_entry)
|
||||
|
||||
# States extrahieren
|
||||
state_definitions = device.get('states', [])
|
||||
for state in state_definitions:
|
||||
state_name = state.get('name', '')
|
||||
if state_name:
|
||||
state_entry = {
|
||||
'name': state_name,
|
||||
'type': state.get('type', 0)
|
||||
}
|
||||
if 'value' in state:
|
||||
state_entry['current_value'] = state['value']
|
||||
states.append(state_entry)
|
||||
|
||||
return commands, states
|
||||
|
||||
@classmethod
|
||||
def extract_sensor_data(cls, device: Dict) -> list:
|
||||
"""Extrahiert States aus Sensor"""
|
||||
states = []
|
||||
|
||||
state_definitions = device.get('states', [])
|
||||
for state in state_definitions:
|
||||
state_name = state.get('name', '')
|
||||
if state_name:
|
||||
state_entry = {
|
||||
'name': state_name,
|
||||
'type': state.get('type', 0)
|
||||
}
|
||||
if 'value' in state:
|
||||
state_entry['current_value'] = state['value']
|
||||
states.append(state_entry)
|
||||
|
||||
return states
|
||||
|
||||
|
||||
class TahomaModule(BaseModule):
|
||||
"""
|
||||
Tahoma Modul - Implementiert BaseModule Interface
|
||||
Gibt nur Actors/Sensors zurück, KEINE DB-Operationen
|
||||
"""
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
"""Prüft ob Tahoma aktiviert ist"""
|
||||
return (self.config.tahoma_enable and
|
||||
self.config.tahoma_ip and
|
||||
self.config.tahoma_token)
|
||||
|
||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||
"""
|
||||
Führt Tahoma Discovery durch
|
||||
|
||||
Returns:
|
||||
Tuple (actors, sensors) - Listen von Dicts im vereinheitlichten Format
|
||||
"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("TAHOMA-GERÄTE WERDEN ABGERUFEN")
|
||||
logger.info("=" * 60)
|
||||
|
||||
actors = []
|
||||
sensors = []
|
||||
|
||||
# TahomaAPI initialisieren
|
||||
tahoma = TahomaAPI(self.config.tahoma_ip, self.config.tahoma_token)
|
||||
devices = tahoma.get_devices()
|
||||
|
||||
if not devices:
|
||||
logger.warning("Keine Tahoma-Geräte gefunden")
|
||||
return actors, sensors
|
||||
|
||||
# Geräte gruppieren (Original-Logik)
|
||||
device_groups = {}
|
||||
standalone_devices = []
|
||||
|
||||
for device in devices:
|
||||
device_url = device.get('deviceURL', '')
|
||||
match = re.match(r'(.+)#(\d+)$', device_url)
|
||||
|
||||
if match:
|
||||
base_url = match.group(1)
|
||||
if base_url not in device_groups:
|
||||
device_groups[base_url] = []
|
||||
device_groups[base_url].append(device)
|
||||
else:
|
||||
standalone_devices.append(device)
|
||||
|
||||
# Gruppierte Geräte verarbeiten
|
||||
for base_url, group_devices in device_groups.items():
|
||||
main_device = None
|
||||
for dev in group_devices:
|
||||
if dev.get('deviceURL', '').endswith('#1'):
|
||||
main_device = dev
|
||||
break
|
||||
|
||||
if not main_device and group_devices:
|
||||
main_device = group_devices[0]
|
||||
|
||||
main_name = main_device.get('label', 'Unbekannt') if main_device else 'Unbekannt'
|
||||
|
||||
for device in group_devices:
|
||||
actor, sensor = self._process_device(device, main_name)
|
||||
if actor:
|
||||
actors.append(actor)
|
||||
if sensor:
|
||||
sensors.append(sensor)
|
||||
|
||||
# Standalone Geräte verarbeiten
|
||||
for device in standalone_devices:
|
||||
device_name = device.get('label', 'Unbekannt')
|
||||
actor, sensor = self._process_device(device, device_name)
|
||||
if actor:
|
||||
actors.append(actor)
|
||||
if sensor:
|
||||
sensors.append(sensor)
|
||||
|
||||
logger.info(f"Tahoma: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
|
||||
return actors, sensors
|
||||
|
||||
def _process_device(self, device: Dict, device_name: str) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""
|
||||
Verarbeitet ein einzelnes Gerät
|
||||
|
||||
Returns:
|
||||
Tuple (actor_dict or None, sensor_dict or None)
|
||||
"""
|
||||
device_url = device.get('deviceURL', '')
|
||||
device_type = device.get('controllableName', device.get('uiClass', 'Unknown'))
|
||||
|
||||
is_actor = DeviceClassifier.is_actor(device)
|
||||
is_sensor = DeviceClassifier.is_sensor(device)
|
||||
|
||||
actor = None
|
||||
sensor = None
|
||||
|
||||
if is_actor:
|
||||
commands, states = DeviceClassifier.extract_actor_data(device)
|
||||
actor = {
|
||||
'type': device_type,
|
||||
'name': device_name,
|
||||
'url': device_url,
|
||||
'commands': commands,
|
||||
'states': states
|
||||
}
|
||||
|
||||
elif is_sensor:
|
||||
states = DeviceClassifier.extract_sensor_data(device)
|
||||
sensor = {
|
||||
'type': device_type,
|
||||
'name': device_name,
|
||||
'url': device_url,
|
||||
'states': states
|
||||
}
|
||||
|
||||
return actor, sensor
|
||||
Reference in New Issue
Block a user