#-------------------------------------------------------------------------------
# Name:        Controller for the pumps
# Purpose:     Allow for the control of the pumps
#              
# Author:      Jonathan G
#
# Created:     10.08.2021
# Copyright:   (c) Institute of technical biocatalysis TUHH
# Licence:     GNU GPL-v3 
#-------------------------------------------------------------------------------
##Imports
import serial, time
import numpy as np
import asyncio
from serial.tools import list_ports
from serial import PARITY_NONE, EIGHTBITS, STOPBITS_ONE
from statusCodes import ConnectionState, DiscoveryReplyStatus, DeviceInfo

class PumpCmd:
    """
        """
    getSerialNo     : str = b'GSER'
    setSpeed        : str = b'V'
    disable         : str = b'DI'
    enable          : str = b'EN'
    getActualSpeed  : str = b'GN'
    getTargetSpeed  : str = b'GV'



class SerialPump(serial.Serial):
    """ Serielle Schnitstelle
        für HNP Pumprn mit Faulhaber Motion Controller
        """
    
    termCmd     = b'\r'     # Command terminator
    termRep     = b'\r\n'   # Reply terminator
    
    def __init__(self, baudrate =9600, bytesize=8, parity=PARITY_NONE, stopbits=STOPBITS_ONE, *args, **kwargs):
        """ Serial port details
            """
        super().__init__(*args, **kwargs)
        self.velocity = 0
        self.port     = ''
        self.baudrate = 9600
        self.bytesize = bytesize
        self.parity = parity
        #self.timeout  = timeout
        self.stopped = False
        # ließt Geräteinformationen aus "StatusCodes.py" aus
        self.deviceInfo = DeviceInfo() # legt die Strings leer an, ohne die Gerätinformationen zu kennen
        self.discoveryReplyStatus  = DiscoveryReplyStatus.UNDEFINED
        self.connectionState        = ConnectionState.UNKNOWN
        #self.lastReadings = deque(maxlen=6)
        self.lastReading = ('')
        
    async def discoverDevice(self):
        """ Sucht (asynchron) die COM-Ports nach einer HNP Pumpe ab
            """

        print("Searching pump")

        availableDevices = list_ports.comports()

        for port in availableDevices:
            if self.discoveryReplyStatus != DiscoveryReplyStatus.READY:
                self.port = port.device
                if await self.tryHNP():
                    self.discoveryReplyStatus = DiscoveryReplyStatus.READY
                    print('pump found!')
                    await self.connect()
            await asyncio.sleep(0.1)
        
        if self.discoveryReplyStatus == DiscoveryReplyStatus.UNDEFINED:
            raise Exception("No pump found")
    
    async def tryHNP(self):
        """ Testen,
            ob es sich um eine HNP mzr-xx09 handelt
            """
        try:
            await asyncio.sleep(0.1)
            self.open()
            await self.sendCmd(PumpCmd.getSerialNo)
        except Exception:
            found = False
            print('Port not a HNP pump. Skipping')
            #raise
        else:
            found = True
            print('Pump found')
        finally: self.close()
        return found
    
    async def setVel(self,vel):
        """ Stellt die Geschwindigkeit auf einen bestimmten Wert ein
            """
        
        if abs(vel - self.velocity) <200:
            """ bei kleineren Geschwindigkeitsänderungen
                wird der Pumpe sofort der neue Wert übermittelt """
            cmd = ("V"+str(vel)).encode('utf-8')
            self.write(cmd+self.termCmd)
            self.velocity = vel
        else:
            """ für größere Geschwindigkeitsänderungen wird der
                Anstieg in jeweils kleinere unterteilt """
            velArray = self.velCurve(vel)
            
            for velty in velArray:
                cmd = ("V"+str(velty)).encode('utf-8')
                self.write(cmd+self.termCmd)
                self.velocity = velty
                print(f'Pump velocity {cmd} set.')
                await asyncio.sleep(0.05)
        
    
    def velCurve(self,vel,velstep = 200):
        """ unterteilt den Geschwindigkeitsanstieg in Schritte
            der Grüße "velstep" """
        deltaVelocity = abs(vel-self.velocity)
        stepNumber = int(deltaVelocity/velstep)+1
        velArray = np.round(np.linspace(self.velocity,vel,stepNumber),2)
        return velArray
        
    
    async def sendCmd(self, cmd, amount=0):
        """ Kommando an Pumpe senden und auf Reply warten
            """
        self.write(cmd + self.termCmd)
        print(f'Pump command {cmd} send.')
        await self.readData()
        print(f'Reply: {self.lastReading}')
    
    async def connect(self, **kwargs):
        """ Gerät verbinden und initialisieren
            """
        # Kein Context Manager, da das Gerät permanent verbunden bleiben soll!
        self.open()
        await self.initialStartUp()
        self.connectionState = ConnectionState.CONNECTED
    
    async def initialStartUp(self):
        """ ließt aus, welche Geschwindigkeit die Pumpe zu Beginn besitzt
            """
        await self.sendCmd(PumpCmd.getTargetSpeed) # TargetSpeed meint jetzige Geschwindigkeit (?)
        self.velocity = float(self.lastReading)
    
    async def readData(self):
        """ ließt die Antwort der Pumpe aus und entschlüsselt sie
            """
        serial_reply = b''
        #Wait until data is sent
        while serial_reply == b'':
            if self.in_waiting>0:
                serial_reply = self.read_until(expected=self.termRep)
            await asyncio.sleep(0.1)
        self.lastReading = serial_reply.rstrip(self.termRep).decode()

    async def getLastReading(self):
        """ gibt aus, was in LastReading gespeichert ist
            """
        lastReading = self.lastReading
        return self.handleDataReply(lastReading)
    
    async def disconnect(self):
        """ beendet die Verbindung und setzt den Verbindungsstatus zurück
            """
        self.close()
        self.connectionState = ConnectionState.UNCONNECTED

if __name__ == '__main__':
    """ Testet das Suchen, Verbinden und Einstellen der Pumpe
        """
    pump = SerialPump()
    time.sleep(1)
    pump.discoverDevice()
    pump.setVel(400)
    time.sleep(10)
    pump.setVel(0)
    pump.disconnect()
