This week I was able to make progress on being able to read the recipe files and load the data to the PLC. I also made some modifications to the OEE data collection to add a header line to the log file.
PLC Recipe Files:
In order to load recipes, the operator will interact through the PanelView display. The operator chooses to load a new recipe by first selecting a recipe 'Family', then selecting the actual product. As can be seen below N10:23 and N10:24 hold the current screen that the operator is on and the next screen to load. When the operator has selected a product to load, bit B3:6/0 is set to signal that a product has been selected. The RPi is reading this bit, along with a couple of others, every 0.2 seconds. When it detects that this bit is True, it reads the product that was selected from the PLC, then reads the recipe file from the SD card on the RPi and selects the appropriate recipe data from the file. The RPi then sends this data to the PLC and when the data transfer is complete will clear B3:6/0.
The operator will select the recipe 'Family' as shown in the below picture. Due to corporate policy I had to distort the choices, but I think you get the point.
Once the 'Family' is selected, the actual recipe will be selected as shown in the below picture.
Once the recipe is selected, bit B3:6/0 is set in the PLC.
Once the RPi recognizes the change in state of B3:6/0, the RPi will retrieve the recipe information and transmit it to the PLC.
if (bits[1]): # self.loaded added so multiple uploads won't be initiated if (not self.loaded): serialLog.debug("Loading Values to PLC") print "Load values to PLC" self.loaded = True def clearRecipeBit(response): request = protectedWriteRequest(1, self.ALARMS[1], [0]) d = self.sendRequest(request) d.addErrback(self.errorHandler, 'clearRecipeBit') def sendRecipe(recipe): PLCRecipe = self.config.getPLCRecipe() index = 1 # Index 0 is recipe name var = [] for address in PLCRecipe: request = protectedWriteRequest(1, address, [float(recipe[index])]) result = self.sendRequest(request) result.addErrback(self.errorHandler, "sendRecipe") var.append(result) d = defer.gatherResults(var) d.addCallback(clearRecipeBit) d.addErrback(self.errorHandler, 'saving data in StartOEEData failed') def getRecipeValues(recipeName): localDir, remoteDir = self.config.getRecipeDirectories() filename = localDir + '/' + 'families.csv' fObj = open(filename, 'r') for recipe in fObj: if recipe.strip() in recipeName[0]: recipeFile = localDir + '/' + recipe.strip() + '.csv' fRecipe = open(recipeFile, 'r') for line in fRecipe: if recipeName[0] in line.strip(): sendRecipe(line.strip().split(',')) request = protectedReadRequest(1, 'ST15:20') d = self.sendRequest(request) d.addCallback(getRecipeValues) d.addErrback(self.errorHandler, 'saving recipe data') else: self.loaded = False
PLC Data Logging
The RPi scans the PLC every 60 seconds to retreive OEE information that will be stored to a local file and eventually FTP'd to a OEE server. The data is stored to a logfile that rotates on a daily basis. The loggerfile is saved in csv format. I added some code to the loggerfile program to add header information to the first line of the file.
def _openFile(self): self.closed = False if os.path.exists(self.path): self._file = file(self.path, "r+", 1) self._file.seek(0, 2) else: config = utilities.optionReader() if self.defaultMode is not None: # Set the lowest permissions oldUmask = os.umask(0o777) try: self._file = file(self.path, "w+", 1) #write header information self._file.write(','.join(map(str,config.getLoggerHeader()))) finally: os.umask(oldUmask) else: self._file = file(self.path, "w+", 1) #write header information self._file.write(','.join(map(str,config.getLoggerHeader()))) if self.defaultMode is not None: try: os.chmod(self.path, self.defaultMode) except OSError: # Probably /dev/null or something? pass self.lastDate = self.toDate(os.stat(self.path)[8])
Last thing to do is integrate the MTrim serial program into the DF1 serial program to write recipe values to it.