Title: Crop and plant height monitoring in greenhouses with a Raspberry Pi, FarmBot and Intel RealSense D435 depth camera. Author: Credner, J. Biosystems Engineering Lab (BLab), University of Applied Sciences Osnabrueck Year: 2023 Abstract: The Code described here can be used in combination with a Raspberry Pi 4, Halcon, FarmBot Hardware and a Intel RealSense depth camera for contactless and automated measurement and phenotyping of crops cultivated on tables in greenhouses. The plant heights can be measured with an accuracy of about 5 mm, as well as the leaf area in pixels. An RGB-D camera is used for this purpose, which can be moved on the planting table with the help of stepper motors. The system basically consists of two units: 1. mechanical unit: a frameork consisting of aluminium profiles that form an X, Y and Z axis and carry the stepper motors for driving the individual axes, as well as the motor control unit (derived from the FarmBot). 2. software and sensor unit: a Linux-based single-board computer with touch display and the RGB-D camera. This programm communicates with the FarmBot motor control unit (G-Code style communication), reading the actual positions and states of the individual axes and sending the movement commands to move the depth camera over the plants. Measurements can be started manually or a daily measurement can be set automatically at a freely selectable time. The measuring system runs on rollers on the edge of the planting table, so that the usable table surface is not restricted. The system can remain on one planting table or be moved to other tables after the measurement has been completed. Software: Halcon 20.11 running on a Raspberry Pi 4 (Raspberry Pi OS 64-Bit) Keywords: Automatic plant height measurements in greenhouses, crop monitoring Literature: Halcon (2022): Halcon - Die Leistungsstarke Software fuer Ihre Bildverarbeitungsanwendung. MVTec Software GmbH, Munich, Germany /////////////////////////////////////////////////////////////////////////////// // File generated by HDevelop for HALCON/C++ Version 20.11.2.0 // Non-ASCII strings in this file are encoded in local-8-bit encoding (cp1252). // Ensure that the interface encoding is set to locale encoding by calling // SetHcppInterfaceStringEncodingIsUtf8(false) at the beginning of the program. // // Please note that non-ASCII characters in string constants are exported // as octal codes in order to guarantee that the strings are correctly // created on all systems, independent on any compiler settings. // // Source files with different encoding should not be mixed in one project. /////////////////////////////////////////////////////////////////////////////// #ifndef __APPLE__ # include "HalconCpp.h" # include "HDevThread.h" #else # ifndef HC_LARGE_IMAGES # include # include # include # else # include # include # include # endif # include # include #endif using namespace HalconCpp; // Procedure declarations // Chapter: Develop // Short Description: Switch dev_update_pc, dev_update_var and dev_update_window to 'off'. extern void dev_update_off (); // Local procedures void Display_Buttons (HTuple hv_String, HTuple hv_Row, HTuple hv_Column, HTuple hv_Width, HTuple hv_Height, HTuple hv_ColorString, HTuple hv_ColorBackground, HTuple hv_ColorBackgroundActive, HTuple hv_WindowHandle, HTuple hv_ButtonQueueHandle, HTuple hv_EventHandle); void FarmBotReadSerial (HTuple hv_SerialHandle, HTuple hv_DataNum, HTuple hv_SerialQueueHandle, HTuple *hv_SerialData, HTuple *hv_ReadSerialFinished, HTuple *hv_ThreadRunning); void StartCamera (HTuple hv_CameraHandle, HTuple hv_ImageWindow, HTuple hv_CameraQueueHandle, HTuple hv_FarmBotSerialHandle, HTuple hv_EventHandle, HTuple hv_OutputWindow, HTuple hv_TableLength, HTuple hv_TableWidth, HTuple *hv_CameraStarted); // Generated stubs for parallel procedure calls. Wrapped in name // space to avoid name conflicts with actual procedure names namespace HDevExportCpp { // Parallel execution wrapper for FarmBotReadSerial(...) static void* _hcppthread_FarmBotReadSerial(void *hcthread); // Parallel execution wrapper for Display_Buttons(...) static void* _hcppthread_Display_Buttons(void *hcthread); // Parallel execution wrapper for StartCamera(...) static void* _hcppthread_StartCamera(void *hcthread); } // Procedures // Local procedures void Display_Buttons (HTuple hv_String, HTuple hv_Row, HTuple hv_Column, HTuple hv_Width, HTuple hv_Height, HTuple hv_ColorString, HTuple hv_ColorBackground, HTuple hv_ColorBackgroundActive, HTuple hv_WindowHandle, HTuple hv_ButtonQueueHandle, HTuple hv_EventHandle) { // Local iconic variables HObject ho_ButtonRect, ho_ObjectSelected; // Local control variables HTuple hv_condition, hv_ButtonMessageHandle; HTuple hv_ColorBackgroundOut, hv_ColumnOut, hv_RowOut; HTuple hv__, hv_WindowWidth, hv_WindowHeight, hv_PartHeight; HTuple hv_PartWidth, hv_WindowImageWidthRatio, hv_WindowImageHeightRatio; HTuple hv_ButtonRowScaled, hv_ButtonColumnScaled, hv_ButtonHeightScaled; HTuple hv_ButtonWidthScaled, hv_HighlightColors, hv_Ascents; HTuple hv_Descents, hv_TextWidths, hv_Index, hv_Ascent; HTuple hv_Descent, hv_TextWidth, hv_TextHeight, hv_TextRow; HTuple hv_TextColumn, hv_SelectedButton, hv_Button, hv_ButtonIndex; HTuple hv_IsInside, hv_Exeption; hv_condition = 1; CreateMessage(&hv_ButtonMessageHandle); while (0 != hv_condition) { hv_ColorBackgroundOut = hv_ColorBackground; hv_ColumnOut = hv_Column; hv_RowOut = hv_Row; // //This procedure displays one or more interactive buttons. //It is possible to customize the size, position, and color of the buttons. //The index of the clicked button is returned. // //get_system ('clip_region', ClipRegion) SetSystem("clip_region", "false"); //dev_get_preferences ('suppress_handled_exceptions_dlg', SuppressHandledExceptionsDlg) // dev_set_preferences(...); only in hdevelop //get_window_param (WindowHandle, 'flush', Flush) SetWindowParam(hv_WindowHandle, "flush", "false"); // //Get the scaling factor for window/image coordinate conversion. GetWindowExtents(hv_WindowHandle, &hv__, &hv__, &hv_WindowWidth, &hv_WindowHeight); GetPart(hv_WindowHandle, &hv__, &hv__, &hv_PartHeight, &hv_PartWidth); hv_WindowImageWidthRatio = hv_WindowWidth/(1.0*hv_PartWidth); hv_WindowImageHeightRatio = hv_WindowHeight/(1.0*hv_PartHeight); // //Scale the button coordinates. hv_ButtonRowScaled = hv_RowOut/hv_WindowImageHeightRatio; hv_ButtonColumnScaled = hv_ColumnOut/hv_WindowImageWidthRatio; hv_ButtonHeightScaled = hv_Height/hv_WindowImageHeightRatio; hv_ButtonWidthScaled = hv_Width/hv_WindowImageWidthRatio; // GenRectangle1(&ho_ButtonRect, hv_ButtonRowScaled, hv_ButtonColumnScaled, hv_ButtonRowScaled+hv_ButtonHeightScaled, hv_ButtonColumnScaled+hv_ButtonWidthScaled); if (0 != (int((hv_ColorBackgroundOut.TupleLength())==1))) { hv_ColorBackgroundOut = HTuple(hv_String.TupleLength(),hv_ColorBackgroundOut); } hv_HighlightColors = hv_ColorBackgroundOut; // //We want to place the text centered in the button. hv_Ascents = HTuple(); hv_Descents = HTuple(); hv_TextWidths = HTuple(); { HTuple end_val44 = (hv_String.TupleLength())-1; HTuple step_val44 = 1; for (hv_Index=0; hv_Index.Continue(end_val44, step_val44); hv_Index += step_val44) { GetStringExtents(hv_WindowHandle, HTuple(hv_String[hv_Index]), &hv_Ascent, &hv_Descent, &hv_TextWidth, &hv_TextHeight); hv_Ascents = hv_Ascents.TupleConcat(hv_Ascent); hv_Descents = hv_Descents.TupleConcat(hv_Descent); hv_TextWidths = hv_TextWidths.TupleConcat(hv_TextWidth); } } // hv_TextRow = (hv_RowOut+((hv_Height-hv_TextHeight)/2))-hv_Descent; hv_TextColumn = hv_ColumnOut+((hv_Width-hv_TextWidths)/2); hv_SelectedButton = 0; hv_Button = 0; // while (0 != (HTuple(int(hv_SelectedButton==0)).TupleOr(int(hv_Button!=1)))) { WaitEvent(hv_EventHandle); HDevWindowStack::SetActive(hv_WindowHandle); if (HDevWindowStack::IsOpen()) SetColor(HDevWindowStack::GetActive(),hv_HighlightColors); if (HDevWindowStack::IsOpen()) DispObj(ho_ButtonRect, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_String, "window", hv_TextRow, hv_TextColumn, hv_ColorString, "box", "false"); //Actual display. FlushBuffer(hv_WindowHandle); try { GetMposition(hv_WindowHandle, &hv_RowOut, &hv_ColumnOut, &hv_Button); hv_SelectedButton = 0; { HTuple end_val70 = hv_ButtonRowScaled.TupleLength(); HTuple step_val70 = 1; for (hv_ButtonIndex=1; hv_ButtonIndex.Continue(end_val70, step_val70); hv_ButtonIndex += step_val70) { SelectObj(ho_ButtonRect, &ho_ObjectSelected, hv_ButtonIndex); TestRegionPoint(ho_ObjectSelected, hv_RowOut, hv_ColumnOut, &hv_IsInside); if (0 != hv_IsInside) { hv_SelectedButton = hv_ButtonIndex; break; } } } } // catch (Exeption) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exeption); } if (0 != (int(hv_SelectedButton>0))) { TupleReplace(hv_ColorBackgroundOut, hv_SelectedButton-1, hv_ColorBackgroundActive, &hv_HighlightColors); } else { hv_HighlightColors = hv_ColorBackgroundOut; } } //Return index. hv_SelectedButton = hv_SelectedButton-1; // SetMessageTuple(hv_ButtonMessageHandle, "ButtonPressed", hv_SelectedButton); EnqueueMessage(hv_ButtonQueueHandle, hv_ButtonMessageHandle, HTuple(), HTuple()); hv_SelectedButton = 0; //set_system ('clip_region', ClipRegion) //dev_set_preferences ('suppress_handled_exceptions_dlg', SuppressHandledExceptionsDlg) //set_window_param (WindowHandle, 'flush', Flush) WaitSeconds(0.1); } return; } void FarmBotReadSerial (HTuple hv_SerialHandle, HTuple hv_DataNum, HTuple hv_SerialQueueHandle, HTuple *hv_SerialData, HTuple *hv_ReadSerialFinished, HTuple *hv_ThreadRunning) { // Local iconic variables // Local control variables HTuple hv_condition, hv_SerialMessageHandle; HTuple hv_Data, hv_DataNew, hv_SerialLength, hv_FarmBotIdleIndex; HTuple hv_FarmBotRunningIndex, hv_FarmBotPositionIndex; HTuple hv_FarmBotHomingXIndex, hv_FarmBotHomingYIndex; HTuple hv_i, hv_Idle, hv_Running, hv_Status, hv_HomingX; HTuple hv_StatusX, hv_HomingY, hv_StatusY, hv_BotPosition; HTuple hv_FarmBotPositions, hv_Substrings, hv_X, hv_Y; HTuple hv_Z, hv_XPos, hv_YPos, hv_ZPos, hv_XPosition, hv_YPosition; HTuple hv_ZPosition, hv_Exception; //Reads the infomation coming from the Farmbot and gives it to the message handle hv_condition = 1; CreateMessage(&hv_SerialMessageHandle); while (0 != hv_condition) { do { ReadSerial(hv_SerialHandle, hv_DataNum, &hv_Data); hv_DataNew = hv_Data.TupleChrt(); TupleSplit(hv_DataNew, "\r\n", &(*hv_SerialData)); TupleLength((*hv_SerialData), &hv_SerialLength); } while (0 == (int(hv_SerialLength>2))); try { TupleStrstr((*hv_SerialData), "R00", &hv_FarmBotIdleIndex); TupleStrstr((*hv_SerialData), "R04", &hv_FarmBotRunningIndex); TupleStrstr((*hv_SerialData), "R82", &hv_FarmBotPositionIndex); TupleStrstr((*hv_SerialData), "R11", &hv_FarmBotHomingXIndex); TupleStrstr((*hv_SerialData), "R12", &hv_FarmBotHomingYIndex); //tuple_strstr (SerialData, 'R06', FarmBotCalibrateIndex) { HTuple end_val27 = hv_SerialLength-1; HTuple step_val27 = 1; for (hv_i=0; hv_i.Continue(end_val27, step_val27); hv_i += step_val27) { hv_Idle = HTuple(hv_FarmBotIdleIndex[hv_i]); hv_Running = HTuple(hv_FarmBotRunningIndex[hv_i]); if (0 != (int(hv_Idle!=-1))) { hv_Status = "Idle"; break; //set_message_tuple (SerialMessageHandle, 'BotStatus', Status) } else if (0 != (int(hv_Running!=-1))) { hv_Status = "Running"; break; //set_message_tuple (SerialMessageHandle, 'BotStatus', Status) } else { hv_Status = "Unknown"; //set_message_tuple (SerialMessageHandle, 'BotStatus', Status) } } } SetMessageTuple(hv_SerialMessageHandle, "BotStatus", hv_Status); { HTuple end_val55 = hv_SerialLength-1; HTuple step_val55 = 1; for (hv_i=0; hv_i.Continue(end_val55, step_val55); hv_i += step_val55) { hv_HomingX = HTuple(hv_FarmBotHomingXIndex[hv_i]); if (0 != (int(hv_HomingX!=-1))) { hv_StatusX = "Complete"; break; } else { hv_StatusX = "NotComplete"; } } } SetMessageTuple(hv_SerialMessageHandle, "HomingX", hv_StatusX); { HTuple end_val74 = hv_SerialLength-1; HTuple step_val74 = 1; for (hv_i=0; hv_i.Continue(end_val74, step_val74); hv_i += step_val74) { hv_HomingY = HTuple(hv_FarmBotHomingYIndex[hv_i]); if (0 != (int(hv_HomingY!=-1))) { hv_StatusY = "Complete"; break; } else { hv_StatusY = "NotComplete"; } } } SetMessageTuple(hv_SerialMessageHandle, "HomingY", hv_StatusY); { HTuple end_val94 = hv_SerialLength-1; HTuple step_val94 = 1; for (hv_i=0; hv_i.Continue(end_val94, step_val94); hv_i += step_val94) { hv_BotPosition = HTuple(hv_FarmBotPositionIndex[hv_i]); if (0 != (int(hv_BotPosition!=-1))) { hv_FarmBotPositions = HTuple((*hv_SerialData)[hv_i]); TupleSplit(hv_FarmBotPositions, " ", &hv_Substrings); try { hv_X = ((const HTuple&)hv_Substrings)[1]; hv_Y = ((const HTuple&)hv_Substrings)[2]; hv_Z = ((const HTuple&)hv_Substrings)[3]; TupleRegexpMatch(hv_X, "X(.*)", &hv_XPos); TupleRegexpMatch(hv_Y, "Y(.*)", &hv_YPos); TupleRegexpMatch(hv_Z, "Z(.*)", &hv_ZPos); TupleNumber(hv_XPos, &hv_XPosition); TupleNumber(hv_YPos, &hv_YPosition); TupleNumber(hv_ZPos, &hv_ZPosition); //tuple_real (XPosition, XPosition) //tuple_real (YPosition, YPosition) //tuple_real (ZPosition, ZPosition) SetMessageTuple(hv_SerialMessageHandle, "XPosition", hv_XPosition); SetMessageTuple(hv_SerialMessageHandle, "YPosition", hv_YPosition); SetMessageTuple(hv_SerialMessageHandle, "ZPosition", hv_ZPosition); break; } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); hv_XPosition = 0; hv_YPosition = 0; hv_ZPosition = 0; SetMessageTuple(hv_SerialMessageHandle, "XPosition", hv_XPosition); SetMessageTuple(hv_SerialMessageHandle, "YPosition", hv_YPosition); SetMessageTuple(hv_SerialMessageHandle, "ZPosition", hv_ZPosition); } } } } //for i := 0 to SerialLength -1 by 1 //BotCalibrate := FarmBotCalibrateIndex[i] //if (BotCalibrate != -1) //FarmBotCalibration := SerialData[i] //tuple_split (FarmBotCalibration, ' ', Substrings) //CX := Substrings[1] //CY := Substrings[2] //CZ := Substrings[3] //CY := 'dummy' //CZ := 'dummy' //set_message_tuple (SerialMessageHandle, 'CalibrateX', CX) //set_message_tuple (SerialMessageHandle, 'CalibrateY', CY) //set_message_tuple (SerialMessageHandle, 'CalibrateZ', CZ) //break //endif //endfor EnqueueMessage(hv_SerialQueueHandle, hv_SerialMessageHandle, HTuple(), HTuple()); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); //Status := 'Unknown' //XPosition := 0 //YPosition := 0 //ZPosition := 0 //StatusY := 'NotComplete' //StatusX := 'NotComplete' //CX := 'dummy' //CY := 'dummy' //CZ := 'dummy' //set_message_tuple (SerialMessageHandle, 'BotStatus', Status) //set_message_tuple (SerialMessageHandle, 'HomingX', StatusX) //set_message_tuple (SerialMessageHandle, 'HomingY', StatusY) //set_message_tuple (SerialMessageHandle, 'XPosition', XPosition) //set_message_tuple (SerialMessageHandle, 'YPosition', YPosition) //set_message_tuple (SerialMessageHandle, 'ZPosition', ZPosition) //set_message_tuple (SerialMessageHandle, 'CalibrateX', CX) //set_message_tuple (SerialMessageHandle, 'CalibrateY', CY) //set_message_tuple (SerialMessageHandle, 'CalibrateZ', CZ) //enqueue_message (SerialQueueHandle, SerialMessageHandle, [], []) } } (*hv_ReadSerialFinished) = 1; (*hv_ThreadRunning) = 0; return; } void StartCamera (HTuple hv_CameraHandle, HTuple hv_ImageWindow, HTuple hv_CameraQueueHandle, HTuple hv_FarmBotSerialHandle, HTuple hv_EventHandle, HTuple hv_OutputWindow, HTuple hv_TableLength, HTuple hv_TableWidth, HTuple *hv_CameraStarted) { // Local iconic variables HObject ho_Images, ho_Image, ho_DataRegion, ho_DataContours; HObject ho_ColorImage, ho_DepthImage, ho_RegionWithoutError; HObject ho_DepthImageWithoutError, ho_RegionBackground; HObject ho_RegionPlants, ho_RegionsSinglePlants, ho_PlantRegionSelected; // Local control variables HTuple hv_condition, hv_HomingX, hv_HomingY; HTuple hv_BotStatus, hv_XPosition, hv_YPosition, hv_X; HTuple hv_FOVHorizontal, hv_FOVVertical, hv_XOffset, hv_YOffset; HTuple hv_MSecond, hv_Second, hv_Minute, hv_Hour, hv_Day; HTuple hv_YDay, hv_Month, hv_Year, hv_DirName, hv_OutputFileHandle; HTuple hv_Value, hv_i, hv_Data1, hv_UsedThreshold, hv_MRow; HTuple hv_MCol, hv_Alpha, hv_Beta, hv_MeanBackground, hv_ImageSizeX; HTuple hv_ImageSizeY, hv_MessageHandle, hv_Exception, hv_Y; HTuple hv_NumberPlants, hv_p, hv_Rows, hv_Columns, hv_GrayvalsSinglePlants; HTuple hv_MeanGrayvalSinglePlants, hv_PlantHeight, hv_Area; HTuple hv_Row, hv_Column, hv_EndSignal, hv_FileExists; //some variables hv_condition = 1; hv_HomingX = "NotComplete"; hv_HomingY = "NotComplete"; hv_BotStatus = "Unknown"; hv_XPosition = 0; hv_YPosition = 0; hv_X = 10; //Field of view of the RealSense in horizontal and vertical direction (in rad) hv_FOVHorizontal = 1.204277; hv_FOVVertical = 0.733038; hv_XOffset = 0; hv_YOffset = 0; dev_update_off(); GetSystemTime(&hv_MSecond, &hv_Second, &hv_Minute, &hv_Hour, &hv_Day, &hv_YDay, &hv_Month, &hv_Year); GetCurrentDir(&hv_DirName); //Generates a text file to store the positions where the images were taken OpenFile(((((((((((hv_DirName+"/Positionen_")+hv_Hour)+"_")+hv_Minute)+"_")+hv_Day)+"_")+hv_Month)+"_")+hv_Year)+".txt", "output", &hv_OutputFileHandle); FwriteString(hv_OutputFileHandle, "X:Y"); FnewLine(hv_OutputFileHandle); GenEmptyObj(&ho_Images); ClearSerial(hv_FarmBotSerialHandle, "input"); GetFramegrabberParam(hv_CameraHandle, "device", &hv_Value); //Taking 20 images to "warm up" the camera. Remember: getting the images asynchronous reads the images from //the camera buffer. So the images can be old and wont fit the actual camera position (FirstIn-FirstOut). //However, the synchronous reading often generates image errors. The Intel RealSense //sends image packages cointaining the x and y-positions of the individual pixel, a color and depth image. for (hv_i=0; hv_i<=19; hv_i+=1) { GrabDataAsync(&ho_Image, &ho_DataRegion, &ho_DataContours, hv_CameraHandle, -1, &hv_Data1); SelectObj(ho_Image, &ho_ColorImage, 4); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispObj(ho_ColorImage, HDevWindowStack::GetActive()); FlushBuffer(hv_ImageWindow); } //Select the last image and determine the mean distance between camera and background //The real image size is determined by the distance and camera opening angle //With the real image size, the camera can be positioned in such a way that no table edges //are recorded and the image series do not overlap SelectObj(ho_Image, &ho_DepthImage, 3); Threshold(ho_DepthImage, &ho_RegionWithoutError, 1, 1000); ReduceDomain(ho_DepthImage, ho_RegionWithoutError, &ho_DepthImageWithoutError); ConvertImageType(ho_DepthImageWithoutError, &ho_DepthImageWithoutError, "uint2"); BinaryThreshold(ho_DepthImageWithoutError, &ho_RegionBackground, "max_separability", "light", &hv_UsedThreshold); MomentsGrayPlane(ho_RegionBackground, ho_DepthImageWithoutError, &hv_MRow, &hv_MCol, &hv_Alpha, &hv_Beta, &hv_MeanBackground); hv_ImageSizeX = ((2.0*hv_MeanBackground)*((hv_FOVVertical/2.0).TupleTan())).TupleAbs(); hv_ImageSizeY = ((2.0*hv_MeanBackground)*((hv_FOVHorizontal/2.0).TupleTan())).TupleAbs(); hv_XOffset = (hv_ImageSizeX/2.0).TupleRound(); hv_YOffset = (hv_ImageSizeY/2.0).TupleRound(); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),("Distance to background: "+hv_MeanBackground)+" mm", "window", "top", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),("X offset: "+hv_XOffset)+" mm", "window", "center", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),("Y offset: "+hv_YOffset)+" mm", "window", "bottom", "left", "black", HTuple(), HTuple()); FlushBuffer(hv_ImageWindow); WaitSeconds(2); //Homing the x axis and continuously take color images until the FarmBot reports an idle state (reaching the home position) WriteSerial(hv_FarmBotSerialHandle, HTuple("F11\r\n").TupleOrds()); ClearSerial(hv_FarmBotSerialHandle, "input"); SetMessageQueueParam(hv_CameraQueueHandle, "flush_queue", 1); hv_BotStatus = "Running"; do { try { DequeueMessage(hv_CameraQueueHandle, "timeout", 0.5, &hv_MessageHandle); //get_message_tuple (MessageHandle, 'HomingX', HomingX) GetMessageTuple(hv_MessageHandle, "BotStatus", &hv_BotStatus); GetMessageTuple(hv_MessageHandle, "XPosition", &hv_XPosition); GetMessageTuple(hv_MessageHandle, "YPosition", &hv_YPosition); ClearMessage(hv_MessageHandle); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } GrabDataAsync(&ho_Image, &ho_DataRegion, &ho_DataContours, hv_CameraHandle, -1, &hv_Data1); SelectObj(ho_Image, &ho_ColorImage, 4); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispObj(ho_ColorImage, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_BotStatus, "window", "bottom", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(("X: "+hv_XPosition)+" Y: ")+hv_YPosition, "window", "bottom", "right", "black", HTuple(), HTuple()); FlushBuffer(hv_ImageWindow); } while (0 == (int(hv_BotStatus==HTuple("Idle")))); //Home the y axis WaitSeconds(1); WriteSerial(hv_FarmBotSerialHandle, HTuple("F12\r\n").TupleOrds()); ClearSerial(hv_FarmBotSerialHandle, "input"); SetMessageQueueParam(hv_CameraQueueHandle, "flush_queue", 1); hv_BotStatus = "Running"; do { try { DequeueMessage(hv_CameraQueueHandle, "timeout", 0.5, &hv_MessageHandle); //get_message_tuple (MessageHandle, 'HomingY', HomingY) GetMessageTuple(hv_MessageHandle, "BotStatus", &hv_BotStatus); GetMessageTuple(hv_MessageHandle, "XPosition", &hv_XPosition); GetMessageTuple(hv_MessageHandle, "YPosition", &hv_YPosition); ClearMessage(hv_MessageHandle); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } if (0 != (int(hv_Value!=HTuple("0")))) { GrabDataAsync(&ho_Image, &ho_DataRegion, &ho_DataContours, hv_CameraHandle, -1, &hv_Data1); SelectObj(ho_Image, &ho_ColorImage, 4); } else { GrabImageAsync(&ho_ColorImage, hv_CameraHandle, -1); } HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispObj(ho_ColorImage, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_BotStatus, "window", "bottom", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(("X: "+hv_XPosition)+" Y: ")+hv_YPosition, "window", "bottom", "right", "black", HTuple(), HTuple()); FlushBuffer(hv_ImageWindow); } while (0 == (int(hv_BotStatus==HTuple("Idle")))); WaitSeconds(1); ClearSerial(hv_FarmBotSerialHandle, "input"); SetMessageQueueParam(hv_CameraQueueHandle, "flush_queue", 1); //Move the camera to the first y-position (half of the real image width) and move the camera in x direction //in one image height steps until the table length is reached. Continuously take image until the FarmBot reports //and idle state. Save the last Image and camera position move to the next location. When the FarmBot reaches the //table length and width, the images and camera positions will be saved to the working directory and the thread will //return to the main loop { HTuple end_val134 = hv_TableWidth; HTuple step_val134 = hv_YOffset*2; for (hv_Y=hv_YOffset; hv_Y.Continue(end_val134, step_val134); hv_Y += step_val134) { SetMessageQueueParam(hv_CameraQueueHandle, "flush_queue", 1); WriteSerial(hv_FarmBotSerialHandle, (((("G00 X"+hv_X)+" Y")+hv_Y)+" Z0\r\n").TupleOrds()); hv_BotStatus = "Running"; do { try { DequeueMessage(hv_CameraQueueHandle, "timeout", 0.5, &hv_MessageHandle); GetMessageTuple(hv_MessageHandle, "BotStatus", &hv_BotStatus); GetMessageTuple(hv_MessageHandle, "XPosition", &hv_XPosition); GetMessageTuple(hv_MessageHandle, "YPosition", &hv_YPosition); ClearMessage(hv_MessageHandle); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } GrabDataAsync(&ho_Image, &ho_DataRegion, &ho_DataContours, hv_CameraHandle, -1, &hv_Data1); SelectObj(ho_Image, &ho_DepthImage, 3); SelectObj(ho_Image, &ho_ColorImage, 4); Threshold(ho_DepthImage, &ho_RegionWithoutError, 1, 1000); ReduceDomain(ho_DepthImage, ho_RegionWithoutError, &ho_DepthImageWithoutError ); ConvertImageType(ho_DepthImageWithoutError, &ho_DepthImageWithoutError, "uint2"); BinaryThreshold(ho_DepthImageWithoutError, &ho_RegionPlants, "max_separability", "dark", &hv_UsedThreshold); BinaryThreshold(ho_DepthImageWithoutError, &ho_RegionBackground, "max_separability", "light", &hv_UsedThreshold); Connection(ho_RegionPlants, &ho_RegionsSinglePlants); SelectShape(ho_RegionsSinglePlants, &ho_RegionsSinglePlants, "area", "and", 10000, 999999999999); MomentsGrayPlane(ho_RegionBackground, ho_DepthImageWithoutError, &hv_MRow, &hv_MCol, &hv_Alpha, &hv_Beta, &hv_MeanBackground); CountObj(ho_RegionsSinglePlants, &hv_NumberPlants); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispObj(ho_ColorImage, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_BotStatus, "window", "bottom", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(((("Objects: "+hv_NumberPlants)+" X: ")+hv_XPosition)+" Y: ")+hv_YPosition, "window", "bottom", "right", "black", HTuple(), HTuple()); { HTuple end_val169 = hv_NumberPlants; HTuple step_val169 = 1; for (hv_p=1; hv_p.Continue(end_val169, step_val169); hv_p += step_val169) { SelectObj(ho_RegionsSinglePlants, &ho_PlantRegionSelected, hv_p); GetRegionPoints(ho_PlantRegionSelected, &hv_Rows, &hv_Columns); GetGrayval(ho_DepthImageWithoutError, hv_Rows, hv_Columns, &hv_GrayvalsSinglePlants); TupleMean(hv_GrayvalsSinglePlants, &hv_MeanGrayvalSinglePlants); hv_PlantHeight = (hv_MeanBackground*1.0)-(hv_MeanGrayvalSinglePlants*1.0); AreaCenter(ho_PlantRegionSelected, &hv_Area, &hv_Row, &hv_Column); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) DispObj(ho_PlantRegionSelected, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Height: "+hv_PlantHeight, "image", hv_Row, hv_Column, "red", HTuple(), HTuple()); } } FlushBuffer(hv_ImageWindow); } while (0 == (int(hv_BotStatus==HTuple("Idle")))); { HTuple end_val187 = hv_TableLength; HTuple step_val187 = hv_XOffset*2; for (hv_X=hv_XOffset; hv_X.Continue(end_val187, step_val187); hv_X += step_val187) { hv_EndSignal = 0; hv_i = 0; hv_BotStatus = "Running"; ClearSerial(hv_FarmBotSerialHandle, "input"); SetMessageQueueParam(hv_CameraQueueHandle, "flush_queue", 1); WriteSerial(hv_FarmBotSerialHandle, (((("G00 X"+hv_X)+" Y")+hv_Y)+" Z0\r\n").TupleOrds()); do { try { DequeueMessage(hv_CameraQueueHandle, "timeout", 0.5, &hv_MessageHandle); //get_message_tuple (MessageHandle, 'CalibrateX', CalibrateX) GetMessageTuple(hv_MessageHandle, "BotStatus", &hv_BotStatus); GetMessageTuple(hv_MessageHandle, "XPosition", &hv_XPosition); GetMessageTuple(hv_MessageHandle, "YPosition", &hv_YPosition); ClearMessage(hv_MessageHandle); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } try { GrabDataAsync(&ho_Image, &ho_DataRegion, &ho_DataContours, hv_CameraHandle, -1, &hv_Data1); SelectObj(ho_Image, &ho_DepthImage, 3); SelectObj(ho_Image, &ho_ColorImage, 4); Threshold(ho_DepthImage, &ho_RegionWithoutError, 1, 1000); ReduceDomain(ho_DepthImage, ho_RegionWithoutError, &ho_DepthImageWithoutError ); ConvertImageType(ho_DepthImageWithoutError, &ho_DepthImageWithoutError, "uint2"); BinaryThreshold(ho_DepthImageWithoutError, &ho_RegionPlants, "max_separability", "dark", &hv_UsedThreshold); BinaryThreshold(ho_DepthImageWithoutError, &ho_RegionBackground, "max_separability", "light", &hv_UsedThreshold); Connection(ho_RegionPlants, &ho_RegionsSinglePlants); SelectShape(ho_RegionsSinglePlants, &ho_RegionsSinglePlants, "area", "and", 10000, 999999999999); MomentsGrayPlane(ho_RegionBackground, ho_DepthImageWithoutError, &hv_MRow, &hv_MCol, &hv_Alpha, &hv_Beta, &hv_MeanBackground); CountObj(ho_RegionsSinglePlants, &hv_NumberPlants); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispObj(ho_ColorImage, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_BotStatus, "window", "bottom", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(((("Objects: "+hv_NumberPlants)+" X: ")+hv_XPosition)+" Y: ")+hv_YPosition, "window", "bottom", "right", "black", HTuple(), HTuple()); { HTuple end_val233 = hv_NumberPlants; HTuple step_val233 = 1; for (hv_p=1; hv_p.Continue(end_val233, step_val233); hv_p += step_val233) { SelectObj(ho_RegionsSinglePlants, &ho_PlantRegionSelected, hv_p); GetRegionPoints(ho_PlantRegionSelected, &hv_Rows, &hv_Columns); GetGrayval(ho_DepthImageWithoutError, hv_Rows, hv_Columns, &hv_GrayvalsSinglePlants); TupleMean(hv_GrayvalsSinglePlants, &hv_MeanGrayvalSinglePlants); hv_PlantHeight = (hv_MeanBackground*1.0)-(hv_MeanGrayvalSinglePlants*1.0); AreaCenter(ho_PlantRegionSelected, &hv_Area, &hv_Row, &hv_Column); HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) DispObj(ho_PlantRegionSelected, HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Height: "+hv_PlantHeight, "image", hv_Row, hv_Column, "red", HTuple(), HTuple()); } } FlushBuffer(hv_ImageWindow); if (0 != (int(hv_BotStatus==HTuple("Idle")))) { hv_EndSignal = 1; ConcatObj(ho_Images, ho_Image, &ho_Images); FwriteString(hv_OutputFileHandle, (hv_X+":")+hv_Y); FnewLine(hv_OutputFileHandle); } } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } if (0 != (int(hv_BotStatus==HTuple("Idle")))) { hv_EndSignal = 1; } } while (0 == (int(hv_BotStatus==HTuple("Idle")))); } } hv_X = hv_XOffset; } } HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Saving images. Please wait...", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ImageWindow); WriteObject(ho_Images, (((((((((hv_DirName+"/Images_")+hv_Hour)+"_")+hv_Minute)+"_")+hv_Day)+"_")+hv_Month)+"_")+hv_Year); FileExists(((((((((((hv_DirName+"/Images_")+hv_Hour)+"_")+hv_Minute)+"_")+hv_Day)+"_")+hv_Month)+"_")+hv_Year)+".hobj", &hv_FileExists); CloseFile(hv_OutputFileHandle); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (0 != (int(hv_FileExists==1))) { if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Images saved successfully!", "window", "center", "center", "black", HTuple(), HTuple()); } else if (0 != (int(hv_FileExists==0))) { if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Oops! Something went wrong! No Images saved!", "window", "center", "center", "black", HTuple(), HTuple()); } FlushBuffer(hv_ImageWindow); WaitSeconds(2); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); FlushBuffer(hv_ImageWindow); (*hv_CameraStarted) = 0; SignalEvent(hv_EventHandle); return; } // Generated stubs for parallel procedure calls. Wrapped in name // space to avoid name conflicts with actual procedure names namespace HDevExportCpp { // Parallel execution wrapper for FarmBotReadSerial(...) static void* _hcppthread_FarmBotReadSerial(void *hcthread) { // +++ define thread context for this procedure HDevThread* hcppthread = (HDevThread*) hcthread; try { // Input parameters const HTuple &cbhv_SerialHandle = hcppthread->GetInputCtrlParamTuple(0); const HTuple &cbhv_DataNum = hcppthread->GetInputCtrlParamTuple(1); const HTuple &cbhv_SerialQueueHandle = hcppthread->GetInputCtrlParamTuple(2); // Output parameters HTuple cbhv_SerialData; HTuple cbhv_ReadSerialFinished; HTuple cbhv_ThreadRunning; // Call FarmBotReadSerial FarmBotReadSerial(cbhv_SerialHandle, cbhv_DataNum, cbhv_SerialQueueHandle, &cbhv_SerialData, &cbhv_ReadSerialFinished, &cbhv_ThreadRunning); // Store output parameters in thread object hcppthread->StoreOutputCtrlParamTuple(0,cbhv_SerialData); hcppthread->StoreOutputCtrlParamTuple(1,cbhv_ReadSerialFinished); hcppthread->StoreOutputCtrlParamTuple(2,cbhv_ThreadRunning); // Reduce reference counter of thread object hcppthread->Exit(); delete hcppthread; } catch (HException& exc) { // No exceptions may be raised from stub in parallel case, // so we need to store this information prior to cleanup bool is_direct_call = hcppthread->IsDirectCall(); // Attempt to clean up in error case, too hcppthread->Exit(); delete hcppthread; // Propagate exception if called directly if (is_direct_call) throw exc; } return NULL; } // Parallel execution wrapper for Display_Buttons(...) static void* _hcppthread_Display_Buttons(void *hcthread) { // +++ define thread context for this procedure HDevThread* hcppthread = (HDevThread*) hcthread; try { // Input parameters const HTuple &cbhv_String = hcppthread->GetInputCtrlParamTuple(0); const HTuple &cbhv_Row = hcppthread->GetInputCtrlParamTuple(1); const HTuple &cbhv_Column = hcppthread->GetInputCtrlParamTuple(2); const HTuple &cbhv_Width = hcppthread->GetInputCtrlParamTuple(3); const HTuple &cbhv_Height = hcppthread->GetInputCtrlParamTuple(4); const HTuple &cbhv_ColorString = hcppthread->GetInputCtrlParamTuple(5); const HTuple &cbhv_ColorBackground = hcppthread->GetInputCtrlParamTuple(6); const HTuple &cbhv_ColorBackgroundActive = hcppthread->GetInputCtrlParamTuple(7); const HTuple &cbhv_WindowHandle = hcppthread->GetInputCtrlParamTuple(8); const HTuple &cbhv_ButtonQueueHandle = hcppthread->GetInputCtrlParamTuple(9); const HTuple &cbhv_EventHandle = hcppthread->GetInputCtrlParamTuple(10); // Call Display_Buttons Display_Buttons(cbhv_String, cbhv_Row, cbhv_Column, cbhv_Width, cbhv_Height, cbhv_ColorString, cbhv_ColorBackground, cbhv_ColorBackgroundActive, cbhv_WindowHandle, cbhv_ButtonQueueHandle, cbhv_EventHandle); // Reduce reference counter of thread object hcppthread->Exit(); delete hcppthread; } catch (HException& exc) { // No exceptions may be raised from stub in parallel case, // so we need to store this information prior to cleanup bool is_direct_call = hcppthread->IsDirectCall(); // Attempt to clean up in error case, too hcppthread->Exit(); delete hcppthread; // Propagate exception if called directly if (is_direct_call) throw exc; } return NULL; } // Parallel execution wrapper for StartCamera(...) static void* _hcppthread_StartCamera(void *hcthread) { // +++ define thread context for this procedure HDevThread* hcppthread = (HDevThread*) hcthread; try { // Input parameters const HTuple &cbhv_CameraHandle = hcppthread->GetInputCtrlParamTuple(0); const HTuple &cbhv_ImageWindow = hcppthread->GetInputCtrlParamTuple(1); const HTuple &cbhv_CameraQueueHandle = hcppthread->GetInputCtrlParamTuple(2); const HTuple &cbhv_FarmBotSerialHandle = hcppthread->GetInputCtrlParamTuple(3); const HTuple &cbhv_EventHandle = hcppthread->GetInputCtrlParamTuple(4); const HTuple &cbhv_OutputWindow = hcppthread->GetInputCtrlParamTuple(5); const HTuple &cbhv_TableLength = hcppthread->GetInputCtrlParamTuple(6); const HTuple &cbhv_TableWidth = hcppthread->GetInputCtrlParamTuple(7); // Output parameters HTuple cbhv_CameraStarted; // Call StartCamera StartCamera(cbhv_CameraHandle, cbhv_ImageWindow, cbhv_CameraQueueHandle, cbhv_FarmBotSerialHandle, cbhv_EventHandle, cbhv_OutputWindow, cbhv_TableLength, cbhv_TableWidth, &cbhv_CameraStarted); // Store output parameters in thread object hcppthread->StoreOutputCtrlParamTuple(0,cbhv_CameraStarted); // Reduce reference counter of thread object hcppthread->Exit(); delete hcppthread; } catch (HException& exc) { // No exceptions may be raised from stub in parallel case, // so we need to store this information prior to cleanup bool is_direct_call = hcppthread->IsDirectCall(); // Attempt to clean up in error case, too hcppthread->Exit(); delete hcppthread; // Propagate exception if called directly if (is_direct_call) throw exc; } return NULL; } } #ifndef NO_EXPORT_MAIN // Main procedure void action() { // Local iconic variables HObject ho_SmartGreenLogo, ho_SmartGreenLogoZoom; // Local control variables HTuple hv_ApproveConfig, hv_ActivateEndstopsX; HTuple hv_ActivateEndstopsY, hv_ActivateEndstopsZ, hv_InvertEndstopsX_NOtoNC; HTuple hv_HomingAxisX, hv_HomingAxisY, hv_HomingAxisZ; HTuple hv_CalibrateAxisX, hv_CalibrateAxisY, hv_CalibrateAxisZ; HTuple hv_DirName, hv_ConfigFileHandle, hv_StartHour, hv_IsEOF; HTuple hv_StartMinute, hv_TableLength, hv_TableWidth, hv_Information; HTuple hv_SerialPort, hv_SerialQueueHandle, hv_ButtonQueueHandle; HTuple hv_CameraQueueHandle, hv_WaitEvent, hv_LogoWindow; HTuple hv_OutputWindow, hv_ControlWindow, hv_ImageWindow; HTuple hv_UpperLogoWindowRow, hv_UpperLogoWindowColumn; HTuple hv_UpperLogoWindowWidth, hv_UpperLogoWindowHeight; HTuple hv_SerialData, hv_ReadSerialFinished, hv_ThreadRunning; HTuple hv_BotStatus, hv_EmergencyActive, hv_SerialLength; HTuple hv_ButtonPressed, hv_Homing, hv_CameraStarted, hv_CalibrateX; HTuple hv_Auto, hv_MeasurementDone, hv_CameraStarts, hv_XPosition; HTuple hv_YPosition, hv_DebugMode, hv_MSecond, hv_Second; HTuple hv_Minute, hv_Hour, hv_Day, hv_YDay, hv_Month, hv_Year; HTuple hv_OldDay, hv_FarmBotSerial, hv_Exception, hv_AcqHandle; HTuple hv_ThreadID, hv_ThreadID1, hv_condition, hv_MessageHandle; HTuple hv_HomingX, hv_HomingY, hv_ButtonMessageHandle; HTuple hv_ThreadID2; // +++ Threading variables HDevThread* hcppthread_handle; HDevThreadContext hcppthread_context; // <-signals begin of procedure dev_update_off(); //Some FarmBot control commands hv_ApproveConfig = "F22 P2 V1 Q0\r\n"; hv_ActivateEndstopsX = "F22 P25 V1\r\n"; hv_ActivateEndstopsY = "F22 P26 V1\r\n"; hv_ActivateEndstopsZ = "F22 P27 V1\r\n"; hv_InvertEndstopsX_NOtoNC = "F22 P75 V1\r\n"; hv_HomingAxisX = "F11\r\n"; hv_HomingAxisY = "F12\r\n"; hv_HomingAxisZ = "F13\r\n"; hv_CalibrateAxisX = "F14\r\n"; hv_CalibrateAxisY = "F15\r\n"; hv_CalibrateAxisZ = "F16\r\n"; //Loading config file with time point for daily automatic measurement (hour and minute) and table length and width GetCurrentDir(&hv_DirName); OpenFile(hv_DirName+"/Config.txt", "input", &hv_ConfigFileHandle); FreadString(hv_ConfigFileHandle, &hv_StartHour, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_StartMinute, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_TableLength, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_TableWidth, &hv_IsEOF); TupleNumber(hv_StartHour, &hv_StartHour); TupleNumber(hv_StartMinute, &hv_StartMinute); TupleNumber(hv_TableLength, &hv_TableLength); TupleNumber(hv_TableWidth, &hv_TableWidth); CloseFile(hv_ConfigFileHandle); //Getting the operating system the script is running on GetSystem("operating_system", &hv_Information); //Loading a small sponsor logo if (0 != (int(hv_Information!=HTuple("Windows NT")))) { hv_SerialPort = "/dev/ttyACM0"; ReadImage(&ho_SmartGreenLogo, "/home/pi/halcon/SMARTGREEN-02A-colour.png"); } else { hv_SerialPort = "COM4"; ReadImage(&ho_SmartGreenLogo, "example_directory/SMARTGREEN-02A-colour.png"); } //Creating message handles for information exchange between different threads CreateMessageQueue(&hv_SerialQueueHandle); CreateMessageQueue(&hv_ButtonQueueHandle); CreateMessageQueue(&hv_CameraQueueHandle); //Wait event to stop a specific thread CreateEvent("type", "sleep", &hv_WaitEvent); //Open some windows for displaying buttons and information SetWindowAttr("background_color","black"); OpenWindow(0,0,256,128,0,"visible","",&hv_LogoWindow); HDevWindowStack::Push(hv_LogoWindow); SetWindowAttr("background_color","black"); OpenWindow(180,0,256,100,0,"visible","",&hv_OutputWindow); HDevWindowStack::Push(hv_OutputWindow); SetWindowAttr("background_color","black"); OpenWindow(320,0,256,100,0,"visible","",&hv_ControlWindow); HDevWindowStack::Push(hv_ControlWindow); SetWindowAttr("background_color","black"); OpenWindow(0,257,512,400,0,"visible","",&hv_ImageWindow); HDevWindowStack::Push(hv_ImageWindow); //Sets the windows to "flush". You have to manually flush the window buffer to display information or images -> flicker free display method SetWindowParam(hv_ImageWindow, "flush", "false"); SetWindowParam(hv_OutputWindow, "flush", "false"); SetWindowParam(hv_ControlWindow, "flush", "false"); //Display sponsor logo GetWindowExtents(hv_LogoWindow, &hv_UpperLogoWindowRow, &hv_UpperLogoWindowColumn, &hv_UpperLogoWindowWidth, &hv_UpperLogoWindowHeight); ZoomImageSize(ho_SmartGreenLogo, &ho_SmartGreenLogoZoom, hv_UpperLogoWindowWidth, hv_UpperLogoWindowHeight, "constant"); HDevWindowStack::SetActive(hv_LogoWindow); if (HDevWindowStack::IsOpen()) DispObj(ho_SmartGreenLogoZoom, HDevWindowStack::GetActive()); //System variables hv_SerialData = HTuple(); hv_ReadSerialFinished = 0; hv_ThreadRunning = 0; hv_BotStatus = "Unknown"; hv_EmergencyActive = 0; hv_SerialLength = 0; hv_ButtonPressed = "None"; hv_Homing = "NotComplete"; hv_CameraStarted = 0; hv_CalibrateX = "dummy"; hv_Auto = 0; hv_MeasurementDone = 0; hv_CameraStarts = 0; hv_XPosition = "Unknown"; hv_YPosition = "Unknown"; hv_DebugMode = 0; GetSystemTime(&hv_MSecond, &hv_Second, &hv_Minute, &hv_Hour, &hv_Day, &hv_YDay, &hv_Month, &hv_Year); hv_OldDay = hv_Day; //Connectng to FarmBot motherboard (FarmDuino). If the connection fails the programm will close HDevWindowStack::SetActive(hv_ControlWindow); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Starting system...", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connecting to FarmDuino...", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); try { OpenSerial(hv_SerialPort, &hv_FarmBotSerial); SetSerialParam(hv_FarmBotSerial, 115200, 8, "none", "none", "unchanged", 1000, "unchanged"); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connection failed!", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); exit(0); } if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connection successful!", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); //Connecting to Intel RealSense. If the connection fails the programm will close. You have to check the serial number of the camera you are using and replace it in open_framegrabber if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connecting to RealSense...", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); try { OpenFramegrabber("RealSense", 0, 0, 0, 0, 0, 0, "default", -1, "default", "", "default", "default", "Intel_RealSense_D435_838212073829", -1, -1, &hv_AcqHandle); GrabImageStart(hv_AcqHandle, -1); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connection failed!", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); exit(0); } if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Connection successful!", "window", "center", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); WaitSeconds(2); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); //Approving the FarmDuino configuration. Otherwise the FarmBot wont move. WriteSerial(hv_FarmBotSerial, hv_ApproveConfig.TupleOrds()); WaitSeconds(2); ClearSerial(hv_FarmBotSerial, "input"); //Starting two parallel threads running besides the main programm. One Thread is reading the incoming information //from the FarmBot and the otherone displays some Buttons to control the system // Create a thread instance hcppthread_handle = new HDevThread(hcppthread_context, (void*)HDevExportCpp::_hcppthread_FarmBotReadSerial,3,3); // Set thread procedure call arguments hcppthread_handle->SetInputCtrlParamTuple(0,hv_FarmBotSerial); hcppthread_handle->SetInputCtrlParamTuple(1,200); hcppthread_handle->SetInputCtrlParamTuple(2,hv_SerialQueueHandle); hcppthread_handle->BindOutputCtrlParamTuple(0,0,&hv_SerialData); hcppthread_handle->BindOutputCtrlParamTuple(1,0,&hv_ReadSerialFinished); hcppthread_handle->BindOutputCtrlParamTuple(2,0,&hv_ThreadRunning); // Start proc line in thread hcppthread_handle->ParStart(&hv_ThreadID); // Create a thread instance hcppthread_handle = new HDevThread(hcppthread_context, (void*)HDevExportCpp::_hcppthread_Display_Buttons,11,0); // Set thread procedure call arguments hcppthread_handle->SetInputCtrlParamTuple(0,(((((((HTuple("Start").Append("Stop")).Append("Home X")).Append("Home Y")).Append("Exit")).Append("Set")).Append("Auto")).Append("Debug"))); hcppthread_handle->SetInputCtrlParamTuple(1,(((((((HTuple(0).Append(0)).Append(0)).Append(0)).Append(0)).Append(50)).Append(50)).Append(50))); hcppthread_handle->SetInputCtrlParamTuple(2,(((((((HTuple(0).Append(50)).Append(100)).Append(150)).Append(200)).Append(0)).Append(50)).Append(100))); hcppthread_handle->SetInputCtrlParamTuple(3,40); hcppthread_handle->SetInputCtrlParamTuple(4,40); hcppthread_handle->SetInputCtrlParamTuple(5,"black"); hcppthread_handle->SetInputCtrlParamTuple(6,"white"); hcppthread_handle->SetInputCtrlParamTuple(7,"gray"); hcppthread_handle->SetInputCtrlParamTuple(8,hv_OutputWindow); hcppthread_handle->SetInputCtrlParamTuple(9,hv_ButtonQueueHandle); hcppthread_handle->SetInputCtrlParamTuple(10,hv_WaitEvent); // Start proc line in thread hcppthread_handle->ParStart(&hv_ThreadID1); //Never ending main loop starts hv_condition = 1; while (0 != hv_condition) { if (0 != (int(hv_CameraStarted==0))) { //Try to read the information from the FarmDuino thread try { DequeueMessage(hv_SerialQueueHandle, "timeout", 0.5, &hv_MessageHandle); GetMessageTuple(hv_MessageHandle, "BotStatus", &hv_BotStatus); GetMessageTuple(hv_MessageHandle, "XPosition", &hv_XPosition); GetMessageTuple(hv_MessageHandle, "YPosition", &hv_YPosition); //get_message_tuple (MessageHandle, 'ZPosition', ZPosition) GetMessageTuple(hv_MessageHandle, "HomingX", &hv_HomingX); GetMessageTuple(hv_MessageHandle, "HomingY", &hv_HomingY); //get_message_tuple (MessageHandle, 'CalibrateX', CalibrateX) //get_message_tuple (MessageHandle, 'CalibrateY', CalibrateY) //get_message_tuple (MessageHandle, 'CalibrateZ', CalibrateZ) } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } } //Try to read the information from the Button thread whether a button was pushed try { DequeueMessage(hv_ButtonQueueHandle, "timeout", 0.1, &hv_ButtonMessageHandle); GetMessageTuple(hv_ButtonMessageHandle, "ButtonPressed", &hv_ButtonPressed); ClearMessage(hv_ButtonMessageHandle); } // catch (Exception) catch (HException &HDevExpDefaultException) { HDevExpDefaultException.ToHTuple(&hv_Exception); } //performs an action when the corresponding button is pressed if (0 != (int(hv_ButtonPressed==0))) { //starts a measurement hv_CameraStarted = 1; // Create a thread instance hcppthread_handle = new HDevThread(hcppthread_context, (void*)HDevExportCpp::_hcppthread_StartCamera,8,1); // Set thread procedure call arguments hcppthread_handle->SetInputCtrlParamTuple(0,hv_AcqHandle); hcppthread_handle->SetInputCtrlParamTuple(1,hv_ImageWindow); hcppthread_handle->SetInputCtrlParamTuple(2,hv_SerialQueueHandle); hcppthread_handle->SetInputCtrlParamTuple(3,hv_FarmBotSerial); hcppthread_handle->SetInputCtrlParamTuple(4,hv_WaitEvent); hcppthread_handle->SetInputCtrlParamTuple(5,hv_OutputWindow); hcppthread_handle->SetInputCtrlParamTuple(6,hv_TableLength); hcppthread_handle->SetInputCtrlParamTuple(7,hv_TableWidth); hcppthread_handle->BindOutputCtrlParamTuple(0,0,&hv_CameraStarted); // Start proc line in thread hcppthread_handle->ParStart(&hv_ThreadID2); WaitSeconds(0.5); hv_ButtonPressed = "None"; hv_CameraStarts += 1; } else if (0 != (int(hv_ButtonPressed==1))) { //activate or deactivate an emergency stop if (0 != (int(hv_EmergencyActive==0))) { WriteSerial(hv_FarmBotSerial, HTuple("E \r\n").TupleOrds()); hv_EmergencyActive = 1; WaitSeconds(0.5); hv_ButtonPressed = "None"; } else if (0 != (int(hv_EmergencyActive==1))) { WriteSerial(hv_FarmBotSerial, HTuple("F09 \r\n").TupleOrds()); WaitSeconds(1); WriteSerial(hv_FarmBotSerial, hv_ApproveConfig.TupleOrds()); WaitSeconds(1); hv_EmergencyActive = 0; hv_ButtonPressed = "None"; } WaitSeconds(0.5); } else if (0 != (int(hv_ButtonPressed==2))) { //homing the x axis WriteSerial(hv_FarmBotSerial, (hv_HomingAxisX+"\r\n").TupleOrds()); WaitSeconds(0.5); hv_ButtonPressed = "None"; } else if (0 != (int(hv_ButtonPressed==3))) { //homing the y axis WriteSerial(hv_FarmBotSerial, (hv_HomingAxisY+"\r\n").TupleOrds()); WaitSeconds(0.5); hv_ButtonPressed = "None"; } else if (0 != (int(hv_ButtonPressed==4))) { //close the application HDevWindowStack::SetActive(hv_ControlWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),"Closing the application!", "window", "bottom", "center", "black", HTuple(), HTuple()); FlushBuffer(hv_ControlWindow); hv_ButtonPressed = "None"; WaitSeconds(3); exit(0); } else if (0 != (int(hv_ButtonPressed==5))) { //reload the config file OpenFile(hv_DirName+"/Config.txt", "input", &hv_ConfigFileHandle); FreadString(hv_ConfigFileHandle, &hv_StartHour, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_StartMinute, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_TableLength, &hv_IsEOF); FreadString(hv_ConfigFileHandle, &hv_TableWidth, &hv_IsEOF); TupleNumber(hv_StartHour, &hv_StartHour); TupleNumber(hv_StartMinute, &hv_StartMinute); TupleNumber(hv_TableLength, &hv_TableLength); TupleNumber(hv_TableWidth, &hv_TableWidth); CloseFile(hv_ConfigFileHandle); WaitSeconds(0.5); hv_ButtonPressed = "None"; } else if (0 != (int(hv_ButtonPressed==6))) { //activate or deactivate the automatic measurement at given time if (0 != (int(hv_Auto==0))) { hv_Auto = 1; } else if (0 != (int(hv_Auto==1))) { hv_Auto = 0; hv_MeasurementDone = 0; } hv_ButtonPressed = "None"; WaitSeconds(0.5); } else if (0 != (int(hv_ButtonPressed==7))) { //activate or deactivate the debugging message if (0 != (int(hv_DebugMode==0))) { hv_DebugMode = 1; } else if (0 != (int(hv_DebugMode==1))) { hv_DebugMode = 0; HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); FlushBuffer(hv_ImageWindow); } WaitSeconds(0.5); hv_ButtonPressed = "None"; } //if no measurement was done at the day and the automatic mode is active a measurement will start at given time if (0 != (HTuple(HTuple(HTuple(HTuple(int(hv_CameraStarted==0)).TupleAnd(int(hv_Auto==1))).TupleAnd(int(hv_Hour==hv_StartHour))).TupleAnd(int(hv_Minute==hv_StartMinute))).TupleAnd(int(hv_MeasurementDone==0)))) { hv_MeasurementDone = 1; hv_CameraStarted = 1; // Create a thread instance hcppthread_handle = new HDevThread(hcppthread_context, (void*)HDevExportCpp::_hcppthread_StartCamera,8,1); // Set thread procedure call arguments hcppthread_handle->SetInputCtrlParamTuple(0,hv_AcqHandle); hcppthread_handle->SetInputCtrlParamTuple(1,hv_ImageWindow); hcppthread_handle->SetInputCtrlParamTuple(2,hv_SerialQueueHandle); hcppthread_handle->SetInputCtrlParamTuple(3,hv_FarmBotSerial); hcppthread_handle->SetInputCtrlParamTuple(4,hv_WaitEvent); hcppthread_handle->SetInputCtrlParamTuple(5,hv_OutputWindow); hcppthread_handle->SetInputCtrlParamTuple(6,hv_TableLength); hcppthread_handle->SetInputCtrlParamTuple(7,hv_TableWidth); hcppthread_handle->BindOutputCtrlParamTuple(0,0,&hv_CameraStarted); // Start proc line in thread hcppthread_handle->ParStart(&hv_ThreadID2); hv_CameraStarts += 1; } GetSystemTime(&hv_MSecond, &hv_Second, &hv_Minute, &hv_Hour, &hv_Day, &hv_YDay, &hv_Month, &hv_Year); //if an automatic measurement was done the variable will be reset, so a new measurement can be done on the next day if (0 != (int(hv_Day!=hv_OldDay))) { hv_OldDay = hv_Day; hv_MeasurementDone = 0; } //Displays some system variables, if the debug mode is avtive if (0 != (HTuple(int(hv_DebugMode==1)).TupleAnd(int(hv_CameraStarted==0)))) { HDevWindowStack::SetActive(hv_ImageWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); WriteString(hv_ImageWindow, (((("Time: "+hv_Hour)+":")+hv_Minute)+":")+hv_Second); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, (((("Date: "+hv_Day)+".")+hv_Month)+".")+hv_Year); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Table length: "+hv_TableLength); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Table width: "+hv_TableWidth); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, (("Auto start: "+hv_StartHour)+":")+hv_StartMinute); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Auto mode: "+hv_Auto); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Measurement done: "+hv_MeasurementDone); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Camera starts (since last reset): "+hv_CameraStarts); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "Current working directory: "+hv_DirName); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "FarmBot x-position: "+hv_XPosition); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "FarmBot y-position: "+hv_YPosition); NewLine(hv_ImageWindow); WriteString(hv_ImageWindow, "FarmBot status: "+hv_BotStatus); NewLine(hv_ImageWindow); FlushBuffer(hv_ImageWindow); } //Displays some variables, if no measurement is currently running if (0 != (int(hv_CameraStarted==0))) { HDevWindowStack::SetActive(hv_ControlWindow); if (HDevWindowStack::IsOpen()) ClearWindow(HDevWindowStack::GetActive()); if (0 != (int(hv_EmergencyActive==1))) { if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive()," Emergency stop activated!", "window", "center", "center", "black", HTuple(), HTuple()); } else { if (0 != (int(hv_Auto==1))) { if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(((hv_BotStatus+" Auto: ")+hv_StartHour)+":")+hv_StartMinute, "window", "top", "left", "black", HTuple(), HTuple()); } else { if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),hv_BotStatus, "window", "top", "left", "black", HTuple(), HTuple()); } if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(((hv_Hour+":")+hv_Minute)+":")+hv_Second, "window", "center", "left", "black", HTuple(), HTuple()); if (HDevWindowStack::IsOpen()) DispText(HDevWindowStack::GetActive(),(("X: "+hv_XPosition)+" Y: ")+hv_YPosition, "window", "bottom", "left", "black", HTuple(), HTuple()); } FlushBuffer(hv_ControlWindow); SignalEvent(hv_WaitEvent); } } } #ifndef NO_EXPORT_APP_MAIN #ifdef __APPLE__ // On OS X systems, we must have a CFRunLoop running on the main thread in // order for the HALCON graphics operators to work correctly, and run the // action function in a separate thread. A CFRunLoopTimer is used to make sure // the action function is not called before the CFRunLoop is running. // Note that starting with macOS 10.12, the run loop may be stopped when a // window is closed, so we need to put the call to CFRunLoopRun() into a loop // of its own. static HMutex* sStartMutex; static H_pthread_t sActionThread; static bool sTerminate = false; static void timer_callback(CFRunLoopTimerRef timer, void *info) { sStartMutex->UnlockMutex(); } static Herror apple_action(void **parameters) { // Wait until the timer has fired to start processing. sStartMutex->LockMutex(); sStartMutex->UnlockMutex(); try { action(); } catch (HException &exception) { fprintf(stderr," Error #%u in %s: %s\n", exception.ErrorCode(), exception.ProcName().TextA(), exception.ErrorMessage().TextA()); } // Tell the main thread to terminate itself. sStartMutex->LockMutex(); sTerminate = true; sStartMutex->UnlockMutex(); CFRunLoopStop(CFRunLoopGetMain()); return H_MSG_OK; } static int apple_main(int argc, char *argv[]) { Herror error; CFRunLoopTimerRef Timer; CFRunLoopTimerContext TimerContext = { 0, 0, 0, 0, 0 }; sStartMutex = new HMutex("type","sleep"); sStartMutex->LockMutex(); error = HpThreadHandleAlloc(&sActionThread); if (H_MSG_OK != error) { fprintf(stderr,"HpThreadHandleAlloc failed: %d\n", error); exit(1); } error = HpThreadCreate(sActionThread,0,apple_action); if (H_MSG_OK != error) { fprintf(stderr,"HpThreadCreate failed: %d\n", error); exit(1); } Timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(),0,0,0, timer_callback,&TimerContext); if (!Timer) { fprintf(stderr,"CFRunLoopTimerCreate failed\n"); exit(1); } CFRunLoopAddTimer(CFRunLoopGetCurrent(),Timer,kCFRunLoopCommonModes); for (;;) { bool terminate; CFRunLoopRun(); sStartMutex->LockMutex(); terminate = sTerminate; sStartMutex->UnlockMutex(); if (terminate) break; } CFRunLoopRemoveTimer(CFRunLoopGetCurrent(),Timer,kCFRunLoopCommonModes); CFRelease(Timer); error = HpThreadHandleFree(sActionThread); if (H_MSG_OK != error) { fprintf(stderr,"HpThreadHandleFree failed: %d\n", error); exit(1); } delete sStartMutex; return 0; } #endif int main(int argc, char *argv[]) { int ret = 0; try { #if defined(_WIN32) SetSystem("use_window_thread", "true"); #endif // file was stored with local-8-bit encoding // -> set the interface encoding accordingly SetHcppInterfaceStringEncodingIsUtf8(false); // Default settings used in HDevelop (can be omitted) SetSystem("width", 512); SetSystem("height", 512); #ifndef __APPLE__ action(); #else ret = apple_main(argc,argv); #endif } catch (HException &exception) { fprintf(stderr," Error #%u in %s: %s\n", exception.ErrorCode(), exception.ProcName().TextA(), exception.ErrorMessage().TextA()); ret = 1; } return ret; } #endif #endif