#! /usr/bin/python3
#
#################################################################
## Copyright (c) 2015 Norbert S. <junky-zs@gmx.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#################################################################
# Ver:0.1.7  / Datum 25.02.2015 first release
# Ver:0.1.7.1/ Datum 04.03.2015 word:'Error;' removed from cportwrite()
#              logging-handling added from ht_utils
# Ver:0.1.7.2/ Datum 31.10.2015 __waitfor_client_register method changed
#              for handling clients which not sending the devicetype
# Ver:0.1.7.3/ Datum 03.12.2019 Issue:'Deprecated property InterCharTimeout #7'
#                                port.setInterCharTimeout() removed
#################################################################

import socketserver, socket, serial
import threading, queue
import ht_utils, logging
import xml.etree.ElementTree as ET
import time, os

__author__  = "junky-zs"
__status__  = "draft"
__version__ = "0.1.7.3"
__date__    = "03.12.2019"

#---------------------------------------------------------------------------
#   targettype related stuff
#---------------------------------------------------------------------------
#
TT_SERVER="SERVER"
TT_CLIENT="CLIENT"

#---------------------------------------------------------------------------
#   devicetype related stuff
#---------------------------------------------------------------------------
#
# devicetype-definitions used in config.xml and proxy-classes
#  client-registration and com-/tty-port select is done
#  with this stuff
#
INT_DT_SERVER = 30
INT_DT_MODEM  = 20
INT_DT_RX     = 10
INT_DT_NOTSET =  0

DT_SERVER = 'SERVER'
DT_MODEM  = 'MODEM'
DT_RX     = 'RX'
DT_NOTSET = 'NONE'

_devicetypes = {
    DT_SERVER    : INT_DT_SERVER,
    DT_MODEM     : INT_DT_MODEM,
    DT_RX        : INT_DT_RX,
    DT_NOTSET    : INT_DT_NOTSET,
    INT_DT_SERVER: DT_SERVER,
    INT_DT_MODEM : DT_MODEM,
    INT_DT_RX    : DT_RX,
    INT_DT_NOTSET: DT_NOTSET,
}
def _getDeviceType(devicetype):
    """Fkt returns devicetype as string
    """
    return "{0}".format(_devicetypes.get(devicetype))

#---------------------------------------------------------------------------
#   priority-queue related stuff
#---------------------------------------------------------------------------
#
# definitions for using priority-queues
#   lower values are defined as higher priorities
#
#   remark:
#     these values are currently not realy used, but will be in the future
#
INT_PRIO_LOW    = 40
INT_PRIO_MEDIUM = 20
INT_PRIO_HIGH   = 10
INT_PRIO_URGEND =  0


class cportread(threading.Thread):
    """class 'cportread' for reading serial asynchronous data from already
       opened port
    """
    global _ClientHandler

    def __init__(self, port, devicetype):
        threading.Thread.__init__(self)
        self.__threadrun=True
        self.__port=port
        self.__devicetype=devicetype
        self.__queueprio=INT_PRIO_MEDIUM

    def __del__(self):
        self.stop()

    def run(self):
        _ClientHandler.log_info("cportread() ;thread start; devicetype:{0}".format(self.__devicetype))
        while self.__threadrun:
            if _ClientHandler.get_clientcounter() > 0:
                try:
                    value=self.__port.read(5)
                except:
                    _ClientHandler.log_critical("cportread();Error;couldn't use/read required port")
                    self.__threadrun=False
                try:
                    for clientQueue in _ClientHandler._txqueue.items():
                        #put comport readvalue in any connected client-queue
                        #  clientQueue[0]:=Client-ID; clientQueue[1]:=queue
                        #    put value into queue
                        clientQueue[1].put(value)
                except:
                    _ClientHandler.log_info("Client-ID:{0};cportread();couldn't write to queue".format(clientQueue[0]))
            else:
                time.sleep(0.5)

        _ClientHandler.log_critical("cportread() ;thread end; devicetype:{0}".format(self.__devicetype))

    def stop(self):
        self.__threadrun=False

class cportwrite(threading.Thread, ht_utils.cht_utils):
    """class 'cportwrite' for writing serial asynchronous data to already
       opened port
    """
    global _ClientHandler

    def __init__(self, port, devicetype):
        threading.Thread.__init__(self)
        ht_utils.cht_utils.__init__(self)
        self.__threadrun=True
        self.__port=port
        self.__devicetype=devicetype
        self.__queueprio=INT_PRIO_MEDIUM


    def __del__(self):
        self.stop()

    def run(self):
        """bytes are read from queue, searched for start-tag '#' and length of
            following bytes. Then all bytes are written to comport
            # following queue-message_structur is supported:
            #   tag  length   class   detail  option  databytes.....
            #    #   <size>   ! or ?   d       o       bytes.....
            #      size := amount of databytes including class, detail and option but without starttag
            #
        """
        _ClientHandler.log_info("cportwrite();thread start; devicetype:{0}".format(self.__devicetype))
        while self.__threadrun:
            if _ClientHandler.get_clientcounter() > 0:
                #preset local buffers
                self.__starttag_found=False
                self.__length        =0
                readbuffer=[0,]
                try:
                    # 1. search for start-tag '#'
                    while not self.__starttag_found and self.__threadrun and (_ClientHandler.get_clientcounter() > 0):
                        #get comport writevalues from any connected client-queue
                        for clientQueue in _ClientHandler._rxqueue.items():
                            try:
                                # use get() with timeout
                                readbuffer=clientQueue[1].get(timeout=0.2)
                                clientQueue[1].task_done()
                                if readbuffer[0] == 0x23:
                                    self.__starttag_found=True
                                    break
                            except:
                                # timeout occured-> no exception
                                pass

                    # 2. now get msg-length from stream (over all length including headerbytes but without starttag)
                    if self.__starttag_found and len(readbuffer) > 1:
                        self.__length=readbuffer[1]

                    # 3. now read rest of msg-headerbytes from stream and set resulting payload-length
                    if self.__starttag_found and len(readbuffer) > 4:
                        msg_class =readbuffer[2]
                        msg_detail=readbuffer[3]
                        msg_option=readbuffer[4]
                        if self.__length >= 3:
                            self.__length -= 3
                        else:
                            self.__length = 0
                    else:
                        self.__starttag_found=False

                    self.__msgbytes=[]
                    try:
                        if self.__starttag_found and self.__length > 0:
                            self.__msgbytes.extend(readbuffer[5:self.__length+5])
                    except:
                        raise

                    # 4. send message-class/detail/option and (data-bytes if available) to transceiver_if
                    if self.__starttag_found:
                        try:
                            self.__send_2_transceiver_if(clientQueue[0], self.__msgbytes, msg_class, msg_detail, msg_option)
                        except:
                            _ClientHandler.log_critical("Client-ID:{0};cportwrite();couldn't write to port".format(clientQueue[0]))
                            self.__threadrun=False
                            self.__starttag_found=False
                except:
                    _ClientHandler.log_info("Client-ID:{0};cportwrite();couldn't read from queue".format(clientQueue[0]))
            else:
                time.sleep(0.2)

        _ClientHandler.log_critical("cportwrite();thread end; devicetype:{0}".format(self.__devicetype))

    def stop(self):
        self.__threadrun=False

    def __send_2_transceiver_if(self, ClientID, data_in, msg_class=0x21, detail=0x53, option=0):
        # header to be send:  <#  ,  msg_class:=! , detail:=S, option, data-length>
        if len(data_in):
            header = [0x23,msg_class,detail,option,len(data_in)]
            data   = header+data_in
        else:
            header = [0x23,msg_class,detail,option,0]
            data   = header

        #generate crc and add crc-byte
        try:
            crc=self.make_crc(data, len(data))
            data += [crc]
        except:
            _ClientHandler.log_critical("Client-ID:{0};cportwrite().__send_2_transceiver_if;Error;couldn't make crc".format(ClientID))
            raise

        try:
            index=0
            while index < len(data):
                self.__port.write(bytearray([data[index]]))
                self.__port.flushOutput()
                time.sleep(0.005)
                _ClientHandler.log_debug("Client-ID:{0};cportwrite();value:{1:02x}".format(ClientID, data[index]))
                index += 1
        except:
            _ClientHandler.log_critical("Client-ID:{0};cportwrite().__send_2_transceiver_if;Error;couldn't write to port".format(ClientID))


class cht_transceiver_if(threading.Thread):
    """class 'cht_transceiver_if' as serial asynchronous interface to 'ht_transceiver'
       The used port must be accessable and is used exclusive one time.
       All received serial data are written to queue(s),(unique for every socket-client)
         this is handled with class: cportread
       All transmitted serial data are read from queue(s),(unique for every socket-client)
         this is handled with class: cportwrite
    """
    global _ClientHandler
    def __init__(self, serialdevice="/dev/ttyUSB0", baudrate=19200, devicetype=DT_RX):
        threading.Thread.__init__(self)
        self.__serialdevice = str(serialdevice)
        self.__baudrate     = baudrate
        self.__devicetype   = devicetype
        self.__port=None
        self.__threadrun=True

    def __del__(self):
        self.stop()
        if self.__port != None:
            self.__port.close()

    def run(self):
        #open serial port for reading HT-data
        try:
            self.__port = serial.Serial(self.__serialdevice, self.__baudrate)
        except:
            _ClientHandler.log_critical("cht_transceiver_if();Error;couldn't open requested device:{0}".format(self.__serialdevice))
            self.__threadrun=False
            raise

        self.__comtx_thread=cportwrite(self.__port, self.__devicetype)
        self.__comtx_thread.start()
        self.__comrx_thread=cportread(self.__port, self.__devicetype)
        self.__comrx_thread.start()

        while self.__threadrun:
            time.sleep(1)

    def stop(self):
        self.__com_txthread.stop()
        self.__com_rxthread.stop()
        self.__threadrun=False


class csocketsendThread(threading.Thread):
    """class 'csocketsendThread' used for sending data from queue to
       already connected socket
    """
    def __init__(self, request, queue):
        threading.Thread.__init__(self)
        self._queue  =queue
        self._request=request
        self.__threadrun=True
        self.__queueprio=INT_PRIO_MEDIUM

    def __del__(self):
        self.__threadrun=False
        #clear queue
        while self._queue.qsize() > 0:
            self._queue.get_nowait()

    def run(self):
        _ClientHandler.log_info("csocketsendThread(); socket.send thread start")
        self._tx=None
        while self.__threadrun==True:
            try:
                # get queue-value in blocking mode
                self._tx=self._queue.get(True)
                self._queue.task_done()
            except:
                self.__threadrun=False
                _ClientHandler.log_critical("csocketsendThread();Error on queue.get()")
                raise

            try:
                self._request.sendall(bytes(self._tx))
            except:
                self.__threadrun=False
                _ClientHandler.log_critical("csocketsendThread();Error on socket.send")
                raise

        _ClientHandler.log_info("csocketsendThread(); socket.send thread terminated")

    def stop(self):
        self.__threadrun=False

class cClientHandling(threading.Thread, ht_utils.clog):
    """class 'cClientHandling' used for add and remove clients to / from queues and
       threads. logging-methods are available for different logging-levels.
    """
    def __init__(self, logfilepath="./proxy_if.log", tcp_ip_type=TT_SERVER, loglevel=logging.INFO):
        threading.Thread.__init__(self)
        # init/setup logging-file
        ht_utils.clog.__init__(self)
        self._logging=ht_utils.clog.create_logfile(self, logfilepath=logfilepath, loglevel=loglevel, loggertag=tcp_ip_type)

        self._indexcounter=0
        self._clientcounter=0
        self._lock=threading.Lock()
        self._rxqueue={}
        self._txqueue={}
        self._thread={}

    def log_critical(self, logmessage):
        self._logging.critical(logmessage)


    def log_error(self, logmessage):
        self._logging.error(logmessage)

    def log_warning(self, logmessage):
        self._logging.warning(logmessage)

    def log_info(self, logmessage):
        self._logging.info(logmessage)

    def log_debug(self, logmessage):
        self._logging.debug(logmessage)

    def inc_indexcounter(self):
        self._lock.acquire()
        self._indexcounter+=1
        self._lock.release()

    def get_indexcounter(self):
        self._lock.acquire()
        counter=self._indexcounter
        self._lock.release()
        return counter

    def inc_clientcounter(self):
        self._lock.acquire()
        self._clientcounter+=1
        self._lock.release()

    def dec_clientcounter(self):
        self._lock.acquire()
        self._clientcounter-=1
        self._lock.release()

    def get_clientcounter(self):
        self._lock.acquire()
        counter=self._clientcounter
        self._lock.release()
        return counter

    def add_client(self, clientID, request):
        self._rxqueue.update({clientID:queue.Queue()})
        self._txqueue.update({clientID:queue.Queue()})

        txThread=csocketsendThread(request, self._txqueue.get(clientID))
        self._thread.update({clientID:txThread})
        txThread.start()
        self._logger.info("Client-ID:{0}; added; number of clients:{1}".format(clientID, self._clientcounter))

    def remove_client(self, clientID):
        txThread=self._thread.pop(clientID)
        txThread.stop()
        queue=self._rxqueue.pop(clientID)
        while queue.qsize() > 0:
            queue.get_nowait()
        queue=self._txqueue.pop(clientID)
        while queue.qsize() > 0:
            queue.get_nowait()
        self._logger.info("Client-ID:{0}; removed; number of clients:{1}".format(clientID, self._clientcounter))


class cht_RequestHandler(socketserver.BaseRequestHandler):
    """
    """
    global _ClientHandler

    def handle(self):
        self._rx=None
        self._client_devicetype=None
        self.__queueprio=INT_PRIO_HIGH
        try:
            addrc, portc = self.client_address
            addrs, ports = self.server.server_address
            _ClientHandler.log_info("Client-ID:{0}; {1} connected".format(self._myownID, (addrc, portc)))
            _ClientHandler.log_info("Server   :{0}".format((addrs,ports)))
        except:
            addrc, portc = self.client_address
            _ClientHandler.log_critical("Client-ID:{0}; {1} No connection possible".format(self._myownID, (addrc, portc)))
            raise
        # wait for client registration
        self.__waitfor_client_register()
        # add client and start threads
        _ClientHandler.add_client(self._myownID, self.request)
        self._rxqueue=_ClientHandler._rxqueue.get(self._myownID)

        _ClientHandler.log_info("Client-ID:{0}; cht_RequestHandler(); socket.receive thread start".format(self._myownID))
        while True:
            try:
                self._rx=self.request.recv(60)
            except:
                _ClientHandler.log_info("Client-ID:{0}; {1} disconnected".format(self._myownID, (addrc, portc)))
                break
            if self._rx:
                # put socket-data in queue
                self._rxqueue.put(self._rx)
                _ClientHandler.log_debug("Client-ID:{0}; recv:{1}".format(self._myownID, self._rx))
            else:
                _ClientHandler.log_info("Client-ID:{0}; {1} disconnected".format(self._myownID, (addrc, portc)))
                break


    def __waitfor_client_register(self):
        self.request.settimeout(5)
        try:
            devicetypetmp=self.request.recv(20)
            self._client_devicetype = devicetypetmp.decode('utf-8')
            _ClientHandler.log_info("Client-ID:{0}; register(); got devicetype:{1}".format(self._myownID,self._client_devicetype))
            #send client-ID to client
            sendtemp=str(self._myownID)
            self.request.sendall(sendtemp.encode("utf-8"))
        except socket.timeout:
            _ClientHandler.log_critical("Client-ID:{0}; Timeout occured, no devicetype was send".format(self._myownID))
            raise
        except socket.error as e:
            # Something else happened, handle error, exit, etc.
            _ClientHandler.log_critical("Client-ID:{0}; error '{1}' on socket.recv or socket.send".format(self._myownID, e))
            raise
        except Exception as e:
            _ClientHandler.log_critical("Client-ID:{0}; unkown error '{1}'".format(self._myownID,e))
            raise
        finally:
            self.request.settimeout(None)


    def setup(self):
        _ClientHandler.inc_indexcounter()
        _ClientHandler.inc_clientcounter()
        self._myownID=_ClientHandler.get_indexcounter()

    def finish(self):
        _ClientHandler.dec_clientcounter()
        _ClientHandler.remove_client(self._myownID)

class cproxyconfig():
    """class 'cproxyconfig', is used for proxy_configuration.
       ip_address, port_number etc. comes from the xml-configuration-file.
        # data-structur: '_configdata':
        #   {target    :[{valuename  :arrayindex},[value1,value2,...]]}
        #
        # data-structur: '_configtransceiver':
        #   {devicename:[{dparam_name:arrayindex},[dvalue1,dvalue2,...]]}
        #
        #    where : target      -> 'SERVER' or 'CLIENT'
        #            valuename   -> as used in configfile
        #            value       -> are from config-file
        #            devicename  -> 'RX' or 'MODEM'
        #            dparam_name -> device-parametername  from config-file
        #            dparam_value-> device-parametervalue from config-file
        #
    """
    global _ClientHandler

    _configdata={}
    _configtransceiver={}
    _configtransceiver_devicenames={}
    def __init__(self, config_target=TT_SERVER, devicetype=DT_RX):
        self.__configfilename=None
        self.__root = None
        self.__configtarget  = config_target.upper()
        self.__devicetype    = devicetype

    def read_config(self,xmlconfigpathname):
        try:
            self.__configfilename=xmlconfigpathname
            self.__tree = ET.parse(xmlconfigpathname)
        except (NameError,EnvironmentError,IOError) as e:
##            print("cproxyconfig().read_config();Error;{0} on file:'{1}'".format(e.args[0], self.__configfilename))
            _ClientHandler.log_critical("cproxyconfig().read_config();Error;{0} on file:'{1}'".format(e, self.__configfilename))
            raise
        else:
            try:
                self.__root = self.__tree.getroot()
                self.__transceiver_numbers=int(self.__root.find('transceiver_numbers').text)
                # currently limited to 1
                if self.__transceiver_numbers > 1:
                    self.__transceiver_numbers = 1

                # find server-/client-configuration values
                if self.__configtarget in (TT_SERVER):
                    searchtarget='proxy_server'
                    storetarget =self.__configtarget
                    cproxyconfig._configdata.update({storetarget:[{}]})
                else:
                    searchtarget='proxy_client'
                    storetarget = DT_RX

                for proxy_part in self.__root.findall(searchtarget):
                    # add new item and value, index is the current array-length
                    # and is set to dir{itemname:index}
                    if self.__configtarget in (TT_CLIENT):
                        item='devicetype'
                        devicetype=proxy_part.find(item).text.upper()
                        if devicetype in (DT_MODEM):
                            storetarget=DT_MODEM
                        else:
                            storetarget=DT_RX

                        cproxyconfig._configdata.update({storetarget:[{}]})
                        cproxyconfig._configdata[storetarget][0].update({str(item).upper():devicetype})


                    item='serveraddress'
                    cproxyconfig._configdata[storetarget][0].update({str(item).upper():proxy_part.find(item).text})
                    item='servername'
                    cproxyconfig._configdata[storetarget][0].update({str(item).upper():proxy_part.find(item).text})
                    item='portnumber'
                    cproxyconfig._configdata[storetarget][0].update({str(item).upper():proxy_part.find(item).text})
                    item='logfilepath'
                    cproxyconfig._configdata[storetarget][0].update({str(item).upper():proxy_part.find(item).text})

                if self.__configtarget in (TT_SERVER):
                    for ht_transceiver in proxy_part.findall('ht_transceiver_if'):
                        devicename=ht_transceiver.attrib['devicename']
                        if not devicename in cproxyconfig._configtransceiver_devicenames.keys():
                            # setup 'devicename' and 'init.flag:=0'
                            cproxyconfig._configtransceiver_devicenames.update({devicename:0})
                        #initialise 'devicename'-dictionary
                        cproxyconfig._configtransceiver.update({devicename:[{}]})
                        #fill in parameters in dictionary
                        for parameter in ht_transceiver.findall('parameter'):
                            item='serialdevice'
                            cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():parameter.find(item).text})
                            item='baudrate'
                            cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():parameter.find(item).text})
                            item='config'
                            cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():parameter.find(item).text})

                        item='devicetype'
                        cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():ht_transceiver.find(item).text})

                        item='deviceaddress_hex'
                        if devicename.upper() != 'RX':
                            cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():ht_transceiver.find(item).text})
                        else:
                            cproxyconfig._configtransceiver[devicename][0].update({str(item).upper():'None'})
            except:
                raise

    def serveraddress(self):
        try:
            rtn=cproxyconfig._configdata[self.__devicetype][0].get('SERVERADDRESS')
        except:
            rtn=None
        return rtn

    def servername(self):
        try:
            rtn=cproxyconfig._configdata[self.__devicetype][0].get('SERVERNAME')
        except:
            rtn=None
        return rtn

    def portnumber(self):
        try:
            rtn=cproxyconfig._configdata[self.__devicetype][0].get('PORTNUMBER')
        except:
            rtn=0
        return int(rtn)

    def logfilepath(self):
        try:
            rtn=cproxyconfig._configdata[self.__devicetype][0].get('LOGFILEPATH')
        except:
            rtn=None
        return os.path.normcase(rtn)

    def transceiver_serialdevice(self, devicename=None):
        try:
            if devicename==None:
                rtn=cproxyconfig._configtransceiver[self.__devicetype][0].get('SERIALDEVICE')
            else:
                rtn=cproxyconfig._configtransceiver[devicename][0].get('SERIALDEVICE')
        except:
            rtn=None
            raise
        return rtn

    def transceiver_baudrate(self, devicename=None):
        try:
            if devicename==None:
                rtn=cproxyconfig._configtransceiver[self.__devicetype][0].get('BAUDRATE')
            else:
                rtn=cproxyconfig._configtransceiver[devicename][0].get('BAUDRATE')
        except:
            rtn=None
        return int(rtn)

    def transceiver_config(self, devicename=None):
        try:
            if devicename==None:
                rtn=cproxyconfig._configtransceiver[self.__devicetype][0].get('CONFIG')
            else:
                rtn=cproxyconfig._configtransceiver[devicename][0].get('CONFIG')
        except:
            rtn=None
        return rtn

    def transceiver_devicetype(self, devicename=None):
        try:
            if devicename==None:
                rtn=cproxyconfig._configtransceiver[self.__devicetype][0].get('DEVICETYPE')
            else:
                rtn=cproxyconfig._configtransceiver[devicename][0].get('DEVICETYPE')
        except:
            rtn=None
        return rtn

    def transceiver_deviceaddress(self, devicename=None):
        try:
            if devicename==None:
                rtn=cproxyconfig._configtransceiver[self.__devicetype][0].get('DEVICEADDRESS_HEX')
            else:
                rtn=cproxyconfig._configtransceiver[devicename][0].get('DEVICEADDRESS_HEX')
        except:
            rtn=None
        return rtn

    def devicename_keys(self):
        return list(cproxyconfig._configtransceiver_devicenames.keys())

    def devicename_initflag(self, key, initflag=None ):
        if initflag != None:
            cproxyconfig._configtransceiver_devicenames.update({key:initflag})
        return cproxyconfig._configtransceiver_devicenames.get(key)

class cht_proxy_daemon(threading.Thread, cproxyconfig):
    """class 'cht_proxy_daemon', create object using this as server-daemon.
       ip_address and port_number comes from configuration-file.
    """
    global _ClientHandler

    def __init__(self, configfile="./etc/config/ht_proxy_cfg.xml", loglevel=logging.INFO):
        threading.Thread.__init__(self)
        tcp_ip_type=TT_SERVER
        cproxyconfig.__init__(self, tcp_ip_type, devicetype=DT_SERVER)
        self._configfile =configfile
        self._logfile    ="default_proxy.log"
        self._ip_address="localhost"
        self._port_number=8088
        self._server=None
        self._ht_transceiver_if=[]

        #read configfile
        try:
            self.read_config(self._configfile)
            self._port_number = self.portnumber()
            self._logfile     = self.logfilepath()
            # check for available/writable folder
            #   if not available, create the folder
            abs_pathonly=os.path.abspath(os.path.dirname(self._logfile))
            if not os.path.exists(abs_pathonly):
                try:
                    os.makedirs(abs_pathonly)
                except:
                    print("Sorry, can't create that folder: {0}".format(abs_pathonly))
                    print(" What can we do now with that bloody folder?")
                    print(" The Best and the Rest I can do -> fire and raise !")
                    raise
            else:
                # check for writability
                if not os.access(abs_pathonly, os.W_OK):
                    print("Houston, we have got a problem")
                    print(" Can't write to that folder: {0}".format(abs_pathonly))
                    print(" The Best and the Rest I can do -> fire and forget !")
                    raise


            global _ClientHandler
            _ClientHandler=cClientHandling(self._logfile, loglevel=loglevel)
            _ClientHandler.log_info("----------------------")
            _ClientHandler.log_info("cht_proxy_daemon init")
            if not self.servername() == None:
                self._ip_address=self.servername()
            else:
                if not self.serveraddress() == None:
                    self._ip_address=self.serveraddress()
                else:
                    _ClientHandler.log_info("cht_proxy_daemon(); common serveraddress used")
                    self._ip_address=""
        except:
            _ClientHandler=cClientHandling(self._logfile, loglevel=loglevel)
            _ClientHandler.log_critical("cht_proxy_daemon();error can't get/set configurationvalues")
            raise

    def __del__(self):
        while len(self._ht_transceiver_if):
            self._ht_transceiver_if.pop().stop()

    def run(self):
        _ClientHandler.log_info("cht_proxy_daemon start as proxy-server:'{0}';port:'{1}'".format(self._ip_address, self._port_number))
        _ClientHandler.log_info("logfile:'{0}'".format(self._logfile))
        _serialdevice_initialised=[]

        for devicename in self.devicename_keys():
            if self.devicename_initflag(devicename) == 0:
                #check for already initialised serial device, if not then start transceiver_if
                serialdevice   = self.transceiver_serialdevice(devicename)
                if not serialdevice in (_serialdevice_initialised):
                    baudrate       = self.transceiver_baudrate(devicename)
                    devicetype     = self.transceiver_devicetype(devicename)
                    #start transceiver-if for that serial device
                    transceiver_if = cht_transceiver_if(serialdevice, baudrate, devicetype)
                    #add used serial-device to list
                    _serialdevice_initialised.append(serialdevice)
                    #add transceiver to list
                    self._ht_transceiver_if.append(transceiver_if)
                    transceiver_if.setDaemon(True)
                    transceiver_if.start()

                #set initialise-flag for devicename
                self.devicename_initflag(devicename, 1)



        try:
            self._server=socketserver.ThreadingTCPServer((self._ip_address, self._port_number), cht_RequestHandler)
            self._server.serve_forever()
            _ClientHandler.log_critical("cht_proxy_daemon terminated")
            _ClientHandler.log_info("---------------------------")
            raise
        except:
            _ClientHandler.log_critical("cht_proxy_daemon terminated")
            _ClientHandler.log_info("---------------------------")
            raise

    def get_indexcounter(self):
        global _ClientHandler
        return _ClientHandler.get_indexcounter()

    def get_clientcounter(self):
        global _ClientHandler
        return _ClientHandler.get_clientcounter()



#--- class cht_proxy_if end ---#

class cht_socket_client(cproxyconfig, ht_utils.clog):
    """class 'cht_socket_client', create object to connect to server
       using ip_address and port_number from configuration-file.
    """
    def __init__(self, configfile="./etc/config/ht_proxy_cfg.xml", devicetype=DT_RX, loglevel=logging.INFO):
        tcp_ip_type=TT_CLIENT
        self._devicetype =devicetype
        cproxyconfig.__init__(self, tcp_ip_type, self._devicetype)
        self._configfile =configfile
        self._ip_address ="192.168.2.1"
        self._port_number=8088
        self._logfile    ="./ht_client_default.log"
        self._loglevel   = loglevel
        self._loggertag  = tcp_ip_type
        self._clientID   =0

        self._socket=None
        #read configfile
        try:
            self.read_config(self._configfile)
        except:
            # setup logging-file only for this exception
            _handler=logging.handlers.RotatingFileHandler(self._logfile, maxBytes=1000000)
            _frm = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%d.%m.%Y %H:%M:%S")
            _handler.setFormatter(_frm)
            self._logging     = logging.getLogger(tcp_ip_type)
            self._logging.addHandler(_handler)
            self._logging.setLevel(loglevel)

            self.log_critical("cht_socket_client();error can't get configurationvalues")
            raise

        try:
            self._port_number = self.portnumber()
            self._logfile     = self.logfilepath()

            # setup logging-file using class: ht_utils.clog
            ht_utils.clog.__init__(self)
            self._logging=self.create_logfile(self._logfile, self._loglevel, self._loggertag)

            self.log_info("----------------------")
            self.log_info("cht_socket_client init")
            if not self.servername() == None:
                self._ip_address=self.servername()
            else:
                if not self.serveraddress() == None:
                    self._ip_address=self.serveraddress()
                else:
                    self.log_critical("cht_socket_client.__init__(); error:no serveraddress defined")
                    raise ValueError("cht_socket_client.__init__(); error:no serveraddress defined")

        except:
            self.log_critical("cht_socket_client();error can't set configurationvalues")
            raise

        try:
            self._socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self._socket.connect((self._ip_address, self._port_number))
            self.log_info("connected to server:'{0}';port:'{1}'".format(self._ip_address, self._port_number))
            self.log_info("logfile:'{0}'".format(self._logfile))
        except:
            self.log_critical("cht_socket_client.__init__();error:can't connect to socket")
            raise

        #send registration to proxy-server and receive client-related informations from server
        self.__registration();
        self.log_info("Client-ID:{0}; registered with devicetype:'{1}'".format(self._clientID, self._devicetype))

    def __del__(self):
        if self._socket != None:
            self._socket.close()
            self.log_info("Client-ID:{0}; socket closed".format(self._clientID))

    def __registration(self):
        try:
            # send devicetype to server
            devicetype=bytearray(self._devicetype.encode("utf-8"))
            self.write(devicetype)

            # read answer from server (client-ID) and store it
            self._clientID=self._socket.recv(10).decode('utf-8')
            ## only for test## print("client got ID:{0}".format(self._clientID))
        except:
            self._socket.close()
            self.log_critical("cht_socket_client.__registration();error:can't register to master")
            raise

    def run(self):
        try:
            self.log_info("Client-ID:{0}; cht_socket_client run".format(self._clientID))
            while True:
                antwort=self._socket.recv(1)
                print("{0}".format(antwort))
        except:
            self._socket.close()
            self.log_critical ("Client-ID:{0} ; cht_socket_client.run(); error on socket.recv".format(self._clientID))
            raise

    def close(self):
        if self._socket != None:
            self._socket.close()
            self._socket = None
            self.log_info("Client-ID:{0}; socket closed".format(self._clientID))

    def read(self, size=1):
        """Read size bytes from the connected socket. It will block
           until the requested number of bytes are read.
        """
        if self._socket==None:
            raise ("Client-ID:{0}; cht_socket_client.read(); error:socket not initialised".format(self._clientID))
        read=bytearray()
        while len(read) < size:
            try:
                buffer=self._socket.recv(size)
            except:
                self._socket.close()
                self.log_critical("Client-ID:{0}; cht_socket_client.read(); error on socket.recv".format(self._clientID))
                raise

            if not buffer:
                self._socket.close()
                self.log_critical("Client-ID:{0}; cht_socket_client.read(); peer closed socket".format(self._clientID))
                raise
            else:
                read.extend(buffer)

        return bytes(read)

    def write(self, data):
        """write data to connected socket. It will block
           until all data is written.
        """
        if self._socket==None:
            self.log_critical("Client-ID:{0}; cht_socket_client.write(); socket not initialised".format(self._clientID))
            raise
        try:
            self._socket.sendall(bytes(data))
        except:
            self.log_critical("Client-ID:{0}; cht_socket_client.write(); error on socket.sendall".format(self._clientID))
            raise

    def log_critical(self, logmessage):
        self._logging.critical(logmessage)

    def log_error(self, logmessage):
        self._logging.error(logmessage)

    def log_warning(self, logmessage):
        self._logging.warning(logmessage)

    def log_info(self, logmessage):
        self._logging.info(logmessage)

    def log_debug(self, logmessage):
        self._logging.debug(logmessage)

#--- class cht_socket_client end ---#



################################################

if __name__ == "__main__":
    import time

    configfile="./../etc/config/4test/ht_proxy_cfg_test.xml"

    print("----- do some daemon-server-checks with connected client -----")
    print("   -- start socket.server --")
    ht_proxy=cht_proxy_daemon(configfile)
    ht_proxy.start()
    print("   -- start socket.client --")
    client=cht_socket_client(configfile, devicetype=DT_MODEM)
    time.sleep(1)
    # data-stream send to ht_transceifer_if: '#' ,'!' ,'S',option, length(payload)
    #   data = [0x23,0x21,0x53,0,0]
    # data-stream to be send on socket : '#' , <length> ,'!' ,'S',option
    print("     -- write 5 bytes to proxy-server for test --")
    data = [0x23,3,0x21,0x53,0]
    client.write(data)
    time.sleep(1)

    print("     -- cylic send reconfigure-commands to transceiver_if --")
    # data-stream send to ht_transceifer_if: '#' ,'!' ,'M', 0xF0, 0
    #   data = [0x23,0x21,0x53,0xF0,0]
    # data-stream to be send on socket : '#' , <length> ,'!' ,'M',0xF0
    reset_command = [0x23,3,0x21,0x4D,0xF0]
    # data-stream for config Mode 1    : '#' , <length> ,'!' ,'C',1,1
    cfg_nosend_command = [0x23,4,0x21,0x43,1,1]
    # data-stream for config Mode 3    : '#' , <length> ,'!' ,'C',1,3
    cfg_fullsend_command = [0x23,4,0x21,0x43,1,3]

    while True:
        print("     -->  command: config Mode 1 to ht_ransceiver!")
        client.write(cfg_nosend_command)
        time.sleep(1)
        print("     -->  command: reset to ht_transceiver!")
        client.write(reset_command)
        time.sleep(9)
        print("     -->  command: config Mode 3 to ht_ransceiver!")
        client.write(cfg_fullsend_command)
        time.sleep(1)
        print("     -->  command: reset to ht_transceiver!")
        client.write(reset_command)
        time.sleep(9)