class LASER: # Preset LASER power level CONSTANTS (units are Watts) HIGH_POWER = 10.00 STANDARD_POWER = 5.00 LOW_POWER = 2.50 MAX_POWER_LEVEL = HIGH_POWER DEFAULT_LASER_CONSTANT = 0.05264472 #TODO Adjust this until LASER branding looks good # Global class variable laserConstant = -1 laserConfig = -1 def __init__(self, gpioFirePin, supplierPartNumber, cocoPartNumber, powerLevel, maxPowerLevel, brandingArt): """ Create a LASER object with power settings, part numbers, and image data to used when fired via GPIO pin Key arguments: gpioFirePin -- 5V GPIO pin used to control a LASER or a 12V relay connected to a LASER supplierPartNumber -- External supplier part number (i.e. PA-07-12-5V) cocoPartNumber -- Internal part number (i.e XXX-YYYYY-Z)) linked to one supplier part number powerLevel -- Power in Wats to intialize a LASER module first fire to maxPowerLevel -- Max power in Watts that LASER can support in continous operation (> 30 seconds) brandingArt -- Black & White PNG image to brand / burn into an object Return value: New LASER() object """ currentProgramFilename = os.path.basename(__file__) self.DebugObject = Debug(True, currentProgramFilename) #self.gpioFirePin = gpiozero.OutputDevice(gpioFirePin) self.supplierPartNumber = supplierPartNumber if (len(cocoPartNumber) != 10): self.DebugObject.Dprint( "Invalid part number format, please verify part number looks like XXX-YYYYY-Z" ) # List of current valid internal LASER part numbers if (cocoPartNumber == "205-0003-A" or cocoPartNumber == "???-????-?"): self.cocoPartNumber = cocoPartNumber else: self.DebugObject.Dprint("Invalid part number format") if (0 > powerLevel or powerLevel > LASER.MAX_POWER_LEVEL): # Check for valid power level and default to 10 Watts if invalid Debug.Dprint( DebugOject, "Invalid power. I'm setting the LASER power to " + repr(self.maxPowerLevel / 2) + " Watts") self.powerLevel = self.MAX_POWER_LEVEL / 2 else: self.powerLevel = powerLevel self.brandingArt = LASER.__WarpImage( CocoDrink.COCOTAPS_LOGO, CocoDrink.SIZE_102MM) # Initialize to standard CocoTaps logo LASER.__ConfigureLaserForNewImage(self.brandingArt) def LoadImage(fileName): """ Key arguments: filename -- PNG file to load into memory Return value: bwImg -- Black & White PNG image """ return bwImg def __WarpImage(fileName, coconutSize): """ Load a PNG image on the local harddrive into RAM Wrap a straight / square image so that after LASER branding on coconut its straight again Key arguments: fileName -- PNG file to load into memory (TODO max size in ? x ? pixels / ?? MB) coconutSize -- Horizontal diameter of coconut in millimeters Return value: newImage -- A new image that has been warpped to to display correctly after LASER branding """ # https://docs.opencv.org/2.4/doc/tutorials/core/mat_the_basic_image_container/mat_the_basic_image_container.html # https://pythonprogramming.net/loading-images-python-opencv-tutorial/ fileNameLength = len(fileName) periodIndex = fileNameLength - 3 fileExtension = fileName[periodIndex:] if (fileExtension.lower() == "png"): path = "static/images/" + fileName originalImage = cv2.imread(path) # Convert to gray scale first to apply better thresholding which will create a better black and white image grayImg = cv2.cvtColor(originalImage, cv2.COLOR_BGR2GRAY) # Anything above 127 on a scale from 0 to 255 is WHITE (thresh, bwImg) = cv2.threshold(grayImg, 127, 255, cv2.THRESH_BINARY) else: self.DebugObject.Dprint( "Please pass a .png file to the LoadImage() function in LASER.py code" ) imgWidth = originalImage.size imgHeight = originalImage.size for xPixel in range(imgWidth): print("xPixel = ", xPixel) for yPixel in range(imgHeight): print("yPixel = ", yPixel) print(" of ", imgHeight) #TODO rgbColor = originalImage.at<Vec3b>(xPixel,yPixel) # Split image into three part vertically and horizonatlly ### TODO SyntaxError: can't assign to comparison # warppedImg.at<Vec3b>(xPixel,yPixel) = rgbColor if (xPixel < (imgWidth / 5)): xPixel = xPixel + 8 # Skip EIGHT pixels since ends warps more at ends elif ((imgWidth / 5) <= xPixel and xPixel < (imgWidth * 2 / 5)): xPixel = xPixel + 4 # Skip EIGHT pixels since ends wraps more at ends elif ((imgWidth * 2 / 5) <= xPixel and xPixel < (imgWidth * 3 / 5)): xPixel = xPixel + 0 # Skip EIGHT pixels since ends wraps more at ends elif ((imgWidth * 3 / 5) <= xPixel and xPixel < (imgWidth * 4 / 5)): xPixel = xPixel + 4 # Skip EIGHT pixels since ends wraps more at ends elif ((imgWidth * 4 / 5) <= xPixel and xPixel < (imgWidth)): xPixel = xPixel + 8 # Skip EIGHT pixels since ends wraps more at ends def __ConfigureLaserForNewImage(img): """ PRIVATE FUNCATION (See __) Calculate pixel dwell duration based on LASER power level and image size Key arguments: img -- PNG file to load into memory Return value: pixelBurnDuration -- Time in seconds that LASER should dwell on coconut pixel """ numOfPixels = LASER.__GetNumOfPixels(img) moistureLevel = GetCoconutMoistureLevel() if (0 < self.powerLevel or self.powerLevel <= LOW_POWER): laserConstant = DEFAULT_LASER_CONSTANT * 0.5 elif (LOW < self.powerLevel or self.powerLevel < STANDARD_POWER): laserConstant = DEFAULT_LASER_CONSTANT * 1.0 elif (self.powerLevel >= STANDARD_POWER): laserConstant = DEFAULT_LASER_CONSTANT * 1.5 else: self.DebugObject.Lprint( "ERROR: Invalid power level choosen in ConfigureLaserForNewImage() function" ) pixelBurnDuration = laserConstant * moistureLevel / 100.0 * numOfPixels / 1000000 return pixelBurnDuration def StopLaser(self): """ Toogle GPIO pin connected to high power relay LOW to turn OFF a LASER Key arguments: NONE Return value: NOTHING """ self.gpioFirePin.off() #gpiozero.off(self.gpioFirePin) def BurnImage(self, laserConfig): """ Toogle GPIO pin possibly connected to a high power relay HIGH to turn ON a LASER Puts CPU to sleep so NOT a threadable function yet Key arguments: laserConfig -- TODO REMOVE? Return value: NOTHING """ pixelBurnDuration = self.__ConfigureLaserForNewImage dutyCycle = self.powerLevel / self.maxPowerLevel imageBurnComplete = False frequency = 100 # Desired LASER pulse in Hz while (not imageBurnComplete): # laserConstant is a class variable highTime = 1 / frequency * dutyCycle * laserConstant sleep(highTime) # Sleep upto 10 ms and keep LASER ON self.gpioFirePin.on() sleep(0.010 - highTime) # Sleep 10 ms minus time is HIGH self.gpioFirePin.off() imageBurnComplete = MoveLaserStepperMotor(pixelDwellDuration, frequency) def MoveLaserStepperMotor(self, frequency, motorID): """ Return value: NOTHING """ for pixelNum in range(0, GetNumOfPixels(filename) - 1): sleep(pixelDwellDuration + 1 / frequency) #TODO if(pixelNum = ) def SetPowerLevel(self, watts, cocoPartNumber): """ Set the power level based on LASER part number being used Key arguments: watts -- Power in Watts to set LASER output to cocoPartNumber -- Internal XXX-YYYYY-Z part number linked to a vendor part number """ if (cocoPartNumber == "205-00003-A"): if (0 > watts or watts > 10): self.DebugObject.Dprint( self.DebugObject, "The 400067260113 LASER must have power level between or equal to 0.1 and 10 Watts" ) else: self.powerLevel = watts else: self.DebugObject.Dprint( "This LASER supplier part number is not supported in LASER.py code base" ) def __GetNumOfPixels(inputImage): """ Calculate the total number of (pixels / 1,000,000) that is in an image file Key argument: inputImage Return value: totalNumOfPixels -- Total number of megapixels (million pixels) in an image """ #img = LoadLImage(self.brandingArt #TODO DOES LoadImage RETURN a img variable) img = cv2.imread(inputImage) imgWidth = img.width imgHeight = img.height totalNumOfPixels = imgWidth * imgHeight return totalNumOfPixels def GetCoconutMoistureLevel(self): """ Moisture level from 0 to 100 corresponing to % humidity Key arguments: NONE Return value: moisturePercentage -- An float from 0.0 to 100.0 """ #TODO Moisture sensor in fridge print("TODO I2C sensor") moisturePercentage = 100 return moisturePercentage def CompileImageAndStoreToEMMC(): """ Key arguments: NONE Return value: NOTHING """ LASER.__WarpImage()