class FakePLC(): def __init__(self): """Fake PLC class constructor""" log.debug("Initialize FakePLC") # Initialize variables self.__pCoolingThread = None self.__pPressureRegulator1Thread = None self.__pPressureRegulator2Thread = None self.__pHomeReagentRobotThread = None self.__pMoveReagentRobotThread = None self.__pReactor1HomeThread = None self.__pReactor2HomeThread = None self.__pReactor3HomeThread = None self.__pReactor1LinearMovementThread = None self.__pReactor2LinearMovementThread = None self.__pReactor3LinearMovementThread = None self.__pReactor1VerticalMovementThread = None self.__pReactor2VerticalMovementThread = None self.__pReactor3VerticalMovementThread = None self.__pReactor1HeatingThread = None self.__pReactor2HeatingThread = None self.__pReactor3HeatingThread = None def StartUp(self): """Starts up the fake PLC""" log.debug("Start up the FakePLC") # Create the hardware layer self.__pHardwareComm = HardwareComm() # Determine the memory range we need to emulate self.__nMemoryLower, self.__nMemoryUpper = self.__pHardwareComm.CalculateMemoryRange() # Create and fill the memory buffer with zeros self.__pMemory = [] for x in range(self.__nMemoryLower, self.__nMemoryUpper + 1): self.__pMemory.append(0) # Fill the memory buffer from the PLC memory dump self.__FillMemoryBuffer("/opt/elixys/hardware/fakeplc/PLC.MEM") # Pass a reference to the memory buffer to the HardwareComm # so we can read from it and write to it at a higher level self.__pHardwareComm.FakePLC_SetMemory(self.__pMemory, self.__nMemoryLower, self.__nMemoryUpper) # Create the system model self.__pSystemModel = SystemModel(self.__pHardwareComm, None) self.__pSystemModel.StartUp() # Create the socket self.__pSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__pSocket.setblocking(False) self.__pSocket.bind(("", 9600)) def Run(self): """Runs the fake PLC""" # Packet processing loop log.info("Fake PLC running, type 'q' and press enter to quit...") while True: # Check if the user pressed 'q' to quit if Utilities.CheckForQuit(): return # Check socket availability pRead, pWrite, pError = select.select([self.__pSocket], [], [], 0.25) #log.info("Spinning") for pReadable in pRead: #log.info("It is readable") if pReadable == self.__pSocket: # Data is available for receiving pBinaryPacket = self.__pSocket.recv(1024) sPacket = pBinaryPacket.encode("hex") #log.debug("Received Packet of length %d" % len(sPacket)) # Check the message length if len(sPacket) >= 36: # Handle read and write messages if sPacket[20:24] == "0101": self.__HandleRead(sPacket) elif sPacket[20:24] == "0102": self.__HandleWrite(sPacket) else: log.error("FakePLC: Unknown command, ignoring") else: log.error("FakePLC: Packet too short, discarding") # Update the state of the PLC try: self.__UpdatePLC() except Exception as ex: log.error("Traceback:\r\n%s\r\n" % traceback.format_exc()) log.error("UpdatePLC error %s" % str(ex)) def ShutDown(self): """Shuts down the fake PLC""" # Clean up self.__pSocket.close() self.__pSocket = None self.__pSystemModel.ShutDown() def __FillMemoryBuffer(self, sFilename): """Fill the buffer from the file containing a dump of the actual PLC memory""" log.debug("FakePLC: Fille the memory buffer") # Open the PLC memory file pMemoryFile = open(sFilename, "r") pMemoryFileLines = pMemoryFile.readlines() # Search for the CIO memory sCIO = None for sLine in pMemoryFileLines: if sLine.startswith("CIO="): sCIO = sLine break # Handle error if sCIO == None: raise Exception("Failed to find CIO memory") # Trim off the "CIO=" string and split into components sCIO = sCIO[4:] pCIO = sCIO.split(",") # Fill the memory for pComponent in pCIO: # Extract the memory range sRange = pComponent.split(":")[0] pRangeComponents = sRange.split("-") nRangeStart = int(pRangeComponents[0]) if len(pRangeComponents) > 1: nRangeEnd = int(pRangeComponents[1]) else: nRangeEnd = nRangeStart # Extract the value nValue = int(pComponent.split(":")[1], 16) # Fill in the memory array for nOffset in range(nRangeStart, nRangeEnd + 1): if (nOffset >= self.__nMemoryLower) and (nOffset <= self.__nMemoryUpper): self.__pMemory[nOffset - self.__nMemoryLower] = nValue # Clean up pMemoryFile.close() def __HandleRead(self, sPacket): """Handle the read command""" #log.debug("FakePLC: HandleRead") # We only read words if sPacket[24:26] != "b0": log.error("Invalid I/O memory area code") return # Determine the read offset and length nReadOffsetWord = int(sPacket[26:30], 16) nReadLength = int(sPacket[32:36], 16) # Read the memory sMemory = self.__pHardwareComm.FakePLC_ReadMemory(nReadOffsetWord, nReadLength) # Create and send the response sResponse = "0000000000000000000001010000" + sMemory pBinaryResponse = sResponse.decode("hex") self.__pSocket.sendto(pBinaryResponse, ("127.0.0.1", 9601)) def __HandleWrite(self, sPacket): """Handle the write command""" log.debug("FakePLC:HandleWrite") # Determine the write offsets and length nWriteOffsetWord = int(sPacket[26:30], 16) nWriteOffsetBit = int(sPacket[30:32], 16) nWriteLength = int(sPacket[32:36], 16) # Are we writing bits or bytes? if sPacket[24:26] == "30": # Bits. We're only writing this for a single bit at this time if nWriteLength != 1: log.error("Implement multibit writing if needed") return # Verify the packet length if len(sPacket) != 38: log.error("Invalid packet length") return # Extract the boolean value bValue = (sPacket[37] == "1") # Set the target bit self.__pHardwareComm.FakePLC_SetBinaryValue(nWriteOffsetWord, nWriteOffsetBit, bValue) elif sPacket[24:26] == "b0": # Bytes. We're only writing this for a single byte at this time if nWriteLength != 1: log.error("Implement multibyte writing if needed") return # Verify the packet length if len(sPacket) != 40: log.error("Invalid packet length") return # Extract the integer value nValue = int(sPacket[36:40], 0x10) # Set the target word self.__pHardwareComm.FakePLC_SetWordValue(nWriteOffsetWord, nValue) else: log.error("Invalid I/O memory area code") return # Send a success packet sResponse = "0000000000000000000001020000" pBinaryResponse = sResponse.decode("hex") self.__pSocket.sendto(pBinaryResponse, ("127.0.0.1", 9601)) def __UpdatePLC(self): """Updates the PLC in response to any changes to system changes""" # Check for errors #log.info("FakePLC: UpdatePLC") self.__pSystemModel.CheckForError() #log.info("Done with CheckForError") # Update the various system components self.__UpdateVacuumPressure() self.__UpdateCoolingSystem() self.__UpdatePressureRegulator(1) self.__UpdatePressureRegulator(2) self.__UpdateCoolingSystem() self.__UpdateReagentRobotStatus() self.__UpdateReagentRobotPosition() #log.info("After components") self.__UpdateReagentRobotGripper() self.__UpdateReactorRobotStatus(1) self.__UpdateReactorRobotStatus(2) self.__UpdateReactorRobotStatus(3) self.__UpdateReactorPosition(1) self.__UpdateReactorPosition(2) self.__UpdateReactorPosition(3) self.__UpdateReactorHeating(1) self.__UpdateReactorHeating(2) self.__UpdateReactorHeating(3) def __UpdateVacuumPressure(self): """Updates the vacuum pressure in response to system changes""" # Check if the vacuum is on and determine the target pressure if self.__pSystemModel.model["VacuumSystem"].getVacuumSystemOn(): nTargetPressure = -12.4 else: nTargetPressure = -71.9 # Compare the pressures. Add leeway to account for rounding errors nActualPressure = self.__pSystemModel.model["VacuumSystem"].getVacuumSystemPressure() if ((nActualPressure + 1) < nTargetPressure) or ((nActualPressure - 1) > nTargetPressure): # Update the actual pressure to the target pressure self.__pHardwareComm.FakePLC_SetVacuumPressure(nTargetPressure) def __UpdateCoolingSystem(self): """Updates the cooling system in response to system changes""" # Check if the cooling system is on bCoolingSystemOn = self.__pSystemModel.model["CoolingSystem"].getCoolingSystemOn() if bCoolingSystemOn: # Yes. Check if the cooling thread is running if (self.__pCoolingThread == None) or not self.__pCoolingThread.is_alive(): # No, so kill any running heating threads if (self.__pReactor1HeatingThread != None) and self.__pReactor1HeatingThread.is_alive(): self.__pReactor1HeatingThread.Stop() self.__pReactor1HeatingThread.join() if (self.__pReactor2HeatingThread != None) and self.__pReactor2HeatingThread.is_alive(): self.__pReactor2HeatingThread.Stop() self.__pReactor2HeatingThread.join() if (self.__pReactor3HeatingThread != None) and self.__pReactor3HeatingThread.is_alive(): self.__pReactor3HeatingThread.Stop() self.__pReactor3HeatingThread.join() # Kick off the cooling thread self.__pCoolingThread = CoolingThread() self.__pCoolingThread.SetParameters(self.__pHardwareComm, self.__pSystemModel.model["Reactor1"]["Thermocouple"].getHeater1CurrentTemperature(), self.__pSystemModel.model["Reactor1"]["Thermocouple"].getHeater2CurrentTemperature(), self.__pSystemModel.model["Reactor1"]["Thermocouple"].getHeater3CurrentTemperature(), self.__pSystemModel.model["Reactor2"]["Thermocouple"].getHeater1CurrentTemperature(), self.__pSystemModel.model["Reactor2"]["Thermocouple"].getHeater2CurrentTemperature(), self.__pSystemModel.model["Reactor2"]["Thermocouple"].getHeater3CurrentTemperature(), self.__pSystemModel.model["Reactor3"]["Thermocouple"].getHeater1CurrentTemperature(), self.__pSystemModel.model["Reactor3"]["Thermocouple"].getHeater2CurrentTemperature(), self.__pSystemModel.model["Reactor3"]["Thermocouple"].getHeater3CurrentTemperature()) self.__pCoolingThread.setDaemon(True) self.__pCoolingThread.start() else: # No, so kill the cooling thread if it is running if (self.__pCoolingThread != None) and self.__pCoolingThread.is_alive(): self.__pCoolingThread.Stop() self.__pCoolingThread.join() def __UpdatePressureRegulator(self, nPressureRegulator): """Updates the pressure regulator in response to system changes""" # Get the set and actual pressures nSetPressure = self.__pSystemModel.model["PressureRegulator" + str(nPressureRegulator)].getSetPressure() nActualPressure = self.__pSystemModel.model["PressureRegulator" + str(nPressureRegulator)].getCurrentPressure() # Compare the pressures. Add leeway to account for rounding errors if ((nActualPressure + 1) < nSetPressure) or ((nActualPressure - 1) > nSetPressure): # Get a reference to the pressure regulator thread log.debug("Actual: %f,Setpt: %f" % (nActualPressure,nSetPressure)) if nPressureRegulator == 1: pThread = self.__pPressureRegulator1Thread else: pThread = self.__pPressureRegulator2Thread # Check if the pressure regulator thread is running if (pThread == None) or not pThread.is_alive(): # No, so kick off the thread pThread = PressureRegulatorThread() pThread.SetParameters(self.__pHardwareComm, nPressureRegulator, nActualPressure, nSetPressure) pThread.setDaemon(True) pThread.start() # Save the new reference if nPressureRegulator == 1: self.__pPressureRegulator1Thread = pThread else: self.__pPressureRegulator2Thread = pThread def __UpdateReagentRobotStatus(self): """Updates the reagent robot status in response to system changes""" # Get the reagent robot control and check words nControlWordX, nCheckWordX = self.__pSystemModel.model["ReagentDelivery"].getRobotXControlWords() nControlWordY, nCheckWordY = self.__pSystemModel.model["ReagentDelivery"].getRobotYControlWords() # Enable or disable the robots if (nControlWordX == 0x10) and not ((nCheckWordX >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_EnableReagentRobotX() elif (nControlWordX == 0x08) and ((nCheckWordX >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_DisableReagentRobotX() if (nControlWordY == 0x10) and not ((nCheckWordY >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_EnableReagentRobotY() elif (nControlWordY == 0x08) and ((nCheckWordX >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_DisableReagentRobotY() # Home the robots if self.__pHardwareComm.FakePLC_CheckForHomingReagentRobotX() and self.__pHardwareComm.FakePLC_CheckForHomingReagentRobotY(): # Check either of the reagent robots movement threads are running if ((self.__pHomeReagentRobotThread == None) or not self.__pHomeReagentRobotThread.is_alive()) and \ ((self.__pMoveReagentRobotThread == None) or not self.__pMoveReagentRobotThread.is_alive()): # No, so kick off the homing thread nReagentRobotActualPositionRawX, nReagentRobotActualPositionRawZ = self.__pSystemModel.model["ReagentDelivery"].getCurrentPositionRaw() self.__pHomeReagentRobotThread = HomeReagentRobotThread() self.__pHomeReagentRobotThread.SetParameters(self.__pHardwareComm, nReagentRobotActualPositionRawX, nReagentRobotActualPositionRawZ) self.__pHomeReagentRobotThread.setDaemon(True) self.__pHomeReagentRobotThread.start() # Clear the homing flags self.__pHardwareComm.FakePLC_ResetReagentRobotHoming() def __UpdateReagentRobotPosition(self): """Updates the reagent robot position in response to system changes""" # Get the set and actual positions nReagentRobotSetPositionRawX, nReagentRobotSetPositionRawZ = self.__pSystemModel.model["ReagentDelivery"].getSetPositionRaw() nReagentRobotActualPositionRawX, nReagentRobotActualPositionRawZ = self.__pSystemModel.model["ReagentDelivery"].getCurrentPositionRaw() # Compare the positions. Add leeway to account for motor positioning errors if ((nReagentRobotSetPositionRawX + 5) < nReagentRobotActualPositionRawX) or ((nReagentRobotSetPositionRawX - 5) > nReagentRobotActualPositionRawX) or \ ((nReagentRobotSetPositionRawZ + 5) < nReagentRobotActualPositionRawZ) or ((nReagentRobotSetPositionRawZ - 5) > nReagentRobotActualPositionRawZ): # Check if either of the reagent robot movement threads are running if ((self.__pMoveReagentRobotThread == None) or not self.__pMoveReagentRobotThread.is_alive()) and \ ((self.__pHomeReagentRobotThread == None) or not self.__pHomeReagentRobotThread.is_alive()): # No, so kick off the movement thread self.__pMoveReagentRobotThread = MoveReagentRobotThread() self.__pMoveReagentRobotThread.SetParameters(self.__pHardwareComm, nReagentRobotActualPositionRawX, nReagentRobotActualPositionRawZ, \ nReagentRobotSetPositionRawX, nReagentRobotSetPositionRawZ) self.__pMoveReagentRobotThread.setDaemon(True) self.__pMoveReagentRobotThread.start() def __UpdateReagentRobotGripper(self): """Updates the reagent robot gripper in response to system changes""" # Get the gripper set values bGripperSetUp = self.__pSystemModel.model["ReagentDelivery"].getSetGripperUp() bGripperSetDown = self.__pSystemModel.model["ReagentDelivery"].getSetGripperDown() bGripperSetOpen = self.__pSystemModel.model["ReagentDelivery"].getSetGripperOpen() bGripperSetClose = self.__pSystemModel.model["ReagentDelivery"].getSetGripperClose() bSetGasTransferUp = self.__pSystemModel.model["ReagentDelivery"].getSetGasTransferUp() bSetGasTransferDown = self.__pSystemModel.model["ReagentDelivery"].getSetGasTransferDown() # Set the current gripper values self.__pHardwareComm.FakePLC_SetReagentRobotGripper(bGripperSetUp, bGripperSetDown, bGripperSetOpen, bGripperSetClose, bSetGasTransferUp, bSetGasTransferDown) def __UpdateReactorRobotStatus(self, nReactor): """Updates the reactor robot status in response to system changes""" # Get the reactor robot control and check words nControlWord, nCheckWord = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getCurrentRobotControlWords() # Enable or disable the robot if (nControlWord == 0x10) and not ((nCheckWord >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_EnableReactorRobot(nReactor) elif (nControlWord == 0x08) and ((nCheckWord >> ROBONET_STATUS_READY) & 1): self.__pHardwareComm.FakePLC_DisableReactorRobot(nReactor) # Home the robots for nReactor in range(1, 4): # Set reactor-specific parameters if nReactor == 1: pReactorHomeThread = self.__pReactor1HomeThread pReactorLinearMovementThread = self.__pReactor1LinearMovementThread elif nReactor == 2: pReactorHomeThread = self.__pReactor2HomeThread pReactorLinearMovementThread = self.__pReactor2LinearMovementThread else: pReactorHomeThread = self.__pReactor3HomeThread pReactorLinearMovementThread = self.__pReactor3LinearMovementThread # Check if we need to home if self.__pHardwareComm.FakePLC_CheckForHomingReactorRobot(nReactor): # Check either of the reactor robots movement threads are running if ((pReactorHomeThread == None) or not pReactorHomeThread.is_alive()) and \ ((pReactorLinearMovementThread == None) or not pReactorLinearMovementThread.is_alive()): # No, so kick off the homing thread nReactorActualPositionY = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getCurrentPositionRaw() pReactorHomeThread = HomeReactorRobotThread() pReactorHomeThread.SetParameters(self.__pHardwareComm, nReactor, nReactorActualPositionY) pReactorHomeThread.setDaemon(True) pReactorHomeThread.start() # Remember the thread if nReactor == 1: self.__pReactor1HomeThread = pReactorHomeThread elif nReactor == 2: self.__pReactor2HomeThread = pReactorHomeThread else: self.__pReactor3HomeThread = pReactorHomeThread # Clear the homing flags self.__pHardwareComm.FakePLC_ResetReactorRobotHoming(nReactor) def __UpdateReactorPosition(self, nReactor): """Updates the reactor position in response to system changes""" # Get the set and actual linear positions nReactorSetPositionZ = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getSetPositionRaw() nReactorActualPositionZ = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getCurrentPositionRaw() # Compare the linear positions. Add leeway to account for motor positioning errors if ((nReactorSetPositionZ + 5) < nReactorActualPositionZ) or ((nReactorSetPositionZ - 5) > nReactorActualPositionZ): # Get a reference to the reactor linear movement thread if nReactor == 1: pThread = self.__pReactor1LinearMovementThread elif nReactor == 2: pThread = self.__pReactor2LinearMovementThread else: pThread = self.__pReactor3LinearMovementThread # Check if the reactor linear movement thread is running if (pThread == None) or not pThread.is_alive(): # No, so kick off the thread pThread = MoveReactorLinearThread() pThread.SetParameters(self.__pHardwareComm, nReactor, nReactorActualPositionZ, nReactorSetPositionZ) pThread.setDaemon(True) pThread.start() # Save the new reference if nReactor == 1: self.__pReactor1LinearMovementThread = pThread elif nReactor == 2: self.__pReactor2LinearMovementThread = pThread else: self.__pReactor3LinearMovementThread = pThread # Get the set and actual vertical positions bReactorSetUp = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getSetReactorUp() bReactorSetDown = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getSetReactorDown() bReactorActualUp = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getCurrentReactorUp() bReactorActualDown = self.__pSystemModel.model["Reactor" + str(nReactor)]["Motion"].getCurrentReactorDown() # Compare the vertical positions if (bReactorSetUp != bReactorActualUp) or (bReactorSetDown != bReactorActualDown): # Get a reference to the reactor vertical movement thread if nReactor == 1: pThread = self.__pReactor1VerticalMovementThread elif nReactor == 2: pThread = self.__pReactor2VerticalMovementThread else: pThread = self.__pReactor3VerticalMovementThread # Check if the reactor linear movement thread is running if (pThread == None) or not pThread.is_alive(): # No, so kick off the thread pThread = MoveReactorVerticalThread() pThread.SetParameters(self.__pHardwareComm, nReactor, bReactorSetUp) pThread.setDaemon(True) pThread.start() # Save the new reference if nReactor == 1: self.__pReactor1VerticalMovementThread = pThread elif nReactor == 2: self.__pReactor2VerticalMovementThread = pThread else: self.__pReactor3VerticalMovementThread = pThread def __UpdateReactorHeating(self, nReactor): """Updates the reactor heating in response to system changes""" # Check if the heater is on bHeaterOn = self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeaterOn() if bHeaterOn: # Yes. Check if the cooling system is on and return if it is bCoolingSystemOn = self.__pSystemModel.model["CoolingSystem"].getCoolingSystemOn() if bCoolingSystemOn: return # Get the set and actual temperatures nReactorSetTemperature = self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getSetTemperature() nReactorActualTemperature = self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getCurrentTemperature() # Compare the temperatures to see if we need to turn on the heater. Add leeway to account for small variations if (nReactorSetTemperature + 2) > nReactorActualTemperature: # Get a reference to the heating thread if nReactor == 1: pThread = self.__pReactor1HeatingThread elif nReactor == 2: pThread = self.__pReactor2HeatingThread else: pThread = self.__pReactor3HeatingThread # Check if the heating thread is running if (pThread == None) or not pThread.is_alive(): # No, so kick off the thread pThread = HeatingThread() pThread.SetParameters(self.__pHardwareComm, nReactor, self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater1On(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater2On(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater3On(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater1CurrentTemperature(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater2CurrentTemperature(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater3CurrentTemperature(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater1SetTemperature(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater2SetTemperature(), self.__pSystemModel.model["Reactor" + str(nReactor)]["Thermocouple"].getHeater3SetTemperature()) pThread.setDaemon(True) pThread.start() # Save the new reference if nReactor == 1: self.__pReactor1HeatingThread = pThread elif nReactor == 2: self.__pReactor2HeatingThread = pThread else: self.__pReactor3HeatingThread = pThread else: # No, so kill the heating thread if it is running if nReactor == 1: pThread = self.__pReactor1HeatingThread elif nReactor == 2: pThread = self.__pReactor2HeatingThread else: pThread = self.__pReactor3HeatingThread if (pThread != None) and pThread.is_alive(): pThread.Stop() pThread.join()