#!/usr/bin/python
# coding: iso-8859-15

#//PY_START
import thread
global Queue
import Queue
global os
import os
global asyncore
import asyncore
import time
#//PY_END

#//LIB_START
from hsl20_4_tcp import *
from hsl20_4_udp import *
from hsl20_4_timer import *
from hsl20_4_http_client import *
from hsl20_4_http_server import *
from hsl20_4_debug_page import *
from hsl20_4_crypto import *
#//LIB_END

## @mainpage
## @section MP1 Übersicht
## Beschreibung der wichtigsten Klassen:
## - @ref hsl20_4.hsl20_4.BaseModule "BaseModule"
## - @ref hsl20_4.hsl20_4.Framework "Framework"

## @brief Package
## @details
## @ref hsl20_4.hsl20_4.BaseModule "BaseModule"
## Fasst alle Klassen des Frameworks zusammen.
class hsl20_4:

    ## Null-Logger, es findet keine Protokollierung statt. Wird vom Generator verwendet.
    LOGGING_NONE = 0

    ## SysLog-Logger, es findet eine Protokollierung per SYSLOG statt. Wird vom Generator verwendet.
    LOGGING_SYSLOG = 1

    ## @cond NO_DOC
    ## Max. Einträge der kontextbezogenen Thread-Queue
    _CONTEXT_THREAD_QUEUE_MAX_SIZE = 0
    # 0 = keine Beschränkung der Queue

    ## Timeout des Asyncore-Loops (und des darin enthaltenen Selects) in Sekunden
    _ASYNCORE_LOOP_TIMEOUT = 1

    ## Globaler HomeServer-Kontext
    _HSCTX = None
    ## @endcond


    ## @brief Basis-Klasse für einen HSL 2.0-Baustein
    ## @details
    ## Ein HSL 2.0-Baustein muss diese Klasse durch Vererbung als Basis haben.
    ## Der Generator erzeugt die notwendigen Code-Segmente:
    ## Ein Beispiel für die Umsetzung des Bausteins @b Sperre liegt bei: @e 10701_MyBaseModule_03.py @n@n


    ## @if OLD_CHANGELOG
    ## @chlg04 altes Beispiel gelöscht. Neues Beispiel erstellt. @n
    ## Methoden @e _has_input_connections und @e _has_output_connections entfernt.
    ## @endif
    class BaseModule:

        ## Konstruktur, muss im Konstruktor der vererbten Klasse aufgerufen werden.
        ##
        ## @param homeserver_context @e object @n Verweis auf die HomeServer-Objektstruktur.
        ## @param module_context @e string @n Schlüssel des Modulkontexts. Der Schlüssel legt fest, in welchem @b Kontext der Baustein läuft.
        def __init__(self, homeserver_context, module_context):
            ## @cond NO_DOC
            if hsl20_4._HSCTX==None:
                hsl20_4._HSCTX = homeserver_context[0]

            self._mc = homeserver_context[0]
            self._context_id = module_context
            self._lock = thread.allocate_lock()
            self._module_id = None
            self._framework = None
            self._refenerce = None
            self._last_timer_ts=0
            self._refenerce=homeserver_context[3]

            # Eingangswerte
            self._input_values = {}
            for i in range(len(homeserver_context[2])): #EN
                if(i>0):
                    self._input_values[i] = homeserver_context[2][i]

            # Speichervariablen
            self._remanent_values = {}
            for i in range(len(homeserver_context[1])): #SN
                if i>2:
                    self._remanent_values[i-2] = homeserver_context[1][i]

            # Ausgangswerte
            self._output_values = []

            if self._mc!=None:
                # Framework instanzieren
                self._framework = hsl20_4.Framework(self._mc, self._context_id, self)
                # Module-ID abrufen
                self._module_id = self._framework._get_module_instance_id()

            ## @endcond


        ## Liefert die Laufzeit-ID des Moduls zurück.
        ## Die ID ist nur während der Laufzeit des Geräts gültig.
        ##
        ## @result @e int @n ID des Moduls
        def _get_module_id(self):
            return self._module_id

        ## Liefert die fünfstellige Baustein-ID (zum Beispiel '10001') zurück.
        ## @result @e string @n Baustein-ID
        ## @if OLD_CHANGELOG
        ## @chlg04 neue Methode
        ## @endif
        ## @chg20_03 Methode @e set_reference(self, hsl_module) entfernt.
        def _get_module_class_id(self):
            return self._refenerce.LogikItem.ID


        ## @cond NO_DOC
        ## Wird durch den HSL-Baustein aufgerufen und übergibt eine Eingangsänderung an das Modul. Diese Methode darf @e nicht überschrieben werden.
        ## @param ec @e array @n Referenz auf Liste
        ## @param en @e array @n Referenz auf Liste
        ## @if OLD_CHANGELOG
        ## @chlg02 Public-Methode - Unterstrich entfernt.
        ## @endif
        ## @chg20_03 Methode umbenannt von @e check_input_values() in @e _hslfw_check_input_values()
        def _hslfw_check_input_values(self, ec, en):
            idx = ec.index(True)
            #Wert in internes Dict übernehmen
            self._input_values[idx]=en[idx]
            # on_input_value soll im kontextbezogenen Thread ausgeführt werden
            self._framework._run_in_context_thread(self.on_input_value, (idx, en[idx]))
        ## @endcond


        ## @cond NO_DOC
        ## Wird durch den HSL-Baustein aufgerufen und überprüft, ob Änderungen an den Speichervariablen und den Ausgängen vorliegen.
        ## @param sc @e array @n Referenz auf Liste
        ## @param sn @e array @n Referenz auf Liste
        ## @param ac @e array @n Referenz auf Liste
        ## @param an @e array @n Referenz auf Liste
        ## chlg02 Public-Methode - Unterstrich entfernt.
        ## @chg20_03 Methode umbenannt von @e check_output_values() in @e _hslfw_check_output_values()
        ## @result @e bool @n Liefert zurück, ob sich eine remanente Speichervariable geändert hat.
        def _hslfw_check_output_values(self, sc, sn, ac, an):
            remanent_value_changed=False
            with self._lock:
                ## @cond NO_DOC
                self._last_timer_ts = time.time()
                ## @endcond
                try:
                    for item in self._remanent_values:
                        idx = int(item)
                        if (sn[idx+2]!=self._remanent_values[item]):
                            remanent_value_changed=True
                            sc[idx+2] = 1
                            sn[idx+2] = self._remanent_values[item]

                    outputs_set=[]
                    while(len(self._output_values)>0):
                        item, value=self._output_values[0]
                        idx = int(item)
                        if idx in outputs_set:
                            break
                        else:
                            outputs_set.append(idx)
                        ac[idx] = 1
                        an[idx] = value
                        del self._output_values[0]

                    if (len(self._output_values) > 0):
                        self._mc.ZyklusWork.addQueue([0, self._refenerce])

                except Exception as e:
                    pass
            return remanent_value_changed
        ## @endcond



        ## Liefert ein Objekt vom Typ hsl20_4.Framework zurück.
        ##
        ## @result @e hsl20_4.hsl20_4.Framework @n Eine Instanz auf das HSL 2.0-Framework
        def _get_framework(self):
            return self._framework


        ## Liefert ein Objekt vom Type hsl20_4.Logger zruück.
        ## @note Diese Methode wird durch den vom Generator erzeugten Code aufgerufen. Ein erneutes Aufrufen dieser Methode liefert
        ## (unabängig von den Parametern) das bereits erzeugte Logger-Objekt zurück.
        ## @param logType @e int @n Logging-Handler (z.B. hsl20_4.LOGGING_SYSLOG)
        ## @param param @e tuple @n Parameter zum verwendeten Logging-Handler
        ## @result @e hsl20_4.hsl20_4.Logger @n Logger-Objekt
        def _get_logger(self, logType, param):
            return self._framework._context.get_logger((logType,param))

        ## Liefert den Wert, der zum Zeitpunkt des Aufrufs am Eingang @n index anliegt, zurück. Der am Eingang anliegende Wert kann sich während des Ablaufs der Bausteinlogik ändern.
        ## @param index @e int @n Index des Eingangs.
        ## @result @e float/str @n Wert des Eingangs. Der Typ des Rückgabewerts hängt von der Definition des Eingangs ab.
        def _get_input_value(self, index):
                if self._input_values.has_key(index):
                    return self._input_values[index]
                else:
                    return None


        ## Wird jedes Mal aufgerufen, wenn ein Wert auf einem Eingang des Baustein eintrifft.
		## Die Methode wird für jedes Eintreffen eines Wertes an einem Eingang genau einmal aufgerufen.
        ##
        ## Diese Methode muss implementiert (überschrieben) werden, wenn ein
        ## Baustein erstellt wird.
        ## @param index @e int @n Index des Eingangs.
        ## @param value @e float/str @n Wert, der auf dem Eingang eingetroffen ist.
        ## Der Datentyp ist abhängig davon, ob der Eingang als numerisch oder alphanumerisch definiert wurde.
        def on_input_value(self, index, value):
            pass

        ## Wird einmalig aufgerufen, wenn der Baustein initialisiert wird.
        ##
        ## Diese Methode kann implementiert (überschrieben) werden, wenn ein
        ## Baustein erstellt wird.
        ## @if OLD_CHANGELOG
        ## @chlg10 Beschreibung der Methode hinzugefügt
        ## @endif
        def on_init(self):
            pass


        ## Setzt einen Wert auf einen Ausgang.
        ## @param index @e int @n Index des Ausgangs, der beschrieben werden soll.
        ## @param value @e float/str @n Wert, der auf den Ausgang geschrieben wird.
        ## Der Datentyp ist abhängig davon, ob der Ausgang als numerisch oder alphanumerisch definiert wurde.
        def _set_output_value(self, index, value):
            with self._lock:
                #if(index>len(self._input_values) or index<=0):
                #    raise IndexError("index %s invalid" % index)
                if(len(self._output_values)>100):
                    raise RuntimeError("Outputqueue overflow (index=%s)" % index)
                self._output_values.append((index, value))
                if len(self._output_values) == 1 or (time.time() - self._last_timer_ts>2):
                    if self._refenerce!=None:
                        self._mc.ZyklusWork.addQueue([0, self._refenerce])

        ## Zeigt an, ob ein Wert auf einen Ausgang gesetzt werden kann. Liefert @e False falls vorerst
        ## kein weiterer Wert geschrieben werden kann.
        ##
        ## @result bool Ein Ausgang kann gesetzt werden
        def _can_set_output(self):
            return len(self._output_values)<100


        ## Liefert den Wert der remanenten Variable zurück.
        ##
        ## Siehe auch die Dokumentation zum @b Remanentspeicher.
        ##
        ## @param index @e int @n Index der remanten Variable
        ## @result @e float/str @n Wert der remanenten Variable
        ## @if OLD_CHANGELOG
        ## @chlg04 @b result war nicht angegeben
        ## @endif
        def _get_remanent(self, index):
            if self._remanent_values.has_key(index):
                return self._remanent_values[index]
            else:
                return None


        ## Beschreibt eine remanente Variable.
        ##
        ## @warning Der Remanentspeicher wird im HomeServer nur alle 15 Minuten persistiert.
        ##
        ## Siehe auch die Dokumentation zum @b Remanentspeicher.
        ##
        ## @param index @e int @n Index der remanenten Variable
        ## @param value @e float/str @n Wert der remanenten Variable (maximale Größe: 30.000 Byte).
        ## @if OLD_CHANGELOG
        ## @chlg02 Datentyp für @e value korrigiert @n
        ## @e max. @e Größe bei Parameter @e value korrigiert
        ## @chlg04 Größe "ausformuliert": @b '30.000 Byte' anstatt @b '30 kB', um Unklarheiten zu beseitigen.
        ## Die Größe ist nur von Bedeutung, wenn @e value vom Typ @e String ist! @n
        ## Text zum Datentyp entfernt, hängt von der übergebenenen Variable und nicht von der Definition in config.xml/hsl ab
        ## @endif
        def _set_remanent(self, index, value):
            if isinstance(value, str):
                self._remanent_values[index] = value[:30000]
            else:
                self._remanent_values[index] = value
            if self._refenerce!=None:
                self._mc.ZyklusWork.addQueue([0, self._refenerce])



    ## @brief Bildet alle Framework-Funktionalitäten ab.
    ## @details
    ## Die Klasse wird automatisch vom Basis-Modul instanziert und sollte @b nicht direkt instanziert werden.
    ## @if OLD_CHANGELOG
    ## @chlg10 In der Doku zu allen Methoden, die ein Klassen-Objekt zurückliefern, wurde ein Link zur Doku der jeweiligen Klasse eingefügt.
    ## @endif
    ## @chg20_05 Fehlerhaftes Verhalten im Zusammenhang mit Timern beseitigt
    class Framework:

        ## @cond NO_DOC
        #_homeserver_context=None
        _timer_thread=None
        ## @endcond


        ## Konstruktor
        ##
        ## @warning Eine Instanz dieser Klasse wird durch den Baustein erzeugt.
        ##
        ## @param homeserver_context @e object @n Verweis auf die HomeServer-Objektstruktur.
        ## @param module_context @e string @n Schlüssel des Modulkontexts.
        ## @param module_instance @e function @n Baustein Instanz
        def __init__(self, homeserver_context, module_context, module_instance):
            ## @cond NO_DOC
            self._mc = homeserver_context
            self._context_id = module_context
            self._logger = None
            hsl20_4.Framework._init_globals(self._mc)
            self._context=hsl20_4._Context.get_context(homeserver_context, module_context)
            self._module_instance_id=self._context.register_module_instance(module_instance)
            ## @endcond

        ## @cond NO_DOC
        ## Initialisiert die global benötigten Variablen, Objekte und Threads (soweit noch nicht geschehen)
        @staticmethod
        def _init_globals(homeserver_context):
            #hsl20_4.Framework._homeserver_context=homeserver_context
            # ID init
            if not hasattr(homeserver_context, "HSL20ID"):
                homeserver_context.HSL20ID = 0
            # DEBUG
            if not hasattr(homeserver_context, "HSL20DBG"):
                homeserver_context.HSL20DBG = {}
            # TIMER Falls der Timer-Thread noch nicht läuft und hsl20_4_timer vorhanden ist, wird er gestartet.
            try:
                hsl20_4_timer
                if not hasattr(homeserver_context, "HSL20TIMERTHREAD_3"):
                    homeserver_context.HSL20TIMERTHREAD_3 = {}
                    homeserver_context.HSL20TIMERTHREAD_3["instance"] = hsl20_4_timer._TimerThread()
                #Verfügbar machen für Zugriff durch Timer und Intervall.
                hsl20_4.Framework._timer_thread=homeserver_context.HSL20TIMERTHREAD_3["instance"]
            except NameError:
                #hsl20_4_timer ist unbekannt: HSL20TIMERTHREAD wird nicht angelegt
                pass

        @staticmethod
        def _get_global_debug_section():
            if not hsl20_4._HSCTX.HSL20DBG.has_key("global"):
                hsl20_4._HSCTX.HSL20DBG["global"] = hsl20_4_debug_page.Section("GLOBAL")
            return hsl20_4._HSCTX.HSL20DBG["global"]
        ## @endcond


        ## Liefert den Versions-Index des Frameworks. In dieser Version beträgt der Versions-Index 7.
        ## @result @e int @n Index (=7)
        @staticmethod
        def get_framework_index():
            return 7

        ## @cond NO_DOC
        ## Führt die Methode @e method_to_call im Thread des Kontexts aus und übergibt dieser optional die Parameter @e args.
        ## @param method_to_call @e callback @n Methode die im Thread des Kontexts ausgeführt werden soll.
        ## @param args @n Parameter für den Methodenaufruf als Tupel.
        def _run_in_context_thread(self, method_to_call, args=None):
            self._context.run_in_context_thread(method_to_call, args)

        def _get_module_instance_id(self):
            return self._module_instance_id

        ## Unterbricht ein Select-Aufuf im Asyncore-Loop.
        ## Muss aufgerufen werden, wenn der Socket-Map ein Socket hinzugefügt wird.
        def _signal_asyncore_select_interrupt(self):
            self._context.signal_asyncore_select_interrupt()
        ## @endcond


        ## Liefert die Version der auf dem Gerät aufgespielten Firmware.
        ## @result @e string @n Version der Firmware.
        def get_homeserver_version(self):
            return self._mc.Debug.Version

        ## Liefert die Hauptversionsnummer (major release) der auf dem Gerät aufgespielten Firmware.
        ## result @e int Hauptversionsnummer der Firmware.
        def get_homeserver_version_major(self):
            return int(self._mc.Debug.Version.split('.')[0])

        ## Liefert die Nebenversionsnummer (minor release) der auf dem Gerät aufgespielten Firmware.
        ## result @e int Nebenversionsnummer der Firmware.
        def get_homeserver_version_minor(self):
            return int(self._mc.Debug.Version.split('.')[1])

        ## Liefert die Seriennummer des Geräts.
        ## @result @e string @n Seriennummer
        def get_homeserver_serial_id(self):
            return self._mc.SystemID

        ## Liefert die private IP-Adresse des Geräts.
        ## @result @e string @n IP-Adresse
        def get_homeserver_private_ip(self):
            return self._mc.Ethernet.IPAdr


        ## Liefert die ID des aktuell aufgespielten Projekts.
        ## @result @e string @n Projekt-ID
        def get_project_id(self):
            return self._mc.ProjectID

        ## Liefert die im Projekt angegebenen Koordinaten für die Sonnenstandsberechnung.
        ## @result @e tuple @n (Breitengrad, Längengrad)
        ## @chg20_07 neue Methode
        def get_coordinates(self):
            return (self._mc.UhrenList.gradBreite, self._mc.UhrenList.gradLaenge)

        ## Liefert den Schlüssel des Modulkontexts.
        ## @result @e string @n Schlüssel des Modulkontexts.
        ## @if OLD_CHANGELOG
        ## @chlg04 neue Methode
        ## @endif
        def get_context(self):
            return self._context_id

        ## Liefert eine Baustein-Instanz zurück. Dient zur Kommunikation zwischen
        ## zwei Bausteinen. Im GLE kann die ID einem anderen Baustein über eine direkte
        ## Verdrahtung der Bausteine bekannt gemacht werden.
        ##
        ## Die ID muss sich innerhalb des Modulkontexts befinden.
        ##
        ## @param instance_id @e int @n Laufzeit-ID des Bausteins.
        ## @result @e object @n Instanz des Bausteins (vererbt von BaseModule).
        ## Liefert @e None, wenn eine ungültige ID übergeben wurde.
        def get_instance_by_id(self, instance_id):
            return self._context.get_module_instance(instance_id)

        ## Liefert eine Baustein-Instanz zurück.
        ## @param module_context @e string @n Schlüssel des Modulkontexts.
        ## @param instance_id @e int @n Laufzeit-ID des Bausteins.
        ## @result @e object @n Instanz eines Bausteins (vererbt von BaseModule).
        ## Liefert @e None, wenn eine ungültige ID übergeben wurde.
        def get_instance_from_module_by_id(self, module_context, instance_id):
            context = hsl20_4._Context.get_context(self._mc, module_context)
            if context!=None:
                return context.get_module_instance(instance_id)
            return None


        ## Erstellt eine Instanz eines HTTP-Servers.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_http_server in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_http_server.hsl20_4_http_server.Server @n HTTP-Server
        def create_http_server(self):
            return hsl20_4_http_server.Server(self, self._context.get_socket_map())


        ## Erstellt eine Instanz eines HTTP-Clients.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_http_client in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_http_client.hsl20_4_http_client.Client @n HTTP-Client
        def create_http_client(self):
            return hsl20_4_http_client.Client(self, self._context.get_socket_map())


        ## Erstellt eine Instanz eines TCP-Clients.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_tcp in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_tcp.hsl20_4_tcp.Client @n TCP-Client
        ## @if OLD_CHANGELOG
        ## @chlg03 Parameter @e host und @e port entfernt (waren überflüssig)
        ## @endif
        def create_tcp_client(self):
            return hsl20_4_tcp.Client(self, self._context.get_socket_map())


        ## Erstellt eine Instanz eines UDP-Unicast-Sockets.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_udp in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_udp.hsl20_4_udp.Unicast @n UDP-Socket
        def create_udp_unicast(self):
            return hsl20_4_udp.Unicast(self, self._context.get_socket_map())


        ## Erstellt eine Instanz eines UDP-Broadcast-Sockets.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_udp in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_udp.hsl20_4_udp.Broadcast @n UDP-Socket
        def create_udp_broadcast(self):
            return hsl20_4_udp.Broadcast(self, self._context.get_socket_map())

        ## Erstellt eine Instanz eines UDP-Multicast-Sockets.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_udp in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_udp.hsl20_4_udp.Multicast @n UDP-Socket
        def create_udp_multicast(self):
            return hsl20_4_udp.Multicast(self, self._context.get_socket_map())


        ## Erstellt eine Instanz eines Timers
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_timer in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_timer.hsl20_4_timer.Timer @n Timer
        def create_timer(self):
            return hsl20_4_timer.Timer(self)


        ## Erstellt eine Instanz eines Intervall-Timers
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_timer in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_timer.hsl20_4_timer.Interval @n Intervall-Timer
        def create_interval(self):
            return hsl20_4_timer.Interval(self)


        ## Erstellt eine Sektion auf der Debug-Seite des HS/FS
        ## @result @e hsl20_4_debug_page.hsl20_4_debug_page.Section @n Sektion
        def create_debug_section(self):
            return self._context.get_debug_section()


        ## Erstellt eine Instanz der MD5-Klasse. Dient zur Erzeugung eines 128 Bit langen MD5-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.MD5Hash @n 128 Bit langer MD5-Hashwert
        def create_md5_hash(self):
            return hsl20_4_crypto.MD5Hash()

        ## Erstellt eine Instanz der SHA1-Klasse. Dient zur Erzeugung eines 160 Bit langen SHA1-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.SHA1Hash @n 160 Bit langer SHA1-Hashwert
        def create_sha1_hash(self):
            return hsl20_4_crypto.SHA1Hash()

        ## Erstellt eine Instanz der SHA2-Klasse. Dient zur Erzeugung eines 224 Bit langen SHA2-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.SHA2Hash @n 224 Bit langen SHA2-Hashwert
        def create_sha224_hash(self):
            return hsl20_4_crypto.SHA2Hash(224)


        ## Erstellt eine Instanz der SHA2-Klasse. Dient zur Erzeugung eines 256 Bit langen SHA2-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.SHA2Hash @n 256 Bit langen SHA2-Hashwert
        def create_sha256_hash(self):
            return hsl20_4_crypto.SHA2Hash(256)


        ## Erstellt eine Instanz der SHA2-Klasse. Dient zur Erzeugung eins 384 Bit langen SHA2-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.SHA2Hash @n 384 Bit langen SHA2-Hashwert
        def create_sha384_hash(self):
            return hsl20_4_crypto.SHA2Hash(384)


        ## Erstellt eine Instanz der SHA2-Klasse. Dient zur Erzeugung von 512 Bit langen SHA2-Hashwerts.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.SHA2Hash @n 512 Bit langen SHA2-Hashwert
        def create_sha512_hash(self):
            return hsl20_4_crypto.SHA2Hash(512)


        ## Erstellt eine Instanz der AES-Klasse. Dient zur Ent-/Verschlüsselung von Daten per AES.
        ## @note Um diese Funktion nutzen zu können, muss das Modul hsl20_4_crypto in der XML-Definition des Bausteins importiert werden.
        ## @result @e hsl20_4_crypto.hsl20_4_crypto.AESCipher
        def create_aes(self):
            return hsl20_4_crypto.AESCipher()

        ## Liefert eine IP-Addresse. Nutzt den DNS-Dienst des HS/FS um den Hostnamen aufzulösen.
        ## @result @e string @n IP-Addresse. Liefert None, wenn die Adresse nicht aufgelöst werden konnte.
        ## @warning Dieser Aufruf blockiert bis eine Antwort eingetroffen ist oder der Timeout abgelaufen ist.
        ## @if OLD_CHANGELOG
        ## @chlg11 Neue Methode.
        ## @endif
        def resolve_dns(self, host):
            ip = self._mc.DNSResolver.getHostIP(host)
            if (len(ip)==0):
                return None
            else:
                return ip


    ## @cond NO_DOC
    # Kontext (interne Klasse): Initialisiert und verwaltet einen Modul-Kontext (inkl. Kontext-Thread).
    class _Context:

        # Gibt den zu context_id passenden Kontext zurück. Existiert dieser noch nicht, wird er angelegt.
        @staticmethod
        def get_context(homeserver_context, context_id):
            if hasattr(homeserver_context, "HSL20DATA") and homeserver_context.HSL20DATA.has_key(context_id):
                return homeserver_context.HSL20DATA[context_id]["instance"]
            else:
                return hsl20_4._Context(homeserver_context, context_id)

        def __init__(self, homeserver_context, context_id):
            self._mc = homeserver_context
            self._context_id = context_id
            # CONTEXT
            if not hasattr(homeserver_context, "HSL20DATA"):
                homeserver_context.HSL20DATA = {}
            homeserver_context.HSL20DATA[context_id] = {}
            homeserver_context.HSL20DATA[context_id]["debug"] = None
            homeserver_context.HSL20DATA[context_id]["logger"] = None
            homeserver_context.HSL20DATA[context_id]["instances"] = {}
            # Queue für Kontext-Thread anlegen
            homeserver_context.HSL20DATA[context_id]["context_queue"] = Queue.Queue(hsl20_4._CONTEXT_THREAD_QUEUE_MAX_SIZE)
            self.__thread_queue=homeserver_context.HSL20DATA[context_id]["context_queue"]
            #Eigene Instanz für Frameworkinstanzen abrufbar machen
            homeserver_context.HSL20DATA[context_id]["instance"]=self
            self._global_debug_section=hsl20_4.Framework._get_global_debug_section()
            #self._local_debug_section=self.get_debug_section()
            #self._local_debug_section._internal_register(self.__on_debug)
            self.__start_context_queue_thread()
            self.__start_asyncore_loop()

        # Die Instanz wird im Kontext gespeichert. Die Instanz-ID wird generiert und zurückgegeben.
        def register_module_instance(self, instance):
            # ID abrufen
            self._mc.HSL20ID+=1
            instance_id=self._mc.HSL20ID
            self._mc.HSL20DATA[self._context_id]["instances"][instance_id] = instance
            return instance_id

        # Liefert die unter der Instanz-ID hinterlegte Instanz eines Bausteins zurück.
        # @param instance_id int ID der Baustein-Instanz
        # @result hsl20_4.BaseModule Liefert None, wenn keine Instanz gefunden wurde.
        def get_module_instance(self, instance_id):
            if (self._mc.HSL20DATA[self._context_id]["instances"].has_key(instance_id)):
                return self._mc.HSL20DATA[self._context_id]["instances"][instance_id]
            else:
                return None

        # Liefert die Debug-Sektion des aktuellen Kontexts zurück.
        # @result hsl20_4_debug_page.Section Debug-Sektion
        def get_debug_section(self):
            if self._mc.HSL20DATA[self._context_id]["debug"]==None:
                self._mc.HSL20DATA[self._context_id]["debug"] = hsl20_4_debug_page.Section(self._context_id)
            return self._mc.HSL20DATA[self._context_id]["debug"]

        def get_logger(self, create=None):
            if self._mc.HSL20DATA[self._context_id]["logger"] != None:
                return self._mc.HSL20DATA[self._context_id]["logger"]

            try:
                if create:
                    logType, param = create
                else:
                    logType = hsl20_4.LOGGING_NONE

                if logType==hsl20_4.LOGGING_NONE:
                    self._mc.HSL20DATA[self._context_id]["logger"] = hsl20_4.Logger()
                elif logType==hsl20_4.LOGGING_SYSLOG:
                    self._mc.HSL20DATA[self._context_id]["logger"] = hsl20_4_logging_syslog(self, self._context_id, param)
            except NameError:
                self._mc.HSL20DATA[self._context_id]["logger"] = hsl20_4.Logger()
            return self._mc.HSL20DATA[self._context_id]["logger"]

        # Liefert die Anzahl der Threads die in diesem Kontext laufen.
        def get_thread_count(self):
            cnt=0
            if self._mc.HSL20DATA[self._context_id].has_key("context_thread_id"):
                cnt+=1
            if self._mc.HSL20DATA[self._context_id].has_key("asyncore_loop_thread_id"):
                cnt+=1
            #if hasattr(self._mc, "HSL20TIMERTHREAD"):
            #    if self._mc.HSL20TIMERTHREAD.has_key("instance"):
            #        cnt+=1
            return cnt

        def get_socket_map(self):
            if not self._mc.HSL20DATA[self._context_id].has_key("socket_map"):
                self._mc.HSL20DATA[self._context_id]["socket_map"] = {}
            return self._mc.HSL20DATA[self._context_id]["socket_map"]

        # Führt die Methode method_to_call im Thread des Kontextes aus und übergibt dieser optional die Parameter args.
        # @param method_to_call @e function @n Methode die im Thread des Kontextes ausgeführt werden soll.
        # @param args @n Parameter für den Methodenaufruf als Tupel.
        def run_in_context_thread(self, method_to_call, args=None):
            self.__thread_queue.put_nowait((method_to_call, args))

        # Falls der kontextbezogenen Thread noch nicht läuft, wird er gestartet.
        def __start_context_queue_thread(self):
            ## Thread für die Abarbeitung der Queue starten
            if not self._mc.HSL20DATA[self._context_id].has_key("context_thread_id"):
                self._mc.HSL20DATA[self._context_id]["context_thread_id"] = thread.start_new_thread(self.__thread_queue_consumer,())

        # Wird im kontextbezogenen Thread gestartet. Holt aus der Thread-Queue Funktionsaufrufe und führt diese aus.
        def __thread_queue_consumer(self):
            while(True):
                method_to_call, args = self.__thread_queue.get(block=True)
                try:
                    if(args==None):
                        method_to_call()
                    else:
                        method_to_call(*args)
                except:
                    self._global_debug_section.add_exception()
                self.__thread_queue.task_done()

        # Startet den Asyncore-Loop in einem eigenen Thread
        def __start_asyncore_loop(self):
            if(os.name!="nt"):
                self._signal_pipe_out,self._signal_pipe_in=os.pipe()
                # fcntl ist unter Windows nicht verfügbar
                import fcntl
                fcntl.fcntl(self._signal_pipe_out,fcntl.F_SETFL,os.O_NONBLOCK)
                self._signal_pipe_out=os.fdopen(self._signal_pipe_out,'r',0)
                self._signal_pipe_in=os.fdopen(self._signal_pipe_in,'a',0)
                dd=hsl20_4._dummyDispatcher()
                dd._signal_pipe_out=self._signal_pipe_out
                self.get_socket_map()[self._signal_pipe_out]=dd
            if not self._mc.HSL20DATA[self._context_id].has_key("asyncore_loop_thread_id"):
                self._mc.HSL20DATA[self._context_id]["asyncore_loop_thread_id"] = thread.start_new_thread(self.__thread_asyncore_loop,())

        # Unterbricht ein Select-Aufuf im Asyncore-Loop.
        # Muss aufgerufen werden, wenn der Socket-Map ein Socket hinzugefügt wird.
        # Nur möglich unter Unix
        def signal_asyncore_select_interrupt(self):
            if(os.name!="nt"):
                self._signal_pipe_in.write('1')
                self._signal_pipe_in.flush()

        def __thread_asyncore_loop(self):
            socket_map=self.get_socket_map()
            while(True):
                try:
                    asyncore.loop(map=socket_map, timeout=hsl20_4._ASYNCORE_LOOP_TIMEOUT)
                    time.sleep(0.1)
                except:
                    try:
                        dd=socket_map[self._signal_pipe_out]
                        for fd, obj in socket_map.items():
                            try:
                                obj.handle_error()
                            except:
                                self._global_debug_section.add_exception()
                        socket_map.clear()
                        socket_map[self._signal_pipe_out]=dd
                    except:
                        self._global_debug_section.add_exception()
                    time.sleep(0)

        def __on_debug(self):
            result = {}
            try:
                result["Socket Map Size"]=str(len(self.get_socket_map().keys()))
                result["Thread Queue Size"]=str(self.__thread_queue.qsize())
            except:
                self._global_debug_section.add_exception()
            return result
    ## @endcond

    ## @cond NO_DOC
    # Hilfsklasse für das Unterbrechen des Asyncore-Loop (bzw. Select im Asyncore-Loop).
    class _dummyDispatcher:

        def readable(self):
            return True

        def writable(self):
            return False

        def handle_read_event(self):
            data=self._signal_pipe_out.read()

        def handle_error(self):
            pass

    ## @endcond

    ## @brief Protokollierung
    ## @details
    ## Protokolliert Zustände auf verschiedenen Ebenen @n
    ## @note Bei Verwendung eines Loggers muss in der  @b XML-Definitionsdatei das Tag @c \<logging\> angegeben werden.
    ## @if OLD_CHANGELOG
    ## @chlg02 Neue Klasse
    ## @chlg03 Beschreibung korrigiert
    ## @chlg11 Neues Attribut: DISABLE
    ## @endif
    class Logger:

        ## Meldungen abschalten
        DISABLE = 100

        ## Nur kritische Meldungen
        CRITICAL = 50

        ## Mindestens Fehlermeldungen protokollieren
        ERROR = 40

        ## Mindestens Warnung protokollieren
        WARNING = 30

        ## Mindestens Information protokollieren
        INFO = 20

        ## Mindestens Debug-Meldungen protokollieren
        DEBUG = 10

        ## Python Standard-Einstellung für Logger ( = WARNING)
        NOTSET = 0

        ## Setzt das Log-Level. Alle Nachrichten ab dem gesetzten Level werden protokolliert. @n
        ## Nachrichten mit einem Level kleiner als dem gesetzten werden nicht ausgegeben. @n
        ## @note Wird das Level auf @b NOTSET ( = 0) gesetzt, werden @b keine Nachrichten ausgegeben!
        ## @param level @e int @n Level (siehe Konstanten). @n Zur Sicherheit sollte an dieser Stelle @b immer eine der Konstanten angegeben werden!
        ## @if OLD_CHANGELOG
        ## @chlg03 Methode von @e setLevel in @e set_level umbenannt
        ## @endif
        def set_level(self, level):
            pass

        ## Loggt eine Nachricht mit Level INFO
        ## @param msg @e string @n Nachricht
        def info(self, msg):
            pass

        ## Loggt eine Nachricht mit Level ERROR
        ## @param msg @e string @n Nachricht
        def error(self, msg):
            pass

        ## Loggt eine Nachricht mit Level DEBUG
        ## @param msg @e string @n Nachricht
        def debug(self, msg):
            pass

        ## Loggt eine Nachricht mit Level WARNING
        ## @param msg @e string @n Nachricht
        def warning(self, msg):
            pass

        ## Loggt eine Nachricht mit Level CRITICAL
        ## @param msg @e string @n Nachricht
        def critical(self, msg):
            pass

        ## Loggt eine Exception mit Level ERROR. Die Nachricht besteht aus dem Traceback der Exception und einem Kommentar.
        ## @param comment @e string @n Kommentar
        ## @if OLD_CHANGELOG
        ## @chlg04 neue Methode
        ## @endif
        def exception(self, comment):
            pass
