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

global asyncore
import asyncore
global socket
import socket

## @brief Klassen zur Kommunikation per UDP
## @deprecated Dieses Modul sollte nicht mehr eingesetzt werden. In den Beispielen wird gezeigt, wie es durch Standardmodule von Python ersetzt werden kann.
class hsl20_4_udp:

    ## @cond NO_DOC
    _BUFFER_SIZE = 8192
    _MULTICAST_TTL = 31
    ## @endcond

    ## Basisklasse für UDP-Kommunikation.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## Die Klasse wird von den Klassen Unicast, Broadcast und Multicast geerbt.
    ## @if OLD_CHANGELOG
    ## @chlg11 set_on_close entfällt
    ## @endif
    class UdpBase:

        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self, framework, context_map):
            ## @cond NO_DOC
            self._on_data_callback=None
            self._on_error_callback=None
            self._server_ip=None
            self._server_port=None
            self._is_server=False
            self._framework=framework
            self._dispatcher = hsl20_4_udp._Dispatcher(self, context_map)
            self._dispatcher.create_socket(socket.AF_INET,socket.SOCK_DGRAM)
            self._dispatcher.set_reuse_addr()
            ## @endcond

        ## Setzt einen Callback für den Datenempfang.
        ##
        ## @param callback @n Callback
        ## - @b data @e string @n Daten
        ## - @b address @e object @n IP-Adresse und IP-Port
        ## @if OLD_CHANGELOG
        ## @chlg03 Erläuterung Callback hinzugefügt
        ## @endif
        def set_on_data(self, callback):
            ## @cond NO_DOC
            self._on_data_callback=callback
            ## @endcond


        ## Setzt einen Callback für den Fehlerfall.
        ##
        ## Parameter für Callback:
        ## - @b exception @e Exception @n Das Exception-Objekt
        ##
        ## @param callback @n Callback
        ## @if OLD_CHANGELOG
        ## @chlg03 Erläuterung Callback hinzugefügt
        ## @chlg04 Parameter @e exception beim Callback hinzugefügt
        ## @endif
        def set_on_error(self, callback):
            ## @cond NO_DOC
            self._on_error_callback=callback
            ## @endcond

                
        ## Startet den UDP-Empfangsserver.
        def start_server(self):
            ## @cond NO_DOC
            if self._is_server:
                raise RuntimeError("Server is already running!")
            if self._server_ip == None:
                raise AttributeError("no ip")
            if self._server_port == None:
                raise AttributeError("no port")
            self._is_server=True
            self._dispatcher.bind((self._server_ip, self._server_port))
            self._framework._signal_asyncore_select_interrupt()
            ## @endcond


    ## Alle Methoden für die Kommunikation per UDP-Unicast.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## @details
    ## Bietet die Kommunikation per UDP/IP in beide Richtungen.
    ##
    ## Wird abgeleitet von UdpBase.
    ##
    ## Beispiel 1
    ## @code
    ## class UDP_Server(hsl20_4.BaseModule):
    ##
    ##    def __init__(self, homeserver_context):
    ##      ...
    ##      #####################################################
    ##
    ##    def on_init(self):
    ##      self.server=None
    ##
    ##    def on_input_value(self, index, value):
    ##        if index == self.PIN_I_START:
    ##              if self.server == None:
    ##                  self.server = self.FRAMEWORK.create_udp_unicast()
    ##                  self.server.set_address("", int(self._get_input_value(self.PIN_I_PORT)))
    ##                  self.server.set_on_data(self.on_data)
    ##                  self.server.start_server()
    ##        
    ##    def on_data(self, data, addr):
    ##        self._set_output_value(self.PIN_O_RECV_DATA, data)
    ##
    ## @endcode
    ## In Beispiel 1 wird beim Eintreffen eines Telegramms auf Eingang 1 (PIN_I_START) ein UDP-Server gestartet. Dieser horcht auf dem Port, der durch Eingang 2 (PIN_I_PORT) festgelegt ist.
    ## Alle auf diesem Port eintreffenden UDP-Telegramme werden auf Ausgang 1 (PIN_O_RECV_DATA) ausgegeben.
    ##
    ##
    ## Beispiel 2
    ## @code
    ## class UDP_Client(hsl20_4.BaseModule):
    ##
    ##    def __init__(self, homeserver_context):
    ##      ...
    ##      #####################################################
    ##
    ##    def on_init(self):
    ##      self.client=None
    ##
    ##    def on_input_value(self, index, value):
    ##        if index == self.PIN_I_DATA:
    ##              if self.client == None:
    ##                  self.client = self.FRAMEWORK.create_udp_unicast()
    ##              self.client.send_to(value, self._get_input_value(self.PIN_I_IP), int(self._get_input_value(self.PIN_I_PORT)))
    ##        
    ## @endcode
    ## Beispiel 2 beinhaltet einen einfachen UDP-Client. Trifft auf Eingang 1 (PIN_I_DATA) ein Telegramm ein, wird dieses per UDP versendet.
    ## Über die Eingänge 2 (PIN_I_IP) und 3 (PIN_I_PORT) wird der Zielrechner bestimmt.
    ##
    ## @note Die Klasse arbeitet asynchron. Keine der angebotenen Methoden blockiert.
    ## @if OLD_CHANGELOG
    ## @chlg04 Code-Beispiele hinzugefügt
    ## @endif
    class Unicast(UdpBase):
    
        ## @cond NO_DOC
        def __init__(self, framework, context_map):
            hsl20_4_udp.UdpBase.__init__(self, framework, context_map)
        ## @endcond

        ## Setzt die Adresse, unter der Daten empfangen werden können.
        ## @param ip @e string @n IP-Adresse. Wird ein Leerstring angegeben, werden die Daten unter der HomeServer-Netzwerkadresse empfangen.
        ## @param port @e int @n IP-Port, unter dem die Daten empfangen und gesendet werden können.
        ## @throws RuntimeError @n Wird diese Methode aufgerufen während der Server bereits gestartet wurde, wird eine RuntimeError-Exception ausgelöst.
        ## @if OLD_CHANGELOG
        ## @chlg03 Exception hinzugefügt
        ## @endif
        def set_address(self, ip, port):            
            ## @cond NO_DOC
            if self._is_server:
                raise RuntimeError("Server is already running!")
            self._server_ip=ip
            self._server_port=port
            ## @endcond

        ## Sendet Daten an die angegebene Adresse.
        ## @param data @e string @n Daten
        ## @param ip @e string @n IP-Adresse des Ziels.
        ## @param port @e int @n IP-Port des Ziels.
        ## @if OLD_CHANGELOG
        ## @chlg04 Parameter @e host in @e ip umbenannt.
        ## @endif
        def send_to(self, data, ip, port):
            ## @cond NO_DOC
            self._dispatcher.socket.sendto(data, (ip, port))
            ## @endcond


    ## Alle Methoden für die Kommunikation per UDP-Broadcast.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## @details
    ## Bietet die Kommunikation per UDP/IP in beide Richtungen.
    ##
    ## Wird abgeleitet von UdpBase.
    ##
    ## Beispiel
    ## @code
    ## class UDP_Client(hsl20_4.BaseModule):
    ##
    ##    def __init__(self, homeserver_context):
    ##      ...
    ##    #####################################################
    ##
    ##    def on_init(self):
    ##        self.client = None
    ##
    ##    def on_input_value(self, index, value):
    ##        if index == self.PIN_I_DATA:
    ##              if self.client == None:
    ##                  self.client = self.FRAMEWORK.create_udp_broadcast()
    ##              self.client.send_to(value, "192.168.0.255", 4711)
    ##
    ## @endcode
    ## Das Beispiel beinhaltet einen Broadcast UDP-Client. Trifft auf Eingang 1 (PIN_I_DATA) ein Telegramm ein, wird dieses per UDP versendet.
    ## Über die Eingänge 2 (PIN_I_IP) und 3 (PIN_I_PORT) werden Broadcast-IP und -Port bestimmt.
    ##
    ## @note Die Klasse arbeitet asynchron. Keine der angebotenen Methoden blockiert.
    ## @if OLD_CHANGELOG
    ## @chlg15 Code-Beispiele hinzugefügt
    ## @endif
    class Broadcast(UdpBase):
        
        ## @cond NO_DOC
        def __init__(self, framework, context_map):
            hsl20_4_udp.UdpBase.__init__(self, framework, context_map)
            # Socketoption BROADCAST einschalten
            self._dispatcher.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
        ## @endcond
        
        ## Setzt die Server-Adresse für den Broadcast, unter der Daten empfangen werden können.
        ## @param ip @e string @n IP-Adresse. Wird ein Leerstring angegeben, werden die Daten unter der HomeServer-Netzwerkadresse empfangen.
        ## @param port @e int @n IP-Port, unter dem die Daten empfangen und gesendet werden können.
        ## @throws RuntimeError @n Wird diese Methode aufgerufen während der Server bereits gestartet wurde, wird eine RuntimeError-Exception ausgelöst.
        ## @if OLD_CHANGELOG
        ## @chlg03 Methode hinzugefügt
        ## @endif
        def set_address(self, ip, port):
            ## @cond NO_DOC
            if self._is_server:
                raise RuntimeError("Server is already running!")
            self._server_ip=ip
            self._server_port=port
            ## @endcond
            
        ## Sendet Daten per Broadcast an die angegebene Adresse. 
        ## @param data @e string @n Daten, die per Broadcast versendet werden sollen.
        ## @param ip @e string @n Broadcast-IP
        ## @param port @e int @n Broadcast-Port
        ## @if OLD_CHANGELOG
        ## @chlg04 Methode @b send entfernt und @b send_to hinzugefügt. Die neue Methode @b send_to hat zusätzliche Parameter. @n
        ## Parameter @e host in @e ip umbenannt.
        ## @endif
        def send_to(self, data, ip, port):
            ## @cond NO_DOC
            self._dispatcher.socket.sendto(data, (ip, port))           
            ## @endcond


    ## Alle Methoden für die Kommunikation per UDP-Multicast.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## @details
    ## Bietet die Kommunikation per UDP/IP in beide Richtungen.
    ##
    ## Wird abgeleitet von UdpBase.
    ##
    ## Beispiel
    ## @code
    ## class UDP_Client(hsl20_4.BaseModule):
    ##
    ##    def __init__(self, homeserver_context):
    ##        ...
    ##    #####################################################
    ##
    ##    def on_init(self):
    ##        self.client=None
    ##
    ##    def on_input_value(self, index, value):
    ##        if index == self.PIN_I_DATA:
    ##              if self.client == None:
    ##                  self.client = self.FRAMEWORK.create_udp_multicast()
    ##                  self.client.set_address("", self._get_input_value(self.PIN_I_IP), 4711)
    ##              self.client.send(value)
    ##
    ## @endcode
    ## Beispiel 2 beinhaltet einen Multicast UDP-Client. Trifft auf Eingang 1 (PIN_I_DATA) ein Telegramm ein, wird dieses per UDP in die Multicast-Gruppe gesendet.
    ##
    ## @note Die Klasse arbeitet asynchron. Keine der angebotenen Methoden blockiert.
    ## @if OLD_CHANGELOG
    ## @chlg15 Code-Beispiele hinzugefügt
    ## @endif
    class Multicast(UdpBase):
    
        ## @cond NO_DOC
        def __init__(self, framework, context_map):
            hsl20_4_udp.UdpBase.__init__(self, framework, context_map)
            self._multicast_ip=None
            self._dispatcher.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, hsl20_4_udp._MULTICAST_TTL)
        ## @endcond
            
        ## @cond NO_DOC
        # Destruktor: Socket aus der Multicastgruppe abmelden.
        def __del__(self):
            if(self._multicast_ip != None):
                try:
                    self._dispatcher.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(self._multicast_ip) + socket.inet_aton('0.0.0.0'))
                except:
                    pass
            hsl20_4_udp.UdpBase.__del__(self)
        ## @endcond

        ## Setzt die Server-Adresse und die Multicast-Adresse, unter der Daten empfangen und gesendet werden können.
        ## @param server_ip @e string @n IP-Adresse. Wird ein Leerstring angegeben, werden die Daten unter der HomeServer-Netzwerkadresse empfangen.
        ## @param multicast_ip @e string @n Multicast-Adresse. Gibt die Multicast-Gruppe an, unter der die Daten empfangen und gesendet werden können.
        ## @param port @e int @n IP-Port, unter dem die Daten empfangen und gesendet werden können.
        ## @throws RuntimeError @n Wird diese Methode aufgerufen während der Server bereits gestartet wurde, wird eine RuntimeError-Exception ausgelöst.
        ## @if OLD_CHANGELOG
        ## @chlg03 Parameter @e multicast_ip und Exception hinzugefügt
        ## @endif
        def set_address(self, server_ip, multicast_ip, port):
            ## @cond NO_DOC
            if self._is_server:
                raise RuntimeError("Server is already running!")
            self._server_ip=server_ip
            self._multicast_ip=multicast_ip
            self._server_port=port
            ## @endcond
        
        ## Sendet Daten in die Multicast-Gruppe
        ## @param data @e string @n Daten
        ## @throws AttributeError @n Wird ausgelöst, wenn noch keine Multicast-Adresse oder kein Port definiert wurde.
        ## @if OLD_CHANGELOG
        ## @chlg03 Exception hinzugefügt
        ## @endif
        def send(self, data):
            ## @cond NO_DOC
            if self._multicast_ip == None:
                raise AttributeError("no multicast ip")
            if self._server_port == None:
                raise AttributeError("no port")
            self._dispatcher.socket.sendto(data, (self._multicast_ip, self._server_port))
            ## @endcond
            
        ## Startet den UDP-Empfangsserver.
        def start_server(self):
            ## @cond NO_DOC
            if self._is_server:
                raise RuntimeError("Server is already running!")
            if self._server_ip == None:
                raise AttributeError("no server ip")
            if self._multicast_ip == None:
                raise AttributeError("no multicast ip")
            if self._server_port == None:
                raise AttributeError("no port")
            self._is_server=True
            
            self._dispatcher.bind((self._server_ip, self._server_port))
            self._dispatcher.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
            self._dispatcher.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self._framework.get_homeserver_private_ip()))
            self._dispatcher.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._multicast_ip) + socket.inet_aton(self._framework.get_homeserver_private_ip()))
            
            self._framework._signal_asyncore_select_interrupt()
            ## @endcond
            

    ## @cond NO_DOC
    class _Dispatcher(asyncore.dispatcher):
    
        def __init__(self, client_instance, context_map):
            asyncore.dispatcher.__init__(self, map=context_map)
            self._client_instance=client_instance
            self.sendbuffer=""
            
        def readable(self):
            return self._client_instance._is_server
            
        def writable(self):
            return False
                    
        #Wird ignoriert
        def handle_expt(self):
            pass
                     
        def handle_error(self):
            if(self._client_instance._on_error_callback!=None):
                self._client_instance._framework._run_in_context_thread(self._client_instance._on_error_callback, (sys.exc_info()[1],))
            else:
                # Wenn der Fehler nicht durch den Usercode gehandelt wird, wird die Exception auf der Debug-Seite angezeigt.
                hsl20_4.Framework._get_global_debug_section().add_exception()
        
        def handle_read(self):
            try:
                ## Das standardmässige recv wird umgangen, um die Adresse des Senders zu bekommen.
                recv_data, addr  = self.socket.recvfrom(hsl20_4_udp._BUFFER_SIZE)
                if(self._client_instance._on_data_callback!=None):
                    self._client_instance._framework._run_in_context_thread(self._client_instance._on_data_callback,(recv_data,addr))
            except:
                hsl20_4.Framework._get_global_debug_section().add_exception()
            
        
        def handle_write(self):
            pass
    ## @endcond
