import socket
import math
import time
import struct
import atexit
import errno
import threading
import Queue

VSIM_IP = "10.128.0.1"
MATLAB_IP = "127.0.0.1"   #ip address of MATLAB simulation
SIM_SAMPLE_RATE = 0.1     #sample rate for transmission to the PLC SIM in seconds
MATLAB_SAMPLE_RATE = 0.1    #sample rate for transmission to the MATLAB SIM in seconds
SIM_BUFFER_SIZE = 100    #size of buffer receiving from PLC sim
MATLAB_BUFFER_SIZE = 100    #size of buffer receiving from MATLAB sim

#set port numbers
fromMatlabPressurePort = 9915   #port for receiving pressure data from MATLAB
fromMatlabPumpPort = 9916       #port for receiving pump status data from MATLAB
fromMatlabValvePort = 9917      #port for receiving valve status data from MATLAB
simSendPort = 9913              #port to send SIM data to. I also send my data from this port
fromSimReceivePort = 9912      #port for receiving data from PLC simulation and source port for sending PLC Simulation data  
toMatlabPumpPort = 9918        #port to send MATLAB pump status data to
toMatlabValvePort = 9919       #port to send MATLAB valve status data to
    
def openSockets():
#create ports, set port options, open ports for 5 ports    
    #socket for reciving pressure from MATLAB
    print "opening sockets..."
    socket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socket1.setblocking(0)
    socket1.bind(("", fromMatlabPressurePort))
    #socket for sending data in general
    socket2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socket2.setblocking(0)
    socket2.bind(("", simSendPort))
    #socket for reciving data from simulation
    socket3 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket3.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socket3.setblocking(0)
    socket3.bind(("", fromSimReceivePort))
    #socket for reciving pump status from MATLAB
    socket4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socket4.setblocking(0)
    socket4.bind(("", fromMatlabPumpPort))
    #socket for reciving valve status from MATLAB
    socket5 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket5.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socket5.setblocking(0)
    socket5.bind(("", fromMatlabValvePort))
    #a class to I can return all the sockets I made with just 1 return
    class theSockets: 
        fromMatlabPressureSocket = socket1
        sendSocket = socket2
        fromSimReceiveSocket = socket3
        fromMatlabPumpSocket = socket4
        fromMatlabValveSocket = socket5
    print "sockets open!"    
    return theSockets
    
    
def closeSockets(mySockets):
#a function to close sockets I opened in the openSockets code
    print "closing sockets..."
    mySockets.fromMatlabPressureSocket.close()
    mySockets.sendSocket.close()
    mySockets.fromSimReceiveSocket.close()
    mySockets.fromMatlabPumpSocket.close()
    mySockets.fromMatlabValveSocket.close()
    print "sockets closed!"
    
def fromMatlabPressure(mySockets,pressureQueue):
#a function for receving MATLAB pressure data and putting it on a queue
    while 1:
    #receive pressure, transform to floafot, then to an analog value
        try:
            rawpressure, addr = mySockets.fromMatlabPressureSocket.recvfrom(MATLAB_BUFFER_SIZE)
            pressure = struct.unpack('<d', rawpressure)[0]
            pressure = int((pressure*87)+3131)
            pressureQueue.put(pressure)
        except socket.error, e:
            errorcode = e[0]
            if(errorcode == errno.WSAECONNRESET):
                closeSockets()
                openSockets()    
            else:
                pass
            
def fromMatlabPump(mySockets,pumpQueue):
#a function for receving MATLAB pump data and putting it on a queue
    while 1:
    #transform the pump status to float, then to a binary OFF-ON
        try:            
            rawpump, addr = mySockets.fromMatlabPumpSocket.recvfrom(MATLAB_BUFFER_SIZE)
            pump = struct.unpack('<d',rawpump)[0]
            if(pump>0):
                pump = "1"
            else:
                pump = "0"
            pumpQueue.put(pump)
        except socket.error, e:
            errorcode = e[0]
            if(errorcode == errno.WSAECONNRESET):
                closeSockets()
                openSockets()    
            else:
                pass
            
def fromMatlabValve(mySockets,valveQueue):
#a function for receving MATLAB valve data and putting it on a queue
    while 1:
   #transform the valve status to float, then to a binary CLOSED-OPEN   
        try:
            rawvalve, addr = mySockets.fromMatlabValveSocket.recvfrom(MATLAB_BUFFER_SIZE)
            solenoid = struct.unpack('<d', rawvalve)[0]
            if(solenoid>0):
                solenoid = "1"
            else:
                solenoid = "0"
            valveQueue.put(solenoid)
        except socket.error, e:
            errorcode = e[0]
            if(errorcode == errno.WSAECONNRESET):
                closeSockets()
                openSockets()    
            else:
                pass
            
def fromSimData(mySockets,pumpCmdQueue,solCmdQueue):
#a function for receving PLC Simulation data and putting it on a queue
    while 1:
        try:
            simReceiveData, addr = mySockets.fromSimReceiveSocket.recvfrom(SIM_BUFFER_SIZE)
            #split the simulation data into just the values
            splitSimData =simReceiveData.split(",")
            PumpRunCmd = (splitSimData[0].split(":"))[1]
            updatetime = (splitSimData[1].split(":"))[1]
            SolOpenCmd = (splitSimData[2].split(":"))[1]
            SolOpenCmd = (SolOpenCmd.split("}"))[0]
            #transform from boolean into binary(false/true to 0/1)
            if 'false' in PumpRunCmd :
                PumpRunCmd = '0'
            if 'true' in PumpRunCmd :
                PumpRunCmd = '1'
            if 'false' in SolOpenCmd:
                SolOpenCmd = '0'
            if 'true' in SolOpenCmd:
                SolOpenCmd = '1'
            pumpCmdQueue.put(PumpRunCmd)
            solCmdQueue.put(SolOpenCmd)
        except socket.error, e:
            errorcode = e[0]
            if(errorcode == errno.WSAECONNRESET):
                closeSockets()
                openSockets()    
            else:
                pass

def toSimData(mySockets,pressureQueue,pumpQueue,valveQueue):
#a function for sending PLC simulation data from the queue
    lastTime = 0
    #current time calculation and receive data
    currentTime = time.time()
    #read values from the queue
    pressure = pressureQueue.get()
    pump = pumpQueue.get()
    solenoid = valveQueue.get()
    if(pressure is not None and pump is not None and solenoid is not None):
    #put them all together for transmission and transmit
        toSimPacket =  pressurePrefix + str(pressure) + pumpPrefix +str(pump) + solenoidPrefix + str(solenoid) + timePrefix + str(currentTime) + "}"
        if((currentTime-lastTime)>SIM_SAMPLE_RATE):
            lastTime = currentTime
            mySockets.fromSimReceiveSocket.sendto(toSimPacket,(VSIM_IP,simSendPort))
            print "toSimPacket\n " + toSimPacket + "\n"
    if(pressure is None):
         print "no pressure"
    if(pump is None):
        print "no pump"
    if(solenoid is None):
        print "no valve"
            
def toMatlabData(mySockets,pumpCmdQueue,solCmdQueue):
#a function for sending PLC simulation data from the queue
    lastTime = 0
    #current time calculation and receive data
    currentTime = time.time()
    #read values from the queue
    PumpRunCmd = pumpCmdQueue.get()
    SolOpenCmd = solCmdQueue.get()
    if(SolOpenCmd is not None and PumpRunCmd is not None):
    #compile and send the packet back to MATLAB
        toMatlabPacket = "toMatlabPacket\n pump: " + PumpRunCmd + " sol:" + SolOpenCmd + "\n"
        if((currentTime-lastTime)>MATLAB_SAMPLE_RATE):  
            lastTime = currentTime
            mySockets.sendSocket.sendto(PumpRunCmd,(MATLAB_IP,toMatlabPumpPort))
            mySockets.sendSocket.sendto(SolOpenCmd,(MATLAB_IP,toMatlabValvePort))
            print "toMatlabPacket\n pump: " + PumpRunCmd + " sol:" + SolOpenCmd + "\n"


if __name__ == "__main__":
    #open the sockets
    mySockets = openSockets()
    #intitialize time variables for sample rate   
    lastTimeSim = 0
    lastTimeMatlab = 0
    #set static strings and initialize dymanic strings for transmission
    pressurePrefix = '{"PressureRawInputReg": '
    pumpPrefix = ', "PumpRunCmd": '
    solenoidPrefix = ', "SolOpenCmd": '
    timePrefix = ', "simtime": '
    #create queues for data reads/writes
    pressureQueue = Queue.Queue()
    pumpQueue=Queue.Queue()
    valveQueue=Queue.Queue()
    pumpCmdQueue=Queue.Queue()
    solCmdQueue=Queue.Queue()
    #create threads for receipts of all data
    fromMatlabPressureThread = threading.Thread(target=fromMatlabPressure,args=(mySockets,pressureQueue))
    fromMatlabPumpThread = threading.Thread(target=fromMatlabPump,args=(mySockets,pumpQueue))
    fromMatlabValveThread = threading.Thread(target=fromMatlabValve,args=(mySockets,valveQueue))
    fromSimDataThread = threading.Thread(target=fromSimData,args=(mySockets,pumpCmdQueue,solCmdQueue))
    #make them daemon threads to die when the main code dies
    fromMatlabPressureThread.daemon = True
    fromMatlabPumpThread.daemon = True
    fromMatlabValveThread.daemon = True
    fromSimDataThread.daemon = True
    #start the threads 
    fromMatlabPressureThread.start()
    fromMatlabPumpThread.start()
    fromMatlabValveThread.start()
    fromSimDataThread.start()
    #register a program to execute at exit to close the sockets 
    atexit.register(closeSockets,mySockets=mySockets)
    time.sleep(4)
    
    while 1:
        toSimData(mySockets,pressureQueue,pumpQueue,valveQueue)
        toMatlabData(mySockets,pumpCmdQueue,solCmdQueue)
        
       
        
       
        
        
     



                
        
       
