diff --git a/.gitignore b/.gitignore index d7f14ff..09b2c70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ +_archive/ *.py[cod] *$py.class diff --git a/ai/dataset/images/val/스크린샷 2026-04-27 175231.png b/ai/dataset/images/train/스크린샷 2026-04-27 175231.png similarity index 100% rename from ai/dataset/images/val/스크린샷 2026-04-27 175231.png rename to ai/dataset/images/train/스크린샷 2026-04-27 175231.png diff --git a/ai/dataset/images/train/스크린샷 2026-05-07 095340.png b/ai/dataset/images/train/스크린샷 2026-05-07 095340.png new file mode 100644 index 0000000..0912958 Binary files /dev/null and b/ai/dataset/images/train/스크린샷 2026-05-07 095340.png differ diff --git a/ai/dataset/images/train/스크린샷 2026-05-07 100837.png b/ai/dataset/images/val/스크린샷 2026-05-07 100837.png similarity index 100% rename from ai/dataset/images/train/스크린샷 2026-05-07 100837.png rename to ai/dataset/images/val/스크린샷 2026-05-07 100837.png diff --git a/ai/dataset/labels/train.cache b/ai/dataset/labels/train.cache index 31eb079..6cfc421 100644 Binary files a/ai/dataset/labels/train.cache and b/ai/dataset/labels/train.cache differ diff --git a/ai/dataset/labels/train/스크린샷 2026-04-27 175231.txt b/ai/dataset/labels/train/스크린샷 2026-04-27 175231.txt new file mode 100644 index 0000000..5562e43 --- /dev/null +++ b/ai/dataset/labels/train/스크린샷 2026-04-27 175231.txt @@ -0,0 +1 @@ +2 0.264192 0.312749 0.362445 0.298805 \ No newline at end of file diff --git a/ai/dataset/labels/train/스크린샷 2026-05-07 095340.txt b/ai/dataset/labels/train/스크린샷 2026-05-07 095340.txt new file mode 100644 index 0000000..c57ef78 --- /dev/null +++ b/ai/dataset/labels/train/스크린샷 2026-05-07 095340.txt @@ -0,0 +1,2 @@ +0 0.356148 0.438479 0.378190 0.290828 +0 0.814385 0.224832 0.306265 0.252796 \ No newline at end of file diff --git a/ai/dataset/labels/val.cache b/ai/dataset/labels/val.cache index 9650645..89b6a1e 100644 Binary files a/ai/dataset/labels/val.cache and b/ai/dataset/labels/val.cache differ diff --git a/ai/dataset/labels/val/스크린샷 2026-04-27 175231.txt b/ai/dataset/labels/val/스크린샷 2026-04-27 175231.txt deleted file mode 100644 index 8ac7a0c..0000000 --- a/ai/dataset/labels/val/스크린샷 2026-04-27 175231.txt +++ /dev/null @@ -1,2 +0,0 @@ -0 0.227074 0.195219 0.401747 0.215139 -1 0.853712 0.252988 0.257642 0.330677 \ No newline at end of file diff --git a/ai/dataset/labels/train/스크린샷 2026-05-07 100837.txt b/ai/dataset/labels/val/스크린샷 2026-05-07 100837.txt similarity index 100% rename from ai/dataset/labels/train/스크린샷 2026-05-07 100837.txt rename to ai/dataset/labels/val/스크린샷 2026-05-07 100837.txt diff --git a/ai/runs/train/results.csv b/ai/runs/train/results.csv index be3da04..1756cce 100644 --- a/ai/runs/train/results.csv +++ b/ai/runs/train/results.csv @@ -1,101 +1,4 @@ epoch,time,train/box_loss,train/cls_loss,train/dfl_loss,metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),val/box_loss,val/cls_loss,val/dfl_loss,lr/pg0,lr/pg1,lr/pg2 -1,0.744601,2.4784,4.2576,2.37292,0.00581,0.5,0.00599,0.00178,2.08106,4.5092,2.3941,0,0,0 -2,1.17594,3.54636,4.89413,2.76008,0.00588,0.5,0.00622,0.00124,2.10159,4.51392,2.40401,1.23763e-05,1.23763e-05,1.23763e-05 -3,1.4434,3.2212,4.47996,3.91873,0.01514,1,0.01568,0.0022,2.24221,4.37976,2.4629,2.4505e-05,2.4505e-05,2.4505e-05 -4,1.81097,3.23832,4.18544,3.38558,0.01459,1,0.01534,0.0037,2.32948,4.47892,2.46147,3.63862e-05,3.63862e-05,3.63862e-05 -5,2.17724,2.76973,4.43589,2.74067,0.00556,0.5,0.00711,0.00195,2.33679,4.46132,2.45867,4.802e-05,4.802e-05,4.802e-05 -6,2.47758,2.93916,4.25681,2.81574,0.00562,0.5,0.00711,0.00197,2.23356,4.4439,2.55513,5.94062e-05,5.94062e-05,5.94062e-05 -7,2.77856,2.95798,3.80506,2.41629,0.01433,1,0.0167,0.00167,2.13047,4.47684,2.46296,7.0545e-05,7.0545e-05,7.0545e-05 -8,3.08538,2.97272,3.8706,2.66583,0.00847,0.5,0.00939,0.00094,1.93983,4.4964,2.44096,8.14362e-05,8.14362e-05,8.14362e-05 -9,3.38628,1.96121,4.27233,2.04591,0,0,0,0,1.67434,4.64554,2.3051,9.208e-05,9.208e-05,9.208e-05 -10,3.68733,2.62766,3.67184,2.50334,0,0,0,0,1.44683,4.71852,2.19216,0.000102476,0.000102476,0.000102476 -11,3.97817,2.60241,4.1424,2.70991,0.00556,0.5,0.01605,0.0016,1.42181,4.70011,2.16441,0.000112625,0.000112625,0.000112625 -12,4.28608,3.28551,4.52089,2.67545,0.00588,0.5,0.01345,0.00269,1.52292,4.67835,2.13383,0.000122526,0.000122526,0.000122526 -13,4.58546,2.2033,4.2669,2.49512,0.00562,0.5,0.01157,0.00231,1.68387,4.61194,2.17338,0.00013218,0.00013218,0.00013218 -14,4.88485,2.87394,4.40826,2.70916,0.00568,0.5,0.00975,0.00195,1.89075,4.58147,2.30027,0.000141586,0.000141586,0.000141586 -15,5.19968,2.71982,4.27341,2.72914,0,0,0,0,1.96866,4.44855,2.33624,0.000150745,0.000150745,0.000150745 -16,5.49215,1.68605,4.24646,1.87531,0,0,0,0,2.00553,4.48707,2.29285,0.000159656,0.000159656,0.000159656 -17,5.79325,2.00138,3.94773,2.26758,0,0,0,0,2.09648,4.63246,2.4007,0.00016832,0.00016832,0.00016832 -18,6.09155,3.05671,5.53908,2.66707,0,0,0,0,2.09648,4.63246,2.4007,0.000176736,0.000176736,0.000176736 -19,6.39052,2.20901,3.52372,2.04393,0.00806,0.5,0.02369,0.00237,2.20865,4.54601,2.56285,0.000184905,0.000184905,0.000184905 -20,6.66892,2.4483,3.70065,2.23467,0.00806,0.5,0.02369,0.00237,2.20865,4.54601,2.56285,0.000192826,0.000192826,0.000192826 -21,6.9332,2.19448,3.86943,2.10117,0.00463,0.5,0.03317,0.00663,2.32559,4.48125,2.62548,0.0002005,0.0002005,0.0002005 -22,7.30519,2.9479,4.3599,2.76934,0.00463,0.5,0.03317,0.00663,2.32559,4.48125,2.62548,0.000207926,0.000207926,0.000207926 -23,7.68986,1.39714,3.40426,1.6151,0.01578,1,0.08837,0.01146,2.44041,4.6826,2.72339,0.000215105,0.000215105,0.000215105 -24,7.99967,2.46841,3.48691,2.13383,0.01578,1,0.08837,0.01146,2.44041,4.6826,2.72339,0.000222036,0.000222036,0.000222036 -25,8.31396,2.56835,4.10281,2.20972,0.00481,0.5,0.01777,0.00355,2.61255,4.61058,2.86581,0.00022872,0.00022872,0.00022872 -26,8.55453,1.65391,3.70866,1.88456,0.00481,0.5,0.01777,0.00355,2.61255,4.61058,2.86581,0.000235156,0.000235156,0.000235156 -27,8.80235,2.00119,3.45089,1.89926,0.01604,1,0.05212,0.00521,2.74287,4.6437,2.91424,0.000241345,0.000241345,0.000241345 -28,9.04528,1.89956,3.68555,2.16165,0.01604,1,0.05212,0.00521,2.74287,4.6437,2.91424,0.000247286,0.000247286,0.000247286 -29,9.29273,1.42472,2.92227,1.75125,0.01111,0.5,0.12437,0.01244,3.06977,4.5736,3.06093,0.00025298,0.00025298,0.00025298 -30,9.60359,1.35767,2.62078,1.55004,0.01111,0.5,0.12437,0.01244,3.06977,4.5736,3.06093,0.000258426,0.000258426,0.000258426 -31,9.94422,2.37035,4.66417,2.27559,0.01316,0.5,0.16583,0.01658,3.36893,4.24859,3.23425,0.000263625,0.000263625,0.000263625 -32,10.2594,1.61322,2.86179,1.53237,0.01316,0.5,0.16583,0.01658,3.36893,4.24859,3.23425,0.000268576,0.000268576,0.000268576 -33,10.6412,1.85856,3.15064,1.88045,0.02083,0.5,0.16583,0.01658,3.44544,4.47775,3.34489,0.00027328,0.00027328,0.00027328 -34,10.9661,1.62452,2.88937,1.72429,0.02083,0.5,0.16583,0.01658,3.44544,4.47775,3.34489,0.000277736,0.000277736,0.000277736 -35,11.2888,1.21431,3.51628,1.62717,0,0,0,0,3.47481,4.57555,3.33539,0.000281945,0.000281945,0.000281945 -36,11.5318,1.39337,2.94668,1.47319,0,0,0,0,3.47481,4.57555,3.33539,0.000285906,0.000285906,0.000285906 -37,11.7887,2.14196,3.0238,1.80533,0,0,0,0,3.7452,4.84862,3.44227,0.00028962,0.00028962,0.00028962 -38,12.0408,1.81846,2.33607,1.79617,0,0,0,0,3.7452,4.84862,3.44227,0.000293086,0.000293086,0.000293086 -39,12.2951,1.17175,1.92086,1.3652,0,0,0,0,4.06912,4.91101,3.62994,0.000296305,0.000296305,0.000296305 -40,12.5315,1.62593,2.48208,1.50529,0,0,0,0,4.06912,4.91101,3.62994,0.000299276,0.000299276,0.000299276 -41,12.8727,2.11165,2.67321,1.7814,0,0,0,0,4.39582,4.54093,3.81935,0.000302,0.000302,0.000302 -42,13.1287,1.58216,2.68408,1.63978,0,0,0,0,4.39582,4.54093,3.81935,0.000304476,0.000304476,0.000304476 -43,13.4027,1.4236,2.75829,1.527,0.01351,0.5,0.02163,0.00216,4.3829,4.34724,3.73475,0.000306705,0.000306705,0.000306705 -44,13.6611,1.58086,3.04931,1.97095,0.01351,0.5,0.02163,0.00216,4.3829,4.34724,3.73475,0.000308686,0.000308686,0.000308686 -45,13.94,1.70552,2.1028,1.7847,0.0119,0.5,0.03109,0.00311,4.02893,4.68709,3.46274,0.00031042,0.00031042,0.00031042 -46,14.2063,1.17287,2.30212,1.21687,0.0119,0.5,0.03109,0.00311,4.02893,4.68709,3.46274,0.000311906,0.000311906,0.000311906 -47,14.4787,0.75953,1.79745,0.99198,0.01111,0.5,0.04523,0.00452,4.16401,4.4644,3.55846,0.000313145,0.000313145,0.000313145 -48,14.7356,2.12559,4.16293,2.21951,0.01111,0.5,0.04523,0.00452,4.16401,4.4644,3.55846,0.000314136,0.000314136,0.000314136 -49,15.0753,0.92121,2.05049,1.10199,0.00962,0.5,0.04146,0.00829,3.90365,4.64333,3.48688,0.00031488,0.00031488,0.00031488 -50,15.3345,1.19831,1.99876,1.28056,0.00962,0.5,0.04146,0.00829,3.90365,4.64333,3.48688,0.000315376,0.000315376,0.000315376 -51,15.6062,2.135,2.40625,1.81904,0.01042,0.5,0.05528,0.0229,3.18088,5.77238,3.2612,0.000315625,0.000315625,0.000315625 -52,15.9429,1.72253,3.24847,1.71661,0.01042,0.5,0.05528,0.0229,3.18088,5.77238,3.2612,0.000315626,0.000315626,0.000315626 -53,16.2756,0.98827,2.52014,1.26921,0.01042,0.5,0.05528,0.0229,3.18088,5.77238,3.2612,0.00031538,0.00031538,0.00031538 -54,16.6349,1.8328,2.53368,1.66716,0.01316,0.5,0.12437,0.01244,3.8633,4.65938,3.23911,0.000314886,0.000314886,0.000314886 -55,16.8944,1.1818,1.78296,1.15008,0.01316,0.5,0.12437,0.01244,3.8633,4.65938,3.23911,0.000314145,0.000314145,0.000314145 -56,17.2351,1.26713,2.38825,1.40594,0.01316,0.5,0.12437,0.01244,3.8633,4.65938,3.23911,0.000313156,0.000313156,0.000313156 -57,17.5174,1.32827,1.94378,1.44297,0.01562,0.5,0.02073,0.00207,3.86529,4.77191,3.13775,0.00031192,0.00031192,0.00031192 -58,17.7998,0.93866,1.97455,1.19817,0.01562,0.5,0.02073,0.00207,3.86529,4.77191,3.13775,0.000310436,0.000310436,0.000310436 -59,18.0565,1.34164,4.69334,1.52493,0.01562,0.5,0.02073,0.00207,3.86529,4.77191,3.13775,0.000308705,0.000308705,0.000308705 -60,18.3262,1.70827,3.61104,1.67179,0,0,0,0,3.93656,4.99738,3.11248,0.000306726,0.000306726,0.000306726 -61,18.6133,0.82569,1.96032,1.1105,0,0,0,0,3.93656,4.99738,3.11248,0.0003045,0.0003045,0.0003045 -62,18.8669,1.33918,2.16787,1.37237,0,0,0,0,3.93656,4.99738,3.11248,0.000302026,0.000302026,0.000302026 -63,19.133,0.9404,2.00419,1.09295,0,0,0,0,3.54929,5.50927,3.04358,0.000299305,0.000299305,0.000299305 -64,19.4517,0.83957,1.71834,1.04536,0,0,0,0,3.54929,5.50927,3.04358,0.000296336,0.000296336,0.000296336 -65,19.7046,1.05975,1.98836,1.25573,0,0,0,0,3.54929,5.50927,3.04358,0.00029312,0.00029312,0.00029312 -66,19.966,0.79495,1.71511,1.07986,0.01111,0.5,0.02073,0.00415,3.48677,6.07562,2.94905,0.000289656,0.000289656,0.000289656 -67,20.2315,0.89674,1.76647,1.12805,0.01111,0.5,0.02073,0.00415,3.48677,6.07562,2.94905,0.000285945,0.000285945,0.000285945 -68,20.4951,1.27186,2.05522,1.24621,0.01111,0.5,0.02073,0.00415,3.48677,6.07562,2.94905,0.000281986,0.000281986,0.000281986 -69,20.763,0.72455,1.86789,1.0569,0,0,0,0,4.16796,5.98914,3.4865,0.00027778,0.00027778,0.00027778 -70,21.0206,0.94838,1.5672,1.15049,0,0,0,0,4.16796,5.98914,3.4865,0.000273326,0.000273326,0.000273326 -71,21.2798,1.62956,2.8345,1.75908,0,0,0,0,4.16796,5.98914,3.4865,0.000268625,0.000268625,0.000268625 -72,21.6185,0.82159,1.62683,0.94171,0,0,0,0,4.70051,5.39397,3.87655,0.000263676,0.000263676,0.000263676 -73,21.8885,0.84998,2.02611,1.30797,0,0,0,0,4.70051,5.39397,3.87655,0.00025848,0.00025848,0.00025848 -74,22.1454,0.85087,1.42472,1.15593,0,0,0,0,4.70051,5.39397,3.87655,0.000253036,0.000253036,0.000253036 -75,22.4095,1.1786,1.83474,1.21092,0,0,0,0,4.86689,6.24371,3.95955,0.000247345,0.000247345,0.000247345 -76,22.6694,0.78564,1.66187,0.97539,0,0,0,0,4.86689,6.24371,3.95955,0.000241406,0.000241406,0.000241406 -77,22.9286,0.79013,1.96926,1.26206,0,0,0,0,4.86689,6.24371,3.95955,0.00023522,0.00023522,0.00023522 -78,23.1943,0.8152,1.59465,1.07906,0,0,0,0,4.81344,5.97521,4.01022,0.000228786,0.000228786,0.000228786 -79,23.4546,0.61829,1.29337,0.98868,0,0,0,0,4.81344,5.97521,4.01022,0.000222105,0.000222105,0.000222105 -80,23.7685,0.95007,1.79471,1.1416,0,0,0,0,4.81344,5.97521,4.01022,0.000215176,0.000215176,0.000215176 -81,24.1034,0.93272,2.22583,1.20421,0.00617,0.5,0.00905,0.0009,4.60774,5.39816,3.81369,0.000208,0.000208,0.000208 -82,24.3629,0.63241,1.21512,1.08097,0.00617,0.5,0.00905,0.0009,4.60774,5.39816,3.81369,0.000200576,0.000200576,0.000200576 -83,24.6222,1.39649,2.39204,1.52958,0.00617,0.5,0.00905,0.0009,4.60774,5.39816,3.81369,0.000192905,0.000192905,0.000192905 -84,24.9101,0.63485,1.24139,0.96726,0.00641,0.5,0.01913,0.00459,4.14874,5.15654,3.60735,0.000184986,0.000184986,0.000184986 -85,25.1903,0.88995,1.76109,1.04753,0.00641,0.5,0.01913,0.00459,4.14874,5.15654,3.60735,0.00017682,0.00017682,0.00017682 -86,25.4597,1.16455,2.35159,1.15176,0.00641,0.5,0.01913,0.00459,4.14874,5.15654,3.60735,0.000168406,0.000168406,0.000168406 -87,25.761,0.86574,1.60094,1.1271,0.00641,0.5,0.01913,0.00459,4.14874,5.15654,3.60735,0.000159745,0.000159745,0.000159745 -88,26.1232,0.57931,1.17361,0.95885,0.00704,0.5,0.01658,0.00332,3.66565,5.88071,3.25055,0.000150836,0.000150836,0.000150836 -89,26.4022,0.88857,1.97356,1.07497,0.00704,0.5,0.01658,0.00332,3.66565,5.88071,3.25055,0.00014168,0.00014168,0.00014168 -90,26.7071,0.71015,1.22162,1.07789,0.00704,0.5,0.01658,0.00332,3.66565,5.88071,3.25055,0.000132276,0.000132276,0.000132276 -91,27.0615,0.80611,2.15097,0.95926,0.00704,0.5,0.01658,0.00332,3.66565,5.88071,3.25055,0.000122625,0.000122625,0.000122625 -92,27.3798,0.75395,1.9817,1.18844,0.00758,0.5,0.01463,0.00368,3.64756,5.57493,3.04703,0.000112726,0.000112726,0.000112726 -93,27.6734,0.52999,2.16673,0.94249,0.00758,0.5,0.01463,0.00368,3.64756,5.57493,3.04703,0.00010258,0.00010258,0.00010258 -94,27.9692,0.29519,1.34975,0.94508,0.00758,0.5,0.01463,0.00368,3.64756,5.57493,3.04703,9.21862e-05,9.21862e-05,9.21862e-05 -95,28.2525,0.533,1.80971,0.96369,0.00758,0.5,0.01463,0.00368,3.64756,5.57493,3.04703,8.1545e-05,8.1545e-05,8.1545e-05 -96,28.5584,0.37619,1.67301,0.82287,0.00714,0.5,0.01309,0.00361,3.45585,5.23022,2.909,7.06563e-05,7.06563e-05,7.06563e-05 -97,28.8522,0.58993,1.73032,0.93823,0.00714,0.5,0.01309,0.00361,3.45585,5.23022,2.909,5.952e-05,5.952e-05,5.952e-05 -98,29.2014,1.19689,2.95317,1.27242,0.00714,0.5,0.01309,0.00361,3.45585,5.23022,2.909,4.81363e-05,4.81363e-05,4.81363e-05 -99,29.5194,0.44641,1.50801,0.93925,0.00714,0.5,0.01309,0.00361,3.45585,5.23022,2.909,3.6505e-05,3.6505e-05,3.6505e-05 -100,29.8906,0.52149,1.81476,0.89577,0.0061,0.5,0.01157,0.00231,3.45621,5.51821,2.93443,2.46263e-05,2.46263e-05,2.46263e-05 +1,2.04808,2.95921,4.23808,2.74536,0.01389,1,0.03554,0.00853,3.23067,4.67253,4.0769,0,0,0 +2,2.83517,3.25155,4.15149,2.98379,0.01408,1,0.02369,0.00948,3.18473,5.26193,4.00145,1.23763e-05,1.23763e-05,1.23763e-05 +3,3.18815,2.59377,4.62523,2.6647,0.01351,1,0.02689,0.01173,2.96806,5.66861,3.96969,2.4505e-05,2.4505e-05,2.4505e-05 diff --git a/ai/runs/train/weights/best.pt b/ai/runs/train/weights/best.pt index 2c9e471..bacf555 100644 Binary files a/ai/runs/train/weights/best.pt and b/ai/runs/train/weights/best.pt differ diff --git a/ai/runs/train/weights/last.pt b/ai/runs/train/weights/last.pt index 8d5f0b8..776d843 100644 Binary files a/ai/runs/train/weights/last.pt and b/ai/runs/train/weights/last.pt differ diff --git a/check_build.py b/check_build.py deleted file mode 100644 index 6fe1064..0000000 --- a/check_build.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -dist_path = "E:/ANT/dist/reflector_inspector.exe" -if os.path.exists(dist_path): - size_mb = os.path.getsize(dist_path) / (1024 * 1024) - print(f"빌드 성공: {dist_path}") - print(f"파일 크기: {size_mb:.1f} MB") -else: - print("빌드 실패: exe 파일이 없음") diff --git a/config.json b/config.json index 9fc4f23..39bb010 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "cognex": { - "ip": "169.254.0.1", + "ip": "192.168.1.2", "port": 23 }, "basler": { @@ -12,7 +12,7 @@ "speed_cms": 30.0 }, "db": { - "server": "Wizis.iptime.org,20220", + "server": "tcp:Wizis.iptime.org,20220", "database": "MES_ANT", "username": "AIUser", "password": "AIUser" @@ -23,5 +23,25 @@ }, "ai": { "model_path": "ai/models/best.pt" + }, + "mes": { + "selected_article_ids": [ + "C1A1100001", + "C1A1100002", + "C1A1100003", + "C1A1200001", + "C1A1200002", + "C1A1200003", + "C1A1200005", + "D1A1100008", + "D1A1100009", + "D1A1100010", + "D1A1100011", + "D1A1200006", + "D1A1200007", + "D1A1200008", + "D1A1200009", + "D1I1100019" + ] } } \ No newline at end of file diff --git a/db/__init__.py b/db/__init__.py index fbb8f9c..45d7fd0 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -1,2 +1 @@ -# db 패키지 — MySQLClient 노출 -from .mysql_client import MySQLClient +# db 패키지 diff --git a/db/mysql_client.py b/db/mysql_client.py deleted file mode 100644 index 733c087..0000000 --- a/db/mysql_client.py +++ /dev/null @@ -1,36 +0,0 @@ -# DB 클라이언트 — MySQL 연결 및 리플렉터 데이터 조회 -import pymysql - - -class MySQLClient: - def __init__(self): - self._conn = None - - def connect(self, host: str, port: int, user: str, password: str, database: str): - self._conn = pymysql.connect( - host=host, port=port, user=user, - password=password, database=database, - charset="utf8mb4", autocommit=True, - ) - print(f"[DB] 연결 성공: {host}:{port}/{database}") - - def disconnect(self): - if self._conn: - self._conn.close() - self._conn = None - print("[DB] 연결 종료") - - def is_connected(self) -> bool: - return self._conn is not None - - def get_reflector_list(self) -> list[dict]: - """리플렉터 목록 조회 — 반환: [{"id": ..., "name": ..., "type": ...}, ...]""" - pass - - def save_reflector(self, name: str, type_lr: str, pattern_data: bytes): - """리플렉터 등록/갱신 — 미구현""" - pass - - def save_inspection_result(self, product_id: int, result: str, defects: list): - """검사 결과 저장 — 미구현""" - pass diff --git a/db/sql_client.py b/db/sql_client.py index ed53413..bc51ce7 100644 --- a/db/sql_client.py +++ b/db/sql_client.py @@ -1,5 +1,27 @@ import pyodbc +# 선호 순서 — 앞쪽일수록 우선. 설치 환경마다 다를 수 있어 자동 탐지 후 선택. +_PREFERRED_DRIVERS = ( + "ODBC Driver 18 for SQL Server", + "ODBC Driver 17 for SQL Server", + "ODBC Driver 13 for SQL Server", + "SQL Server Native Client 11.0", + "SQL Server", +) + + +def _pick_driver() -> "str | None": + """이 PC에 설치된 ODBC 드라이버 중 사용할 SQL Server 드라이버를 선택. + 선호 순서대로 먼저 찾고, 없으면 이름에 'SQL Server'가 포함된 아무 드라이버라도 사용.""" + available = pyodbc.drivers() + for name in _PREFERRED_DRIVERS: + if name in available: + return name + for name in available: + if "SQL Server" in name: + return name + return None + class SQLClient: def __init__(self): @@ -8,9 +30,15 @@ class SQLClient: def connect(self, server: str, database: str, username: str, password: str) -> bool: + driver = _pick_driver() + if driver is None: + print("[DB] 연결 실패: 설치된 SQL Server ODBC 드라이버가 없습니다. " + "'ODBC Driver 18 for SQL Server'를 설치하세요.") + self.conn = None + return False try: conn_str = ( - f"DRIVER={{ODBC Driver 18 for SQL Server}};" + f"DRIVER={{{driver}}};" f"SERVER={server};" f"DATABASE={database};" f"UID={username};" @@ -20,10 +48,10 @@ class SQLClient: ) self.conn = pyodbc.connect(conn_str, timeout=10) self.cursor = self.conn.cursor() - print(f"[DB] 연결 성공: {server}/{database}") + print(f"[DB] 연결 성공: {server}/{database} (드라이버: {driver})") return True except Exception as e: - print(f"[DB] 연결 실패: {e}") + print(f"[DB] 연결 실패: {e} (드라이버: {driver})") self.conn = None return False @@ -39,9 +67,145 @@ class SQLClient: def is_connected(self) -> bool: return self.conn is not None - def get_reflector_list(self) -> list: + @staticmethod + def _norm_id(value) -> str: + return str(value).strip() if value is not None else "" + + def get_wk_results(self) -> list: """ - vi_AI_mt_Article 뷰에서 리플렉터 제품 목록 조회. + vi_AI_WK_Result 뷰 조회. + 반환: [{ + "article_id": ..., "machine_id": ..., "machine": ..., + "work_start_date": ..., "work_start_time": ..., + }, ...] + """ + if not self.is_connected(): + return [] + try: + self.cursor.execute(""" + SELECT ArticleID, MachineID, Machine, + WorkStartDate, WorkStartTime + FROM vi_AI_WK_Result + WHERE ArticleID IS NOT NULL + ORDER BY ArticleID + """) + rows = self.cursor.fetchall() + return [self._row_to_wk_result(row) for row in rows] + except Exception as e: + print(f"[DB] WK_Result 조회 실패: {e}") + return [] + + def get_wk_result_article_ids(self) -> list: + """vi_AI_WK_Result에 있는 ArticleID 목록 (중복 제거, 순서 유지).""" + seen = set() + ids = [] + for row in self.get_wk_results(): + norm = self._norm_id(row["article_id"]) + if norm and norm not in seen: + seen.add(norm) + ids.append(row["article_id"]) + return ids + + def get_wk_result_map(self) -> dict: + """ArticleID(정규화) → WK_Result 행. 동일 ID가 여러 행이면 마지막 행 사용.""" + result = {} + for row in self.get_wk_results(): + norm = self._norm_id(row["article_id"]) + if norm: + result[norm] = row + return result + + @staticmethod + def _row_to_wk_result(row) -> dict: + return { + "article_id": row[0], + "machine_id": row[1], + "machine": row[2], + "work_start_date": row[3], + "work_start_time": row[4], + } + + @staticmethod + def format_db_value(value) -> str: + if value is None: + return "—" + if hasattr(value, "strftime"): + if hasattr(value, "hour"): + return value.strftime("%H:%M:%S") + return value.strftime("%Y-%m-%d") + text = str(value).strip() + return text if text else "—" + + def get_reflector_list_ordered(self, article_ids: list) -> list: + """article_ids 순서를 유지한 제품 목록 (PatMax 슬롯 순서용).""" + if not article_ids: + return [] + by_norm = { + self._norm_id(item["article_id"]): item + for item in self.get_reflector_list(article_ids=article_ids) + } + ordered = [] + for article_id in article_ids: + item = by_norm.get(self._norm_id(article_id)) + if item is not None: + ordered.append(item) + return ordered + + def split_articles_by_wk(self, mes_selected_ids: "list | None" = None) -> tuple: + """ + vi_AI_mt_Article 목록을 WK_Result 작업 대상 / 기타로 분류. + 반환: (active_list, inactive_list) + """ + if mes_selected_ids is not None: + if len(mes_selected_ids) == 0: + return [], [] + all_items = self.get_reflector_list_ordered(mes_selected_ids) + else: + all_items = self.get_reflector_list() + + if not all_items: + return [], [] + + wk_norm = set(self.get_wk_result_map().keys()) + active = [ + item for item in all_items + if self._norm_id(item["article_id"]) in wk_norm + ] + inactive = [ + item for item in all_items + if self._norm_id(item["article_id"]) not in wk_norm + ] + return active, inactive + + def get_inspectable_articles(self, mes_selected_ids: "list | None" = None) -> list: + """ + vi_AI_mt_Article ∩ vi_AI_WK_Result. + mes_selected_ids 지정 시 관리자 MES 선택과도 교집합. + """ + wk_ids = self.get_wk_result_article_ids() + if not wk_ids: + return [] + + wk_by_norm = {self._norm_id(article_id): article_id for article_id in wk_ids} + + if mes_selected_ids is not None: + if len(mes_selected_ids) == 0: + return [] + filter_ids = [ + wk_by_norm[self._norm_id(article_id)] + for article_id in mes_selected_ids + if self._norm_id(article_id) in wk_by_norm + ] + else: + filter_ids = wk_ids + + if not filter_ids: + return [] + return self.get_reflector_list(article_ids=filter_ids) + + def get_all_articles(self) -> list: + """ + vi_AI_mt_Article 뷰 전체 조회 (관리자 MES 제품 선택용). 반환: [{"article_id": ..., "article": ..., "buyer_article_no": ...}, ...] """ if not self.is_connected(): @@ -50,22 +214,52 @@ class SQLClient: self.cursor.execute(""" SELECT ArticleID, Article, BuyerArticleNo FROM vi_AI_mt_Article - WHERE Article LIKE '%REF%' ORDER BY ArticleID """) rows = self.cursor.fetchall() - return [ - { - "article_id": row[0], - "article": row[1], - "buyer_article_no": row[2], - } - for row in rows - ] + return [self._row_to_article(row) for row in rows] + except Exception as e: + print(f"[DB] 전체 제품 조회 실패: {e}") + return [] + + def get_reflector_list(self, article_ids: "list | None" = None) -> list: + """ + vi_AI_mt_Article 뷰에서 제품 목록 조회. + article_ids 지정 시 해당 ID만, 미지정 시 REF 포함 제품 전체. + 반환: [{"article_id": ..., "article": ..., "buyer_article_no": ...}, ...] + """ + if not self.is_connected(): + return [] + try: + if article_ids: + placeholders = ",".join("?" * len(article_ids)) + self.cursor.execute(f""" + SELECT ArticleID, Article, BuyerArticleNo + FROM vi_AI_mt_Article + WHERE ArticleID IN ({placeholders}) + ORDER BY ArticleID + """, article_ids) + else: + self.cursor.execute(""" + SELECT ArticleID, Article, BuyerArticleNo + FROM vi_AI_mt_Article + WHERE Article LIKE '%REF%' + ORDER BY ArticleID + """) + rows = self.cursor.fetchall() + return [self._row_to_article(row) for row in rows] except Exception as e: print(f"[DB] 조회 실패: {e}") return [] + @staticmethod + def _row_to_article(row) -> dict: + return { + "article_id": row[0], + "article": row[1], + "buyer_article_no": row[2], + } + def save_inspection_result(self, article_id: str, result: str, score: float) -> bool: """검사 결과 저장 — 테이블 확정 후 구현.""" diff --git a/find_cells_trigger.py b/find_cells_trigger.py deleted file mode 100644 index 2540397..0000000 --- a/find_cells_trigger.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -In-Sight 2001C-353 트리거 후 전체 셀 스캔 스크립트 -SE8 트리거 실행 → 결과 갱신 완료 후 A0~Z50 범위 GV 스캔. -결과는 콘솔 출력과 find_cells_trigger_result.txt 에 동시 저장. -""" - -import socket -import string -import time - -IP = "169.254.0.1" -PORT = 23 -COLS = list(string.ascii_uppercase) # A ~ Z (26개) -ROWS = range(0, 251) # 0 ~ 250 (251개) → 총 6526셀 -RESULT_FILE = "find_cells_trigger_result.txt" - - -# ------------------------------------------------------------------ # -# 저수준 소켓 헬퍼 (insight.py 방식 동일) -# ------------------------------------------------------------------ # - -def _read_line(sock: socket.socket, buf: bytearray) -> str: - """버퍼에서 \\r\\n까지 읽어 문자열로 반환. 남은 데이터는 buf에 보존.""" - while b"\r\n" not in buf: - try: - chunk = sock.recv(4096) - except socket.timeout: - break - if not chunk: - break - buf += chunk - - if b"\r\n" in buf: - idx = buf.index(b"\r\n") - line = bytes(buf[:idx]) - del buf[:idx + 2] - else: - line = bytes(buf) - buf.clear() - - for encoding in ["euc-kr", "cp949", "utf-8"]: - try: - return line.decode(encoding).strip() - except Exception: - continue - return line.decode(errors="ignore").strip() - - -def _send(sock: socket.socket, cmd: str): - sock.sendall((cmd + "\r\n").encode()) - - -# ------------------------------------------------------------------ # -# 로그인 (insight.py connect() 시퀀스와 동일) -# ------------------------------------------------------------------ # - -def _login(sock: socket.socket, buf: bytearray): - banner = _read_line(sock, buf) - print(f"[연결] 배너: {banner!r}") - - _send(sock, "admin") - while b"Password:" not in buf: - try: - chunk = sock.recv(4096) - except socket.timeout: - break - if not chunk: - break - buf += chunk - prompt = buf.decode(errors="ignore").strip() - buf.clear() - print(f"[연결] 프롬프트: {prompt!r}") - - sock.sendall(b"\r\n") # 빈 비밀번호 - result = _read_line(sock, buf) - print(f"[연결] 로그인: {result!r}") - - if "Logged In" not in result: - raise RuntimeError(f"로그인 실패: {result!r}") - - -# ------------------------------------------------------------------ # -# SE8 트리거 -# ------------------------------------------------------------------ # - -def _trigger(sock: socket.socket, buf: bytearray): - """SE8 소프트웨어 트리거 전송 후 응답 수신.""" - _send(sock, "SE8") - response = _read_line(sock, buf) - print(f"[트리거] SE8 응답: {response!r}") - - -# ------------------------------------------------------------------ # -# 셀 스캔 -# ------------------------------------------------------------------ # - -def _scan(sock: socket.socket, buf: bytearray) -> dict: - """GV[열][행] 전체 스캔 → {셀주소: 값문자열}""" - total = len(COLS) * len(ROWS) - found = {} - - for col in COLS: - for row in ROWS: - cell = f"{col}{row}" - _send(sock, f"GV{cell}") - - status = _read_line(sock, buf) # "1" 또는 "0" - - if status == "1": - value = _read_line(sock, buf) - if not value.strip(): - pass - else: - found[cell] = value - try: - print(f"\r[{cell:<4}] = {value:<40}") - except UnicodeEncodeError: - print(f"\r[{cell:<4}] = {value.encode('utf-8', errors='replace')!r}") - - done = (COLS.index(col)) * len(ROWS) + row + 1 - print( - f"스캔 중... [{cell} / Z{ROWS[-1]}] ({done}/{total})", - end="\r", flush=True, - ) - - print() # 진행바 줄 정리 - return found - - -# ------------------------------------------------------------------ # -# 결과 저장 -# ------------------------------------------------------------------ # - -def _save(found: dict, path: str = RESULT_FILE): - with open(path, "w", encoding="utf-8") as f: - f.write(f"스캔 범위: A0 ~ Z{ROWS[-1]} / 유효 셀: {len(found)}개\n") - f.write("=" * 40 + "\n") - for cell, val in found.items(): - f.write(f"[{cell:<4}] = {val}\n") - print(f"결과 저장 완료 → {path}") - - -# ------------------------------------------------------------------ # -# 값 검색 -# ------------------------------------------------------------------ # - -def _search(found: dict, query: str): - matches = {c: v for c, v in found.items() if query.lower() in v.lower()} - if matches: - print(f"\n[검색] '{query}' 포함 셀 {len(matches)}개:") - for cell, val in matches.items(): - print(f" [{cell:<4}] = {val}") - else: - print(f"[검색] '{query}'를 포함한 셀이 없습니다.") - - -# ------------------------------------------------------------------ # -# 메인 -# ------------------------------------------------------------------ # - -def main(): - print(f"[연결] {IP}:{PORT} 접속 중...") - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(3.0) - sock.connect((IP, PORT)) - buf = bytearray() - - try: - _login(sock, buf) - - input("\n준비됐으면 엔터를 누르세요 (리플렉터를 카메라 앞에 올려두세요): ") - - print("[트리거] SE8 전송 중...") - _trigger(sock, buf) - - print("[대기] 카메라 처리 완료 대기 (1초)...") - time.sleep(1) - - print(f"\n스캔 시작: A0 ~ Z{ROWS[-1]} (총 {len(COLS) * len(ROWS)}셀)\n") - found = _scan(sock, buf) - finally: - sock.close() - - print(f"\n스캔 완료. 총 {len(found)}개 셀 발견") - print("=" * 40) - for cell, val in found.items(): - try: - print(f" [{cell:<4}] = {val}") - except UnicodeEncodeError: - print(f" [{cell:<4}] = {val.encode('utf-8', errors='replace')!r}") - - _save(found) - - print() - while True: - query = input("찾는 값 입력 (엔터 시 종료): ").strip() - if not query: - break - _search(found, query) - - -if __name__ == "__main__": - main() diff --git a/gui/dialogs/image_settings_dialog.py b/gui/dialogs/image_settings_dialog.py index 168acfa..d5b9c41 100644 --- a/gui/dialogs/image_settings_dialog.py +++ b/gui/dialogs/image_settings_dialog.py @@ -34,9 +34,15 @@ QDoubleSpinBox, QSpinBox { padding: 4px 8px; min-height: 38px; } -QDoubleSpinBox::up-button, QDoubleSpinBox::down-button, -QSpinBox::up-button, QSpinBox::down-button { - width: 20px; +QDoubleSpinBox::up-button, QSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + width: 30px; +} +QDoubleSpinBox::down-button, QSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + width: 30px; } QPushButton { background: #2e2e2e; diff --git a/gui/image_settings_dialog.py b/gui/image_settings_dialog.py deleted file mode 100644 index c0389df..0000000 --- a/gui/image_settings_dialog.py +++ /dev/null @@ -1,195 +0,0 @@ -from PyQt5.QtWidgets import ( - QDialog, QFormLayout, QVBoxLayout, QHBoxLayout, - QPushButton, QDoubleSpinBox, QSpinBox, QMessageBox, QLabel -) -from PyQt5.QtCore import Qt - - -# (레이블, 셀주소, 위젯종류, min, max, 기본값, 소수점자리) -# D3(노출)은 AcqExposure() 출력 셀 — 읽기 전용, 목록에서 제외 -_PARAMS = [ - ("최대 노출 시간", "F3", "double", 0.01, 1000.0, 950.0, 3), - ("목표 이미지 밝기", "G3", "double", 0.0, 255.0, 50.0, 1), - ("조명 강도", "D6", "int", 0, 100, 70, 0), - ("초점 위치", "D14", "int", 0, 999, 139, 0), -] - - -class ImageSettingsDialog(QDialog): - def __init__(self, insight_cam, parent=None): - super().__init__(parent) - self._cam = insight_cam - self.setWindowTitle("이미지 설정 — In-Sight 2000C") - self.setMinimumWidth(360) - self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) - - self._widgets = {} # 셀주소 → 위젯 - self._originals = {} # 셀주소 → 로드 시 원본값 - - self._build_ui() - self._load_values() - - # ------------------------------------------------------------------ # - # UI 구성 - # ------------------------------------------------------------------ # - - def _build_ui(self): - form = QFormLayout() - form.setLabelAlignment(Qt.AlignRight) - form.setRowWrapPolicy(QFormLayout.DontWrapRows) - form.setHorizontalSpacing(16) - form.setVerticalSpacing(10) - - # 노출(D3)은 읽기 전용 표시 - self._exposure_label = QLabel("—") - self._exposure_label.setStyleSheet("color: gray;") - form.addRow("노출 (밀리초, 읽기 전용):", self._exposure_label) - - for label, cell, kind, lo, hi, default, decimals in _PARAMS: - if kind == "double": - w = QDoubleSpinBox() - w.setRange(lo, hi) - w.setDecimals(decimals) - w.setValue(default) - w.setSingleStep(0.1) - else: - w = QSpinBox() - w.setRange(int(lo), int(hi)) - w.setValue(int(default)) - - w.setMinimumWidth(120) - self._widgets[cell] = w - form.addRow(label, w) - - btn_ok = QPushButton("확인") - btn_ok.setDefault(True) - btn_ok.clicked.connect(self._on_ok) - - btn_cancel = QPushButton("취소") - btn_cancel.clicked.connect(self.reject) - - btn_row = QHBoxLayout() - btn_row.addStretch() - btn_row.addWidget(btn_ok) - btn_row.addWidget(btn_cancel) - - root = QVBoxLayout(self) - root.addLayout(form) - root.addSpacing(8) - root.addLayout(btn_row) - - # ------------------------------------------------------------------ # - # 값 로드 — GV{cell} 명령, 응답 2줄(상태코드 + 값) - # ------------------------------------------------------------------ # - - def _load_values(self): - # D3: 읽기 전용 표시 - exp = self._gv("D3") - print(f"[설정창] GVD3 → {exp!r}") - if exp is not None: - self._exposure_label.setText(f"{exp:.3f} ms") - - for _, cell, kind, _, _, _, _ in _PARAMS: - val = self._gv(cell) - print(f"[설정창] GV{cell} → {val!r}") - w = self._widgets[cell] - if val is not None: - if kind == "double": - w.setValue(val) - else: - w.setValue(int(round(val))) - self._originals[cell] = val - else: - self._originals[cell] = None - - def _gv(self, cell: str): - """GV{cell} 전송 → 숫자 반환, 실패 시 None""" - try: - self._cam._send(f"GV{cell}") - status = self._cam._read_line() # "1" 또는 "0" - if status != "1": - print(f"[설정창] GV{cell} 상태코드: {status!r}") - return None - raw = self._cam._read_line() # 실제 값 - return float(raw) - except Exception as e: - print(f"[설정창] GV{cell} 오류: {e}") - return None - - # ------------------------------------------------------------------ # - # 확인 버튼 — SV{cell} {value} 명령 - # ------------------------------------------------------------------ # - - def _on_ok(self): - import time - - # 변경 항목 수집 - changes = [] - for _, cell, kind, _, _, _, _ in _PARAMS: - w = self._widgets[cell] - current = float(w.value()) - original = self._originals.get(cell) - if original is None or abs(current - original) >= 1e-9: - changes.append((cell, current, kind)) - - if not changes: - self.accept() - return - - # 오프라인 전환 - self._cam._send("SO0") - resp = self._cam._read_line() - print(f"[설정창] SO0 (오프라인) → {resp!r}") - if resp.strip() != "1": - QMessageBox.critical(self, "오류", f"오프라인 전환 실패 (응답: {resp!r})") - return - - time.sleep(0.3) # 카메라가 오프라인 전환 완료 대기 - - # 값 설정 - errors = [] - for cell, value, kind in changes: - if not self._sv(cell, value, kind): - errors.append(cell) - - time.sleep(0.2) # SV 처리 완료 대기 - - # 온라인 복귀 — 최대 3회 재시도 - online_ok = False - for attempt in range(3): - self._cam._send("SO1") - resp = self._cam._read_line() - print(f"[설정창] SO1 (온라인) 시도 {attempt+1} → {resp!r}") - if resp.strip() == "1": - online_ok = True - break - time.sleep(0.5) - - if not online_ok: - QMessageBox.warning( - self, "온라인 복귀 실패", - f"SO1 명령이 실패했습니다 (마지막 응답: {resp!r})\n" - "카메라를 수동으로 재시작하거나 In-Sight Explorer에서 Online으로 전환하세요." - ) - - if errors: - QMessageBox.critical( - self, "설정 실패", - f"다음 셀 설정 실패:\n{chr(10).join(errors)}\n\n" - "셀이 읽기 전용이거나 값 범위를 벗어났을 수 있습니다." - ) - return - - self.accept() - - def _sv(self, cell: str, value: float, kind: str) -> bool: - """SV{cell} {value} 전송 → 응답 '1'이면 True""" - try: - formatted = str(int(round(value))) if kind == "int" else f"{value:.6g}" - self._cam._send(f"SV{cell} {formatted}") - resp = self._cam._read_line() - print(f"[설정창] SV{cell} {formatted} → {resp!r}") - return resp.strip() == "1" - except Exception as e: - print(f"[설정창] SV{cell} 오류: {e}") - return False diff --git a/gui/main_window.py b/gui/main_window.py index 100bb8a..e7e9dc9 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -110,7 +110,7 @@ class MainWindow(QMainWindow): self.db_client = SQLClient() self.plc_client = plc_client - self.setWindowTitle("비전 검사 시스템") + self.setWindowTitle("리플렉터 검사 시스템") self.showFullScreen() # 재학습 탭 연속 클릭(창 최소화 단축) 감지용 @@ -189,12 +189,15 @@ class MainWindow(QMainWindow): ) self._register_page = RegisterPage( - self.insight, matcher=self.matcher, db_client=self.db_client + self.insight, matcher=self.matcher, + db_client=self.db_client, config=self.config, ) self._inspect_page = InspectPage( self.insight, self.basler, detector=self.detector, belt_delay=_belt_delay, + db_client=self.db_client, + config=self.config, ) self._inspect_page.update_matcher(self.matcher) self._settings_page.belt_settings_changed.connect( @@ -271,6 +274,10 @@ class MainWindow(QMainWindow): btn.setActive(i == idx) if idx == 0: self._settings_page._sync_connection_status() + elif idx == 1: + self._register_page.load_products() + elif idx == 2: + self._inspect_page.refresh_wk_results() self.update_connection_status() # ================================================================== # @@ -296,6 +303,8 @@ class MainWindow(QMainWindow): connected = db_client is not None and db_client.is_connected() if hasattr(self, "_register_page"): self._register_page.update_db(db_client) + if hasattr(self, "_inspect_page"): + self._inspect_page.update_db(db_client) if hasattr(self, "_settings_page"): self._settings_page._db_client = db_client self._settings_page._set_db_connected(connected) diff --git a/gui/pages/inspect_page.py b/gui/pages/inspect_page.py index ceaf0b6..944759b 100644 --- a/gui/pages/inspect_page.py +++ b/gui/pages/inspect_page.py @@ -1,4 +1,4 @@ -# 검사 페이지 — 코그넥스/Basler 영상, 그룹 A/B 설정, Pass/Fail 표시 +# 검사 페이지 — 코그넥스/Basler 영상, Pass/Fail 표시 import time import threading import itertools @@ -8,12 +8,14 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QGroupBox, - QPushButton, QLabel, QCheckBox, QFrame, - QGridLayout, QSizePolicy, + QPushButton, QLabel, QFrame, + QGridLayout, QSizePolicy, QScrollArea, QScroller, QMessageBox, ) from logic.inspector import Inspector from logic.group_manager import GroupManager +from logic.products import build_patmax_cells, article_label, MAX_PATMAX_SLOTS +from db.sql_client import SQLClient from logger import log_inspect_result, log_camera_timing, log_action, log_defect_image _DEFECT_COLORS = { @@ -40,33 +42,6 @@ def _draw_detections(frame: np.ndarray, detections: list) -> np.ndarray: ) return img -# 검사 그룹 A/B 선택용 모델 목록 -_MODELS = [ - "LOW REF / LX3 / RH", - "LOW REF / LX3 / LH", - "LOW REF NAS / LX3 / RH", - "LOW REF NAS / LX3 / LH", - "LOW REF NAS / MX5a 2.0TH / RH", - "LOW REF NAS / MX5a 2.0TH / LH", - "HIGH REF / LX3 / RH", - "HIGH REF / LX3 / LH", - "LOW REF NAS 1.5 GEN / CN7 PE / RH", - "LOW REF DOM 1.5 GEN / CN7 PE / LH", -] - -_MODEL_ID_MAP = { - "LOW REF / LX3 / RH": 1, - "LOW REF / LX3 / LH": 2, - "LOW REF NAS / LX3 / RH": 3, - "LOW REF NAS / LX3 / LH": 4, - "LOW REF NAS / MX5a 2.0TH / RH": 5, - "LOW REF NAS / MX5a 2.0TH / LH": 6, - "HIGH REF / LX3 / RH": 7, - "HIGH REF / LX3 / LH": 8, - "LOW REF NAS 1.5 GEN / CN7 PE / RH": 9, - "LOW REF DOM 1.5 GEN / CN7 PE / LH": 10, -} - # ================================================================== # # 백그라운드 워커 — 파이프라인 방식 @@ -97,6 +72,18 @@ class InspectWorker(QThread): self._stop_flag = False self._pause_flag = False self._seq = itertools.count(1) + self._wk_result_ids: set = set() + self._wk_check_enabled = False + self._allowed_article_ids: set = set() + + def set_work_targets(self, allowed_article_ids: "set | None"): + """WK_Result 작업 대상 ArticleID (정규화된 set). None이면 WK 검사 비활성.""" + if allowed_article_ids is None: + self._wk_check_enabled = False + self._allowed_article_ids = set() + return + self._wk_check_enabled = True + self._allowed_article_ids = allowed_article_ids # ── 외부 제어 ──────────────────────────────────────────────────── # @@ -207,18 +194,25 @@ class InspectWorker(QThread): ct.join(timeout=10.0) log_camera_timing(seq, "cognex_join_done", _ms()) - # ── 모델 판별 ── - results = cognex_out.get("results", {}) - active_names = self._groups.get_active_group() - allowed_ids = [_MODEL_ID_MAP[n] for n in active_names if n in _MODEL_ID_MAP] - result_info = { + # ── 모델 판별 (WK_Result 작업 대상만 허용) ── + results = cognex_out.get("results", {}) + result_info = { "matched": False, "in_allowed": False, "model": None, "score": 0.0, "cognex_pass": False, "status": "인식 불가", } try: if results: - result_info = self._inspector.identify_model(results, allowed_ids) + if self._wk_check_enabled: + result_info = self._inspector.identify_model( + results, allowed_article_ids=self._allowed_article_ids, + ) + else: + result_info = self._inspector.identify_model( + results, allowed_model_ids=self._groups.get_allowed_ids(), + ) + if result_info["matched"] and self._wk_check_enabled: + result_info["in_wk_result"] = result_info["in_allowed"] except Exception as e: print(f"[워커 오류] 모델 판별: {e}") @@ -258,18 +252,17 @@ class InspectWorker(QThread): class InspectPage(QWidget): def __init__(self, insight_cam, basler_cam, detector=None, - belt_delay: float = 3.33, parent=None): + belt_delay: float = 3.33, db_client=None, config=None, parent=None): super().__init__(parent) self._insight = insight_cam self._basler = basler_cam + self._db_client = db_client + self._config = config or {} self.detector = detector self._inspector = Inspector() self._groups = GroupManager() - self._counts = { - "A": {"total": 0, "pass": 0, "fail": 0, "unknown": 0}, - "B": {"total": 0, "pass": 0, "fail": 0, "unknown": 0}, - } + self._counts = {"total": 0, "pass": 0, "fail": 0, "unknown": 0} self._matcher = None @@ -284,6 +277,7 @@ class InspectPage(QWidget): self._worker.result_ready.connect(self._on_result) self._build_ui() + self.refresh_wk_results() # ================================================================== # # 최상위 레이아웃 @@ -347,68 +341,48 @@ class InspectPage(QWidget): layout.setContentsMargins(8, 6, 8, 6) layout.setSpacing(0) - layout.addWidget(self._build_col_groups(), stretch=5) + layout.addWidget(self._build_col_products(), stretch=5) layout.addWidget(_vline()) layout.addWidget(self._build_col_controls(), stretch=3) layout.addWidget(_vline()) layout.addWidget(self._build_col_counters(), stretch=4) return w - def _build_col_groups(self) -> QWidget: + def _build_col_products(self) -> QWidget: w = QWidget() layout = QVBoxLayout(w) layout.setContentsMargins(4, 4, 8, 4) layout.setSpacing(6) - checks_row = QHBoxLayout() - checks_row.setSpacing(8) - checks_row.addWidget(self._build_group_section("A"), stretch=1) - checks_row.addWidget(self._build_group_section("B"), stretch=1) - layout.addLayout(checks_row, stretch=1) - - self._switch_btn = QPushButton("현재: 그룹 A 활성 → B로 전환") - self._switch_btn.setFixedHeight(56) - self._switch_btn.setStyleSheet( - "background:#1a3a5c; color:#ffffff; border:none; border-radius:4px;" - "font-size:14px; font-weight:bold;" - ) - self._switch_btn.clicked.connect(self._on_switch) - layout.addWidget(self._switch_btn) - return w - - def _build_group_section(self, name: str) -> QGroupBox: - active_color = "#4488ff" if name == "A" else "#cc8844" - g = QGroupBox(f"그룹 {name} (최대 4종)") + g = QGroupBox("검사 대상") + self._products_group = g g.setStyleSheet( - f"QGroupBox {{ background:#222222; border:1px solid #333333; border-radius:6px;" - f" margin-top:12px; padding:6px 4px 4px 4px; }}" - f"QGroupBox::title {{ color:{active_color}; subcontrol-origin:margin;" - f" left:8px; font-size:13px; font-weight:bold; }}" + "QGroupBox { background:#222222; border:1px solid #333333; border-radius:6px;" + " margin-top:12px; padding:6px 4px 4px 4px; }" + "QGroupBox::title { color:#4488ff; subcontrol-origin:margin;" + " left:8px; font-size:13px; font-weight:bold; }" ) - layout = QVBoxLayout(g) - layout.setSpacing(1) - layout.setContentsMargins(4, 2, 4, 2) + outer = QVBoxLayout(g) + outer.setSpacing(0) + outer.setContentsMargins(4, 2, 4, 2) - checks = [] - for model in _MODELS: - cb = QCheckBox(model) - cb.setStyleSheet( - "QCheckBox { font-size:12px; min-height:24px; color:#cccccc; }" - "QCheckBox::indicator { width:16px; height:16px; }" - ) - checks.append(cb) - layout.addWidget(cb) + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet("background:transparent;") + QScroller.grabGesture(scroll.viewport(), QScroller.LeftMouseButtonGesture) - if name == "A": - self._group_a_checks = checks - for cb in checks: - cb.clicked.connect(lambda checked, c=cb: self._on_group_changed("A", c, checked)) - else: - self._group_b_checks = checks - for cb in checks: - cb.clicked.connect(lambda checked, c=cb: self._on_group_changed("B", c, checked)) + inner = QWidget() + inner.setStyleSheet("background:transparent;") + self._products_inner_layout = QVBoxLayout(inner) + self._products_inner_layout.setSpacing(2) + self._products_inner_layout.setContentsMargins(2, 2, 2, 2) - return g + scroll.setWidget(inner) + outer.addWidget(scroll) + layout.addWidget(g, stretch=1) + return w def _build_col_controls(self) -> QWidget: w = QWidget() @@ -433,9 +407,9 @@ class InspectPage(QWidget): ) self._pause_btn.clicked.connect(self._on_pause) - self._active_lbl = QLabel("활성 그룹: A") - self._active_lbl.setAlignment(Qt.AlignCenter) - self._active_lbl.setStyleSheet("font-size:14px; color:#aaaaaa;") + self._scope_lbl = QLabel("검사 범위: —") + self._scope_lbl.setAlignment(Qt.AlignCenter) + self._scope_lbl.setStyleSheet("font-size:14px; color:#aaaaaa;") self._model_lbl = QLabel("인식 모델: —") self._model_lbl.setAlignment(Qt.AlignCenter) @@ -446,19 +420,19 @@ class InspectPage(QWidget): f"벨트 딜레이: {self._worker._belt_delay:.2f}s" ) self._belt_lbl.setAlignment(Qt.AlignCenter) - self._belt_lbl.setStyleSheet("font-size:12px; color:#666666;") + self._belt_lbl.setStyleSheet("font-size:13px; color:#999999;") self._result_lbl = QLabel("대기 중") self._result_lbl.setAlignment(Qt.AlignCenter) self._result_lbl.setStyleSheet( - "font-size:48px; font-weight:bold; background:#2a2a2a;" - "color:#666666; border-radius:8px;" + "font-size:64px; font-weight:bold; background:#2a2a2a;" + "color:#888888; border-radius:8px;" ) self._result_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self._start_btn) layout.addWidget(self._pause_btn) - layout.addWidget(self._active_lbl) + layout.addWidget(self._scope_lbl) layout.addWidget(self._model_lbl) layout.addWidget(self._belt_lbl) layout.addWidget(self._result_lbl, stretch=1) @@ -473,7 +447,7 @@ class InspectPage(QWidget): grid = QGridLayout() grid.setSpacing(4) - for col, text in enumerate(["", "그룹 A", "그룹 B"]): + for col, text in enumerate(["", "집계"]): lbl = QLabel(text) lbl.setAlignment(Qt.AlignCenter) lbl.setStyleSheet("font-size:13px; color:#888888; font-weight:bold;") @@ -491,16 +465,14 @@ class InspectPage(QWidget): row_lbl.setStyleSheet(f"font-size:14px; color:{color}; font-weight:bold;") grid.addWidget(row_lbl, r, 0) - self._cnt_lbls[key] = {} - for g_col, group in enumerate(["A", "B"], start=1): - lbl = QLabel("0") - lbl.setAlignment(Qt.AlignCenter) - lbl.setStyleSheet( - f"font-size:36px; font-weight:bold; color:{color};" - "background:#222222; border-radius:4px; padding:2px 6px;" - ) - grid.addWidget(lbl, r, g_col) - self._cnt_lbls[key][group] = lbl + lbl = QLabel("0") + lbl.setAlignment(Qt.AlignCenter) + lbl.setStyleSheet( + f"font-size:36px; font-weight:bold; color:{color};" + "background:#222222; border-radius:4px; padding:2px 6px;" + ) + grid.addWidget(lbl, r, 1) + self._cnt_lbls[key] = lbl layout.addLayout(grid) @@ -518,24 +490,6 @@ class InspectPage(QWidget): # 슬롯 — UI 이벤트 # ================================================================== # - def _on_group_changed(self, group: str, changed_cb: QCheckBox, is_checked: bool): - checks = self._group_a_checks if group == "A" else self._group_b_checks - if is_checked and sum(1 for c in checks if c.isChecked()) > GroupManager.MAX_PER_GROUP: - changed_cb.setChecked(False) - return - models = [c.text() for c in checks if c.isChecked()] - if group == "A": - self._groups.set_group_a(models) - else: - self._groups.set_group_b(models) - - def _on_switch(self): - active = self._groups.switch_group() - other = "B" if active == "A" else "A" - self._switch_btn.setText(f"현재: 그룹 {active} 활성 → {other}로 전환") - self._active_lbl.setText(f"활성 그룹: {active}") - print(f"[검사] 그룹 전환 → 활성 그룹 {active}") - def _on_start(self): if self._worker.isRunning(): return @@ -558,11 +512,18 @@ class InspectPage(QWidget): log_action("[검사] 검사 재개") def _on_reset(self): + reply = QMessageBox.question( + self, "카운터 초기화", + "검사 카운트를 0으로 초기화할까요?\n" + "초기화한 집계는 되돌릴 수 없습니다.", + QMessageBox.Yes | QMessageBox.No, QMessageBox.No, + ) + if reply != QMessageBox.Yes: + return log_action("[검사] 카운트 리셋") for key in ("total", "pass", "fail", "unknown"): - for g in ("A", "B"): - self._counts[g][key] = 0 - self._cnt_lbls[key][g].setText("0") + self._counts[key] = 0 + self._cnt_lbls[key].setText("0") def closeEvent(self, event): self._worker.stop() @@ -598,6 +559,106 @@ class InspectPage(QWidget): self._worker.set_belt_delay(delay) self._belt_lbl.setText(f"벨트 딜레이: {delay:.2f}s") + def update_db(self, db_client): + """MainWindow에서 DB 연결/해제 시 호출.""" + self._db_client = db_client + self.refresh_wk_results() + + def refresh_wk_results(self): + """vi_AI_WK_Result 기준 작업 대상을 UI·PatMax 매핑·워커에 반영.""" + if not self._db_client or not self._db_client.is_connected(): + self._inspector.set_pattern_cells({}) + self._worker.set_work_targets(None) + self._update_product_list([], []) + self._scope_lbl.setText("검사 범위: DB 미연결") + self._products_group.setTitle("검사 대상") + print("[검사] WK_Result 비활성 — DB 미연결") + return + + mes_selected = self._config.get("mes", {}).get("selected_article_ids") + if mes_selected is not None and len(mes_selected) == 0: + self._inspector.set_pattern_cells({}) + self._worker.set_work_targets(set()) + self._update_product_list([], []) + self._scope_lbl.setText("검사 범위: MES 제품 미선택") + self._products_group.setTitle("검사 대상 (0종)") + return + + if mes_selected is not None: + all_items = self._db_client.get_reflector_list_ordered(mes_selected) + else: + all_items = self._db_client.get_reflector_list() + + self._inspector.set_pattern_cells(build_patmax_cells(all_items)) + + active, inactive = self._db_client.split_articles_by_wk(mes_selected) + allowed = {SQLClient._norm_id(a["article_id"]) for a in active} + + self._worker.set_work_targets(allowed) + self._update_product_list(active, inactive) + + total = len(active) + len(inactive) + if active: + self._scope_lbl.setText(f"검사 범위: 작업 대상 {len(active)}종") + else: + self._scope_lbl.setText("검사 범위: 작업 대상 없음") + self._products_group.setTitle( + f"검사 대상 (작업 {len(active)}종 / 전체 {total}종)" + ) + print( + f"[검사] WK_Result 작업 대상: {len(active)}종 (전체 {total}종, " + f"PatMax 슬롯 {min(len(all_items), MAX_PATMAX_SLOTS)}개)" + ) + + def _update_product_list(self, active: list, inactive: list): + if not hasattr(self, "_products_inner_layout"): + return + + layout = self._products_inner_layout + while layout.count(): + item = layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + if active: + layout.addWidget(self._make_section_label( + f"작업 대상 — WK_Result ({len(active)})", active=True + )) + for i, article in enumerate(active, 1): + layout.addWidget(self._make_article_label(article, i, active=True)) + + if inactive: + layout.addWidget(self._make_section_label( + f"기타 ({len(inactive)})", active=False + )) + for i, article in enumerate(inactive, 1): + layout.addWidget(self._make_article_label(article, i, active=False)) + + if not active and not inactive: + layout.addWidget(self._make_section_label("작업 대상 없음", active=False)) + + layout.addStretch() + + @staticmethod + def _make_section_label(text: str, active: bool) -> QLabel: + lbl = QLabel(text) + color = "#aaaaaa" if active else "#666666" + lbl.setStyleSheet( + f"font-size:12px; color:{color}; font-weight:bold;" + "padding:6px 4px 2px 4px; background:#2a2a2a;" + ) + return lbl + + @staticmethod + def _make_article_label(article: dict, index: int, active: bool) -> QLabel: + lbl = QLabel(f"{index:2d}. {article_label(article)}") + if active: + style = "font-size:13px; min-height:32px; color:#eeeeee; padding:2px 4px;" + else: + style = "font-size:13px; min-height:32px; color:#555555; padding:2px 4px;" + lbl.setStyleSheet(style) + return lbl + # ================================================================== # # 워커 signal 슬롯 (메인 스레드) # ================================================================== # @@ -606,24 +667,21 @@ class InspectPage(QWidget): self._display_basler_image(frame, detections=detections) def _on_result(self, data: dict): - group = data["group"] matched = data["matched"] result = data["result"] - cognex_pass = data["cognex_pass"] - basler_pass = data["basler_pass"] result_info = data["result_info"] self._model_lbl.setText(result_info["status"]) - self._counts[group]["total"] += 1 + self._counts["total"] += 1 if not matched: - self._counts[group]["unknown"] += 1 + self._counts["unknown"] += 1 elif result == "PASS": - self._counts[group]["pass"] += 1 + self._counts["pass"] += 1 else: - self._counts[group]["fail"] += 1 + self._counts["fail"] += 1 for key in ("total", "pass", "fail", "unknown"): - self._cnt_lbls[key][group].setText(str(self._counts[group][key])) + self._cnt_lbls[key].setText(str(self._counts[key])) if not matched: self._set_result("미인식", "#332200", "#ff9900") @@ -639,7 +697,7 @@ class InspectPage(QWidget): def _set_result(self, text: str, bg: str, fg: str): self._result_lbl.setText(text) self._result_lbl.setStyleSheet( - f"font-size:48px; font-weight:bold; background:{bg};" + f"font-size:64px; font-weight:bold; background:{bg};" f"color:{fg}; border-radius:8px;" ) diff --git a/gui/pages/register_page.py b/gui/pages/register_page.py index 6ebbda6..7057064 100644 --- a/gui/pages/register_page.py +++ b/gui/pages/register_page.py @@ -2,13 +2,15 @@ import cv2 import numpy as np from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QImage, QPixmap +from PyQt5.QtGui import QImage, QPixmap, QColor from PyQt5.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QGroupBox, QPushButton, QListWidget, QListWidgetItem, QLabel, QMessageBox, QScrollArea, QFrame, ) +from db.sql_client import SQLClient + _GRP_STYLE = ( "QGroupBox {" @@ -20,11 +22,13 @@ _GRP_STYLE = ( class RegisterPage(QWidget): - def __init__(self, insight_cam, matcher=None, db_client=None, parent=None): + def __init__(self, insight_cam, matcher=None, db_client=None, config=None, parent=None): super().__init__(parent) self._insight = insight_cam self._db_client = db_client + self._config = config or {} self._db_items = [] + self._wk_map = {} self._selected = None self._captured_img = None @@ -49,12 +53,8 @@ class RegisterPage(QWidget): layout.setSpacing(10) self._btn_mes = QPushButton("MES 불러오기") + self._btn_mes.setVisible(False) self._btn_mes.setEnabled(False) - self._btn_mes.setFixedHeight(56) - self._btn_mes.setToolTip("DB 연결 후 사용 가능") - self._btn_mes.setStyleSheet( - "background:#444444; color:#777777; border:none; border-radius:4px; font-size:15px;" - ) self._btn_mes.clicked.connect(self._on_load_from_db) layout.addWidget(self._btn_mes) @@ -69,10 +69,8 @@ class RegisterPage(QWidget): border-radius:4px; outline:none; font-size:15px; } QListWidget::item { - padding:0px 14px; border-bottom:1px solid #2a2a2a; color:#dddddd; + padding:0px 14px; border-bottom:1px solid #2a2a2a; } - QListWidget::item:selected { background:#185FA5; color:#ffffff; } - QListWidget::item:hover:!selected { background:#2d2d2d; } """) self._list.currentRowChanged.connect(self._on_select) layout.addWidget(self._list, stretch=1) @@ -109,9 +107,19 @@ class RegisterPage(QWidget): self._lbl_name = _info_value("—") self._lbl_model = _info_value("—") self._lbl_type = _info_value("—") + self._lbl_wk = _info_value("—") + self._lbl_machine_id = _info_value("—") + self._lbl_machine = _info_value("—") + self._lbl_work_date = _info_value("—") + self._lbl_work_time = _info_value("—") layout.addLayout(_info_row("카테고리", self._lbl_name)) layout.addLayout(_info_row("모델명", self._lbl_model)) layout.addLayout(_info_row("Type", self._lbl_type)) + layout.addLayout(_info_row("작업상태", self._lbl_wk)) + layout.addLayout(_info_row("설비 ID", self._lbl_machine_id)) + layout.addLayout(_info_row("설비명", self._lbl_machine)) + layout.addLayout(_info_row("작업시작일", self._lbl_work_date)) + layout.addLayout(_info_row("작업시작", self._lbl_work_time)) self._arrow_lbl = QLabel("") self._arrow_lbl.setAlignment(Qt.AlignCenter) @@ -157,21 +165,28 @@ class RegisterPage(QWidget): if row < 0: return item = self._list.item(row) - if item is None: + if item is None or not (item.flags() & Qt.ItemIsSelectable): return article_id = item.data(Qt.UserRole) if article_id is None: return + self._refresh_list_styles(row) + db_item = next( - (x for x in self._db_items if x["article_id"] == article_id), None + (x for x in self._db_items + if SQLClient._norm_id(x["article_id"]) == SQLClient._norm_id(article_id)), + None, ) + name = item.data(Qt.UserRole + 1) or item.text() + in_wk = bool(item.data(Qt.UserRole + 2)) r = { "id": article_id, - "name": item.text(), + "name": name, "model": db_item.get("buyer_article_no", "") if db_item else "", "type": "", + "in_wk": in_wk, } self._selected = r @@ -181,6 +196,20 @@ class RegisterPage(QWidget): self._lbl_model.setText(r["model"]) t = r.get("type", "") self._lbl_type.setText(t if t else "—") + if in_wk: + self._lbl_wk.setText("작업 대상") + self._lbl_wk.setStyleSheet("color:#cccccc; font-size:16px; font-weight:bold;") + wk = self._wk_map.get(SQLClient._norm_id(article_id), {}) + self._lbl_machine_id.setText(SQLClient.format_db_value(wk.get("machine_id"))) + self._lbl_machine.setText(SQLClient.format_db_value(wk.get("machine"))) + self._lbl_work_date.setText(SQLClient.format_db_value(wk.get("work_start_date"))) + self._lbl_work_time.setText(SQLClient.format_db_value(wk.get("work_start_time"))) + else: + self._lbl_wk.setText("작업 대외") + self._lbl_wk.setStyleSheet("color:#888888; font-size:16px; font-weight:bold;") + for lbl in (self._lbl_machine_id, self._lbl_machine, + self._lbl_work_date, self._lbl_work_time): + lbl.setText("—") if t == "RH": self._arrow_lbl.setText("→") @@ -219,35 +248,111 @@ class RegisterPage(QWidget): def update_db(self, db_client): """MainWindow에서 DB 연결/해제 시 호출.""" self._db_client = db_client - enabled = db_client is not None and db_client.is_connected() - self._btn_mes.setEnabled(enabled) - self._btn_mes.setStyleSheet( - "background:#1a3a5c; color:#ffffff; border:none; border-radius:4px; font-size:15px;" - if enabled else - "background:#444444; color:#777777; border:none; border-radius:4px; font-size:15px;" + self.load_products() + + def load_products(self): + """관리자 설정에서 선택한 MES 제품 목록을 자동으로 불러온다.""" + self._list.clear() + self._db_items = [] + self._wk_map = {} + self._selected = None + self._lbl_name.setText("—") + self._lbl_model.setText("—") + self._lbl_type.setText("—") + self._lbl_wk.setText("—") + self._lbl_wk.setStyleSheet("color:#ffffff; font-size:16px; font-weight:bold;") + for lbl in (self._lbl_machine_id, self._lbl_machine, + self._lbl_work_date, self._lbl_work_time): + lbl.setText("—") + self._arrow_lbl.setText("") + self._reset_preview() + + if not self._db_client or not self._db_client.is_connected(): + return + + selected_ids = self._config.get("mes", {}).get("selected_article_ids") + if selected_ids is not None and len(selected_ids) == 0: + return + + all_items = self._db_client.get_reflector_list( + article_ids=selected_ids if selected_ids is not None else None ) + if not all_items: + return + + self._wk_map = self._db_client.get_wk_result_map() + active, inactive = self._db_client.split_articles_by_wk(selected_ids) + + self._db_items = all_items + + if active: + self._add_section_header(f"작업 대상 — WK_Result ({len(active)})") + for item in active: + self._add_product_item(item, in_wk=True) + + if inactive: + self._add_section_header(f"기타 ({len(inactive)})") + for item in inactive: + self._add_product_item(item, in_wk=False) + + self._refresh_list_styles() def _on_load_from_db(self): - if not self._db_client or not self._db_client.is_connected(): - QMessageBox.warning(self, "경고", "DB를 먼저 연결해주세요.") - return + pass - items = self._db_client.get_reflector_list() - if not items: - QMessageBox.warning(self, "경고", "조회된 제품이 없습니다.") - return + def _add_section_header(self, text: str): + hi = QListWidgetItem(text) + hi.setFlags(Qt.NoItemFlags) + hi.setForeground(QColor("#aaaaaa")) + hi.setBackground(QColor("#2a2a2a")) + hi.setSizeHint(QSize(0, 40)) + self._list.addItem(hi) - self._list.clear() - self._db_items = items - for item in items: - li = QListWidgetItem(item['article']) - li.setSizeHint(QSize(0, 52)) - li.setData(Qt.UserRole, item["article_id"]) - self._list.addItem(li) + def _add_product_item(self, item: dict, in_wk: bool): + li = QListWidgetItem(item["article"]) + li.setSizeHint(QSize(0, 52)) + li.setData(Qt.UserRole, item["article_id"]) + li.setData(Qt.UserRole + 1, item["article"]) + li.setData(Qt.UserRole + 2, in_wk) + if in_wk: + wk = self._wk_map.get(SQLClient._norm_id(item["article_id"]), {}) + tips = [] + if wk.get("machine_id") is not None: + tips.append(f"설비 ID: {SQLClient.format_db_value(wk.get('machine_id'))}") + if wk.get("machine") is not None: + tips.append(f"설비: {SQLClient.format_db_value(wk.get('machine'))}") + if wk.get("work_start_date") is not None: + tips.append(f"시작일: {SQLClient.format_db_value(wk.get('work_start_date'))}") + if wk.get("work_start_time") is not None: + tips.append(f"시작: {SQLClient.format_db_value(wk.get('work_start_time'))}") + if tips: + li.setToolTip("\n".join(tips)) + self._list.addItem(li) + self._style_product_item(li, in_wk, selected=False) - QMessageBox.information( - self, "완료", f"{len(items)}개 제품을 불러왔습니다." - ) + @staticmethod + def _style_product_item(item: QListWidgetItem, in_wk: bool, selected: bool): + if in_wk: + if selected: + bg, fg = "#777777", "#ffffff" + else: + bg, fg = "#555555", "#eeeeee" + elif selected: + bg, fg = "#3a3a3a", "#aaaaaa" + else: + bg, fg = "#1e1e1e", "#666666" + item.setBackground(QColor(bg)) + item.setForeground(QColor(fg)) + + def _refresh_list_styles(self, selected_row: int = -1): + if selected_row < 0: + selected_row = self._list.currentRow() + for i in range(self._list.count()): + item = self._list.item(i) + if not (item.flags() & Qt.ItemIsSelectable): + continue + in_wk = bool(item.data(Qt.UserRole + 2)) + self._style_product_item(item, in_wk, selected=(i == selected_row)) # ================================================================== # # 헬퍼 diff --git a/gui/pages/retrain_page.py b/gui/pages/retrain_page.py index afee2a1..87c99ac 100644 --- a/gui/pages/retrain_page.py +++ b/gui/pages/retrain_page.py @@ -754,7 +754,7 @@ class RetrainPage(QWidget): "color:#aaaaaa; font-size:12px; min-width:55px;" ) hint = QLabel("더블클릭: fit | 휠: 줌 | Space+드래그: 패닝 | Del: 박스삭제 | Ctrl+Z: 실행취소") - hint.setStyleSheet("color:#555555; font-size:11px;") + hint.setStyleSheet("color:#888888; font-size:12px;") btn_fit = QPushButton("초기화") btn_fit.setFixedHeight(28) btn_fit.setStyleSheet(_btn_style("#333333", font_size=12)) @@ -1174,6 +1174,12 @@ def _spinbox_style() -> str: " background:#2a2a2a; color:#ffffff; border:1px solid #555555;" " border-radius:4px; padding:4px 8px; font-size:14px; min-height:38px;" "}" + "QSpinBox::up-button {" + " subcontrol-origin:border; subcontrol-position:top right; width:30px;" + "}" + "QSpinBox::down-button {" + " subcontrol-origin:border; subcontrol-position:bottom right; width:30px;" + "}" ) diff --git a/gui/pages/settings_page.py b/gui/pages/settings_page.py index 332e1bf..f12f6db 100644 --- a/gui/pages/settings_page.py +++ b/gui/pages/settings_page.py @@ -1,12 +1,14 @@ import json import os +import sys -from PyQt5.QtCore import Qt, QTimer, pyqtSignal +from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QSize +from PyQt5.QtGui import QColor from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGridLayout, QPushButton, QLineEdit, QSpinBox, QDoubleSpinBox, QLabel, QMessageBox, QApplication, QFileDialog, - QDialog, QTabWidget, QFrame, + QDialog, QTabWidget, QFrame, QListWidget, QListWidgetItem, ) from camera.insight import InSightCamera @@ -16,6 +18,7 @@ from db.sql_client import SQLClient from plc.plc_client import PLCClient from paths import resolve_path, to_project_relative from utils.path_helper import get_path +from utils.touch_keyboard import show_touch_keyboard, hide_touch_keyboard from logger import log_action ADMIN_PASSWORD = "1234" @@ -225,6 +228,19 @@ class PasswordDialog(QDialog): # AdminSettingsDialog # ══════════════════════════════════════════════════════════════════════ # +class _TouchCheckList(QListWidget): + """행 전체 탭으로 체크 토글 (터치 화면용).""" + + def mousePressEvent(self, event): + item = self.itemAt(event.pos()) + if item is not None: + item.setCheckState( + Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked + ) + return + super().mousePressEvent(event) + + class AdminSettingsDialog(QDialog): def __init__(self, settings_page, parent=None): super().__init__(parent) @@ -258,6 +274,7 @@ class AdminSettingsDialog(QDialog): self._tabs.addTab(self._build_tab_cognex(), "코그넥스") self._tabs.addTab(self._build_tab_basler(), "Basler") self._tabs.addTab(self._build_tab_db(), "DB") + self._tabs.addTab(self._build_tab_mes(), "MES 제품") self._tabs.addTab(self._build_tab_ai(), "AI 모델") self._tabs.addTab(self._build_tab_conveyor(), "컨베이어") self._tabs.addTab(self._build_tab_plc(), "PLC") @@ -387,6 +404,74 @@ class AdminSettingsDialog(QDialog): form.addRow("", self._btn_pair(btn_connect, btn_save)) return self._tab_wrap(g) + # ── 탭 3b — MES 제품 선택 ───────────────────────────────────────── # + + def _build_tab_mes(self) -> QWidget: + w = QWidget() + w.setStyleSheet("background:#1a1a1a;") + layout = QVBoxLayout(w) + layout.setContentsMargins(32, 24, 32, 20) + layout.setSpacing(12) + + hint = QLabel( + "제품 등록 탭의 'MES 불러오기'에 표시할 제품을 선택합니다.\n" + "DB 연결 후 목록을 불러온 뒤 체크하고 저장하세요." + ) + hint.setStyleSheet("color:#888888; font-size:13px; background:transparent;") + hint.setWordWrap(True) + layout.addWidget(hint) + + btn_row = QHBoxLayout() + btn_row.setSpacing(8) + + btn_load = QPushButton("목록 불러오기") + btn_load.setFixedHeight(42) + btn_load.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px")) + btn_load.clicked.connect(self._on_mes_load) + + btn_all = QPushButton("전체 선택") + btn_all.setFixedHeight(42) + btn_all.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px")) + btn_all.clicked.connect(lambda: self._on_mes_set_all(True)) + + btn_none = QPushButton("전체 해제") + btn_none.setFixedHeight(42) + btn_none.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px")) + btn_none.clicked.connect(lambda: self._on_mes_set_all(False)) + + btn_row.addWidget(btn_load, stretch=2) + btn_row.addWidget(btn_all, stretch=1) + btn_row.addWidget(btn_none, stretch=1) + layout.addLayout(btn_row) + + self._mes_list = _TouchCheckList() + self._mes_list.setSelectionMode(QListWidget.NoSelection) + self._mes_list.setMinimumHeight(380) + self._mes_list.setStyleSheet(""" + QListWidget { + background:#1a1a1a; border:1px solid #333333; + border-radius:4px; outline:none; font-size:15px; + } + QListWidget::item { + padding:12px 16px; border-bottom:1px solid #2a2a2a; + } + QListWidget::indicator { width:0px; height:0px; } + """) + self._mes_list.itemChanged.connect(self._on_mes_item_changed) + layout.addWidget(self._mes_list, stretch=1) + + self._mes_count_lbl = QLabel("선택: 0 / 0") + self._mes_count_lbl.setStyleSheet("color:#888888; font-size:13px; background:transparent;") + layout.addWidget(self._mes_count_lbl) + + btn_save = QPushButton("선택 저장") + btn_save.setFixedHeight(56) + btn_save.setStyleSheet(_BTN_DLG_PRIMARY) + btn_save.clicked.connect(self._on_mes_save) + layout.addWidget(btn_save) + + return w + # ── 탭 4 — AI 모델 ──────────────────────────────────────────────── # def _build_tab_ai(self) -> QWidget: @@ -538,10 +623,60 @@ class AdminSettingsDialog(QDialog): btn_close.setStyleSheet(_BTN_DLG) btn_close.clicked.connect(self.accept) + # 터치 키보드 표시/숨김 토글 버튼 (물리 키보드 없는 터치 모니터용) + self._kb_btn = QPushButton("\u2328") # ⌨ 키보드 글리프 + self._kb_btn.setCheckable(True) + self._kb_btn.setFixedSize(56, 56) + self._kb_btn.setToolTip("터치 키보드 표시 / 숨기기") + self._kb_btn.setStyleSheet( + "QPushButton {" + " background:#2a2a2a; color:#cccccc; border:1px solid #555555;" + " border-radius:4px; font-size:24px;" + "}" + "QPushButton:hover { background:#333333; color:#ffffff; }" + "QPushButton:checked { background:#1D9E75; color:#ffffff; border:none; }" + ) + self._kb_btn.toggled.connect(self._on_toggle_keyboard) + + row.addWidget(self._kb_btn) row.addWidget(btn_save_all, stretch=2) row.addWidget(btn_close, stretch=1) return bar + # ── 터치 키보드 토글 ─────────────────────────────────────────────── # + + def _on_toggle_keyboard(self, checked: bool): + if checked: + if self._show_touch_keyboard(): + log_action("[설정] 터치 키보드 표시") + else: + # 실패 시 토글 상태 원복 (시그널 재발생 방지) + self._kb_btn.blockSignals(True) + self._kb_btn.setChecked(False) + self._kb_btn.blockSignals(False) + QMessageBox.warning( + self, "터치 키보드", + "터치 키보드를 열 수 없습니다.\n" + "Windows 터치 키보드 또는 화상 키보드를 사용할 수 있는지 확인하세요.", + ) + else: + self._hide_touch_keyboard() + log_action("[설정] 터치 키보드 숨김") + + def _show_touch_keyboard(self) -> bool: + """Windows 터치 키보드(TabTip)를 ITipInvocation COM으로 표시한다.""" + return show_touch_keyboard() + + def _hide_touch_keyboard(self): + """표시 중인 터치 키보드를 숨긴다.""" + hide_touch_keyboard() + + def done(self, result: int): + # 키보드를 켠 상태에서만 숨김 (Toggle은 반전이라 미사용 시 닫으면 오히려 켜짐) + if self._kb_btn.isChecked(): + self._hide_touch_keyboard() + super().done(result) + @staticmethod def _make_group(title: str): from PyQt5.QtWidgets import QGroupBox @@ -688,6 +823,95 @@ class AdminSettingsDialog(QDialog): self._sp._config.setdefault("db", {}).update(cfg) QMessageBox.information(self, "저장", "DB 설정이 저장되었습니다.") + # ── MES 제품 탭 슬롯 ─────────────────────────────────────────────── # + + def _get_mes_selected_ids(self) -> list: + ids = [] + for i in range(self._mes_list.count()): + item = self._mes_list.item(i) + if item.checkState() == Qt.Checked: + ids.append(item.data(Qt.UserRole)) + return ids + + def _update_mes_count(self): + total = self._mes_list.count() + selected = len(self._get_mes_selected_ids()) + self._mes_count_lbl.setText(f"선택: {selected} / {total}") + + @staticmethod + def _style_mes_item(item): + name = item.data(Qt.UserRole + 1) or item.text().lstrip("✓ ").lstrip(" ") + item.setData(Qt.UserRole + 1, name) + if item.checkState() == Qt.Checked: + item.setText(f"✓ {name}") + item.setBackground(QColor("#666666")) + item.setForeground(QColor("#ffffff")) + else: + item.setText(f" {name}") + item.setBackground(QColor("#1e1e1e")) + item.setForeground(QColor("#888888")) + + def _on_mes_item_changed(self, item): + self._style_mes_item(item) + self._update_mes_count() + + def _on_mes_set_all(self, checked: bool): + state = Qt.Checked if checked else Qt.Unchecked + self._mes_list.blockSignals(True) + for i in range(self._mes_list.count()): + item = self._mes_list.item(i) + item.setCheckState(state) + self._style_mes_item(item) + self._mes_list.blockSignals(False) + self._update_mes_count() + + def _on_mes_load(self): + client = self._sp._db_client + if not client or not client.is_connected(): + QMessageBox.warning( + self, "경고", + "DB가 연결되어 있지 않습니다.\n" + "DB 탭에서 먼저 연결해주세요.", + ) + return + + items = client.get_all_articles() + if not items: + QMessageBox.warning(self, "경고", "조회된 제품이 없습니다.") + return + + saved_ids = set(self._sp._config.get("mes", {}).get("selected_article_ids", [])) + + self._mes_list.blockSignals(True) + self._mes_list.clear() + for item in items: + li = QListWidgetItem(item["article"]) + li.setSizeHint(QSize(0, 52)) + li.setFlags(li.flags() | Qt.ItemIsUserCheckable) + li.setData(Qt.UserRole, item["article_id"]) + li.setData(Qt.UserRole + 1, item["article"]) + li.setToolTip( + f"ID: {item['article_id']} | 모델: {item.get('buyer_article_no', '')}" + ) + li.setCheckState( + Qt.Checked if item["article_id"] in saved_ids else Qt.Unchecked + ) + self._style_mes_item(li) + self._mes_list.addItem(li) + self._mes_list.blockSignals(False) + self._update_mes_count() + log_action(f"[설정] MES 제품 목록 불러오기: {len(items)}개") + + def _on_mes_save(self): + selected_ids = self._get_mes_selected_ids() + log_action(f"[설정] MES 제품 선택 저장: {len(selected_ids)}개") + self._sp._save_config({"mes": {"selected_article_ids": selected_ids}}) + self._sp._config.setdefault("mes", {})["selected_article_ids"] = selected_ids + QMessageBox.information( + self, "저장", + f"{len(selected_ids)}개 제품이 MES 불러오기 목록에 저장되었습니다.", + ) + # ── AI 탭 슬롯 ───────────────────────────────────────────────────── # def _on_ai_browse(self): @@ -778,6 +1002,13 @@ class AdminSettingsDialog(QDialog): "ip": self._plc_ip.text().strip(), "port": self._plc_port.value(), }, + "mes": { + "selected_article_ids": ( + self._get_mes_selected_ids() + if self._mes_list.count() > 0 + else self._sp._config.get("mes", {}).get("selected_article_ids", []) + ), + }, } try: self._sp._save_config(data) diff --git a/logic/group_manager.py b/logic/group_manager.py index ce9d44a..d5ab9e5 100644 --- a/logic/group_manager.py +++ b/logic/group_manager.py @@ -1,25 +1,18 @@ -# 그룹 관리 — A/B 모델 그룹 수동 전환 (최대 4종 per group) +# 검사 대상 관리 — DB 미연결 시 PatMax 슬롯 1~16 허용 (레거시 호환) +from logic.products import MAX_PATMAX_SLOTS + + class GroupManager: - MAX_PER_GROUP = 4 - - def __init__(self): - self._group_a: list = [] - self._group_b: list = [] - self._active: str = "A" - - def set_group_a(self, model_list: list): - self._group_a = model_list[: self.MAX_PER_GROUP] - - def set_group_b(self, model_list: list): - self._group_b = model_list[: self.MAX_PER_GROUP] + """레거시 호환용. 그룹 A/B 전환·선택 제한은 비활성화.""" def get_active_group(self) -> list: - return self._group_a if self._active == "A" else self._group_b + return [] def get_active_name(self) -> str: - return self._active + return "ALL" + + def get_allowed_ids(self) -> list: + return list(range(1, MAX_PATMAX_SLOTS + 1)) def switch_group(self) -> str: - """A↔B 전환 후 활성 그룹 이름 반환""" - self._active = "B" if self._active == "A" else "A" - return self._active + return "ALL" diff --git a/logic/inspector.py b/logic/inspector.py index f816cdb..e19d9d8 100644 --- a/logic/inspector.py +++ b/logic/inspector.py @@ -2,17 +2,19 @@ import cv2 import numpy as np -# Cognex 카메라 셀 매핑 (GV 방식 fallback용으로 유지) -PATTERN_RESULT_CELLS = { - "A27": {"id": 1, "name": "LOW REF", "model": "LX3", "type": "RH"}, - "A77": {"id": 2, "name": "LOW REF", "model": "LX3", "type": "LH"}, - "A127": {"id": 3, "name": "LOW REF NAS", "model": "LX3", "type": "RH"}, - "A177": {"id": 4, "name": "LOW REF NAS", "model": "LX3", "type": "LH"}, -} +from db.sql_client import SQLClient +from logic.products import model_display_label class Inspector: + def __init__(self): + self._pattern_cells: dict = {} + + def set_pattern_cells(self, cells: dict): + """DB 기반 PatMax 셀 매핑 (refresh_wk_results 시 갱신).""" + self._pattern_cells = cells or {} + # ── Python PatMax 매칭 (주 경로) ─────────────────────────────────── # def match_image(self, image_bytes: bytes, matcher: "PatternMatcher") -> dict: @@ -47,9 +49,9 @@ class Inspector: # ── Cognex GV 셀 방식 (fallback) ────────────────────────────────── # def read_patmax_results(self, insight) -> dict: - """A27/A77/A127/A177 셀 조회 → #ERR이면 실패, 그 외 점수 파싱.""" + """PatMax 결과 셀 조회 → #ERR이면 실패, 그 외 점수 파싱.""" results = {} - for cell, model_info in PATTERN_RESULT_CELLS.items(): + for cell, model_info in self._pattern_cells.items(): try: insight._send(f"GV{cell}") code = insight._read_line() @@ -85,8 +87,10 @@ class Inspector: # ── 공통: 모델 판별 + 판정 ──────────────────────────────────────── # - def identify_model(self, results: dict, allowed_model_ids: list) -> dict: - """매칭된 패턴 중 점수가 가장 높은 것을 선택해 허용 모델 여부 판별.""" + def identify_model(self, results: dict, + allowed_model_ids: "list | None" = None, + allowed_article_ids: "set | None" = None) -> dict: + """매칭된 패턴 중 점수가 가장 높은 것을 선택해 허용 여부 판별.""" matched_patterns = [ (cell, info) for cell, info in results.items() if info["matched"] @@ -100,19 +104,26 @@ class Inspector: } _best_cell, best_info = max(matched_patterns, key=lambda x: x[1]["score"]) - model = best_info["model"] - in_allowed = model["id"] in allowed_model_ids + model = best_info["model"] + label = model_display_label(model) + + if allowed_article_ids is not None: + in_allowed = ( + SQLClient._norm_id(model.get("article_id")) in allowed_article_ids + ) + else: + in_allowed = model["id"] in (allowed_model_ids or []) return { - "matched": True, - "in_allowed": in_allowed, - "model": model, - "score": best_info["score"], + "matched": True, + "in_allowed": in_allowed, + "model": model, + "score": best_info["score"], "cognex_pass": in_allowed, "status": ( - f"{model['name']} {model['model']} {model['type']} ({best_info['score']:.1f}점)" + f"{label} ({best_info['score']:.1f}점)" if in_allowed - else f"허용 외 모델: {model['name']} {model['model']} {model['type']}" + else f"작업 대상 외: {label}" ), } diff --git a/logic/products.py b/logic/products.py new file mode 100644 index 0000000..5619b6d --- /dev/null +++ b/logic/products.py @@ -0,0 +1,56 @@ +# Cognex PatMax GV 셀 주소 + DB 제품 기반 런타임 매핑 + +MAX_PATMAX_SLOTS = 16 + +# Cognex job: PatMax 블록마다 50행 간격, 첫 결과 셀 = A27 +_PATMAX_FIRST_ROW = 27 +_PATMAX_ROW_STEP = 50 + + +def patmax_cell_address(slot_index: int) -> str: + """0-based PatMax 슬롯 → GV 셀 주소 (A27, A77, …).""" + row = _PATMAX_FIRST_ROW + slot_index * _PATMAX_ROW_STEP + return f"A{row}" + + +def build_patmax_cells(articles: list) -> dict: + """ + DB 제품 목록(관리자 MES 선택 순서)으로 PatMax 셀 → 제품 정보 매핑 생성. + Cognex job 슬롯 순서와 mes.selected_article_ids 순서가 일치해야 함. + """ + cells = {} + for i, article in enumerate(articles[:MAX_PATMAX_SLOTS]): + cells[patmax_cell_address(i)] = article_to_model(article, slot_id=i + 1) + return cells + + +def article_to_model(article: dict, slot_id: int) -> dict: + """DB article 행 → PatMax/검사용 model dict.""" + return { + "id": slot_id, + "article_id": article["article_id"], + "article": article.get("article", ""), + "name": article.get("article", ""), + "model": article.get("buyer_article_no", "") or "", + "type": "", + } + + +def article_label(article: dict) -> str: + """DB article / model dict UI 표시용.""" + if article.get("article"): + return article["article"] + if article.get("name"): + return article["name"] + return str(article.get("article_id", "")) + + +def model_display_label(model: dict) -> str: + """PatMax 인식 결과 model dict 표시용.""" + if model.get("article"): + return model["article"] + parts = [model.get("name", ""), model.get("model", "")] + parts = [p for p in parts if p] + if model.get("type"): + parts.append(model["type"]) + return " / ".join(parts) if parts else str(model.get("article_id", "")) diff --git a/logs/inspect/2026-06-12.csv b/logs/inspect/2026-06-12.csv new file mode 100644 index 0000000..f5ca68b --- /dev/null +++ b/logs/inspect/2026-06-12.csv @@ -0,0 +1,110 @@ +timestamp,group,result,cognex_pass,basler_pass,detected_models +2026-06-12 15:06:10,A,UNKNOWN,N,Y, +2026-06-12 15:06:13,A,UNKNOWN,N,Y, +2026-06-12 15:06:17,A,UNKNOWN,N,Y, +2026-06-12 15:06:21,A,UNKNOWN,N,Y, +2026-06-12 15:06:24,A,UNKNOWN,N,Y, +2026-06-12 15:06:28,A,UNKNOWN,N,Y, +2026-06-12 15:06:31,A,UNKNOWN,N,Y, +2026-06-12 15:06:35,A,UNKNOWN,N,Y, +2026-06-12 15:06:38,A,UNKNOWN,N,Y, +2026-06-12 15:06:42,A,UNKNOWN,N,Y, +2026-06-12 15:06:45,A,UNKNOWN,N,Y, +2026-06-12 15:06:49,A,UNKNOWN,N,Y, +2026-06-12 15:06:53,A,UNKNOWN,N,Y, +2026-06-12 15:06:56,A,UNKNOWN,N,Y, +2026-06-12 15:07:00,A,UNKNOWN,N,Y, +2026-06-12 15:07:04,A,UNKNOWN,N,Y, +2026-06-12 15:07:07,A,UNKNOWN,N,Y, +2026-06-12 15:07:11,A,UNKNOWN,N,Y, +2026-06-12 15:07:14,A,UNKNOWN,N,Y, +2026-06-12 15:07:18,A,UNKNOWN,N,Y, +2026-06-12 15:07:22,A,UNKNOWN,N,Y, +2026-06-12 15:07:25,A,UNKNOWN,N,Y, +2026-06-12 15:07:29,A,UNKNOWN,N,Y, +2026-06-12 15:07:33,A,UNKNOWN,N,Y, +2026-06-12 15:07:36,A,UNKNOWN,N,Y, +2026-06-12 15:07:40,A,UNKNOWN,N,Y, +2026-06-12 15:07:43,A,UNKNOWN,N,Y, +2026-06-12 15:07:47,A,UNKNOWN,N,Y, +2026-06-12 15:07:51,A,UNKNOWN,N,Y, +2026-06-12 15:24:53,A,UNKNOWN,N,Y, +2026-06-12 15:24:57,A,UNKNOWN,N,Y, +2026-06-12 15:25:00,A,UNKNOWN,N,Y, +2026-06-12 15:25:04,A,UNKNOWN,N,Y, +2026-06-12 15:25:07,A,UNKNOWN,N,Y, +2026-06-12 15:25:11,A,UNKNOWN,N,Y, +2026-06-12 15:25:15,A,UNKNOWN,N,Y, +2026-06-12 15:25:18,A,UNKNOWN,N,Y, +2026-06-12 15:25:22,A,UNKNOWN,N,Y, +2026-06-12 15:25:26,A,UNKNOWN,N,Y, +2026-06-12 15:25:29,A,UNKNOWN,N,Y, +2026-06-12 15:25:33,A,UNKNOWN,N,Y, +2026-06-12 15:25:36,A,UNKNOWN,N,Y, +2026-06-12 15:25:40,A,UNKNOWN,N,Y, +2026-06-12 15:25:44,A,UNKNOWN,N,Y, +2026-06-12 15:25:47,A,UNKNOWN,N,Y, +2026-06-12 15:25:51,A,UNKNOWN,N,Y, +2026-06-12 15:25:55,A,UNKNOWN,N,Y, +2026-06-12 15:25:58,A,UNKNOWN,N,Y, +2026-06-12 15:26:02,A,UNKNOWN,N,Y, +2026-06-12 15:26:05,A,UNKNOWN,N,Y, +2026-06-12 15:26:09,A,UNKNOWN,N,Y, +2026-06-12 15:26:13,A,UNKNOWN,N,Y, +2026-06-12 15:26:16,A,UNKNOWN,N,Y, +2026-06-12 15:26:20,A,UNKNOWN,N,Y, +2026-06-12 15:26:24,A,UNKNOWN,N,Y, +2026-06-12 15:26:27,A,UNKNOWN,N,Y, +2026-06-12 15:26:31,A,UNKNOWN,N,Y, +2026-06-12 15:26:35,A,UNKNOWN,N,Y, +2026-06-12 15:26:38,A,UNKNOWN,N,Y, +2026-06-12 15:26:42,A,UNKNOWN,N,Y, +2026-06-12 15:26:45,A,UNKNOWN,N,Y, +2026-06-12 15:26:49,A,UNKNOWN,N,Y, +2026-06-12 15:26:53,A,UNKNOWN,N,Y, +2026-06-12 15:26:56,A,UNKNOWN,N,Y, +2026-06-12 15:27:00,A,UNKNOWN,N,Y, +2026-06-12 15:27:04,A,UNKNOWN,N,Y, +2026-06-12 15:27:07,A,UNKNOWN,N,Y, +2026-06-12 15:27:11,A,UNKNOWN,N,Y, +2026-06-12 15:27:14,A,UNKNOWN,N,Y, +2026-06-12 15:27:18,A,UNKNOWN,N,Y, +2026-06-12 15:27:22,A,UNKNOWN,N,Y, +2026-06-12 15:27:25,A,UNKNOWN,N,Y, +2026-06-12 15:36:50,A,UNKNOWN,N,Y, +2026-06-12 15:36:53,A,UNKNOWN,N,Y, +2026-06-12 15:36:57,A,UNKNOWN,N,Y, +2026-06-12 15:37:00,A,UNKNOWN,N,Y, +2026-06-12 15:37:03,A,UNKNOWN,N,Y, +2026-06-12 15:37:07,A,UNKNOWN,N,Y, +2026-06-12 16:09:20,A,UNKNOWN,N,Y, +2026-06-12 16:09:23,A,UNKNOWN,N,Y, +2026-06-12 16:09:27,A,UNKNOWN,N,Y, +2026-06-12 16:09:30,A,UNKNOWN,N,Y, +2026-06-12 16:09:33,A,UNKNOWN,N,Y, +2026-06-12 16:09:40,A,UNKNOWN,N,Y, +2026-06-12 16:09:43,A,UNKNOWN,N,Y, +2026-06-12 16:09:47,A,UNKNOWN,N,Y, +2026-06-12 16:09:53,A,UNKNOWN,N,Y, +2026-06-12 16:09:57,A,UNKNOWN,N,Y, +2026-06-12 16:10:00,A,UNKNOWN,N,Y, +2026-06-12 16:10:03,A,UNKNOWN,N,Y, +2026-06-12 16:10:07,A,UNKNOWN,N,Y, +2026-06-12 16:10:10,A,UNKNOWN,N,Y, +2026-06-12 16:10:14,A,UNKNOWN,N,Y, +2026-06-12 16:50:10,A,UNKNOWN,N,Y, +2026-06-12 16:50:13,A,UNKNOWN,N,Y, +2026-06-12 16:53:55,A,UNKNOWN,N,Y, +2026-06-12 16:54:12,A,UNKNOWN,N,Y, +2026-06-12 16:54:22,A,UNKNOWN,N,Y, +2026-06-12 16:54:25,A,UNKNOWN,N,Y, +2026-06-12 16:54:29,A,UNKNOWN,N,Y, +2026-06-12 16:54:32,A,UNKNOWN,N,Y, +2026-06-12 16:54:35,A,UNKNOWN,N,Y, +2026-06-12 16:54:39,A,UNKNOWN,N,Y, +2026-06-12 16:54:42,A,UNKNOWN,N,Y, +2026-06-12 16:54:45,A,UNKNOWN,N,Y, +2026-06-12 16:54:49,A,UNKNOWN,N,Y, +2026-06-12 16:54:52,A,UNKNOWN,N,Y, +2026-06-12 16:54:55,A,UNKNOWN,N,Y, +2026-06-12 16:54:59,A,UNKNOWN,N,Y, diff --git a/logs/inspect/2026-06-15.csv b/logs/inspect/2026-06-15.csv new file mode 100644 index 0000000..22f1849 --- /dev/null +++ b/logs/inspect/2026-06-15.csv @@ -0,0 +1,20 @@ +timestamp,group,result,cognex_pass,basler_pass,detected_models +2026-06-15 15:36:58,A,UNKNOWN,N,Y, +2026-06-15 15:37:02,A,UNKNOWN,N,Y, +2026-06-15 15:37:05,A,UNKNOWN,N,Y, +2026-06-15 15:37:08,A,UNKNOWN,N,Y, +2026-06-15 15:37:12,A,UNKNOWN,N,Y, +2026-06-15 15:37:15,A,UNKNOWN,N,Y, +2026-06-15 15:37:18,A,UNKNOWN,N,Y, +2026-06-15 15:37:22,A,UNKNOWN,N,Y, +2026-06-15 15:37:25,A,UNKNOWN,N,Y, +2026-06-15 15:37:28,A,UNKNOWN,N,Y, +2026-06-15 15:37:32,A,UNKNOWN,N,Y, +2026-06-15 15:37:35,A,UNKNOWN,N,Y, +2026-06-15 17:20:49,ALL,UNKNOWN,N,Y, +2026-06-15 17:20:53,ALL,UNKNOWN,N,Y, +2026-06-15 17:20:56,ALL,UNKNOWN,N,Y, +2026-06-15 17:20:59,ALL,UNKNOWN,N,Y, +2026-06-15 17:21:03,ALL,UNKNOWN,N,Y, +2026-06-15 17:21:06,ALL,UNKNOWN,N,Y, +2026-06-15 17:21:10,ALL,UNKNOWN,N,Y, diff --git a/logs/timing/2026-06-12.csv b/logs/timing/2026-06-12.csv new file mode 100644 index 0000000..67498b0 --- /dev/null +++ b/logs/timing/2026-06-12.csv @@ -0,0 +1,1777 @@ +timestamp,seq,event,elapsed_ms,detail +15:06:05.234,1,cycle_start,0.0,group=A belt_delay=3.33s +15:06:05.234,1,cognex_trigger_send,2.0, +15:06:05.249,1,cognex_trigger_ok,8.9, +15:06:06.257,1,cognex_ftp_start,1011.0, +15:06:07.012,1,cognex_ftp_done,1766.2,3686454bytes +15:06:07.013,1,cognex_patmax_start,1767.6, +15:06:07.013,1,cognex_patmax_done,1778.9, +15:06:08.579,1,basler_capture_start,3333.9, +15:06:09.008,1,basler_capture_done,3775.8,"(4504, 4504)" +15:06:10.417,1,cognex_join_wait,5174.7, +15:06:10.417,1,cognex_join_done,5176.6, +15:06:10.432,1,cycle_done,5193.3,result=FAIL cognex=FAIL basler=PASS +15:06:10.432,2,cycle_start,0.0,group=A belt_delay=3.33s +15:06:10.446,2,cognex_trigger_send,2.6, +15:06:10.449,2,cognex_trigger_ok,9.2, +15:06:11.455,2,cognex_ftp_start,1011.4, +15:06:12.211,2,cognex_ftp_done,1767.7,3686454bytes +15:06:12.213,2,cognex_patmax_start,1769.1, +15:06:12.213,2,cognex_patmax_done,1781.0, +15:06:13.777,2,basler_capture_start,3334.0, +15:06:13.925,2,basler_capture_done,3495.8,"(4504, 4504)" +15:06:13.941,2,cognex_join_wait,3501.1, +15:06:13.963,2,cognex_join_done,3518.6, +15:06:13.965,2,cycle_done,3522.2,result=FAIL cognex=FAIL basler=PASS +15:06:13.965,3,cycle_start,0.0,group=A belt_delay=3.33s +15:06:13.965,3,cognex_trigger_send,2.4, +15:06:13.965,3,cognex_trigger_ok,9.2, +15:06:14.981,3,cognex_ftp_start,1011.4, +15:06:15.729,3,cognex_ftp_done,1759.7,3686454bytes +15:06:15.729,3,cognex_patmax_start,1761.2, +15:06:15.729,3,cognex_patmax_done,1772.9, +15:06:17.303,3,basler_capture_start,3334.1, +15:06:17.460,3,basler_capture_done,3500.9,"(4504, 4504)" +15:06:17.476,3,cognex_join_wait,3511.9, +15:06:17.492,3,cognex_join_done,3522.5, +15:06:17.492,3,cycle_done,3533.4,result=FAIL cognex=FAIL basler=PASS +15:06:17.492,4,cycle_start,0.0,group=A belt_delay=3.33s +15:06:17.507,4,cognex_trigger_send,2.4, +15:06:17.513,4,cognex_trigger_ok,8.9, +15:06:18.517,4,cognex_ftp_start,1010.9, +15:06:19.263,4,cognex_ftp_done,1756.6,3686454bytes +15:06:19.263,4,cognex_patmax_start,1758.2, +15:06:19.263,4,cognex_patmax_done,1769.9, +15:06:20.841,4,basler_capture_start,3334.2, +15:06:20.992,4,basler_capture_done,3499.7,"(4504, 4504)" +15:06:21.008,4,cognex_join_wait,3510.7, +15:06:21.023,4,cognex_join_done,3513.0, +15:06:21.039,4,cycle_done,3532.9,result=FAIL cognex=FAIL basler=PASS +15:06:21.039,5,cycle_start,0.0,group=A belt_delay=3.33s +15:06:21.039,5,cognex_trigger_send,2.4, +15:06:21.046,5,cognex_trigger_ok,9.3, +15:06:22.055,5,cognex_ftp_start,1011.5, +15:06:22.812,5,cognex_ftp_done,1768.7,3686454bytes +15:06:22.813,5,cognex_patmax_start,1770.3, +15:06:22.813,5,cognex_patmax_done,1781.9, +15:06:24.377,5,basler_capture_start,3333.9, +15:06:24.542,5,basler_capture_done,3501.0,"(4504, 4504)" +15:06:24.542,5,cognex_join_wait,3511.9, +15:06:24.573,5,cognex_join_done,3516.1, +15:06:24.573,5,cycle_done,3532.9,result=FAIL cognex=FAIL basler=PASS +15:06:24.579,6,cycle_start,0.0,group=A belt_delay=3.33s +15:06:24.579,6,cognex_trigger_send,2.5, +15:06:24.579,6,cognex_trigger_ok,9.2, +15:06:25.591,6,cognex_ftp_start,1011.5, +15:06:26.346,6,cognex_ftp_done,1766.9,3686454bytes +15:06:26.346,6,cognex_patmax_start,1768.4, +15:06:26.346,6,cognex_patmax_done,1780.0, +15:06:27.913,6,basler_capture_start,3333.4, +15:06:28.076,6,basler_capture_done,3498.9,"(4504, 4504)" +15:06:28.076,6,cognex_join_wait,3509.9, +15:06:28.107,6,cognex_join_done,3527.9, +15:06:28.107,6,cycle_done,3530.4,result=FAIL cognex=FAIL basler=PASS +15:06:28.114,7,cycle_start,0.0,group=A belt_delay=3.33s +15:06:28.115,7,cognex_trigger_send,2.5, +15:06:28.115,7,cognex_trigger_ok,9.3, +15:06:29.125,7,cognex_ftp_start,1011.3, +15:06:29.863,7,cognex_ftp_done,1749.5,3686454bytes +15:06:29.863,7,cognex_patmax_start,1750.9, +15:06:29.863,7,cognex_patmax_done,1760.4, +15:06:31.448,7,basler_capture_start,3333.8, +15:06:31.607,7,basler_capture_done,3504.8,"(4504, 4504)" +15:06:31.622,7,cognex_join_wait,3515.7, +15:06:31.622,7,cognex_join_done,3517.4, +15:06:31.648,7,cycle_done,3533.8,result=FAIL cognex=FAIL basler=PASS +15:06:31.649,8,cycle_start,0.0,group=A belt_delay=3.33s +15:06:31.649,8,cognex_trigger_send,1.8, +15:06:31.649,8,cognex_trigger_ok,8.4, +15:06:32.663,8,cognex_ftp_start,1010.8, +15:06:33.396,8,cognex_ftp_done,1744.2,3686454bytes +15:06:33.396,8,cognex_patmax_start,1745.7, +15:06:33.396,8,cognex_patmax_done,1757.3, +15:06:34.985,8,basler_capture_start,3333.9, +15:06:35.156,8,basler_capture_done,3505.2,"(4504, 4504)" +15:06:35.156,8,cognex_join_wait,3516.2, +15:06:35.171,8,cognex_join_done,3520.0, +15:06:35.187,8,cycle_done,3538.3,result=FAIL cognex=FAIL basler=PASS +15:06:35.187,9,cycle_start,0.0,group=A belt_delay=3.33s +15:06:35.196,9,cognex_trigger_send,2.3, +15:06:35.196,9,cognex_trigger_ok,9.0, +15:06:36.205,9,cognex_ftp_start,1011.4, +15:06:36.963,9,cognex_ftp_done,1772.1,3686454bytes +15:06:36.963,9,cognex_patmax_start,1773.5, +15:06:36.978,9,cognex_patmax_done,1785.2, +15:06:38.527,9,basler_capture_start,3333.8, +15:06:38.692,9,basler_capture_done,3511.2,"(4504, 4504)" +15:06:38.708,9,cognex_join_wait,3522.0, +15:06:38.733,9,cognex_join_done,3532.2, +15:06:38.733,9,cycle_done,3542.8,result=FAIL cognex=FAIL basler=PASS +15:06:38.733,10,cycle_start,0.0,group=A belt_delay=3.33s +15:06:38.733,10,cognex_trigger_send,2.3, +15:06:38.748,10,cognex_trigger_ok,8.8, +15:06:39.751,10,cognex_ftp_start,1011.0, +15:06:40.482,10,cognex_ftp_done,1753.7,3686454bytes +15:06:40.495,10,cognex_patmax_start,1755.2, +15:06:40.496,10,cognex_patmax_done,1766.8, +15:06:42.074,10,basler_capture_start,3333.8, +15:06:42.239,10,basler_capture_done,3511.7,"(4504, 4504)" +15:06:42.255,10,cognex_join_wait,3523.3, +15:06:42.281,10,cognex_join_done,3533.8, +15:06:42.283,10,cycle_done,3544.7,result=FAIL cognex=FAIL basler=PASS +15:06:42.283,11,cycle_start,0.0,group=A belt_delay=3.33s +15:06:42.283,11,cognex_trigger_send,2.3, +15:06:42.297,11,cognex_trigger_ok,9.1, +15:06:43.299,11,cognex_ftp_start,1011.1, +15:06:44.045,11,cognex_ftp_done,1756.7,3686454bytes +15:06:44.046,11,cognex_patmax_start,1758.3, +15:06:44.046,11,cognex_patmax_done,1769.9, +15:06:45.622,11,basler_capture_start,3333.7, +15:06:45.870,11,basler_capture_done,3590.5,"(4504, 4504)" +15:06:45.886,11,cognex_join_wait,3602.3, +15:06:45.886,11,cognex_join_done,3604.5, +15:06:45.901,11,cycle_done,3622.6,result=FAIL cognex=FAIL basler=PASS +15:06:45.914,12,cycle_start,0.0,group=A belt_delay=3.33s +15:06:45.916,12,cognex_trigger_send,2.5, +15:06:45.916,12,cognex_trigger_ok,9.0, +15:06:46.926,12,cognex_ftp_start,1011.6, +15:06:47.662,12,cognex_ftp_done,1747.1,3686454bytes +15:06:47.663,12,cognex_patmax_start,1748.6, +15:06:47.663,12,cognex_patmax_done,1760.3, +15:06:49.248,12,basler_capture_start,3333.8, +15:06:49.500,12,basler_capture_done,3590.6,"(4504, 4504)" +15:06:49.516,12,cognex_join_wait,3602.5, +15:06:49.516,12,cognex_join_done,3604.6, +15:06:49.531,12,cycle_done,3621.7,result=FAIL cognex=FAIL basler=PASS +15:06:49.531,13,cycle_start,0.0,group=A belt_delay=3.33s +15:06:49.531,13,cognex_trigger_send,2.4, +15:06:49.548,13,cognex_trigger_ok,8.9, +15:06:50.551,13,cognex_ftp_start,1011.0, +15:06:51.279,13,cognex_ftp_done,1739.5,3686454bytes +15:06:51.279,13,cognex_patmax_start,1741.0, +15:06:51.279,13,cognex_patmax_done,1752.9, +15:06:52.874,13,basler_capture_start,3334.1, +15:06:53.117,13,basler_capture_done,3591.4,"(4504, 4504)" +15:06:53.133,13,cognex_join_wait,3603.2, +15:06:53.148,13,cognex_join_done,3605.5, +15:06:53.165,13,cycle_done,3624.7,result=FAIL cognex=FAIL basler=PASS +15:06:53.166,14,cycle_start,0.0,group=A belt_delay=3.33s +15:06:53.166,14,cognex_trigger_send,2.4, +15:06:53.166,14,cognex_trigger_ok,8.9, +15:06:54.180,14,cognex_ftp_start,1010.7, +15:06:54.929,14,cognex_ftp_done,1762.4,3686454bytes +15:06:54.929,14,cognex_patmax_start,1763.8, +15:06:54.929,14,cognex_patmax_done,1773.5, +15:06:56.502,14,basler_capture_start,3334.0, +15:06:56.753,14,basler_capture_done,3591.0,"(4504, 4504)" +15:06:56.768,14,cognex_join_wait,3603.1, +15:06:56.768,14,cognex_join_done,3604.9, +15:06:56.784,14,cycle_done,3621.6,result=FAIL cognex=FAIL basler=PASS +15:06:56.784,15,cycle_start,0.0,group=A belt_delay=3.33s +15:06:56.784,15,cognex_trigger_send,1.7, +15:06:56.797,15,cognex_trigger_ok,8.5, +15:06:57.805,15,cognex_ftp_start,1011.0, +15:06:58.563,15,cognex_ftp_done,1769.8,3686454bytes +15:06:58.563,15,cognex_patmax_start,1771.3, +15:06:58.563,15,cognex_patmax_done,1783.2, +15:07:00.127,15,basler_capture_start,3333.8, +15:07:00.384,15,basler_capture_done,3594.6,"(4504, 4504)" +15:07:00.400,15,cognex_join_wait,3606.1, +15:07:00.416,15,cognex_join_done,3608.5, +15:07:00.416,15,cycle_done,3627.3,result=FAIL cognex=FAIL basler=PASS +15:07:00.416,16,cycle_start,0.0,group=A belt_delay=3.33s +15:07:00.416,16,cognex_trigger_send,2.3, +15:07:00.430,16,cognex_trigger_ok,8.8, +15:07:01.436,16,cognex_ftp_start,1011.1, +15:07:02.196,16,cognex_ftp_done,1773.3,3686454bytes +15:07:02.196,16,cognex_patmax_start,1774.7, +15:07:02.196,16,cognex_patmax_done,1786.6, +15:07:03.759,16,basler_capture_start,3334.1, +15:07:04.003,16,basler_capture_done,3591.8,"(4504, 4504)" +15:07:04.018,16,cognex_join_wait,3603.6, +15:07:04.047,16,cognex_join_done,3614.3, +15:07:04.049,16,cycle_done,3625.7,result=FAIL cognex=FAIL basler=PASS +15:07:04.049,17,cycle_start,0.0,group=A belt_delay=3.33s +15:07:04.049,17,cognex_trigger_send,2.3, +15:07:04.062,17,cognex_trigger_ok,8.9, +15:07:05.066,17,cognex_ftp_start,1011.9, +15:07:05.796,17,cognex_ftp_done,1742.2,3686454bytes +15:07:05.796,17,cognex_patmax_start,1743.9, +15:07:05.796,17,cognex_patmax_done,1755.8, +15:07:07.388,17,basler_capture_start,3334.1, +15:07:07.636,17,basler_capture_done,3590.3,"(4504, 4504)" +15:07:07.652,17,cognex_join_wait,3602.1, +15:07:07.667,17,cognex_join_done,3619.6, +15:07:07.667,17,cycle_done,3624.8,result=FAIL cognex=FAIL basler=PASS +15:07:07.682,18,cycle_start,0.0,group=A belt_delay=3.33s +15:07:07.682,18,cognex_trigger_send,2.4, +15:07:07.682,18,cognex_trigger_ok,8.9, +15:07:08.696,18,cognex_ftp_start,1013.6, +15:07:09.446,18,cognex_ftp_done,1764.3,3686454bytes +15:07:09.446,18,cognex_patmax_start,1765.7, +15:07:09.446,18,cognex_patmax_done,1777.5, +15:07:11.017,18,basler_capture_start,3334.2, +15:07:11.268,18,basler_capture_done,3591.8,"(4504, 4504)" +15:07:11.284,18,cognex_join_wait,3604.0, +15:07:11.300,18,cognex_join_done,3621.5, +15:07:11.300,18,cycle_done,3624.8,result=FAIL cognex=FAIL basler=PASS +15:07:11.300,19,cycle_start,0.0,group=A belt_delay=3.33s +15:07:11.312,19,cognex_trigger_send,2.3, +15:07:11.315,19,cognex_trigger_ok,8.7, +15:07:12.321,19,cognex_ftp_start,1010.7, +15:07:13.079,19,cognex_ftp_done,1771.6,3686454bytes +15:07:13.079,19,cognex_patmax_start,1773.1, +15:07:13.095,19,cognex_patmax_done,1784.8, +15:07:14.645,19,basler_capture_start,3333.9, +15:07:14.887,19,basler_capture_done,3591.3,"(4504, 4504)" +15:07:14.903,19,cognex_join_wait,3603.7, +15:07:14.932,19,cognex_join_done,3608.1, +15:07:14.932,19,cycle_done,3624.8,result=FAIL cognex=FAIL basler=PASS +15:07:14.932,20,cycle_start,0.0,group=A belt_delay=3.33s +15:07:14.932,20,cognex_trigger_send,2.3, +15:07:14.947,20,cognex_trigger_ok,8.9, +15:07:15.950,20,cognex_ftp_start,1011.4, +15:07:16.696,20,cognex_ftp_done,1756.5,3686454bytes +15:07:16.696,20,cognex_patmax_start,1757.9, +15:07:16.696,20,cognex_patmax_done,1769.8, +15:07:18.273,20,basler_capture_start,3333.8, +15:07:18.518,20,basler_capture_done,3589.8,"(4504, 4504)" +15:07:18.533,20,cognex_join_wait,3601.6, +15:07:18.549,20,cognex_join_done,3612.4, +15:07:18.549,20,cycle_done,3622.7,result=FAIL cognex=FAIL basler=PASS +15:07:18.565,21,cycle_start,0.0,group=A belt_delay=3.33s +15:07:18.565,21,cognex_trigger_send,2.5, +15:07:18.565,21,cognex_trigger_ok,9.1, +15:07:19.577,21,cognex_ftp_start,1011.7, +15:07:20.329,21,cognex_ftp_done,1764.6,3686454bytes +15:07:20.329,21,cognex_patmax_start,1766.0, +15:07:20.329,21,cognex_patmax_done,1775.7, +15:07:21.900,21,basler_capture_start,3334.3, +15:07:22.152,21,basler_capture_done,3591.3,"(4504, 4504)" +15:07:22.168,21,cognex_join_wait,3603.2, +15:07:22.168,21,cognex_join_done,3605.0, +15:07:22.183,21,cycle_done,3622.9,result=FAIL cognex=FAIL basler=PASS +15:07:22.183,22,cycle_start,0.0,group=A belt_delay=3.33s +15:07:22.183,22,cognex_trigger_send,1.9, +15:07:22.197,22,cognex_trigger_ok,8.4, +15:07:23.202,22,cognex_ftp_start,1010.4, +15:07:23.929,22,cognex_ftp_done,1740.3,3686454bytes +15:07:23.929,22,cognex_patmax_start,1741.8, +15:07:23.946,22,cognex_patmax_done,1753.7, +15:07:25.526,22,basler_capture_start,3334.1, +15:07:25.768,22,basler_capture_done,3591.1,"(4504, 4504)" +15:07:25.784,22,cognex_join_wait,3603.0, +15:07:25.814,22,cognex_join_done,3614.0, +15:07:25.815,22,cycle_done,3624.4,result=FAIL cognex=FAIL basler=PASS +15:07:25.815,23,cycle_start,0.0,group=A belt_delay=3.33s +15:07:25.815,23,cognex_trigger_send,2.3, +15:07:25.815,23,cognex_trigger_ok,8.8, +15:07:26.831,23,cognex_ftp_start,1011.1, +15:07:27.563,23,cognex_ftp_done,1743.3,3686454bytes +15:07:27.563,23,cognex_patmax_start,1744.8, +15:07:27.563,23,cognex_patmax_done,1756.3, +15:07:29.154,23,basler_capture_start,3333.7, +15:07:29.403,23,basler_capture_done,3590.7,"(4504, 4504)" +15:07:29.418,23,cognex_join_wait,3602.5, +15:07:29.434,23,cognex_join_done,3620.3, +15:07:29.434,23,cycle_done,3623.8,result=FAIL cognex=FAIL basler=PASS +15:07:29.447,24,cycle_start,0.0,group=A belt_delay=3.33s +15:07:29.449,24,cognex_trigger_send,2.5, +15:07:29.449,24,cognex_trigger_ok,9.1, +15:07:30.459,24,cognex_ftp_start,1011.6, +15:07:31.213,24,cognex_ftp_done,1767.8,3686454bytes +15:07:31.213,24,cognex_patmax_start,1769.3, +15:07:31.213,24,cognex_patmax_done,1780.4, +15:07:32.781,24,basler_capture_start,3333.8, +15:07:33.034,24,basler_capture_done,3591.9,"(4504, 4504)" +15:07:33.050,24,cognex_join_wait,3603.7, +15:07:33.066,24,cognex_join_done,3621.7, +15:07:33.066,24,cycle_done,3625.1,result=FAIL cognex=FAIL basler=PASS +15:07:33.066,25,cycle_start,0.0,group=A belt_delay=3.33s +15:07:33.066,25,cognex_trigger_send,2.3, +15:07:33.082,25,cognex_trigger_ok,8.7, +15:07:34.087,25,cognex_ftp_start,1010.7, +15:07:34.846,25,cognex_ftp_done,1770.6,3686454bytes +15:07:34.846,25,cognex_patmax_start,1772.1, +15:07:34.846,25,cognex_patmax_done,1783.9, +15:07:36.410,25,basler_capture_start,3334.0, +15:07:36.668,25,basler_capture_done,3591.8,"(4504, 4504)" +15:07:36.668,25,cognex_join_wait,3603.6, +15:07:36.698,25,cognex_join_done,3608.2, +15:07:36.699,25,cycle_done,3624.9,result=FAIL cognex=FAIL basler=PASS +15:07:36.699,26,cycle_start,0.0,group=A belt_delay=3.33s +15:07:36.699,26,cognex_trigger_send,2.3, +15:07:36.712,26,cognex_trigger_ok,8.7, +15:07:37.715,26,cognex_ftp_start,1011.0, +15:07:38.463,26,cognex_ftp_done,1759.6,3686454bytes +15:07:38.463,26,cognex_patmax_start,1761.0, +15:07:38.463,26,cognex_patmax_done,1772.9, +15:07:40.038,26,basler_capture_start,3333.9, +15:07:40.287,26,basler_capture_done,3591.3,"(4504, 4504)" +15:07:40.302,26,cognex_join_wait,3603.2, +15:07:40.318,26,cognex_join_done,3614.1, +15:07:40.329,26,cycle_done,3624.6,result=FAIL cognex=FAIL basler=PASS +15:07:40.332,27,cycle_start,0.0,group=A belt_delay=3.33s +15:07:40.332,27,cognex_trigger_send,2.4, +15:07:40.332,27,cognex_trigger_ok,8.9, +15:07:41.344,27,cognex_ftp_start,1011.3, +15:07:42.079,27,cognex_ftp_done,1760.8,3686454bytes +15:07:42.095,27,cognex_patmax_start,1762.4, +15:07:42.096,27,cognex_patmax_done,1774.3, +15:07:43.667,27,basler_capture_start,3334.1, +15:07:43.920,27,basler_capture_done,3591.0,"(4504, 4504)" +15:07:43.936,27,cognex_join_wait,3603.0, +15:07:43.951,27,cognex_join_done,3620.8, +15:07:43.951,27,cycle_done,3626.3,result=FAIL cognex=FAIL basler=PASS +15:07:43.962,28,cycle_start,0.0,group=A belt_delay=3.33s +15:07:43.964,28,cognex_trigger_send,2.4, +15:07:43.965,28,cognex_trigger_ok,9.0, +15:07:44.973,28,cognex_ftp_start,1010.5, +15:07:45.728,28,cognex_ftp_done,1765.9,3686454bytes +15:07:45.729,28,cognex_patmax_start,1767.6, +15:07:45.729,28,cognex_patmax_done,1776.8, +15:07:47.296,28,basler_capture_start,3333.8, +15:07:47.539,28,basler_capture_done,3591.3,"(4504, 4504)" +15:07:47.555,28,cognex_join_wait,3604.0, +15:07:47.555,28,cognex_join_done,3605.7, +15:07:47.582,28,cycle_done,3622.7,result=FAIL cognex=FAIL basler=PASS +15:07:47.582,29,cycle_start,0.0,group=A belt_delay=3.33s +15:07:47.582,29,cognex_trigger_send,1.7, +15:07:47.597,29,cognex_trigger_ok,8.6, +15:07:48.600,29,cognex_ftp_start,1011.0, +15:07:49.345,29,cognex_ftp_done,1755.7,3686454bytes +15:07:49.346,29,cognex_patmax_start,1757.3, +15:07:49.346,29,cognex_patmax_done,1769.2, +15:07:50.922,29,basler_capture_start,3333.5, +15:07:51.178,29,basler_capture_done,3589.8,"(4504, 4504)" +15:07:51.181,29,cognex_join_wait,3596.6, +15:07:51.181,29,cognex_join_done,3598.8, +15:07:51.197,29,cycle_done,3615.4,result=FAIL cognex=FAIL basler=PASS +15:24:49.234,1,cycle_start,0.0,group=A belt_delay=3.33s +15:24:49.234,1,cognex_trigger_send,1.6, +15:24:49.244,1,cognex_trigger_ok,8.2, +15:24:50.247,1,cognex_ftp_start,1010.6, +15:24:50.995,1,cognex_ftp_done,1758.9,3686454bytes +15:24:50.995,1,cognex_patmax_start,1760.4, +15:24:51.010,1,cognex_patmax_done,1773.4, +15:24:52.571,1,basler_capture_start,3333.9, +15:24:52.816,1,basler_capture_done,3591.4,"(4504, 4504)" +15:24:53.365,1,cognex_join_wait,4128.7, +15:24:53.365,1,cognex_join_done,4130.8, +15:24:53.397,1,cycle_done,4133.1,result=FAIL cognex=FAIL basler=PASS +15:24:53.399,2,cycle_start,0.0,group=A belt_delay=3.33s +15:24:53.399,2,cognex_trigger_send,2.3, +15:24:53.399,2,cognex_trigger_ok,8.6, +15:24:54.412,2,cognex_ftp_start,1010.7, +15:24:55.161,2,cognex_ftp_done,1762.6,3686454bytes +15:24:55.161,2,cognex_patmax_start,1764.0, +15:24:55.161,2,cognex_patmax_done,1775.3, +15:24:56.735,2,basler_capture_start,3333.8, +15:24:56.984,2,basler_capture_done,3591.4,"(4504, 4504)" +15:24:56.984,2,cognex_join_wait,3597.5, +15:24:56.999,2,cognex_join_done,3599.9, +15:24:57.020,2,cycle_done,3623.8,result=FAIL cognex=FAIL basler=PASS +15:24:57.029,3,cycle_start,0.0,group=A belt_delay=3.33s +15:24:57.030,3,cognex_trigger_send,2.5, +15:24:57.030,3,cognex_trigger_ok,9.0, +15:24:58.040,3,cognex_ftp_start,1011.1, +15:24:58.795,3,cognex_ftp_done,1767.1,3686454bytes +15:24:58.795,3,cognex_patmax_start,1768.5, +15:24:58.795,3,cognex_patmax_done,1779.8, +15:25:00.363,3,basler_capture_start,3334.2, +15:25:00.620,3,basler_capture_done,3591.6,"(4504, 4504)" +15:25:00.620,3,cognex_join_wait,3603.3, +15:25:00.654,3,cognex_join_done,3607.1, +15:25:00.655,3,cycle_done,3627.7,result=FAIL cognex=FAIL basler=PASS +15:25:00.655,4,cycle_start,0.0,group=A belt_delay=3.33s +15:25:00.662,4,cognex_trigger_send,2.5, +15:25:00.663,4,cognex_trigger_ok,9.3, +15:25:01.672,4,cognex_ftp_start,1011.7, +15:25:02.428,4,cognex_ftp_done,1768.8,3686454bytes +15:25:02.428,4,cognex_patmax_start,1770.3, +15:25:02.428,4,cognex_patmax_done,1781.6, +15:25:03.994,4,basler_capture_start,3334.3, +15:25:04.238,4,basler_capture_done,3591.2,"(4504, 4504)" +15:25:04.253,4,cognex_join_wait,3603.0, +15:25:04.285,4,cognex_join_done,3616.9, +15:25:04.285,4,cycle_done,3628.2,result=FAIL cognex=FAIL basler=PASS +15:25:04.285,5,cycle_start,0.0,group=A belt_delay=3.33s +15:25:04.285,5,cognex_trigger_send,2.3, +15:25:04.295,5,cognex_trigger_ok,9.1, +15:25:05.303,5,cognex_ftp_start,1011.2, +15:25:06.061,5,cognex_ftp_done,1770.0,3686454bytes +15:25:06.061,5,cognex_patmax_start,1771.6, +15:25:06.061,5,cognex_patmax_done,1782.9, +15:25:07.626,5,basler_capture_start,3333.9, +15:25:07.868,5,basler_capture_done,3590.1,"(4504, 4504)" +15:25:07.883,5,cognex_join_wait,3602.0, +15:25:07.915,5,cognex_join_done,3615.2, +15:25:07.918,5,cycle_done,3626.4,result=FAIL cognex=FAIL basler=PASS +15:25:07.921,6,cycle_start,0.0,group=A belt_delay=3.33s +15:25:07.921,6,cognex_trigger_send,2.4, +15:25:07.930,6,cognex_trigger_ok,8.9, +15:25:08.933,6,cognex_ftp_start,1011.4, +15:25:09.661,6,cognex_ftp_done,1739.1,3686454bytes +15:25:09.661,6,cognex_patmax_start,1740.6, +15:25:09.661,6,cognex_patmax_done,1749.9, +15:25:11.256,6,basler_capture_start,3334.1, +15:25:11.501,6,basler_capture_done,3591.1,"(4504, 4504)" +15:25:11.516,6,cognex_join_wait,3603.5, +15:25:11.516,6,cognex_join_done,3605.3, +15:25:11.539,6,cycle_done,3621.7,result=FAIL cognex=FAIL basler=PASS +15:25:11.548,7,cycle_start,0.0,group=A belt_delay=3.33s +15:25:11.549,7,cognex_trigger_send,2.2, +15:25:11.553,7,cognex_trigger_ok,8.8, +15:25:12.558,7,cognex_ftp_start,1011.0, +15:25:13.310,7,cognex_ftp_done,1763.3,3686454bytes +15:25:13.311,7,cognex_patmax_start,1764.9, +15:25:13.311,7,cognex_patmax_done,1776.3, +15:25:14.881,7,basler_capture_start,3334.0, +15:25:15.135,7,basler_capture_done,3591.6,"(4504, 4504)" +15:25:15.150,7,cognex_join_wait,3603.6, +15:25:15.170,7,cognex_join_done,3621.0, +15:25:15.172,7,cycle_done,3625.3,result=FAIL cognex=FAIL basler=PASS +15:25:15.172,8,cycle_start,0.0,group=A belt_delay=3.33s +15:25:15.179,8,cognex_trigger_send,2.5, +15:25:15.184,8,cognex_trigger_ok,9.4, +15:25:16.188,8,cognex_ftp_start,1011.7, +15:25:16.928,8,cognex_ftp_done,1752.4,3686454bytes +15:25:16.928,8,cognex_patmax_start,1753.9, +15:25:16.928,8,cognex_patmax_done,1765.2, +15:25:18.511,8,basler_capture_start,3334.2, +15:25:18.767,8,basler_capture_done,3591.4,"(4504, 4504)" +15:25:18.767,8,cognex_join_wait,3603.3, +15:25:18.783,8,cognex_join_done,3607.4, +15:25:18.801,8,cycle_done,3625.3,result=FAIL cognex=FAIL basler=PASS +15:25:18.801,9,cycle_start,0.0,group=A belt_delay=3.33s +15:25:18.801,9,cognex_trigger_send,2.4, +15:25:18.814,9,cognex_trigger_ok,8.9, +15:25:19.817,9,cognex_ftp_start,1011.1, +15:25:20.561,9,cognex_ftp_done,1758.0,3686454bytes +15:25:20.561,9,cognex_patmax_start,1759.4, +15:25:20.561,9,cognex_patmax_done,1770.7, +15:25:22.139,9,basler_capture_start,3333.9, +15:25:22.385,9,basler_capture_done,3591.3,"(4504, 4504)" +15:25:22.400,9,cognex_join_wait,3603.4, +15:25:22.426,9,cognex_join_done,3614.3, +15:25:22.431,9,cycle_done,3625.9,result=FAIL cognex=FAIL basler=PASS +15:25:22.435,10,cycle_start,0.0,group=A belt_delay=3.33s +15:25:22.437,10,cognex_trigger_send,2.3, +15:25:22.444,10,cognex_trigger_ok,8.9, +15:25:23.447,10,cognex_ftp_start,1011.4, +15:25:24.195,10,cognex_ftp_done,1760.8,3686454bytes +15:25:24.195,10,cognex_patmax_start,1762.3, +15:25:24.195,10,cognex_patmax_done,1773.6, +15:25:25.769,10,basler_capture_start,3333.9, +15:25:26.016,10,basler_capture_done,3591.3,"(4504, 4504)" +15:25:26.032,10,cognex_join_wait,3604.0, +15:25:26.057,10,cognex_join_done,3616.2, +15:25:26.063,10,cycle_done,3627.7,result=FAIL cognex=FAIL basler=PASS +15:25:26.066,11,cycle_start,0.0,group=A belt_delay=3.33s +15:25:26.069,11,cognex_trigger_send,2.4, +15:25:26.070,11,cognex_trigger_ok,8.9, +15:25:27.081,11,cognex_ftp_start,1013.8, +15:25:27.811,11,cognex_ftp_done,1745.2,3686454bytes +15:25:27.811,11,cognex_patmax_start,1746.7, +15:25:27.811,11,cognex_patmax_done,1757.9, +15:25:29.401,11,basler_capture_start,3333.8, +15:25:29.651,11,basler_capture_done,3590.9,"(4504, 4504)" +15:25:29.667,11,cognex_join_wait,3602.8, +15:25:29.689,11,cognex_join_done,3622.6, +15:25:29.689,11,cycle_done,3625.9,result=FAIL cognex=FAIL basler=PASS +15:25:29.696,12,cycle_start,0.0,group=A belt_delay=3.33s +15:25:29.699,12,cognex_trigger_send,2.4, +15:25:29.704,12,cognex_trigger_ok,9.0, +15:25:30.708,12,cognex_ftp_start,1011.4, +15:25:31.461,12,cognex_ftp_done,1766.3,3686454bytes +15:25:31.461,12,cognex_patmax_start,1767.8, +15:25:31.461,12,cognex_patmax_done,1779.0, +15:25:33.030,12,basler_capture_start,3333.9, +15:25:33.285,12,basler_capture_done,3591.3,"(4504, 4504)" +15:25:33.285,12,cognex_join_wait,3603.3, +15:25:33.301,12,cognex_join_done,3605.6, +15:25:33.323,12,cycle_done,3627.4,result=FAIL cognex=FAIL basler=PASS +15:25:33.328,13,cycle_start,0.0,group=A belt_delay=3.33s +15:25:33.331,13,cognex_trigger_send,2.5, +15:25:33.337,13,cognex_trigger_ok,9.2, +15:25:34.339,13,cognex_ftp_start,1011.4, +15:25:35.078,13,cognex_ftp_done,1752.0,3686454bytes +15:25:35.078,13,cognex_patmax_start,1753.5, +15:25:35.078,13,cognex_patmax_done,1762.7, +15:25:36.662,13,basler_capture_start,3333.9, +15:25:36.915,13,basler_capture_done,3591.7,"(4504, 4504)" +15:25:36.931,13,cognex_join_wait,3603.6, +15:25:36.954,13,cognex_join_done,3605.4, +15:25:36.954,13,cycle_done,3628.9,result=FAIL cognex=FAIL basler=PASS +15:25:36.954,14,cycle_start,0.0,group=A belt_delay=3.33s +15:25:36.961,14,cognex_trigger_send,1.7, +15:25:36.968,14,cognex_trigger_ok,8.0, +15:25:37.970,14,cognex_ftp_start,1010.3, +15:25:38.728,14,cognex_ftp_done,1769.4,3686454bytes +15:25:38.728,14,cognex_patmax_start,1771.1, +15:25:38.728,14,cognex_patmax_done,1782.3, +15:25:40.294,14,basler_capture_start,3333.8, +15:25:40.536,14,basler_capture_done,3589.7,"(4504, 4504)" +15:25:40.552,14,cognex_join_wait,3601.7, +15:25:40.579,14,cognex_join_done,3616.4, +15:25:40.579,14,cycle_done,3628.9,result=FAIL cognex=FAIL basler=PASS +15:25:40.579,15,cycle_start,0.0,group=A belt_delay=3.33s +15:25:40.595,15,cognex_trigger_send,2.4, +15:25:40.599,15,cognex_trigger_ok,9.1, +15:25:41.604,15,cognex_ftp_start,1011.5, +15:25:42.361,15,cognex_ftp_done,1769.9,3686454bytes +15:25:42.361,15,cognex_patmax_start,1771.4, +15:25:42.361,15,cognex_patmax_done,1782.9, +15:25:43.926,15,basler_capture_start,3333.8, +15:25:44.184,15,basler_capture_done,3591.2,"(4504, 4504)" +15:25:44.184,15,cognex_join_wait,3603.2, +15:25:44.214,15,cognex_join_done,3608.0, +15:25:44.215,15,cycle_done,3624.6,result=FAIL cognex=FAIL basler=PASS +15:25:44.215,16,cycle_start,0.0,group=A belt_delay=3.33s +15:25:44.215,16,cognex_trigger_send,2.3, +15:25:44.230,16,cognex_trigger_ok,9.0, +15:25:45.232,16,cognex_ftp_start,1011.5, +15:25:45.978,16,cognex_ftp_done,1758.1,3686454bytes +15:25:45.978,16,cognex_patmax_start,1759.6, +15:25:45.978,16,cognex_patmax_done,1770.7, +15:25:47.554,16,basler_capture_start,3333.9, +15:25:47.802,16,basler_capture_done,3589.8,"(4504, 4504)" +15:25:47.818,16,cognex_join_wait,3602.0, +15:25:47.840,16,cognex_join_done,3619.0, +15:25:47.840,16,cycle_done,3622.4,result=FAIL cognex=FAIL basler=PASS +15:25:47.847,17,cycle_start,0.0,group=A belt_delay=3.33s +15:25:47.849,17,cognex_trigger_send,2.5, +15:25:47.853,17,cognex_trigger_ok,8.9, +15:25:48.858,17,cognex_ftp_start,1011.6, +15:25:49.578,17,cognex_ftp_done,1746.3,3686454bytes +15:25:49.595,17,cognex_patmax_start,1747.9, +15:25:49.595,17,cognex_patmax_done,1759.6, +15:25:51.181,17,basler_capture_start,3334.2, +15:25:51.435,17,basler_capture_done,3591.8,"(4504, 4504)" +15:25:51.450,17,cognex_join_wait,3603.7, +15:25:51.468,17,cognex_join_done,3619.9, +15:25:51.472,17,cycle_done,3625.3,result=FAIL cognex=FAIL basler=PASS +15:25:51.472,18,cycle_start,0.0,group=A belt_delay=3.33s +15:25:51.478,18,cognex_trigger_send,2.4, +15:25:51.485,18,cognex_trigger_ok,8.9, +15:25:52.487,18,cognex_ftp_start,1010.8, +15:25:53.245,18,cognex_ftp_done,1769.5,3686454bytes +15:25:53.245,18,cognex_patmax_start,1771.0, +15:25:53.253,18,cognex_patmax_done,1782.4, +15:25:54.810,18,basler_capture_start,3333.8, +15:25:55.053,18,basler_capture_done,3589.7,"(4504, 4504)" +15:25:55.069,18,cognex_join_wait,3601.7, +15:25:55.095,18,cognex_join_done,3612.2, +15:25:55.096,18,cycle_done,3624.7,result=FAIL cognex=FAIL basler=PASS +15:25:55.096,19,cycle_start,0.0,group=A belt_delay=3.33s +15:25:55.096,19,cognex_trigger_send,2.4, +15:25:55.112,19,cognex_trigger_ok,9.1, +15:25:56.116,19,cognex_ftp_start,1011.4, +15:25:56.861,19,cognex_ftp_done,1758.4,3686454bytes +15:25:56.861,19,cognex_patmax_start,1759.9, +15:25:56.861,19,cognex_patmax_done,1771.2, +15:25:58.438,19,basler_capture_start,3334.1, +15:25:58.683,19,basler_capture_done,3591.1,"(4504, 4504)" +15:25:58.699,19,cognex_join_wait,3603.6, +15:25:58.722,19,cognex_join_done,3614.4, +15:25:58.728,19,cycle_done,3624.6,result=FAIL cognex=FAIL basler=PASS +15:25:58.733,20,cycle_start,0.0,group=A belt_delay=3.33s +15:25:58.735,20,cognex_trigger_send,2.4, +15:25:58.738,20,cognex_trigger_ok,9.1, +15:25:59.744,20,cognex_ftp_start,1011.5, +15:26:00.478,20,cognex_ftp_done,1746.2,3686454bytes +15:26:00.478,20,cognex_patmax_start,1747.8, +15:26:00.478,20,cognex_patmax_done,1757.0, +15:26:02.067,20,basler_capture_start,3334.2, +15:26:02.319,20,basler_capture_done,3591.7,"(4504, 4504)" +15:26:02.335,20,cognex_join_wait,3603.7, +15:26:02.335,20,cognex_join_done,3605.3, +15:26:02.354,20,cycle_done,3622.2,result=FAIL cognex=FAIL basler=PASS +15:26:02.356,21,cycle_start,0.0,group=A belt_delay=3.33s +15:26:02.356,21,cognex_trigger_send,1.7, +15:26:02.366,21,cognex_trigger_ok,8.1, +15:26:03.369,21,cognex_ftp_start,1010.3, +15:26:04.128,21,cognex_ftp_done,1769.9,3686454bytes +15:26:04.128,21,cognex_patmax_start,1771.3, +15:26:04.128,21,cognex_patmax_done,1782.5, +15:26:05.692,21,basler_capture_start,3333.9, +15:26:05.936,21,basler_capture_done,3591.1,"(4504, 4504)" +15:26:05.952,21,cognex_join_wait,3603.2, +15:26:05.980,21,cognex_join_done,3614.1, +15:26:05.983,21,cycle_done,3624.4,result=FAIL cognex=FAIL basler=PASS +15:26:05.985,22,cycle_start,0.0,group=A belt_delay=3.33s +15:26:05.986,22,cognex_trigger_send,2.3, +15:26:05.995,22,cognex_trigger_ok,8.9, +15:26:06.997,22,cognex_ftp_start,1011.2, +15:26:07.744,22,cognex_ftp_done,1759.3,3686454bytes +15:26:07.744,22,cognex_patmax_start,1760.9, +15:26:07.744,22,cognex_patmax_done,1772.3, +15:26:09.320,22,basler_capture_start,3333.6, +15:26:09.569,22,basler_capture_done,3589.9,"(4504, 4504)" +15:26:09.585,22,cognex_join_wait,3602.3, +15:26:09.606,22,cognex_join_done,3619.8, +15:26:09.607,22,cycle_done,3623.5,result=FAIL cognex=FAIL basler=PASS +15:26:09.613,23,cycle_start,0.0,group=A belt_delay=3.33s +15:26:09.615,23,cognex_trigger_send,2.4, +15:26:09.619,23,cognex_trigger_ok,8.9, +15:26:10.624,23,cognex_ftp_start,1010.9, +15:26:11.378,23,cognex_ftp_done,1765.6,3686454bytes +15:26:11.378,23,cognex_patmax_start,1767.1, +15:26:11.378,23,cognex_patmax_done,1778.5, +15:26:12.947,23,basler_capture_start,3334.0, +15:26:13.201,23,basler_capture_done,3590.8,"(4504, 4504)" +15:26:13.201,23,cognex_join_wait,3602.7, +15:26:13.234,23,cognex_join_done,3619.7, +15:26:13.237,23,cycle_done,3623.4,result=FAIL cognex=FAIL basler=PASS +15:26:13.239,24,cycle_start,0.0,group=A belt_delay=3.33s +15:26:13.239,24,cognex_trigger_send,2.4, +15:26:13.250,24,cognex_trigger_ok,9.1, +15:26:14.252,24,cognex_ftp_start,1011.5, +15:26:15.011,24,cognex_ftp_done,1772.7,3686454bytes +15:26:15.011,24,cognex_patmax_start,1774.2, +15:26:15.011,24,cognex_patmax_done,1785.6, +15:26:16.575,24,basler_capture_start,3334.2, +15:26:16.819,24,basler_capture_done,3591.7,"(4504, 4504)" +15:26:16.835,24,cognex_join_wait,3603.7, +15:26:16.863,24,cognex_join_done,3614.7, +15:26:16.864,24,cycle_done,3624.7,result=FAIL cognex=FAIL basler=PASS +15:26:16.864,25,cycle_start,0.0,group=A belt_delay=3.33s +15:26:16.864,25,cognex_trigger_send,2.3, +15:26:16.878,25,cognex_trigger_ok,8.9, +15:26:17.880,25,cognex_ftp_start,1010.9, +15:26:18.628,25,cognex_ftp_done,1759.0,3686454bytes +15:26:18.628,25,cognex_patmax_start,1760.6, +15:26:18.628,25,cognex_patmax_done,1771.9, +15:26:20.203,25,basler_capture_start,3334.1, +15:26:20.451,25,basler_capture_done,3590.3,"(4504, 4504)" +15:26:20.467,25,cognex_join_wait,3602.4, +15:26:20.489,25,cognex_join_done,3620.2, +15:26:20.489,25,cycle_done,3623.7,result=FAIL cognex=FAIL basler=PASS +15:26:20.496,26,cycle_start,0.0,group=A belt_delay=3.33s +15:26:20.499,26,cognex_trigger_send,2.5, +15:26:20.503,26,cognex_trigger_ok,9.0, +15:26:21.507,26,cognex_ftp_start,1011.2, +15:26:22.264,26,cognex_ftp_done,1768.9,3686454bytes +15:26:22.264,26,cognex_patmax_start,1770.3, +15:26:22.277,26,cognex_patmax_done,1781.4, +15:26:23.830,26,basler_capture_start,3334.1, +15:26:24.082,26,basler_capture_done,3591.6,"(4504, 4504)" +15:26:24.098,26,cognex_join_wait,3604.0, +15:26:24.119,26,cognex_join_done,3622.4, +15:26:24.119,26,cycle_done,3626.8,result=FAIL cognex=FAIL basler=PASS +15:26:24.119,27,cycle_start,0.0,group=A belt_delay=3.33s +15:26:24.129,27,cognex_trigger_send,2.3, +15:26:24.136,27,cognex_trigger_ok,9.0, +15:26:25.138,27,cognex_ftp_start,1011.1, +15:26:25.895,27,cognex_ftp_done,1770.6,3686454bytes +15:26:25.895,27,cognex_patmax_start,1772.0, +15:26:25.895,27,cognex_patmax_done,1781.4, +15:26:27.461,27,basler_capture_start,3334.2, +15:26:27.702,27,basler_capture_done,3590.3,"(4504, 4504)" +15:26:27.718,27,cognex_join_wait,3602.3, +15:26:27.718,27,cognex_join_done,3603.8, +15:26:27.748,27,cycle_done,3621.1,result=FAIL cognex=FAIL basler=PASS +15:26:27.751,28,cycle_start,0.0,group=A belt_delay=3.33s +15:26:27.753,28,cognex_trigger_send,1.7, +15:26:27.754,28,cognex_trigger_ok,8.1, +15:26:28.762,28,cognex_ftp_start,1010.4, +15:26:29.494,28,cognex_ftp_done,1758.3,3686454bytes +15:26:29.511,28,cognex_patmax_start,1759.8, +15:26:29.511,28,cognex_patmax_done,1771.2, +15:26:31.086,28,basler_capture_start,3334.6, +15:26:31.335,28,basler_capture_done,3590.6,"(4504, 4504)" +15:26:31.351,28,cognex_join_wait,3602.8, +15:26:31.372,28,cognex_join_done,3620.7, +15:26:31.373,28,cycle_done,3624.1,result=FAIL cognex=FAIL basler=PASS +15:26:31.379,29,cycle_start,0.0,group=A belt_delay=3.33s +15:26:31.381,29,cognex_trigger_send,2.7, +15:26:31.386,29,cognex_trigger_ok,9.3, +15:26:32.391,29,cognex_ftp_start,1011.5, +15:26:33.111,29,cognex_ftp_done,1747.2,3686454bytes +15:26:33.128,29,cognex_patmax_start,1748.7, +15:26:33.128,29,cognex_patmax_done,1760.0, +15:26:34.713,29,basler_capture_start,3334.0, +15:26:34.968,29,basler_capture_done,3590.7,"(4504, 4504)" +15:26:34.968,29,cognex_join_wait,3602.6, +15:26:35.000,29,cognex_join_done,3606.9, +15:26:35.002,29,cycle_done,3625.2,result=FAIL cognex=FAIL basler=PASS +15:26:35.002,30,cycle_start,0.0,group=A belt_delay=3.33s +15:26:35.002,30,cognex_trigger_send,2.4, +15:26:35.017,30,cognex_trigger_ok,8.8, +15:26:36.020,30,cognex_ftp_start,1011.4, +15:26:36.778,30,cognex_ftp_done,1770.1,3686454bytes +15:26:36.778,30,cognex_patmax_start,1771.6, +15:26:36.778,30,cognex_patmax_done,1782.8, +15:26:38.342,30,basler_capture_start,3333.7, +15:26:38.586,30,basler_capture_done,3590.6,"(4504, 4504)" +15:26:38.601,30,cognex_join_wait,3602.7, +15:26:38.629,30,cognex_join_done,3613.1, +15:26:38.632,30,cycle_done,3623.3,result=FAIL cognex=FAIL basler=PASS +15:26:38.635,31,cycle_start,0.0,group=A belt_delay=3.33s +15:26:38.637,31,cognex_trigger_send,2.5, +15:26:38.644,31,cognex_trigger_ok,9.0, +15:26:39.647,31,cognex_ftp_start,1011.2, +15:26:40.395,31,cognex_ftp_done,1761.1,3686454bytes +15:26:40.395,31,cognex_patmax_start,1762.6, +15:26:40.395,31,cognex_patmax_done,1773.9, +15:26:41.969,31,basler_capture_start,3334.0, +15:26:42.215,31,basler_capture_done,3591.3,"(4504, 4504)" +15:26:42.231,31,cognex_join_wait,3603.4, +15:26:42.254,31,cognex_join_done,3613.9, +15:26:42.254,31,cycle_done,3624.8,result=FAIL cognex=FAIL basler=PASS +15:26:42.264,32,cycle_start,0.0,group=A belt_delay=3.33s +15:26:42.266,32,cognex_trigger_send,2.5, +15:26:42.271,32,cognex_trigger_ok,9.0, +15:26:43.275,32,cognex_ftp_start,1011.4, +15:26:44.027,32,cognex_ftp_done,1763.1,3686454bytes +15:26:44.028,32,cognex_patmax_start,1764.6, +15:26:44.028,32,cognex_patmax_done,1775.7, +15:26:45.598,32,basler_capture_start,3334.2, +15:26:45.851,32,basler_capture_done,3591.8,"(4504, 4504)" +15:26:45.867,32,cognex_join_wait,3604.2, +15:26:45.886,32,cognex_join_done,3621.8, +15:26:45.888,32,cycle_done,3625.2,result=FAIL cognex=FAIL basler=PASS +15:26:45.888,33,cycle_start,0.0,group=A belt_delay=3.33s +15:26:45.895,33,cognex_trigger_send,2.4, +15:26:45.896,33,cognex_trigger_ok,8.9, +15:26:46.904,33,cognex_ftp_start,1011.1, +15:26:47.644,33,cognex_ftp_done,1754.2,3686454bytes +15:26:47.644,33,cognex_patmax_start,1755.6, +15:26:47.644,33,cognex_patmax_done,1766.8, +15:26:49.227,33,basler_capture_start,3334.1, +15:26:49.484,33,basler_capture_done,3592.9,"(4504, 4504)" +15:26:49.484,33,cognex_join_wait,3604.9, +15:26:49.516,33,cognex_join_done,3622.7, +15:26:49.518,33,cycle_done,3625.3,result=FAIL cognex=FAIL basler=PASS +15:26:49.521,34,cycle_start,0.0,group=A belt_delay=3.33s +15:26:49.521,34,cognex_trigger_send,2.5, +15:26:49.531,34,cognex_trigger_ok,9.0, +15:26:50.533,34,cognex_ftp_start,1011.1, +15:26:51.278,34,cognex_ftp_done,1755.4,3686454bytes +15:26:51.278,34,cognex_patmax_start,1756.9, +15:26:51.278,34,cognex_patmax_done,1766.0, +15:26:52.856,34,basler_capture_start,3334.1, +15:26:53.100,34,basler_capture_done,3591.4,"(4504, 4504)" +15:26:53.116,34,cognex_join_wait,3603.8, +15:26:53.116,34,cognex_join_done,3605.6, +15:26:53.144,34,cycle_done,3621.8,result=FAIL cognex=FAIL basler=PASS +15:26:53.148,35,cycle_start,0.0,group=A belt_delay=3.33s +15:26:53.149,35,cognex_trigger_send,1.9, +15:26:53.153,35,cognex_trigger_ok,8.4, +15:26:54.158,35,cognex_ftp_start,1010.8, +15:26:54.911,35,cognex_ftp_done,1764.1,3686454bytes +15:26:54.911,35,cognex_patmax_start,1765.5, +15:26:54.911,35,cognex_patmax_done,1776.9, +15:26:56.482,35,basler_capture_start,3334.1, +15:26:56.735,35,basler_capture_done,3591.8,"(4504, 4504)" +15:26:56.751,35,cognex_join_wait,3603.9, +15:26:56.770,35,cognex_join_done,3621.1, +15:26:56.772,35,cycle_done,3625.9,result=FAIL cognex=FAIL basler=PASS +15:26:56.772,36,cycle_start,0.0,group=A belt_delay=3.33s +15:26:56.779,36,cognex_trigger_send,2.3, +15:26:56.784,36,cognex_trigger_ok,9.0, +15:26:57.790,36,cognex_ftp_start,1012.7, +15:26:58.545,36,cognex_ftp_done,1774.6,3686454bytes +15:26:58.545,36,cognex_patmax_start,1776.0, +15:26:58.565,36,cognex_patmax_done,1787.2, +15:27:00.111,36,basler_capture_start,3333.9, +15:27:00.353,36,basler_capture_done,3590.1,"(4504, 4504)" +15:27:00.368,36,cognex_join_wait,3602.1, +15:27:00.397,36,cognex_join_done,3612.9, +15:27:00.398,36,cycle_done,3623.0,result=FAIL cognex=FAIL basler=PASS +15:27:00.398,37,cycle_start,0.0,group=A belt_delay=3.33s +15:27:00.398,37,cognex_trigger_send,2.3, +15:27:00.412,37,cognex_trigger_ok,8.8, +15:27:01.415,37,cognex_ftp_start,1011.0, +15:27:02.145,37,cognex_ftp_done,1741.8,3686454bytes +15:27:02.145,37,cognex_patmax_start,1743.2, +15:27:02.145,37,cognex_patmax_done,1754.4, +15:27:03.738,37,basler_capture_start,3333.8, +15:27:03.984,37,basler_capture_done,3590.4,"(4504, 4504)" +15:27:03.999,37,cognex_join_wait,3603.0, +15:27:04.021,37,cognex_join_done,3613.7, +15:27:04.029,37,cycle_done,3625.7,result=FAIL cognex=FAIL basler=PASS +15:27:04.029,38,cycle_start,0.0,group=A belt_delay=3.33s +15:27:04.029,38,cognex_trigger_send,2.3, +15:27:04.029,38,cognex_trigger_ok,8.8, +15:27:05.044,38,cognex_ftp_start,1010.8, +15:27:05.795,38,cognex_ftp_done,1761.9,3686454bytes +15:27:05.795,38,cognex_patmax_start,1763.3, +15:27:05.795,38,cognex_patmax_done,1774.6, +15:27:07.367,38,basler_capture_start,3333.8, +15:27:07.617,38,basler_capture_done,3590.9,"(4504, 4504)" +15:27:07.633,38,cognex_join_wait,3602.6, +15:27:07.655,38,cognex_join_done,3621.0, +15:27:07.655,38,cycle_done,3624.8,result=FAIL cognex=FAIL basler=PASS +15:27:07.661,39,cycle_start,0.0,group=A belt_delay=3.33s +15:27:07.664,39,cognex_trigger_send,2.5, +15:27:07.670,39,cognex_trigger_ok,9.2, +15:27:08.673,39,cognex_ftp_start,1011.3, +15:27:09.411,39,cognex_ftp_done,1749.8,3686454bytes +15:27:09.411,39,cognex_patmax_start,1751.4, +15:27:09.411,39,cognex_patmax_done,1762.7, +15:27:10.996,39,basler_capture_start,3334.0, +15:27:11.250,39,basler_capture_done,3591.6,"(4504, 4504)" +15:27:11.250,39,cognex_join_wait,3603.5, +15:27:11.283,39,cognex_join_done,3620.6, +15:27:11.286,39,cycle_done,3624.0,result=FAIL cognex=FAIL basler=PASS +15:27:11.288,40,cycle_start,0.0,group=A belt_delay=3.33s +15:27:11.288,40,cognex_trigger_send,2.3, +15:27:11.298,40,cognex_trigger_ok,8.9, +15:27:12.300,40,cognex_ftp_start,1011.0, +15:27:13.044,40,cognex_ftp_done,1756.0,3686454bytes +15:27:13.044,40,cognex_patmax_start,1757.5, +15:27:13.044,40,cognex_patmax_done,1768.8, +15:27:14.623,40,basler_capture_start,3334.0, +15:27:14.867,40,basler_capture_done,3589.8,"(4504, 4504)" +15:27:14.883,40,cognex_join_wait,3601.8, +15:27:14.909,40,cognex_join_done,3612.8, +15:27:14.912,40,cycle_done,3622.7,result=FAIL cognex=FAIL basler=PASS +15:27:14.913,41,cycle_start,0.0,group=A belt_delay=3.33s +15:27:14.913,41,cognex_trigger_send,2.3, +15:27:14.913,41,cognex_trigger_ok,8.9, +15:27:15.928,41,cognex_ftp_start,1011.5, +15:27:16.661,41,cognex_ftp_done,1745.3,3686454bytes +15:27:16.661,41,cognex_patmax_start,1746.8, +15:27:16.661,41,cognex_patmax_done,1756.0, +15:27:18.250,41,basler_capture_start,3334.0, +15:27:18.500,41,basler_capture_done,3591.4,"(4504, 4504)" +15:27:18.516,41,cognex_join_wait,3603.6, +15:27:18.516,41,cognex_join_done,3605.1, +15:27:18.537,41,cycle_done,3621.6,result=FAIL cognex=FAIL basler=PASS +15:27:18.538,42,cycle_start,0.0,group=A belt_delay=3.33s +15:27:18.538,42,cognex_trigger_send,1.7, +15:27:18.549,42,cognex_trigger_ok,8.3, +15:27:19.551,42,cognex_ftp_start,1010.2, +15:27:20.311,42,cognex_ftp_done,1770.3,3686454bytes +15:27:20.311,42,cognex_patmax_start,1771.8, +15:27:20.311,42,cognex_patmax_done,1783.0, +15:27:21.875,42,basler_capture_start,3333.9, +15:27:22.118,42,basler_capture_done,3591.1,"(4504, 4504)" +15:27:22.133,42,cognex_join_wait,3603.2, +15:27:22.163,42,cognex_join_done,3613.9, +15:27:22.165,42,cycle_done,3624.1,result=FAIL cognex=FAIL basler=PASS +15:27:22.168,43,cycle_start,0.0,group=A belt_delay=3.33s +15:27:22.168,43,cognex_trigger_send,2.4, +15:27:22.178,43,cognex_trigger_ok,9.1, +15:27:23.180,43,cognex_ftp_start,1011.3, +15:27:23.928,43,cognex_ftp_done,1759.7,3686454bytes +15:27:23.928,43,cognex_patmax_start,1761.2, +15:27:23.928,43,cognex_patmax_done,1772.5, +15:27:25.503,43,basler_capture_start,3334.1, +15:27:25.745,43,basler_capture_done,3590.5,"(4504, 4504)" +15:27:25.761,43,cognex_join_wait,3602.9, +15:27:25.785,43,cognex_join_done,3613.5, +15:27:25.785,43,cycle_done,3623.6,result=FAIL cognex=FAIL basler=PASS +15:35:41.832,44,cycle_start,0.0,group=A belt_delay=3.33s +15:35:41.834,44,cognex_trigger_send,2.1, +15:35:41.834,44,cognex_trigger_ok,8.7, +15:35:42.843,44,cognex_ftp_start,1011.2, +15:35:43.577,44,cognex_ftp_done,1746.7,3686454bytes +15:35:43.577,44,cognex_patmax_start,1748.2, +15:35:43.593,44,cognex_patmax_done,1760.8, +15:35:45.166,44,basler_capture_start,3333.6, +15:35:45.166,44,basler_capture_done,3339.8,failed +15:35:45.166,44,cognex_join_wait,3341.8, +15:35:45.166,44,cognex_join_done,3343.8, +15:35:45.166,44,cycle_done,3345.6,result=PASS cognex=PASS basler=PASS +15:35:45.166,45,cycle_start,0.0,group=A belt_delay=3.33s +15:35:45.182,45,cognex_trigger_send,2.8, +15:35:45.189,45,cognex_trigger_ok,9.3, +15:35:46.191,45,cognex_ftp_start,1011.5, +15:35:46.944,45,cognex_ftp_done,1765.2,3686454bytes +15:35:46.944,45,cognex_patmax_start,1766.7, +15:35:46.944,45,cognex_patmax_done,1778.1, +15:35:48.514,45,basler_capture_start,3334.3, +15:35:48.517,45,basler_capture_done,3339.1,failed +15:35:48.517,45,cognex_join_wait,3341.1, +15:35:48.517,45,cognex_join_done,3343.0, +15:35:48.517,45,cycle_done,3344.9,result=PASS cognex=PASS basler=PASS +15:35:48.517,46,cycle_start,0.0,group=A belt_delay=3.33s +15:35:48.529,46,cognex_trigger_send,2.6, +15:35:48.537,46,cognex_trigger_ok,9.4, +15:35:49.539,46,cognex_ftp_start,1011.8, +15:35:50.294,46,cognex_ftp_done,1766.5,3686454bytes +15:35:50.294,46,cognex_patmax_start,1768.0, +15:35:50.294,46,cognex_patmax_done,1779.5, +15:35:51.861,46,basler_capture_start,3334.4, +15:35:51.865,46,basler_capture_done,3339.6,failed +15:35:51.865,46,cognex_join_wait,3341.4, +15:35:51.865,46,cognex_join_done,3343.3, +15:35:51.865,46,cycle_done,3345.1,result=PASS cognex=PASS basler=PASS +15:35:51.865,47,cycle_start,0.0,group=A belt_delay=3.33s +15:35:51.877,47,cognex_trigger_send,2.6, +15:35:51.884,47,cognex_trigger_ok,9.1, +15:35:52.886,47,cognex_ftp_start,1011.3, +15:35:53.626,47,cognex_ftp_done,1751.5,3686454bytes +15:35:53.627,47,cognex_patmax_start,1753.0, +15:35:53.627,47,cognex_patmax_done,1764.5, +15:35:55.209,47,basler_capture_start,3334.1, +15:35:55.209,47,basler_capture_done,3339.2,failed +15:35:55.209,47,cognex_join_wait,3341.3, +15:35:55.217,47,cognex_join_done,3343.2, +15:35:55.217,47,cycle_done,3345.1,result=PASS cognex=PASS basler=PASS +15:35:55.217,48,cycle_start,0.0,group=A belt_delay=3.33s +15:35:55.225,48,cognex_trigger_send,2.6, +15:35:55.231,48,cognex_trigger_ok,9.2, +15:35:56.234,48,cognex_ftp_start,1011.4, +15:35:56.995,48,cognex_ftp_done,1772.9,3686454bytes +15:35:56.997,48,cognex_patmax_start,1774.5, +15:35:57.000,48,cognex_patmax_done,1783.6, +15:35:58.556,48,basler_capture_start,3333.9, +15:35:58.561,48,basler_capture_done,3338.7,failed +15:35:58.561,48,cognex_join_wait,3340.1, +15:35:58.561,48,cognex_join_done,3341.4, +15:35:58.561,48,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +15:36:47.184,49,cycle_start,0.0,group=A belt_delay=3.33s +15:36:47.184,49,cognex_trigger_send,2.2, +15:36:47.192,49,cognex_trigger_ok,8.9, +15:36:48.195,49,cognex_ftp_start,1011.2, +15:36:48.927,49,cognex_ftp_done,1757.5,3686454bytes +15:36:48.943,49,cognex_patmax_start,1759.0, +15:36:48.944,49,cognex_patmax_done,1771.6, +15:36:50.518,49,basler_capture_start,3334.0, +15:36:50.518,49,basler_capture_done,3339.1,failed +15:36:50.518,49,cognex_join_wait,3341.1, +15:36:50.518,49,cognex_join_done,3343.1, +15:36:50.518,49,cycle_done,3344.9,result=FAIL cognex=FAIL basler=PASS +15:36:50.532,50,cycle_start,0.0,group=A belt_delay=3.33s +15:36:50.535,50,cognex_trigger_send,2.5, +15:36:50.541,50,cognex_trigger_ok,9.0, +15:36:51.543,50,cognex_ftp_start,1011.0, +15:36:52.277,50,cognex_ftp_done,1759.0,3686454bytes +15:36:52.293,50,cognex_patmax_start,1760.5, +15:36:52.299,50,cognex_patmax_done,1771.8, +15:36:53.866,50,basler_capture_start,3333.7, +15:36:53.866,50,basler_capture_done,3338.8,failed +15:36:53.866,50,cognex_join_wait,3340.7, +15:36:53.866,50,cognex_join_done,3342.5, +15:36:53.866,50,cycle_done,3344.3,result=FAIL cognex=FAIL basler=PASS +15:36:53.866,51,cycle_start,0.0,group=A belt_delay=3.33s +15:36:53.882,51,cognex_trigger_send,2.3, +15:36:53.887,51,cognex_trigger_ok,8.8, +15:36:54.891,51,cognex_ftp_start,1011.2, +15:36:55.626,51,cognex_ftp_done,1746.1,3686454bytes +15:36:55.627,51,cognex_patmax_start,1747.6, +15:36:55.627,51,cognex_patmax_done,1759.0, +15:36:57.214,51,basler_capture_start,3334.0, +15:36:57.214,51,basler_capture_done,3338.6,failed +15:36:57.214,51,cognex_join_wait,3340.6, +15:36:57.214,51,cognex_join_done,3342.5, +15:36:57.214,51,cycle_done,3344.4,result=FAIL cognex=FAIL basler=PASS +15:36:57.214,52,cycle_start,0.0,group=A belt_delay=3.33s +15:36:57.230,52,cognex_trigger_send,2.3, +15:36:57.237,52,cognex_trigger_ok,8.8, +15:36:58.239,52,cognex_ftp_start,1011.3, +15:36:58.994,52,cognex_ftp_done,1766.3,3686454bytes +15:36:58.994,52,cognex_patmax_start,1767.9, +15:36:58.994,52,cognex_patmax_done,1778.9, +15:37:00.562,52,basler_capture_start,3334.2, +15:37:00.566,52,basler_capture_done,3339.0,failed +15:37:00.566,52,cognex_join_wait,3340.9, +15:37:00.566,52,cognex_join_done,3342.8, +15:37:00.566,52,cycle_done,3344.6,result=FAIL cognex=FAIL basler=PASS +15:37:00.566,53,cycle_start,0.0,group=A belt_delay=3.33s +15:37:00.566,53,cognex_trigger_send,2.2, +15:37:00.584,53,cognex_trigger_ok,8.8, +15:37:01.587,53,cognex_ftp_start,1010.8, +15:37:02.344,53,cognex_ftp_done,1767.9,3686454bytes +15:37:02.344,53,cognex_patmax_start,1769.4, +15:37:02.344,53,cognex_patmax_done,1780.7, +15:37:03.910,53,basler_capture_start,3334.0, +15:37:03.910,53,basler_capture_done,3338.9,failed +15:37:03.910,53,cognex_join_wait,3340.8, +15:37:03.918,53,cognex_join_done,3342.8, +15:37:03.920,53,cycle_done,3344.9,result=FAIL cognex=FAIL basler=PASS +15:37:03.924,54,cycle_start,0.0,group=A belt_delay=3.33s +15:37:03.927,54,cognex_trigger_send,2.4, +15:37:03.933,54,cognex_trigger_ok,9.2, +15:37:04.936,54,cognex_ftp_start,1011.2, +15:37:05.683,54,cognex_ftp_done,1767.9,3686454bytes +15:37:05.694,54,cognex_patmax_start,1769.5, +15:37:05.694,54,cognex_patmax_done,1780.8, +15:37:07.258,54,basler_capture_start,3333.5, +15:37:07.258,54,basler_capture_done,3338.1,failed +15:37:07.258,54,cognex_join_wait,3340.0, +15:37:07.258,54,cognex_join_done,3341.8, +15:37:07.268,54,cycle_done,3343.6,result=FAIL cognex=FAIL basler=PASS +16:09:17.142,1,cycle_start,0.0,group=A belt_delay=3.33s +16:09:17.144,1,cognex_trigger_send,1.7, +16:09:17.151,1,cognex_trigger_ok,8.2, +16:09:18.153,1,cognex_ftp_start,1010.3, +16:09:18.879,1,cognex_ftp_done,1736.3,3686454bytes +16:09:18.880,1,cognex_patmax_start,1737.7, +16:09:18.890,1,cognex_patmax_done,1747.3, +16:09:20.476,1,basler_capture_start,3333.4, +16:09:20.481,1,basler_capture_done,3338.8,failed +16:09:20.483,1,cognex_join_wait,3340.3, +16:09:20.484,1,cognex_join_done,3341.6, +16:09:20.486,1,cycle_done,3342.9,result=FAIL cognex=FAIL basler=PASS +16:09:20.488,2,cycle_start,0.0,group=A belt_delay=3.33s +16:09:20.490,2,cognex_trigger_send,1.7, +16:09:20.495,2,cognex_trigger_ok,7.7, +16:09:21.498,2,cognex_ftp_start,1009.4, +16:09:22.245,2,cognex_ftp_done,1757.6,3686454bytes +16:09:22.247,2,cognex_patmax_start,1759.1, +16:09:22.258,2,cognex_patmax_done,1770.4, +16:09:23.822,2,basler_capture_start,3333.6, +16:09:23.825,2,basler_capture_done,3336.6,failed +16:09:23.827,2,cognex_join_wait,3338.8, +16:09:23.829,2,cognex_join_done,3340.8, +16:09:23.831,2,cycle_done,3342.7,result=FAIL cognex=FAIL basler=PASS +16:09:23.834,3,cycle_start,0.0,group=A belt_delay=3.33s +16:09:23.837,3,cognex_trigger_send,2.3, +16:09:23.843,3,cognex_trigger_ok,8.8, +16:09:24.846,3,cognex_ftp_start,1010.8, +16:09:25.571,3,cognex_ftp_done,1736.4,3686454bytes +16:09:25.572,3,cognex_patmax_start,1737.9, +16:09:25.584,3,cognex_patmax_done,1749.5, +16:09:27.168,3,basler_capture_start,3333.5, +16:09:27.171,3,basler_capture_done,3336.5,failed +16:09:27.173,3,cognex_join_wait,3338.6, +16:09:27.176,3,cognex_join_done,3340.7, +16:09:27.178,3,cycle_done,3342.7,result=FAIL cognex=FAIL basler=PASS +16:09:27.180,4,cycle_start,0.0,group=A belt_delay=3.33s +16:09:27.183,4,cognex_trigger_send,2.3, +16:09:27.189,4,cognex_trigger_ok,8.9, +16:09:28.192,4,cognex_ftp_start,1010.9, +16:09:28.945,4,cognex_ftp_done,1765.3,3686454bytes +16:09:28.948,4,cognex_patmax_start,1766.9, +16:09:28.959,4,cognex_patmax_done,1778.2, +16:09:30.515,4,basler_capture_start,3333.9, +16:09:30.518,4,basler_capture_done,3336.9,failed +16:09:30.520,4,cognex_join_wait,3339.4, +16:09:30.521,4,cognex_join_done,3341.4, +16:09:30.524,4,cycle_done,3343.3,result=FAIL cognex=FAIL basler=PASS +16:09:30.528,5,cycle_start,0.0,group=A belt_delay=3.33s +16:09:30.530,5,cognex_trigger_send,2.3, +16:09:30.536,5,cognex_trigger_ok,9.0, +16:09:31.539,5,cognex_ftp_start,1011.2, +16:09:32.266,5,cognex_ftp_done,1738.4,3686454bytes +16:09:32.267,5,cognex_patmax_start,1739.8, +16:09:32.279,5,cognex_patmax_done,1751.4, +16:09:33.862,5,basler_capture_start,3333.6, +16:09:33.865,5,basler_capture_done,3336.5,failed +16:09:33.867,5,cognex_join_wait,3338.8, +16:09:33.869,5,cognex_join_done,3340.8, +16:09:33.871,5,cycle_done,3342.8,result=FAIL cognex=FAIL basler=PASS +16:09:33.874,6,cycle_start,0.0,group=A belt_delay=3.33s +16:09:33.876,6,cognex_trigger_send,2.4, +16:09:33.883,6,cognex_trigger_ok,9.0, +16:09:34.886,6,cognex_ftp_start,1011.2, +16:09:35.642,6,cognex_ftp_done,1767.9,3686454bytes +16:09:35.643,6,cognex_patmax_start,1769.4, +16:09:35.654,6,cognex_patmax_done,1779.4, +16:09:37.208,6,basler_capture_start,3333.7, +16:09:37.211,6,basler_capture_done,3336.7,failed +16:09:37.213,6,cognex_join_wait,3338.7, +16:09:37.215,6,cognex_join_done,3340.7, +16:09:37.217,6,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +16:09:37.220,7,cycle_start,0.0,group=A belt_delay=3.33s +16:09:37.222,7,cognex_trigger_send,2.6, +16:09:37.229,7,cognex_trigger_ok,9.5, +16:09:38.232,7,cognex_ftp_start,1011.9, +16:09:38.957,7,cognex_ftp_done,1738.2,3686454bytes +16:09:38.959,7,cognex_patmax_start,1740.0, +16:09:38.971,7,cognex_patmax_done,1751.8, +16:09:40.553,7,basler_capture_start,3333.5, +16:09:40.556,7,basler_capture_done,3336.5,failed +16:09:40.558,7,cognex_join_wait,3338.6, +16:09:40.560,7,cognex_join_done,3340.6, +16:09:40.562,7,cycle_done,3342.6,result=FAIL cognex=FAIL basler=PASS +16:09:40.565,8,cycle_start,0.0,group=A belt_delay=3.33s +16:09:40.568,8,cognex_trigger_send,2.3, +16:09:40.574,8,cognex_trigger_ok,8.8, +16:09:41.576,8,cognex_ftp_start,1010.8, +16:09:42.335,8,cognex_ftp_done,1769.4,3686454bytes +16:09:42.336,8,cognex_patmax_start,1770.9, +16:09:42.348,8,cognex_patmax_done,1782.3, +16:09:43.899,8,basler_capture_start,3333.4, +16:09:43.902,8,basler_capture_done,3336.4,failed +16:09:43.903,8,cognex_join_wait,3337.9, +16:09:43.904,8,cognex_join_done,3339.2, +16:09:43.906,8,cycle_done,3340.4,result=FAIL cognex=FAIL basler=PASS +16:09:43.909,9,cycle_start,0.0,group=A belt_delay=3.33s +16:09:43.910,9,cognex_trigger_send,1.8, +16:09:43.917,9,cognex_trigger_ok,7.8, +16:09:44.918,9,cognex_ftp_start,1009.4, +16:09:45.661,9,cognex_ftp_done,1752.7,3686454bytes +16:09:45.663,9,cognex_patmax_start,1754.3, +16:09:45.672,9,cognex_patmax_done,1763.7, +16:09:47.243,9,basler_capture_start,3333.6, +16:09:47.246,9,basler_capture_done,3336.6,failed +16:09:47.248,9,cognex_join_wait,3338.8, +16:09:47.250,9,cognex_join_done,3340.6, +16:09:47.251,9,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +16:09:47.254,10,cycle_start,0.0,group=A belt_delay=3.33s +16:09:47.257,10,cognex_trigger_send,2.2, +16:09:47.264,10,cognex_trigger_ok,8.9, +16:09:48.266,10,cognex_ftp_start,1011.3, +16:09:49.029,10,cognex_ftp_done,1774.1,3686454bytes +16:09:49.030,10,cognex_patmax_start,1775.5, +16:09:49.040,10,cognex_patmax_done,1785.4, +16:09:50.589,10,basler_capture_start,3333.6, +16:09:50.592,10,basler_capture_done,3336.6,failed +16:09:50.593,10,cognex_join_wait,3338.7, +16:09:50.595,10,cognex_join_done,3340.7, +16:09:50.597,10,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +16:09:50.600,11,cycle_start,0.0,group=A belt_delay=3.33s +16:09:50.602,11,cognex_trigger_send,2.4, +16:09:50.608,11,cognex_trigger_ok,9.0, +16:09:51.611,11,cognex_ftp_start,1011.2, +16:09:52.336,11,cognex_ftp_done,1736.1,3686454bytes +16:09:52.338,11,cognex_patmax_start,1737.6, +16:09:52.348,11,cognex_patmax_done,1749.3, +16:09:53.934,11,basler_capture_start,3333.7, +16:09:53.937,11,basler_capture_done,3336.8,failed +16:09:53.939,11,cognex_join_wait,3338.8, +16:09:53.940,11,cognex_join_done,3340.8, +16:09:53.942,11,cycle_done,3342.7,result=FAIL cognex=FAIL basler=PASS +16:09:53.945,12,cycle_start,0.0,group=A belt_delay=3.33s +16:09:53.948,12,cognex_trigger_send,2.2, +16:09:53.954,12,cognex_trigger_ok,8.9, +16:09:54.957,12,cognex_ftp_start,1010.9, +16:09:55.714,12,cognex_ftp_done,1768.7,3686454bytes +16:09:55.716,12,cognex_patmax_start,1770.2, +16:09:55.727,12,cognex_patmax_done,1781.7, +16:09:57.280,12,basler_capture_start,3333.6, +16:09:57.282,12,basler_capture_done,3336.5,failed +16:09:57.284,12,cognex_join_wait,3338.5, +16:09:57.286,12,cognex_join_done,3340.5, +16:09:57.288,12,cycle_done,3342.4,result=FAIL cognex=FAIL basler=PASS +16:09:57.291,13,cycle_start,0.0,group=A belt_delay=3.33s +16:09:57.294,13,cognex_trigger_send,2.4, +16:09:57.301,13,cognex_trigger_ok,9.1, +16:09:58.303,13,cognex_ftp_start,1011.4, +16:09:59.044,13,cognex_ftp_done,1751.9,3686454bytes +16:09:59.045,13,cognex_patmax_start,1753.4, +16:09:59.057,13,cognex_patmax_done,1764.8, +16:10:00.626,13,basler_capture_start,3333.6, +16:10:00.629,13,basler_capture_done,3336.5,failed +16:10:00.630,13,cognex_join_wait,3338.6, +16:10:00.632,13,cognex_join_done,3340.5, +16:10:00.634,13,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +16:10:00.637,14,cycle_start,0.0,group=A belt_delay=3.33s +16:10:00.640,14,cognex_trigger_send,2.2, +16:10:00.647,14,cognex_trigger_ok,8.7, +16:10:01.649,14,cognex_ftp_start,1011.0, +16:10:02.396,14,cognex_ftp_done,1757.7,3686454bytes +16:10:02.397,14,cognex_patmax_start,1759.2, +16:10:02.408,14,cognex_patmax_done,1770.6, +16:10:03.971,14,basler_capture_start,3333.6, +16:10:03.974,14,basler_capture_done,3336.5,failed +16:10:03.977,14,cognex_join_wait,3339.1, +16:10:03.979,14,cognex_join_done,3341.1, +16:10:03.981,14,cycle_done,3343.0,result=FAIL cognex=FAIL basler=PASS +16:10:03.984,15,cycle_start,0.0,group=A belt_delay=3.33s +16:10:03.987,15,cognex_trigger_send,2.2, +16:10:03.993,15,cognex_trigger_ok,8.8, +16:10:04.996,15,cognex_ftp_start,1011.0, +16:10:05.721,15,cognex_ftp_done,1737.2,3686454bytes +16:10:05.723,15,cognex_patmax_start,1738.7, +16:10:05.734,15,cognex_patmax_done,1750.4, +16:10:07.319,15,basler_capture_start,3333.9, +16:10:07.321,15,basler_capture_done,3336.8,failed +16:10:07.323,15,cognex_join_wait,3338.9, +16:10:07.324,15,cognex_join_done,3340.2, +16:10:07.326,15,cycle_done,3341.5,result=FAIL cognex=FAIL basler=PASS +16:10:07.328,16,cycle_start,0.0,group=A belt_delay=3.33s +16:10:07.330,16,cognex_trigger_send,1.6, +16:10:07.336,16,cognex_trigger_ok,7.5, +16:10:08.338,16,cognex_ftp_start,1009.0, +16:10:09.092,16,cognex_ftp_done,1763.8,3686454bytes +16:10:09.094,16,cognex_patmax_start,1765.3, +16:10:09.103,16,cognex_patmax_done,1774.7, +16:10:10.663,16,basler_capture_start,3333.6, +16:10:10.666,16,basler_capture_done,3337.0,failed +16:10:10.668,16,cognex_join_wait,3339.1, +16:10:10.670,16,cognex_join_done,3341.1, +16:10:10.671,16,cycle_done,3343.0,result=FAIL cognex=FAIL basler=PASS +16:10:10.676,17,cycle_start,0.0,group=A belt_delay=3.33s +16:10:10.678,17,cognex_trigger_send,2.5, +16:10:10.684,17,cognex_trigger_ok,9.0, +16:10:11.687,17,cognex_ftp_start,1011.3, +16:10:12.414,17,cognex_ftp_done,1738.5,3686454bytes +16:10:12.416,17,cognex_patmax_start,1740.0, +16:10:12.426,17,cognex_patmax_done,1751.4, +16:10:14.010,17,basler_capture_start,3333.9, +16:10:14.012,17,basler_capture_done,3337.0,failed +16:10:14.015,17,cognex_join_wait,3339.9, +16:10:14.017,17,cognex_join_done,3341.9, +16:10:14.019,17,cycle_done,3343.9,result=FAIL cognex=FAIL basler=PASS +16:10:14.023,18,cycle_start,0.0,group=A belt_delay=3.33s +16:10:14.025,18,cognex_trigger_send,2.3, +16:10:14.032,18,cognex_trigger_ok,9.0, +16:10:15.035,18,cognex_ftp_start,1011.2, +16:10:15.799,18,cognex_ftp_done,1776.1,3686454bytes +16:10:15.800,18,cognex_patmax_start,1777.6, +16:10:15.811,18,cognex_patmax_done,1788.9, +16:10:17.357,18,basler_capture_start,3333.7, +16:10:17.361,18,basler_capture_done,3337.8,failed +16:10:17.364,18,cognex_join_wait,3341.6, +16:10:17.366,18,cognex_join_done,3343.6, +16:10:17.368,18,cycle_done,3345.5,result=PASS cognex=PASS basler=PASS +16:10:17.371,19,cycle_start,0.0,group=A belt_delay=3.33s +16:10:17.373,19,cognex_trigger_send,2.4, +16:10:17.380,19,cognex_trigger_ok,9.0, +16:10:18.383,19,cognex_ftp_start,1011.4, +16:10:19.132,19,cognex_ftp_done,1761.7,3686454bytes +16:10:19.134,19,cognex_patmax_start,1763.2, +16:10:19.146,19,cognex_patmax_done,1774.9, +16:10:20.705,19,basler_capture_start,3333.7, +16:10:20.709,19,basler_capture_done,3339.5,failed +16:10:20.712,19,cognex_join_wait,3341.7, +16:10:20.714,19,cognex_join_done,3343.6, +16:10:20.716,19,cycle_done,3345.5,result=PASS cognex=PASS basler=PASS +16:10:20.718,20,cycle_start,0.0,group=A belt_delay=3.33s +16:10:20.721,20,cognex_trigger_send,2.4, +16:10:20.728,20,cognex_trigger_ok,8.9, +16:10:21.731,20,cognex_ftp_start,1011.3, +16:10:22.480,20,cognex_ftp_done,1760.4,3686454bytes +16:10:22.481,20,cognex_patmax_start,1761.9, +16:10:22.492,20,cognex_patmax_done,1773.4, +16:10:24.053,20,basler_capture_start,3333.8, +16:10:24.055,20,basler_capture_done,3336.7,failed +16:10:24.057,20,cognex_join_wait,3338.8, +16:10:24.059,20,cognex_join_done,3340.8, +16:10:24.062,20,cycle_done,3343.0,result=PASS cognex=PASS basler=PASS +16:10:24.064,21,cycle_start,0.0,group=A belt_delay=3.33s +16:10:24.067,21,cognex_trigger_send,2.3, +16:10:24.073,21,cognex_trigger_ok,8.8, +16:10:25.076,21,cognex_ftp_start,1011.2, +16:10:25.799,21,cognex_ftp_done,1735.3,3686454bytes +16:10:25.801,21,cognex_patmax_start,1736.8, +16:10:25.812,21,cognex_patmax_done,1748.2, +16:10:27.398,21,basler_capture_start,3333.4, +16:10:27.401,21,basler_capture_done,3336.4,failed +16:10:27.402,21,cognex_join_wait,3338.5, +16:10:27.404,21,cognex_join_done,3340.4, +16:10:27.407,21,cycle_done,3342.4,result=PASS cognex=PASS basler=PASS +16:48:47.459,1,cycle_start,0.0,group=A belt_delay=3.33s +16:48:47.459,1,cognex_trigger_send,1.7, +16:48:47.471,1,cognex_trigger_ok,8.3, +16:48:48.474,1,cognex_ftp_start,1010.9, +16:48:49.221,1,cognex_ftp_done,1758.4,3686454bytes +16:48:49.222,1,cognex_patmax_start,1760.1, +16:48:49.222,1,cognex_patmax_done,1771.7, +16:48:50.797,1,basler_capture_start,3334.1, +16:48:50.797,1,basler_capture_done,3337.2,failed +16:48:50.797,1,cognex_join_wait,3339.2, +16:48:50.797,1,cognex_join_done,3341.2, +16:48:50.797,1,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +16:48:50.797,2,cycle_start,0.0,group=A belt_delay=3.33s +16:48:50.809,2,cognex_trigger_send,1.9, +16:48:50.815,2,cognex_trigger_ok,7.8, +16:48:51.817,2,cognex_ftp_start,1009.2, +16:48:52.572,2,cognex_ftp_done,1766.7,3686454bytes +16:48:52.572,2,cognex_patmax_start,1768.2, +16:48:52.572,2,cognex_patmax_done,1777.4, +16:48:54.142,2,basler_capture_start,3334.3, +16:48:54.142,2,basler_capture_done,3337.4,failed +16:48:54.145,2,cognex_join_wait,3338.9, +16:48:54.145,2,cognex_join_done,3340.8, +16:48:54.145,2,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +16:48:54.145,3,cycle_start,0.0,group=A belt_delay=3.33s +16:48:54.145,3,cognex_trigger_send,2.3, +16:48:54.161,3,cognex_trigger_ok,9.0, +16:48:55.164,3,cognex_ftp_start,1011.3, +16:48:55.938,3,cognex_ftp_done,1785.6,3686454bytes +16:48:55.939,3,cognex_patmax_start,1787.1, +16:48:55.939,3,cognex_patmax_done,1798.4, +16:48:57.487,3,basler_capture_start,3334.0, +16:48:57.487,3,basler_capture_done,3337.1,failed +16:48:57.487,3,cognex_join_wait,3339.0, +16:48:57.487,3,cognex_join_done,3340.9, +16:48:57.487,3,cycle_done,3342.8,result=PASS cognex=PASS basler=PASS +16:48:57.496,4,cycle_start,0.0,group=A belt_delay=3.33s +16:48:57.496,4,cognex_trigger_send,2.4, +16:48:57.506,4,cognex_trigger_ok,9.1, +16:48:58.509,4,cognex_ftp_start,1011.1, +16:48:59.272,4,cognex_ftp_done,1775.8,3686454bytes +16:48:59.272,4,cognex_patmax_start,1777.3, +16:48:59.272,4,cognex_patmax_done,1788.5, +16:49:00.832,4,basler_capture_start,3334.0, +16:49:00.832,4,basler_capture_done,3337.1,failed +16:49:00.832,4,cognex_join_wait,3339.1, +16:49:00.832,4,cognex_join_done,3340.9, +16:49:00.832,4,cycle_done,3342.8,result=PASS cognex=PASS basler=PASS +16:49:00.832,5,cycle_start,0.0,group=A belt_delay=3.33s +16:49:00.844,5,cognex_trigger_send,2.4, +16:49:00.851,5,cognex_trigger_ok,8.9, +16:49:01.854,5,cognex_ftp_start,1010.9, +16:49:02.622,5,cognex_ftp_done,1782.4,3686454bytes +16:49:02.622,5,cognex_patmax_start,1783.8, +16:49:02.638,5,cognex_patmax_done,1795.5, +16:49:04.176,5,basler_capture_start,3333.9, +16:49:04.178,5,basler_capture_done,3336.9,failed +16:49:04.178,5,cognex_join_wait,3338.8, +16:49:04.178,5,cognex_join_done,3340.7, +16:49:04.178,5,cycle_done,3342.6,result=PASS cognex=PASS basler=PASS +16:49:04.178,6,cycle_start,0.0,group=A belt_delay=3.33s +16:49:04.178,6,cognex_trigger_send,2.5, +16:49:04.196,6,cognex_trigger_ok,9.1, +16:49:05.199,6,cognex_ftp_start,1011.5, +16:49:05.955,6,cognex_ftp_done,1770.0,3686454bytes +16:49:05.955,6,cognex_patmax_start,1771.6, +16:49:05.955,6,cognex_patmax_done,1782.9, +16:49:07.521,6,basler_capture_start,3333.7, +16:49:07.521,6,basler_capture_done,3336.8,failed +16:49:07.521,6,cognex_join_wait,3338.8, +16:49:07.528,6,cognex_join_done,3340.6, +16:49:07.528,6,cycle_done,3342.5,result=PASS cognex=PASS basler=PASS +16:49:07.528,7,cycle_start,0.0,group=A belt_delay=3.33s +16:49:07.528,7,cognex_trigger_send,2.3, +16:49:07.541,7,cognex_trigger_ok,9.1, +16:49:08.544,7,cognex_ftp_start,1011.5, +16:49:09.289,7,cognex_ftp_done,1756.4,3686454bytes +16:49:09.289,7,cognex_patmax_start,1757.9, +16:49:09.289,7,cognex_patmax_done,1769.5, +16:49:10.866,7,basler_capture_start,3333.7, +16:49:10.866,7,basler_capture_done,3336.9,failed +16:49:10.866,7,cognex_join_wait,3338.8, +16:49:10.866,7,cognex_join_done,3340.6, +16:49:10.866,7,cycle_done,3342.5,result=PASS cognex=PASS basler=PASS +16:49:10.876,8,cycle_start,0.0,group=A belt_delay=3.33s +16:49:10.876,8,cognex_trigger_send,2.3, +16:49:10.886,8,cognex_trigger_ok,9.0, +16:49:11.889,8,cognex_ftp_start,1011.3, +16:49:12.622,8,cognex_ftp_done,1748.8,3686454bytes +16:49:12.622,8,cognex_patmax_start,1750.3, +16:49:12.639,8,cognex_patmax_done,1761.7, +16:49:14.211,8,basler_capture_start,3333.9, +16:49:14.211,8,basler_capture_done,3337.0,failed +16:49:14.211,8,cognex_join_wait,3338.8, +16:49:14.211,8,cognex_join_done,3340.7, +16:49:14.211,8,cycle_done,3342.5,result=PASS cognex=PASS basler=PASS +16:49:14.211,9,cycle_start,0.0,group=A belt_delay=3.33s +16:49:14.211,9,cognex_trigger_send,1.7, +16:49:14.230,9,cognex_trigger_ok,7.6, +16:49:15.231,9,cognex_ftp_start,1009.0, +16:49:15.988,9,cognex_ftp_done,1765.5,3686454bytes +16:49:15.989,9,cognex_patmax_start,1767.2, +16:49:15.989,9,cognex_patmax_done,1776.4, +16:49:17.556,9,basler_capture_start,3333.8, +16:49:17.556,9,basler_capture_done,3336.9,failed +16:49:17.560,9,cognex_join_wait,3338.2, +16:49:17.560,9,cognex_join_done,3339.5, +16:49:17.560,9,cycle_done,3340.8,result=PASS cognex=PASS basler=PASS +16:49:17.560,10,cycle_start,0.0,group=A belt_delay=3.33s +16:49:17.560,10,cognex_trigger_send,2.4, +16:49:17.575,10,cognex_trigger_ok,9.1, +16:49:18.577,10,cognex_ftp_start,1011.1, +16:49:19.322,10,cognex_ftp_done,1756.8,3686454bytes +16:49:19.322,10,cognex_patmax_start,1758.2, +16:49:19.322,10,cognex_patmax_done,1769.6, +16:49:20.900,10,basler_capture_start,3333.9, +16:49:20.900,10,basler_capture_done,3337.1,failed +16:49:20.900,10,cognex_join_wait,3339.3, +16:49:20.900,10,cognex_join_done,3341.2, +16:49:20.900,10,cycle_done,3343.1,result=PASS cognex=PASS basler=PASS +16:49:20.911,11,cycle_start,0.0,group=A belt_delay=3.33s +16:49:20.911,11,cognex_trigger_send,2.4, +16:49:20.920,11,cognex_trigger_ok,9.0, +16:49:21.923,11,cognex_ftp_start,1011.6, +16:49:22.688,11,cognex_ftp_done,1781.3,3686454bytes +16:49:22.688,11,cognex_patmax_start,1782.7, +16:49:22.705,11,cognex_patmax_done,1794.3, +16:49:24.245,11,basler_capture_start,3333.9, +16:49:24.245,11,basler_capture_done,3336.9,failed +16:49:24.245,11,cognex_join_wait,3338.9, +16:49:24.245,11,cognex_join_done,3340.9, +16:49:24.245,11,cycle_done,3342.9,result=PASS cognex=PASS basler=PASS +16:49:24.245,12,cycle_start,0.0,group=A belt_delay=3.33s +16:49:24.245,12,cognex_trigger_send,2.4, +16:49:24.266,12,cognex_trigger_ok,9.0, +16:49:25.268,12,cognex_ftp_start,1011.6, +16:49:26.122,12,cognex_ftp_done,1879.0,3686454bytes +16:49:26.122,12,cognex_patmax_start,1880.4, +16:49:26.144,12,cognex_patmax_done,1892.0, +16:49:27.590,12,basler_capture_start,3333.6, +16:49:27.590,12,basler_capture_done,3336.6,failed +16:49:27.590,12,cognex_join_wait,3338.6, +16:49:27.590,12,cognex_join_done,3340.7, +16:49:27.590,12,cycle_done,3342.7,result=PASS cognex=PASS basler=PASS +16:49:27.602,13,cycle_start,0.0,group=A belt_delay=3.33s +16:49:27.602,13,cognex_trigger_send,2.3, +16:49:27.611,13,cognex_trigger_ok,9.1, +16:49:28.613,13,cognex_ftp_start,1011.2, +16:49:29.372,13,cognex_ftp_done,1771.2,3686454bytes +16:49:29.372,13,cognex_patmax_start,1772.6, +16:49:29.372,13,cognex_patmax_done,1784.1, +16:49:30.936,13,basler_capture_start,3333.8, +16:49:30.936,13,basler_capture_done,3336.9,failed +16:49:30.936,13,cognex_join_wait,3339.0, +16:49:30.936,13,cognex_join_done,3340.9, +16:49:30.945,13,cycle_done,3343.0,result=PASS cognex=PASS basler=PASS +16:49:30.945,14,cycle_start,0.0,group=A belt_delay=3.33s +16:49:30.945,14,cognex_trigger_send,2.4, +16:49:30.956,14,cognex_trigger_ok,9.1, +16:49:31.959,14,cognex_ftp_start,1011.3, +16:49:32.739,14,cognex_ftp_done,1795.8,3686454bytes +16:49:32.739,14,cognex_patmax_start,1797.3, +16:49:32.756,14,cognex_patmax_done,1808.6, +16:49:34.282,14,basler_capture_start,3334.1, +16:49:34.282,14,basler_capture_done,3337.1,failed +16:49:34.282,14,cognex_join_wait,3339.2, +16:49:34.282,14,cognex_join_done,3341.2, +16:49:34.282,14,cycle_done,3343.2,result=PASS cognex=PASS basler=PASS +16:49:34.282,15,cycle_start,0.0,group=A belt_delay=3.33s +16:49:34.282,15,cognex_trigger_send,2.4, +16:49:34.302,15,cognex_trigger_ok,9.3, +16:49:35.305,15,cognex_ftp_start,1011.8, +16:49:36.055,15,cognex_ftp_done,1761.9,3686454bytes +16:49:36.055,15,cognex_patmax_start,1763.4, +16:49:36.055,15,cognex_patmax_done,1774.7, +16:49:37.627,15,basler_capture_start,3334.3, +16:49:37.627,15,basler_capture_done,3337.4,failed +16:49:37.627,15,cognex_join_wait,3339.5, +16:49:37.627,15,cognex_join_done,3341.4, +16:49:37.627,15,cycle_done,3343.3,result=PASS cognex=PASS basler=PASS +16:49:37.627,16,cycle_start,0.0,group=A belt_delay=3.33s +16:49:37.627,16,cognex_trigger_send,2.3, +16:49:37.647,16,cognex_trigger_ok,8.5, +16:49:38.649,16,cognex_ftp_start,1010.4, +16:49:39.438,16,cognex_ftp_done,1799.3,3686454bytes +16:49:39.439,16,cognex_patmax_start,1801.0, +16:49:39.439,16,cognex_patmax_done,1810.5, +16:49:40.972,16,basler_capture_start,3333.7, +16:49:40.973,16,basler_capture_done,3336.8,failed +16:49:40.973,16,cognex_join_wait,3338.3, +16:49:40.973,16,cognex_join_done,3339.6, +16:49:40.973,16,cycle_done,3340.9,result=PASS cognex=PASS basler=PASS +16:50:06.903,17,cycle_start,0.0,group=A belt_delay=3.33s +16:50:06.905,17,cognex_trigger_send,2.5, +16:50:06.905,17,cognex_trigger_ok,9.4, +16:50:07.915,17,cognex_ftp_start,1011.6, +16:50:08.672,17,cognex_ftp_done,1774.4,3686454bytes +16:50:08.672,17,cognex_patmax_start,1775.9, +16:50:08.690,17,cognex_patmax_done,1787.4, +16:50:10.237,17,basler_capture_start,3334.1, +16:50:10.237,17,basler_capture_done,3337.0,failed +16:50:10.237,17,cognex_join_wait,3339.0, +16:50:10.243,17,cognex_join_done,3341.0, +16:50:10.243,17,cycle_done,3342.8,result=FAIL cognex=FAIL basler=PASS +16:50:10.243,18,cycle_start,0.0,group=A belt_delay=3.33s +16:50:10.243,18,cognex_trigger_send,2.3, +16:50:10.243,18,cognex_trigger_ok,8.8, +16:50:11.260,18,cognex_ftp_start,1011.3, +16:50:12.005,18,cognex_ftp_done,1758.2,3686454bytes +16:50:12.005,18,cognex_patmax_start,1759.7, +16:50:12.005,18,cognex_patmax_done,1771.0, +16:50:13.583,18,basler_capture_start,3334.2, +16:50:13.583,18,basler_capture_done,3337.2,failed +16:50:13.583,18,cognex_join_wait,3339.3, +16:50:13.583,18,cognex_join_done,3341.2, +16:50:13.583,18,cycle_done,3343.1,result=FAIL cognex=FAIL basler=PASS +16:53:32.289,1,cycle_start,0.0,group=A belt_delay=3.33s +16:53:32.291,1,cognex_trigger_send,2.0, +16:53:32.297,1,cognex_trigger_ok,8.5, +16:53:33.300,1,cognex_ftp_start,1010.9, +16:53:34.051,1,cognex_ftp_done,1762.1,3686454bytes +16:53:34.052,1,cognex_patmax_start,1763.6, +16:53:34.068,1,cognex_patmax_done,1780.2, +16:53:35.622,1,basler_capture_start,3333.7, +16:53:35.625,1,basler_capture_done,3336.8,failed +16:53:35.627,1,cognex_join_wait,3338.8, +16:53:35.629,1,cognex_join_done,3340.7, +16:53:35.631,1,cycle_done,3342.5,result=PASS cognex=PASS basler=PASS +16:53:35.633,2,cycle_start,0.0,group=A belt_delay=3.33s +16:53:35.635,2,cognex_trigger_send,2.8, +16:53:35.642,2,cognex_trigger_ok,9.2, +16:53:36.645,2,cognex_ftp_start,1011.4, +16:53:37.397,2,cognex_ftp_done,1763.7,3686454bytes +16:53:37.398,2,cognex_patmax_start,1765.2, +16:53:37.409,2,cognex_patmax_done,1776.3, +16:53:38.967,2,basler_capture_start,3333.4, +16:53:38.970,2,basler_capture_done,3336.5,failed +16:53:38.972,2,cognex_join_wait,3338.5, +16:53:38.973,2,cognex_join_done,3340.4, +16:53:38.975,2,cycle_done,3342.3,result=PASS cognex=PASS basler=PASS +16:53:38.978,3,cycle_start,0.0,group=A belt_delay=3.33s +16:53:38.981,3,cognex_trigger_send,2.4, +16:53:38.987,3,cognex_trigger_ok,8.7, +16:53:39.990,3,cognex_ftp_start,1011.2, +16:53:40.739,3,cognex_ftp_done,1760.5,3686454bytes +16:53:40.740,3,cognex_patmax_start,1762.0, +16:53:40.751,3,cognex_patmax_done,1773.2, +16:53:42.312,3,basler_capture_start,3333.7, +16:53:42.315,3,basler_capture_done,3336.8,failed +16:53:42.317,3,cognex_join_wait,3338.9, +16:53:42.319,3,cognex_join_done,3340.8, +16:53:42.321,3,cycle_done,3342.7,result=PASS cognex=PASS basler=PASS +16:53:42.323,4,cycle_start,0.0,group=A belt_delay=3.33s +16:53:42.325,4,cognex_trigger_send,2.3, +16:53:42.332,4,cognex_trigger_ok,8.8, +16:53:43.335,4,cognex_ftp_start,1011.0, +16:53:44.075,4,cognex_ftp_done,1751.3,3686454bytes +16:53:44.076,4,cognex_patmax_start,1752.8, +16:53:44.087,4,cognex_patmax_done,1764.2, +16:53:45.657,4,basler_capture_start,3333.4, +16:53:45.659,4,basler_capture_done,3336.1,failed +16:53:45.661,4,cognex_join_wait,3338.1, +16:53:45.663,4,cognex_join_done,3339.9, +16:53:45.666,4,cycle_done,3341.8,result=PASS cognex=PASS basler=PASS +16:53:45.668,5,cycle_start,0.0,group=A belt_delay=3.33s +16:53:45.670,5,cognex_trigger_send,2.3, +16:53:45.677,5,cognex_trigger_ok,8.8, +16:53:46.679,5,cognex_ftp_start,1011.0, +16:53:47.407,5,cognex_ftp_done,1738.5,3686454bytes +16:53:47.407,5,cognex_patmax_start,1740.0, +16:53:47.418,5,cognex_patmax_done,1750.2, +16:53:49.002,5,basler_capture_start,3333.6, +16:53:49.004,5,basler_capture_done,3336.6,failed +16:53:49.005,5,cognex_join_wait,3338.0, +16:53:49.007,5,cognex_join_done,3339.2, +16:53:49.009,5,cycle_done,3340.4,result=PASS cognex=PASS basler=PASS +16:53:49.010,6,cycle_start,0.0,group=A belt_delay=3.33s +16:53:49.012,6,cognex_trigger_send,1.7, +16:53:49.018,6,cognex_trigger_ok,7.5, +16:53:50.020,6,cognex_ftp_start,1009.8, +16:53:50.776,6,cognex_ftp_done,1765.3,3686454bytes +16:53:50.777,6,cognex_patmax_start,1766.9, +16:53:50.788,6,cognex_patmax_done,1777.9, +16:53:52.344,6,basler_capture_start,3333.8, +16:53:52.347,6,basler_capture_done,3336.6,failed +16:53:52.349,6,cognex_join_wait,3338.7, +16:53:52.350,6,cognex_join_done,3340.5, +16:53:52.353,6,cycle_done,3342.4,result=PASS cognex=PASS basler=PASS +16:53:52.355,7,cycle_start,0.0,group=A belt_delay=3.33s +16:53:52.357,7,cognex_trigger_send,2.3, +16:53:52.363,7,cognex_trigger_ok,8.8, +16:53:53.366,7,cognex_ftp_start,1010.9, +16:53:54.129,7,cognex_ftp_done,1773.8,3686454bytes +16:53:54.130,7,cognex_patmax_start,1775.3, +16:53:54.140,7,cognex_patmax_done,1786.2, +16:53:55.688,7,basler_capture_start,3333.4, +16:53:55.691,7,basler_capture_done,3336.4,failed +16:53:55.693,7,cognex_join_wait,3338.5, +16:53:55.695,7,cognex_join_done,3340.4, +16:53:55.697,7,cycle_done,3342.3,result=FAIL cognex=FAIL basler=PASS +16:53:55.700,8,cycle_start,0.0,group=A belt_delay=3.33s +16:53:55.702,8,cognex_trigger_send,2.3, +16:53:55.710,8,cognex_trigger_ok,8.9, +16:53:56.712,8,cognex_ftp_start,1011.1, +16:53:57.460,8,cognex_ftp_done,1759.2,3686454bytes +16:53:57.461,8,cognex_patmax_start,1760.7, +16:53:57.472,8,cognex_patmax_done,1771.4, +16:53:59.034,8,basler_capture_start,3333.5, +16:53:59.037,8,basler_capture_done,3336.4,failed +16:53:59.039,8,cognex_join_wait,3338.5, +16:53:59.041,8,cognex_join_done,3340.3, +16:53:59.043,8,cycle_done,3342.1,result=PASS cognex=PASS basler=PASS +16:53:59.045,9,cycle_start,0.0,group=A belt_delay=3.33s +16:53:59.048,9,cognex_trigger_send,2.4, +16:53:59.054,9,cognex_trigger_ok,9.1, +16:54:00.057,9,cognex_ftp_start,1011.1, +16:54:00.813,9,cognex_ftp_done,1767.9,3686454bytes +16:54:00.815,9,cognex_patmax_start,1769.4, +16:54:00.826,9,cognex_patmax_done,1780.0, +16:54:02.379,9,basler_capture_start,3333.7, +16:54:02.382,9,basler_capture_done,3336.8,failed +16:54:02.384,9,cognex_join_wait,3339.0, +16:54:02.385,9,cognex_join_done,3340.9, +16:54:02.388,9,cycle_done,3342.9,result=PASS cognex=PASS basler=PASS +16:54:02.391,10,cycle_start,0.0,group=A belt_delay=3.33s +16:54:02.393,10,cognex_trigger_send,2.3, +16:54:02.399,10,cognex_trigger_ok,8.9, +16:54:03.402,10,cognex_ftp_start,1011.0, +16:54:04.158,10,cognex_ftp_done,1768.0,3686454bytes +16:54:04.160,10,cognex_patmax_start,1769.5, +16:54:04.171,10,cognex_patmax_done,1780.8, +16:54:05.724,10,basler_capture_start,3333.6, +16:54:05.727,10,basler_capture_done,3336.6,failed +16:54:05.729,10,cognex_join_wait,3339.2, +16:54:05.731,10,cognex_join_done,3341.1, +16:54:05.733,10,cycle_done,3343.1,result=PASS cognex=PASS basler=PASS +16:54:05.735,11,cycle_start,0.0,group=A belt_delay=3.33s +16:54:05.738,11,cognex_trigger_send,2.3, +16:54:05.746,11,cognex_trigger_ok,9.3, +16:54:06.748,11,cognex_ftp_start,1011.5, +16:54:07.498,11,cognex_ftp_done,1761.6,3686454bytes +16:54:07.499,11,cognex_patmax_start,1763.0, +16:54:07.511,11,cognex_patmax_done,1774.3, +16:54:09.070,11,basler_capture_start,3333.6, +16:54:09.072,11,basler_capture_done,3336.3,failed +16:54:09.074,11,cognex_join_wait,3338.3, +16:54:09.076,11,cognex_join_done,3340.3, +16:54:09.078,11,cycle_done,3342.1,result=PASS cognex=PASS basler=PASS +16:54:09.081,12,cycle_start,0.0,group=A belt_delay=3.33s +16:54:09.083,12,cognex_trigger_send,2.4, +16:54:09.089,12,cognex_trigger_ok,8.9, +16:54:10.092,12,cognex_ftp_start,1011.4, +16:54:10.830,12,cognex_ftp_done,1749.8,3686454bytes +16:54:10.832,12,cognex_patmax_start,1751.3, +16:54:10.842,12,cognex_patmax_done,1761.3, +16:54:12.415,12,basler_capture_start,3333.8, +16:54:12.417,12,basler_capture_done,3336.6,failed +16:54:12.419,12,cognex_join_wait,3338.0, +16:54:12.420,12,cognex_join_done,3339.3, +16:54:12.422,12,cycle_done,3340.6,result=FAIL cognex=FAIL basler=PASS +16:54:12.425,13,cycle_start,0.0,group=A belt_delay=3.33s +16:54:12.426,13,cognex_trigger_send,1.6, +16:54:12.432,13,cognex_trigger_ok,7.5, +16:54:13.434,13,cognex_ftp_start,1009.3, +16:54:14.183,13,cognex_ftp_done,1758.1,3686454bytes +16:54:14.184,13,cognex_patmax_start,1759.6, +16:54:14.196,13,cognex_patmax_done,1771.4, +16:54:15.759,13,basler_capture_start,3333.9, +16:54:15.762,13,basler_capture_done,3336.9,failed +16:54:15.764,13,cognex_join_wait,3338.9, +16:54:15.766,13,cognex_join_done,3340.9, +16:54:15.767,13,cycle_done,3342.8,result=PASS cognex=PASS basler=PASS +16:54:15.769,14,cycle_start,0.0,group=A belt_delay=3.33s +16:54:15.772,14,cognex_trigger_send,2.4, +16:54:15.778,14,cognex_trigger_ok,8.9, +16:54:16.781,14,cognex_ftp_start,1011.1, +16:54:17.517,14,cognex_ftp_done,1747.1,3686454bytes +16:54:17.518,14,cognex_patmax_start,1748.6, +16:54:17.529,14,cognex_patmax_done,1759.6, +16:54:19.104,14,basler_capture_start,3333.8, +16:54:19.106,14,basler_capture_done,3336.7,failed +16:54:19.108,14,cognex_join_wait,3338.7, +16:54:19.110,14,cognex_join_done,3340.6, +16:54:19.112,14,cycle_done,3342.5,result=PASS cognex=PASS basler=PASS +16:54:19.114,15,cycle_start,0.0,group=A belt_delay=3.33s +16:54:19.117,15,cognex_trigger_send,2.3, +16:54:19.123,15,cognex_trigger_ok,8.9, +16:54:20.126,15,cognex_ftp_start,1011.4, +16:54:20.876,15,cognex_ftp_done,1762.3,3686454bytes +16:54:20.878,15,cognex_patmax_start,1763.7, +16:54:20.889,15,cognex_patmax_done,1774.8, +16:54:22.448,15,basler_capture_start,3333.4, +16:54:22.450,15,basler_capture_done,3336.3,failed +16:54:22.452,15,cognex_join_wait,3338.4, +16:54:22.454,15,cognex_join_done,3340.3, +16:54:22.457,15,cycle_done,3342.1,result=FAIL cognex=FAIL basler=PASS +16:54:22.460,16,cycle_start,0.0,group=A belt_delay=3.33s +16:54:22.462,16,cognex_trigger_send,2.2, +16:54:22.469,16,cognex_trigger_ok,8.7, +16:54:23.471,16,cognex_ftp_start,1011.1, +16:54:24.209,16,cognex_ftp_done,1749.5,3686454bytes +16:54:24.211,16,cognex_patmax_start,1751.0, +16:54:24.220,16,cognex_patmax_done,1761.3, +16:54:25.794,16,basler_capture_start,3333.4, +16:54:25.797,16,basler_capture_done,3336.3,failed +16:54:25.798,16,cognex_join_wait,3338.4, +16:54:25.800,16,cognex_join_done,3340.3, +16:54:25.802,16,cycle_done,3342.1,result=FAIL cognex=FAIL basler=PASS +16:54:25.806,17,cycle_start,0.0,group=A belt_delay=3.33s +16:54:25.808,17,cognex_trigger_send,2.7, +16:54:25.815,17,cognex_trigger_ok,9.6, +16:54:26.818,17,cognex_ftp_start,1011.7, +16:54:27.567,17,cognex_ftp_done,1760.8,3686454bytes +16:54:27.568,17,cognex_patmax_start,1762.3, +16:54:27.579,17,cognex_patmax_done,1773.3, +16:54:29.140,17,basler_capture_start,3333.8, +16:54:29.142,17,basler_capture_done,3336.8,failed +16:54:29.144,17,cognex_join_wait,3338.8, +16:54:29.147,17,cognex_join_done,3340.8, +16:54:29.148,17,cycle_done,3342.7,result=FAIL cognex=FAIL basler=PASS +16:54:29.152,18,cycle_start,0.0,group=A belt_delay=3.33s +16:54:29.154,18,cognex_trigger_send,2.2, +16:54:29.161,18,cognex_trigger_ok,8.5, +16:54:30.163,18,cognex_ftp_start,1010.6, +16:54:30.913,18,cognex_ftp_done,1762.2,3686454bytes +16:54:30.916,18,cognex_patmax_start,1763.8, +16:54:30.925,18,cognex_patmax_done,1774.2, +16:54:32.486,18,basler_capture_start,3333.7, +16:54:32.488,18,basler_capture_done,3336.4,failed +16:54:32.490,18,cognex_join_wait,3338.4, +16:54:32.492,18,cognex_join_done,3340.3, +16:54:32.494,18,cycle_done,3342.2,result=FAIL cognex=FAIL basler=PASS +16:54:32.498,19,cycle_start,0.0,group=A belt_delay=3.33s +16:54:32.500,19,cognex_trigger_send,2.2, +16:54:32.506,19,cognex_trigger_ok,8.7, +16:54:33.509,19,cognex_ftp_start,1011.1, +16:54:34.265,19,cognex_ftp_done,1767.3,3686454bytes +16:54:34.266,19,cognex_patmax_start,1768.7, +16:54:34.278,19,cognex_patmax_done,1780.3, +16:54:35.831,19,basler_capture_start,3333.4, +16:54:35.834,19,basler_capture_done,3336.4,failed +16:54:35.835,19,cognex_join_wait,3337.7, +16:54:35.837,19,cognex_join_done,3339.0, +16:54:35.838,19,cycle_done,3340.3,result=FAIL cognex=FAIL basler=PASS +16:54:35.841,20,cycle_start,0.0,group=A belt_delay=3.33s +16:54:35.842,20,cognex_trigger_send,1.6, +16:54:35.847,20,cognex_trigger_ok,7.5, +16:54:36.850,20,cognex_ftp_start,1008.8, +16:54:37.598,20,cognex_ftp_done,1757.3,3686454bytes +16:54:37.599,20,cognex_patmax_start,1758.8, +16:54:37.609,20,cognex_patmax_done,1768.9, +16:54:39.175,20,basler_capture_start,3333.8, +16:54:39.177,20,basler_capture_done,3336.5,failed +16:54:39.179,20,cognex_join_wait,3338.5, +16:54:39.181,20,cognex_join_done,3340.5, +16:54:39.183,20,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +16:54:39.187,21,cycle_start,0.0,group=A belt_delay=3.33s +16:54:39.189,21,cognex_trigger_send,2.4, +16:54:39.195,21,cognex_trigger_ok,8.9, +16:54:40.198,21,cognex_ftp_start,1010.9, +16:54:40.961,21,cognex_ftp_done,1774.1,3686454bytes +16:54:40.962,21,cognex_patmax_start,1775.6, +16:54:40.974,21,cognex_patmax_done,1787.1, +16:54:42.520,21,basler_capture_start,3333.7, +16:54:42.523,21,basler_capture_done,3336.6,failed +16:54:42.525,21,cognex_join_wait,3338.6, +16:54:42.527,21,cognex_join_done,3340.6, +16:54:42.529,21,cycle_done,3342.6,result=FAIL cognex=FAIL basler=PASS +16:54:42.532,22,cycle_start,0.0,group=A belt_delay=3.33s +16:54:42.535,22,cognex_trigger_send,2.3, +16:54:42.541,22,cognex_trigger_ok,8.9, +16:54:43.544,22,cognex_ftp_start,1011.2, +16:54:44.303,22,cognex_ftp_done,1771.0,3686454bytes +16:54:44.305,22,cognex_patmax_start,1772.6, +16:54:44.317,22,cognex_patmax_done,1784.1, +16:54:45.867,22,basler_capture_start,3333.7, +16:54:45.869,22,basler_capture_done,3336.5,failed +16:54:45.871,22,cognex_join_wait,3338.6, +16:54:45.873,22,cognex_join_done,3340.5, +16:54:45.875,22,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +16:54:45.878,23,cycle_start,0.0,group=A belt_delay=3.33s +16:54:45.880,23,cognex_trigger_send,2.3, +16:54:45.887,23,cognex_trigger_ok,8.8, +16:54:46.890,23,cognex_ftp_start,1010.9, +16:54:47.649,23,cognex_ftp_done,1770.0,3686454bytes +16:54:47.650,23,cognex_patmax_start,1771.5, +16:54:47.662,23,cognex_patmax_done,1783.2, +16:54:49.213,23,basler_capture_start,3333.7, +16:54:49.215,23,basler_capture_done,3336.7,failed +16:54:49.218,23,cognex_join_wait,3338.8, +16:54:49.219,23,cognex_join_done,3340.8, +16:54:49.222,23,cycle_done,3342.8,result=FAIL cognex=FAIL basler=PASS +16:54:49.225,24,cycle_start,0.0,group=A belt_delay=3.33s +16:54:49.227,24,cognex_trigger_send,2.3, +16:54:49.234,24,cognex_trigger_ok,8.9, +16:54:50.236,24,cognex_ftp_start,1011.1, +16:54:50.986,24,cognex_ftp_done,1760.8,3686454bytes +16:54:50.987,24,cognex_patmax_start,1762.2, +16:54:50.999,24,cognex_patmax_done,1773.4, +16:54:52.559,24,basler_capture_start,3333.8, +16:54:52.562,24,basler_capture_done,3336.8,failed +16:54:52.564,24,cognex_join_wait,3338.9, +16:54:52.566,24,cognex_join_done,3340.9, +16:54:52.568,24,cycle_done,3342.9,result=FAIL cognex=FAIL basler=PASS +16:54:52.571,25,cycle_start,0.0,group=A belt_delay=3.33s +16:54:52.573,25,cognex_trigger_send,2.4, +16:54:52.581,25,cognex_trigger_ok,9.2, +16:54:53.583,25,cognex_ftp_start,1011.2, +16:54:54.335,25,cognex_ftp_done,1762.9,3686454bytes +16:54:54.336,25,cognex_patmax_start,1764.3, +16:54:54.348,25,cognex_patmax_done,1776.1, +16:54:55.905,25,basler_capture_start,3333.5, +16:54:55.908,25,basler_capture_done,3336.5,failed +16:54:55.910,25,cognex_join_wait,3338.6, +16:54:55.912,25,cognex_join_done,3340.6, +16:54:55.914,25,cycle_done,3342.6,result=FAIL cognex=FAIL basler=PASS +16:54:55.917,26,cycle_start,0.0,group=A belt_delay=3.33s +16:54:55.920,26,cognex_trigger_send,2.5, +16:54:55.926,26,cognex_trigger_ok,9.1, +16:54:56.929,26,cognex_ftp_start,1011.3, +16:54:57.679,26,cognex_ftp_done,1761.3,3686454bytes +16:54:57.680,26,cognex_patmax_start,1762.8, +16:54:57.692,26,cognex_patmax_done,1774.2, +16:54:59.251,26,basler_capture_start,3333.6, +16:54:59.254,26,basler_capture_done,3336.5,failed +16:54:59.255,26,cognex_join_wait,3338.2, +16:54:59.257,26,cognex_join_done,3339.6, +16:54:59.259,26,cycle_done,3341.0,result=FAIL cognex=FAIL basler=PASS diff --git a/logs/timing/2026-06-15.csv b/logs/timing/2026-06-15.csv new file mode 100644 index 0000000..fc4daad --- /dev/null +++ b/logs/timing/2026-06-15.csv @@ -0,0 +1,385 @@ +timestamp,seq,event,elapsed_ms,detail +15:36:55.351,1,cycle_start,0.0,group=A belt_delay=3.33s +15:36:55.352,1,cognex_trigger_send,2.0, +15:36:55.387,1,cognex_trigger_ok,38.4, +15:36:56.392,1,cognex_ftp_start,1040.9, +15:36:57.153,1,cognex_ftp_done,1805.0,3686454bytes +15:36:57.153,1,cognex_patmax_start,1806.5, +15:36:57.153,1,cognex_patmax_done,1816.9, +15:36:58.685,1,basler_capture_start,3333.7, +15:36:58.685,1,basler_capture_done,3336.7,failed +15:36:58.685,1,cognex_join_wait,3338.7, +15:36:58.685,1,cognex_join_done,3340.6, +15:36:58.685,1,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +15:36:58.697,2,cycle_start,0.0,group=A belt_delay=3.33s +15:36:58.697,2,cognex_trigger_send,2.4, +15:36:58.729,2,cognex_trigger_ok,36.1, +15:36:59.736,2,cognex_ftp_start,1038.3, +15:37:00.502,2,cognex_ftp_done,1803.7,3686454bytes +15:37:00.503,2,cognex_patmax_start,1805.2, +15:37:00.503,2,cognex_patmax_done,1816.3, +15:37:02.032,2,basler_capture_start,3334.1, +15:37:02.035,2,basler_capture_done,3337.6,failed +15:37:02.037,2,cognex_join_wait,3339.7, +15:37:02.037,2,cognex_join_done,3341.7, +15:37:02.037,2,cycle_done,3343.6,result=FAIL cognex=FAIL basler=PASS +15:37:02.037,3,cycle_start,0.0,group=A belt_delay=3.33s +15:37:02.037,3,cognex_trigger_send,2.3, +15:37:02.068,3,cognex_trigger_ok,36.7, +15:37:03.084,3,cognex_ftp_start,1039.2, +15:37:03.821,3,cognex_ftp_done,1789.4,3686454bytes +15:37:03.821,3,cognex_patmax_start,1790.8, +15:37:03.837,3,cognex_patmax_done,1800.6, +15:37:05.379,3,basler_capture_start,3333.8, +15:37:05.381,3,basler_capture_done,3337.0,failed +15:37:05.384,3,cognex_join_wait,3339.1, +15:37:05.386,3,cognex_join_done,3341.1, +15:37:05.388,3,cycle_done,3343.1,result=FAIL cognex=FAIL basler=PASS +15:37:05.391,4,cycle_start,0.0,group=A belt_delay=3.33s +15:37:05.394,4,cognex_trigger_send,2.3, +15:37:05.427,4,cognex_trigger_ok,36.3, +15:37:06.431,4,cognex_ftp_start,1038.7, +15:37:07.192,4,cognex_ftp_done,1799.9,3686454bytes +15:37:07.193,4,cognex_patmax_start,1801.4, +15:37:07.204,4,cognex_patmax_done,1812.6, +15:37:08.725,4,basler_capture_start,3333.7, +15:37:08.727,4,basler_capture_done,3336.9,failed +15:37:08.727,4,cognex_join_wait,3339.0, +15:37:08.727,4,cognex_join_done,3341.1, +15:37:08.727,4,cycle_done,3343.1,result=FAIL cognex=FAIL basler=PASS +15:37:08.727,5,cycle_start,0.0,group=A belt_delay=3.33s +15:37:08.727,5,cognex_trigger_send,2.3, +15:37:08.759,5,cognex_trigger_ok,36.0, +15:37:09.777,5,cognex_ftp_start,1038.5, +15:37:10.503,5,cognex_ftp_done,1776.7,3686454bytes +15:37:10.503,5,cognex_patmax_start,1778.2, +15:37:10.520,5,cognex_patmax_done,1789.3, +15:37:12.072,5,basler_capture_start,3333.4, +15:37:12.072,5,basler_capture_done,3338.1,failed +15:37:12.072,5,cognex_join_wait,3340.1, +15:37:12.072,5,cognex_join_done,3341.9, +15:37:12.072,5,cycle_done,3343.9,result=FAIL cognex=FAIL basler=PASS +15:37:12.084,6,cycle_start,0.0,group=A belt_delay=3.33s +15:37:12.086,6,cognex_trigger_send,2.3, +15:37:12.120,6,cognex_trigger_ok,36.3, +15:37:13.124,6,cognex_ftp_start,1038.6, +15:37:13.853,6,cognex_ftp_done,1782.4,3686454bytes +15:37:13.869,6,cognex_patmax_start,1783.9, +15:37:13.869,6,cognex_patmax_done,1795.0, +15:37:15.419,6,basler_capture_start,3333.8, +15:37:15.423,6,basler_capture_done,3337.2,failed +15:37:15.424,6,cognex_join_wait,3339.3, +15:37:15.427,6,cognex_join_done,3341.3, +15:37:15.429,6,cycle_done,3343.3,result=FAIL cognex=FAIL basler=PASS +15:37:15.431,7,cycle_start,0.0,group=A belt_delay=3.33s +15:37:15.431,7,cognex_trigger_send,2.3, +15:37:15.462,7,cognex_trigger_ok,36.8, +15:37:16.472,7,cognex_ftp_start,1039.1, +15:37:17.236,7,cognex_ftp_done,1806.2,3686454bytes +15:37:17.236,7,cognex_patmax_start,1807.8, +15:37:17.236,7,cognex_patmax_done,1816.9, +15:37:18.766,7,basler_capture_start,3333.4, +15:37:18.766,7,basler_capture_done,3336.4,failed +15:37:18.766,7,cognex_join_wait,3337.7, +15:37:18.766,7,cognex_join_done,3339.0, +15:37:18.766,7,cycle_done,3340.3,result=FAIL cognex=FAIL basler=PASS +15:37:18.775,8,cycle_start,0.0,group=A belt_delay=3.33s +15:37:18.776,8,cognex_trigger_send,1.7, +15:37:18.806,8,cognex_trigger_ok,34.6, +15:37:19.813,8,cognex_ftp_start,1037.2, +15:37:20.569,8,cognex_ftp_done,1798.4,3686454bytes +15:37:20.569,8,cognex_patmax_start,1799.9, +15:37:20.586,8,cognex_patmax_done,1811.0, +15:37:22.109,8,basler_capture_start,3333.9, +15:37:22.109,8,basler_capture_done,3337.2,failed +15:37:22.109,8,cognex_join_wait,3340.0, +15:37:22.109,8,cognex_join_done,3341.9, +15:37:22.119,8,cycle_done,3343.8,result=FAIL cognex=FAIL basler=PASS +15:37:22.122,9,cycle_start,0.0,group=A belt_delay=3.33s +15:37:22.125,9,cognex_trigger_send,2.3, +15:37:22.152,9,cognex_trigger_ok,35.8, +15:37:23.161,9,cognex_ftp_start,1038.0, +15:37:23.903,9,cognex_ftp_done,1793.8,3686454bytes +15:37:23.918,9,cognex_patmax_start,1795.3, +15:37:23.919,9,cognex_patmax_done,1806.5, +15:37:25.457,9,basler_capture_start,3333.8, +15:37:25.457,9,basler_capture_done,3337.3,failed +15:37:25.462,9,cognex_join_wait,3339.3, +15:37:25.464,9,cognex_join_done,3341.4, +15:37:25.466,9,cycle_done,3343.2,result=FAIL cognex=FAIL basler=PASS +15:37:25.470,10,cycle_start,0.0,group=A belt_delay=3.33s +15:37:25.472,10,cognex_trigger_send,2.3, +15:37:25.496,10,cognex_trigger_ok,36.5, +15:37:26.509,10,cognex_ftp_start,1039.1, +15:37:27.253,10,cognex_ftp_done,1786.2,3686454bytes +15:37:27.253,10,cognex_patmax_start,1787.7, +15:37:27.268,10,cognex_patmax_done,1798.8, +15:37:28.804,10,basler_capture_start,3334.1, +15:37:28.807,10,basler_capture_done,3337.3,failed +15:37:28.807,10,cognex_join_wait,3339.4, +15:37:28.807,10,cognex_join_done,3341.4, +15:37:28.807,10,cycle_done,3343.4,result=FAIL cognex=FAIL basler=PASS +15:37:28.807,11,cycle_start,0.0,group=A belt_delay=3.33s +15:37:28.807,11,cognex_trigger_send,2.2, +15:37:28.852,11,cognex_trigger_ok,36.8, +15:37:29.856,11,cognex_ftp_start,1039.2, +15:37:30.589,11,cognex_ftp_done,1772.6,3686454bytes +15:37:30.591,11,cognex_patmax_start,1774.0, +15:37:30.602,11,cognex_patmax_done,1785.1, +15:37:32.151,11,basler_capture_start,3334.3, +15:37:32.152,11,basler_capture_done,3337.5,failed +15:37:32.152,11,cognex_join_wait,3339.5, +15:37:32.152,11,cognex_join_done,3341.4, +15:37:32.152,11,cycle_done,3343.2,result=FAIL cognex=FAIL basler=PASS +15:37:32.152,12,cycle_start,0.0,group=A belt_delay=3.33s +15:37:32.152,12,cognex_trigger_send,2.2, +15:37:32.199,12,cognex_trigger_ok,36.8, +15:37:33.203,12,cognex_ftp_start,1039.5, +15:37:33.941,12,cognex_ftp_done,1777.1,3686454bytes +15:37:33.942,12,cognex_patmax_start,1778.6, +15:37:33.953,12,cognex_patmax_done,1789.8, +15:37:35.497,12,basler_capture_start,3333.7, +15:37:35.499,12,basler_capture_done,3336.5,failed +15:37:35.499,12,cognex_join_wait,3338.4, +15:37:35.499,12,cognex_join_done,3340.7, +15:37:35.499,12,cycle_done,3342.8,result=FAIL cognex=FAIL basler=PASS +15:37:35.510,13,cycle_start,0.0,group=A belt_delay=3.33s +15:37:35.510,13,cognex_trigger_send,2.3, +15:37:35.541,13,cognex_trigger_ok,36.2, +15:37:36.549,13,cognex_ftp_start,1038.7, +15:37:37.303,13,cognex_ftp_done,1793.3,3686454bytes +15:37:37.303,13,cognex_patmax_start,1794.9, +15:37:37.303,13,cognex_patmax_done,1806.0, +15:37:38.844,13,basler_capture_start,3333.9, +15:37:38.844,13,basler_capture_done,3337.3,failed +15:37:38.844,13,cognex_join_wait,3339.3, +15:37:38.844,13,cognex_join_done,3341.2, +15:37:38.853,13,cycle_done,3343.3,result=FAIL cognex=FAIL basler=PASS +15:37:38.853,14,cycle_start,0.0,group=A belt_delay=3.33s +15:37:38.853,14,cognex_trigger_send,2.2, +15:37:38.886,14,cognex_trigger_ok,35.9, +15:37:39.896,14,cognex_ftp_start,1040.6, +15:37:40.636,14,cognex_ftp_done,1795.1,3686454bytes +15:37:40.652,14,cognex_patmax_start,1796.6, +15:37:40.653,14,cognex_patmax_done,1805.6, +15:37:42.189,14,basler_capture_start,3333.7, +15:37:42.192,14,basler_capture_done,3336.9,failed +15:37:42.194,14,cognex_join_wait,3338.4, +15:37:42.195,14,cognex_join_done,3339.8, +15:37:42.197,14,cycle_done,3341.0,result=FAIL cognex=FAIL basler=PASS +15:37:42.198,15,cycle_start,0.0,group=A belt_delay=3.33s +15:37:42.200,15,cognex_trigger_send,1.6, +15:37:42.233,15,cognex_trigger_ok,35.0, +15:37:43.235,15,cognex_ftp_start,1036.7, +15:37:43.970,15,cognex_ftp_done,1785.7,3686454bytes +15:37:43.986,15,cognex_patmax_start,1787.2, +15:37:43.988,15,cognex_patmax_done,1797.1, +15:37:45.532,15,basler_capture_start,3333.4, +15:37:45.534,15,basler_capture_done,3336.6,failed +15:37:45.537,15,cognex_join_wait,3338.5, +15:37:45.538,15,cognex_join_done,3340.5, +15:37:45.541,15,cycle_done,3342.5,result=FAIL cognex=FAIL basler=PASS +15:37:51.367,16,cycle_start,0.0,group=A belt_delay=3.33s +15:37:51.369,16,cognex_trigger_send,2.2, +15:37:51.403,16,cognex_trigger_ok,36.5, +15:37:52.406,16,cognex_ftp_start,1038.7, +15:37:53.153,16,cognex_ftp_done,1801.2,3686454bytes +15:37:53.169,16,cognex_patmax_start,1802.8, +15:37:53.181,16,cognex_patmax_done,1813.9, +15:37:54.701,16,basler_capture_start,3333.7, +15:37:54.701,16,basler_capture_done,3336.9,failed +15:37:54.701,16,cognex_join_wait,3338.9, +15:37:54.701,16,cognex_join_done,3340.8, +15:37:54.701,16,cycle_done,3342.8,result=PASS cognex=PASS basler=PASS +15:37:54.701,17,cycle_start,0.0,group=A belt_delay=3.33s +15:37:54.714,17,cognex_trigger_send,2.5, +15:37:54.736,17,cognex_trigger_ok,36.3, +15:37:55.752,17,cognex_ftp_start,1039.4, +15:37:56.503,17,cognex_ftp_done,1790.7,3686454bytes +15:37:56.503,17,cognex_patmax_start,1792.3, +15:37:56.507,17,cognex_patmax_done,1803.6, +15:37:58.046,17,basler_capture_start,3333.8, +15:37:58.046,17,basler_capture_done,3337.0,failed +15:37:58.046,17,cognex_join_wait,3339.1, +15:37:58.052,17,cognex_join_done,3341.2, +15:37:58.052,17,cycle_done,3343.2,result=PASS cognex=PASS basler=PASS +15:37:58.052,18,cycle_start,0.0,group=A belt_delay=3.33s +15:37:58.052,18,cognex_trigger_send,2.4, +15:37:58.089,18,cognex_trigger_ok,36.4, +15:37:59.096,18,cognex_ftp_start,1038.4, +15:37:59.837,18,cognex_ftp_done,1793.0,3686454bytes +15:37:59.837,18,cognex_patmax_start,1794.4, +15:37:59.853,18,cognex_patmax_done,1805.6, +15:38:01.392,18,basler_capture_start,3334.1, +15:38:01.394,18,basler_capture_done,3337.4,failed +15:38:01.394,18,cognex_join_wait,3339.7, +15:38:01.394,18,cognex_join_done,3341.6, +15:38:01.394,18,cycle_done,3343.5,result=PASS cognex=PASS basler=PASS +15:38:01.394,19,cycle_start,0.0,group=A belt_delay=3.33s +15:38:01.394,19,cognex_trigger_send,2.4, +15:38:01.439,19,cognex_trigger_ok,36.0, +15:38:02.442,19,cognex_ftp_start,1038.0, +15:38:03.203,19,cognex_ftp_done,1799.0,3686454bytes +15:38:03.203,19,cognex_patmax_start,1800.6, +15:38:03.203,19,cognex_patmax_done,1811.9, +15:38:04.738,19,basler_capture_start,3334.2, +15:38:04.738,19,basler_capture_done,3337.3,failed +15:38:04.738,19,cognex_join_wait,3339.3, +15:38:04.744,19,cognex_join_done,3341.2, +15:38:04.744,19,cycle_done,3343.1,result=PASS cognex=PASS basler=PASS +15:38:04.744,20,cycle_start,0.0,group=A belt_delay=3.33s +15:38:04.744,20,cognex_trigger_send,2.4, +15:38:04.785,20,cognex_trigger_ok,36.1, +15:38:05.788,20,cognex_ftp_start,1038.2, +15:38:06.534,20,cognex_ftp_done,1784.6,3686454bytes +15:38:06.535,20,cognex_patmax_start,1786.0, +15:38:06.545,20,cognex_patmax_done,1795.8, +15:38:08.083,20,basler_capture_start,3333.4, +15:38:08.085,20,basler_capture_done,3336.5,failed +15:38:08.088,20,cognex_join_wait,3338.5, +15:38:08.090,20,cognex_join_done,3340.4, +15:38:08.092,20,cycle_done,3342.3,result=PASS cognex=PASS basler=PASS +15:38:14.311,21,cycle_start,0.0,group=A belt_delay=3.33s +15:38:14.311,21,cognex_trigger_send,1.9, +15:38:14.339,21,cognex_trigger_ok,37.5, +15:38:15.351,21,cognex_ftp_start,1040.0, +15:38:16.099,21,cognex_ftp_done,1789.0,3686454bytes +15:38:16.101,21,cognex_patmax_start,1790.5, +15:38:16.113,21,cognex_patmax_done,1802.1, +15:38:17.645,21,basler_capture_start,3334.2, +15:38:17.645,21,basler_capture_done,3337.4,failed +15:38:17.645,21,cognex_join_wait,3338.7, +15:38:17.650,21,cognex_join_done,3340.0, +15:38:17.650,21,cycle_done,3341.3,result=FAIL cognex=FAIL basler=PASS +15:38:17.650,22,cycle_start,0.0,group=A belt_delay=3.33s +15:38:17.650,22,cognex_trigger_send,1.7, +15:38:17.688,22,cognex_trigger_ok,35.2, +15:38:18.691,22,cognex_ftp_start,1037.0, +15:38:19.451,22,cognex_ftp_done,1797.7,3686454bytes +15:38:19.453,22,cognex_patmax_start,1799.3, +15:38:19.464,22,cognex_patmax_done,1810.7, +15:38:20.987,22,basler_capture_start,3333.5, +15:38:20.987,22,basler_capture_done,3336.5,failed +15:38:20.987,22,cognex_join_wait,3338.4, +15:38:20.987,22,cognex_join_done,3340.3, +15:38:20.987,22,cycle_done,3342.1,result=FAIL cognex=FAIL basler=PASS +15:38:20.987,23,cycle_start,0.0,group=A belt_delay=3.33s +15:38:20.987,23,cognex_trigger_send,2.3, +15:38:21.034,23,cognex_trigger_ok,36.0, +15:38:22.036,23,cognex_ftp_start,1038.3, +15:38:22.769,23,cognex_ftp_done,1771.4,3686454bytes +15:38:22.769,23,cognex_patmax_start,1772.9, +15:38:22.769,23,cognex_patmax_done,1784.0, +15:38:24.332,23,basler_capture_start,3333.7, +15:38:24.334,23,basler_capture_done,3337.0,failed +15:38:24.337,23,cognex_join_wait,3339.0, +15:38:24.339,23,cognex_join_done,3340.9, +15:38:24.341,23,cycle_done,3342.8,result=FAIL cognex=FAIL basler=PASS +15:38:24.343,24,cycle_start,0.0,group=A belt_delay=3.33s +15:38:24.345,24,cognex_trigger_send,2.3, +15:38:24.379,24,cognex_trigger_ok,36.5, +15:38:25.382,24,cognex_ftp_start,1038.8, +15:38:26.140,24,cognex_ftp_done,1797.1,3686454bytes +15:38:26.142,24,cognex_patmax_start,1798.8, +15:38:26.153,24,cognex_patmax_done,1809.8, +15:38:27.677,24,basler_capture_start,3333.4, +15:38:27.679,24,basler_capture_done,3336.4,failed +15:38:27.681,24,cognex_join_wait,3338.4, +15:38:27.681,24,cognex_join_done,3340.3, +15:38:27.681,24,cycle_done,3342.2,result=PASS cognex=PASS basler=PASS +15:39:54.449,25,cycle_start,0.0,group=A belt_delay=3.33s +15:39:54.449,25,cognex_trigger_send,2.0, +15:39:54.486,25,cognex_trigger_ok,36.2, +15:39:55.489,25,cognex_ftp_start,1039.4, +15:39:56.243,25,cognex_ftp_done,1802.6,3686454bytes +15:39:56.243,25,cognex_patmax_start,1804.2, +15:39:56.258,25,cognex_patmax_done,1816.6, +15:39:57.783,25,basler_capture_start,3333.8, +15:39:57.786,25,basler_capture_done,3337.0,failed +15:39:57.788,25,cognex_join_wait,3339.0, +15:39:57.790,25,cognex_join_done,3340.9, +15:39:57.790,25,cycle_done,3342.9,result=PASS cognex=PASS basler=PASS +17:20:46.605,1,cycle_start,0.0,group=ALL belt_delay=3.33s +17:20:46.605,1,cognex_trigger_send,1.7, +17:20:46.699,1,cognex_trigger_ok,101.9, +17:20:47.710,1,cognex_ftp_start,1103.5, +17:20:48.828,1,cognex_ftp_done,2223.2,3686454bytes +17:20:48.828,1,cognex_patmax_start,2224.8, +17:20:48.859,1,cognex_patmax_done,2257.7, +17:20:49.940,1,basler_capture_start,3333.7, +17:20:49.940,1,basler_capture_done,3337.4,failed +17:20:49.940,1,cognex_join_wait,3339.3, +17:20:49.940,1,cognex_join_done,3341.2, +17:20:49.940,1,cycle_done,3343.1,result=FAIL cognex=FAIL basler=PASS +17:20:49.940,2,cycle_start,0.0,group=ALL belt_delay=3.33s +17:20:49.955,2,cognex_trigger_send,2.3, +17:20:50.050,2,cognex_trigger_ok,99.7, +17:20:51.055,2,cognex_ftp_start,1101.5, +17:20:52.165,2,cognex_ftp_done,2221.0,3686454bytes +17:20:52.165,2,cognex_patmax_start,2222.7, +17:20:52.196,2,cognex_patmax_done,2255.7, +17:20:53.288,2,basler_capture_start,3334.2, +17:20:53.288,2,basler_capture_done,3337.9,failed +17:20:53.293,2,cognex_join_wait,3339.8, +17:20:53.293,2,cognex_join_done,3341.6, +17:20:53.293,2,cycle_done,3343.5,result=FAIL cognex=FAIL basler=PASS +17:20:53.293,3,cycle_start,0.0,group=ALL belt_delay=3.33s +17:20:53.293,3,cognex_trigger_send,2.2, +17:20:53.386,3,cognex_trigger_ok,98.9, +17:20:54.401,3,cognex_ftp_start,1100.8, +17:20:55.515,3,cognex_ftp_done,2218.9,3686454bytes +17:20:55.515,3,cognex_patmax_start,2220.4, +17:20:55.547,3,cognex_patmax_done,2253.3, +17:20:56.634,3,basler_capture_start,3334.1, +17:20:56.634,3,basler_capture_done,3337.5,failed +17:20:56.634,3,cognex_join_wait,3338.8, +17:20:56.634,3,cognex_join_done,3340.1, +17:20:56.634,3,cycle_done,3341.3,result=FAIL cognex=FAIL basler=PASS +17:20:56.643,4,cycle_start,0.0,group=ALL belt_delay=3.33s +17:20:56.643,4,cognex_trigger_send,1.6, +17:20:56.738,4,cognex_trigger_ok,98.6, +17:20:57.745,4,cognex_ftp_start,1100.1, +17:20:58.788,4,cognex_ftp_done,2158.4,3686454bytes +17:20:58.804,4,cognex_patmax_start,2160.0, +17:20:58.835,4,cognex_patmax_done,2191.1, +17:20:59.978,4,basler_capture_start,3333.8, +17:20:59.978,4,basler_capture_done,3337.3,failed +17:20:59.978,4,cognex_join_wait,3339.3, +17:20:59.978,4,cognex_join_done,3341.2, +17:20:59.978,4,cycle_done,3343.1,result=FAIL cognex=FAIL basler=PASS +17:20:59.978,5,cycle_start,0.0,group=ALL belt_delay=3.33s +17:20:59.994,5,cognex_trigger_send,2.3, +17:21:00.089,5,cognex_trigger_ok,99.7, +17:21:01.093,5,cognex_ftp_start,1101.6, +17:21:02.206,5,cognex_ftp_done,2219.2,3686454bytes +17:21:02.206,5,cognex_patmax_start,2220.7, +17:21:02.237,5,cognex_patmax_done,2254.0, +17:21:03.325,5,basler_capture_start,3334.0, +17:21:03.325,5,basler_capture_done,3337.6,failed +17:21:03.325,5,cognex_join_wait,3340.1, +17:21:03.333,5,cognex_join_done,3342.0, +17:21:03.333,5,cycle_done,3344.0,result=FAIL cognex=FAIL basler=PASS +17:21:03.333,6,cycle_start,0.0,group=ALL belt_delay=3.33s +17:21:03.333,6,cognex_trigger_send,2.3, +17:21:03.427,6,cognex_trigger_ok,99.6, +17:21:04.440,6,cognex_ftp_start,1101.3, +17:21:05.557,6,cognex_ftp_done,2224.7,3686454bytes +17:21:05.557,6,cognex_patmax_start,2226.3, +17:21:05.588,6,cognex_patmax_done,2259.0, +17:21:06.673,6,basler_capture_start,3334.1, +17:21:06.673,6,basler_capture_done,3337.4,failed +17:21:06.673,6,cognex_join_wait,3339.4, +17:21:06.673,6,cognex_join_done,3341.4, +17:21:06.673,6,cycle_done,3343.4,result=FAIL cognex=FAIL basler=PASS +17:21:06.684,7,cycle_start,0.0,group=ALL belt_delay=3.33s +17:21:06.684,7,cognex_trigger_send,2.4, +17:21:06.778,7,cognex_trigger_ok,99.3, +17:21:07.787,7,cognex_ftp_start,1101.1, +17:21:08.908,7,cognex_ftp_done,2223.3,3686454bytes +17:21:08.908,7,cognex_patmax_start,2224.7, +17:21:08.940,7,cognex_patmax_done,2257.9, +17:21:10.020,7,basler_capture_start,3333.8, +17:21:10.020,7,basler_capture_done,3336.9,failed +17:21:10.020,7,cognex_join_wait,3338.9, +17:21:10.020,7,cognex_join_done,3340.8, +17:21:10.020,7,cycle_done,3342.7,result=FAIL cognex=FAIL basler=PASS diff --git a/main.py b/main.py index 1234eac..48907e1 100644 --- a/main.py +++ b/main.py @@ -119,6 +119,17 @@ QLineEdit, QSpinBox, QDoubleSpinBox { padding: 6px 8px; min-height: 38px; } +/* 터치 화면에서 누르기 쉽도록 스핀박스 ▲▼ 버튼 폭 확대 */ +QSpinBox::up-button, QDoubleSpinBox::up-button { + subcontrol-origin: border; + subcontrol-position: top right; + width: 30px; +} +QSpinBox::down-button, QDoubleSpinBox::down-button { + subcontrol-origin: border; + subcontrol-position: bottom right; + width: 30px; +} QLabel { color: #ffffff; } QListWidget { background-color: #222222; diff --git a/plc_test_gui.py b/plc_test_gui.py deleted file mode 100644 index 0a0e8f0..0000000 --- a/plc_test_gui.py +++ /dev/null @@ -1,372 +0,0 @@ -import sys -import time -from datetime import datetime - -from PyQt5.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QHBoxLayout, - QPushButton, QLabel, QTextEdit, QFrame, -) -from PyQt5.QtCore import Qt, QThread, pyqtSignal - -from pymelsec import Type3E -from pymelsec.constants import DT - -PLC_IP = "192.168.3.39" -PLC_PORT = 5010 - -# ── 스타일 상수 ─────────────────────────────────────────────────────────── # -_PANEL = ( - "QFrame {" - " background:#222222; border:1px solid #333333; border-radius:6px;" - "}" -) -_BTN_GREEN = ( - "QPushButton {" - " background:#1D9E75; color:#ffffff; border:none; border-radius:4px;" - " min-height:38px; font-size:13px;" - "}" - "QPushButton:hover { background:#20b585; }" - "QPushButton:disabled { background:#145f48; color:#5a9e7e; }" -) -_BTN_RED = ( - "QPushButton {" - " background:#8B2020; color:#ffffff; border:none; border-radius:4px;" - " min-height:38px; font-size:13px;" - "}" - "QPushButton:hover { background:#a02828; }" - "QPushButton:disabled { background:#4a1515; color:#9e5a5a; }" -) -_BTN_GRAY = ( - "QPushButton {" - " background:#333333; color:#aaaaaa; border:none; border-radius:4px;" - " min-height:38px; font-size:13px;" - "}" - "QPushButton:hover { background:#444444; color:#ffffff; }" - "QPushButton:disabled { background:#2a2a2a; color:#555555; }" -) -_BTN_RED_OUTLINE = ( - "QPushButton {" - " background:#3D1515; color:#F09595; border:none; border-radius:4px;" - " min-height:34px; font-size:13px;" - "}" - "QPushButton:hover { background:#4a1818; }" - "QPushButton:disabled { background:#222222; color:#555555; }" -) - - -# ══════════════════════════════════════════════════════════════════════════ # -# PLCMonitor — D500 폴링 스레드 -# ══════════════════════════════════════════════════════════════════════════ # - -class PLCMonitor(QThread): - signal_received = pyqtSignal(int) - error_occurred = pyqtSignal(str) - - def __init__(self, plc: Type3E): - super().__init__() - self.plc = plc - self.running = False - - def run(self): - self.running = True - while self.running: - try: - result = self.plc.batch_read( - ref_device="D500", - read_size=1, - data_type=DT.SWORD, - ) - raw = result[0] - value = int(raw.value if hasattr(raw, "value") else raw) - self.signal_received.emit(value) - except Exception as e: - self.error_occurred.emit(str(e)) - time.sleep(0.1) - - def stop(self): - self.running = False - self.wait(2000) - if self.isRunning(): - self.terminate() - - -# ══════════════════════════════════════════════════════════════════════════ # -# PLCTestGUI -# ══════════════════════════════════════════════════════════════════════════ # - -class PLCTestGUI(QWidget): - def __init__(self): - super().__init__() - self.plc = None - self._connected = False - self._monitor = None - self._last_m100 = -1 - - self.setWindowTitle("PLC 신호 테스트") - self.setFixedSize(600, 400) - self.setStyleSheet("background:#1a1a1a; color:#ffffff; font-size:13px;") - - self._build_ui() - self._connect_plc() - - # ── UI 구성 ────────────────────────────────────────────────────────── # - - def _build_ui(self): - root = QVBoxLayout(self) - root.setContentsMargins(16, 12, 16, 12) - root.setSpacing(8) - - root.addWidget(self._build_header()) - root.addWidget(self._separator()) - - center = QHBoxLayout() - center.setSpacing(10) - center.addWidget(self._build_send_panel(), stretch=1) - center.addWidget(self._build_recv_panel(), stretch=1) - root.addLayout(center, stretch=1) - - root.addWidget(self._separator()) - root.addWidget(self._build_log()) - - def _build_header(self) -> QWidget: - w = QWidget() - w.setStyleSheet("background:transparent;") - row = QHBoxLayout(w) - row.setContentsMargins(0, 0, 0, 0) - - lbl_ip = QLabel(f"PLC IP: {PLC_IP} : {PLC_PORT}") - lbl_ip.setStyleSheet("color:#888888; font-size:13px;") - - self._dot = QLabel("●") - self._dot.setStyleSheet("color:#cc2222; font-size:16px;") - - self._lbl_status = QLabel("연결 안됨") - self._lbl_status.setStyleSheet("color:#cc2222; font-size:13px;") - - row.addWidget(lbl_ip) - row.addStretch() - row.addWidget(self._dot) - row.addSpacing(4) - row.addWidget(self._lbl_status) - return w - - def _build_send_panel(self) -> QFrame: - frame = QFrame() - frame.setStyleSheet(_PANEL) - v = QVBoxLayout(frame) - v.setContentsMargins(12, 10, 12, 10) - v.setSpacing(6) - - title = QLabel("PC → PLC 신호 전송") - title.setStyleSheet( - "color:#aaaaaa; font-size:12px; background:transparent; border:none;" - ) - v.addWidget(title) - - self._btn_pass = QPushButton("PASS 신호 전송 (M200 = 1)") - self._btn_pass.setStyleSheet(_BTN_GREEN) - self._btn_pass.clicked.connect(self._send_pass) - - self._btn_fail = QPushButton("FAIL 신호 전송 (M201 = 1)") - self._btn_fail.setStyleSheet(_BTN_RED) - self._btn_fail.clicked.connect(self._send_fail) - - self._btn_reset = QPushButton("신호 초기화 (M200 = M201 = D100 = 0)") - self._btn_reset.setStyleSheet(_BTN_GRAY) - self._btn_reset.clicked.connect(self._send_reset) - - self._send_btns = [self._btn_pass, self._btn_fail, self._btn_reset] - v.addWidget(self._btn_pass) - v.addWidget(self._btn_fail) - v.addWidget(self._btn_reset) - v.addStretch() - return frame - - def _build_recv_panel(self) -> QFrame: - frame = QFrame() - frame.setStyleSheet(_PANEL) - v = QVBoxLayout(frame) - v.setContentsMargins(12, 10, 12, 10) - v.setSpacing(6) - - title = QLabel("PLC → PC 신호 수신") - title.setStyleSheet( - "color:#aaaaaa; font-size:12px; background:transparent; border:none;" - ) - v.addWidget(title) - - self._lbl_d500 = QLabel("● 대기 중") - self._lbl_d500.setAlignment(Qt.AlignCenter) - self._lbl_d500.setStyleSheet( - "color:#555555; font-size:15px; font-weight:normal;" - "background:transparent; border:none;" - ) - v.addWidget(self._lbl_d500) - - self._btn_mon_start = QPushButton("신호 감지 시작") - self._btn_mon_start.setStyleSheet(_BTN_GREEN.replace("min-height:38px", "min-height:34px")) - self._btn_mon_start.clicked.connect(self._start_monitor) - - self._btn_mon_stop = QPushButton("신호 감지 중지") - self._btn_mon_stop.setEnabled(False) - self._btn_mon_stop.setStyleSheet(_BTN_RED_OUTLINE) - self._btn_mon_stop.clicked.connect(self._stop_monitor) - - v.addWidget(self._btn_mon_start) - v.addWidget(self._btn_mon_stop) - v.addStretch() - return frame - - @staticmethod - def _separator() -> QFrame: - line = QFrame() - line.setFrameShape(QFrame.HLine) - line.setStyleSheet("border:none; background:#2a2a2a; max-height:1px;") - return line - - def _build_log(self) -> QTextEdit: - self._log = QTextEdit() - self._log.setReadOnly(True) - self._log.setFixedHeight(108) - self._log.setStyleSheet( - "background:#111111; color:#888888;" - "border:1px solid #2a2a2a; border-radius:4px;" - "font-family: Consolas, monospace; font-size:12px;" - ) - return self._log - - # ── PLC 연결 ───────────────────────────────────────────────────────── # - - def _connect_plc(self): - try: - self.plc = Type3E(host=PLC_IP, port=PLC_PORT, plc_type="Q") - self.plc.connect(PLC_IP, PLC_PORT) - self._connected = True - self._dot.setStyleSheet("color:#1D9E75; font-size:16px;") - self._lbl_status.setStyleSheet("color:#1D9E75; font-size:13px;") - self._lbl_status.setText("연결됨") - self._log_msg(f"PLC 연결 성공: {PLC_IP}:{PLC_PORT}") - except Exception as e: - self._connected = False - self._log_msg(f"PLC 연결 실패: {e}") - for btn in self._send_btns: - btn.setEnabled(False) - self._btn_mon_start.setEnabled(False) - - # ── 신호 전송 ──────────────────────────────────────────────────────── # - - def _send_pass(self): - if not self._connected: - return - try: - self.plc.batch_write(ref_device="M200", values=[1], data_type=DT.BIT) - self._log_msg("PC → PLC: M200 = 1 (PASS)") - except Exception as e: - self._log_msg(f"전송 오류: {e}") - - def _send_fail(self): - if not self._connected: - return - try: - self.plc.batch_write(ref_device="M201", values=[1], data_type=DT.BIT) - self._log_msg("PC → PLC: M201 = 1 (FAIL)") - except Exception as e: - self._log_msg(f"전송 오류: {e}") - - def _send_reset(self): - if not self._connected: - return - try: - self.plc.batch_write(ref_device="M200", values=[0], data_type=DT.BIT) - self.plc.batch_write(ref_device="M201", values=[0], data_type=DT.BIT) - self.plc.batch_write(ref_device="D100", values=[0], data_type=DT.SWORD) - self._log_msg("PC → PLC: M200=0, M201=0, D100=0 (초기화)") - except Exception as e: - self._log_msg(f"초기화 오류: {e}") - - # ── 신호 수신 폴링 ─────────────────────────────────────────────────── # - - def _start_monitor(self): - if not self._connected or self._monitor: - return - self._last_m100 = -1 - self._monitor = PLCMonitor(self.plc) - self._monitor.signal_received.connect(self._on_signal) - self._monitor.error_occurred.connect(self._on_poll_error) - self._monitor.start() - self._btn_mon_start.setEnabled(False) - self._btn_mon_stop.setEnabled(True) - self._log_msg("D500 폴링 시작 (100ms 간격)") - - def _stop_monitor(self): - if self._monitor: - self._monitor.stop() - self._monitor = None - self._btn_mon_start.setEnabled(True) - self._btn_mon_stop.setEnabled(False) - self._lbl_d500.setText("● 대기 중") - self._lbl_d500.setStyleSheet( - "color:#555555; font-size:15px; font-weight:normal;" - "background:transparent; border:none;" - ) - self._log_msg("D500 폴링 중지") - - def _on_signal(self, value: int): - prev = self._last_m100 - self._last_m100 = value - - if value >= 1: - self._lbl_d500.setText(f"● 신호 수신! ({value})") - self._lbl_d500.setStyleSheet( - "color:#1D9E75; font-size:15px; font-weight:bold;" - "background:transparent; border:none;" - ) - if prev < 1: - self._log_msg(f"PLC → PC: D500 = {value} 수신!") - else: - self._lbl_d500.setText("● 대기 중") - self._lbl_d500.setStyleSheet( - "color:#555555; font-size:15px; font-weight:normal;" - "background:transparent; border:none;" - ) - if prev >= 1: - self._log_msg(f"PLC → PC: D500 = {value} (신호 해제)") - - def _on_poll_error(self, msg: str): - self._log_msg(f"폴링 오류: {msg}") - - # ── 로그 ───────────────────────────────────────────────────────────── # - - def _log_msg(self, text: str): - ts = datetime.now().strftime("%H:%M:%S") - self._log.append(f"[{ts}] {text}") - - # ── 종료 ───────────────────────────────────────────────────────────── # - - def closeEvent(self, event): - if self._monitor: - self._monitor.stop() - if self.plc and self._connected: - try: - self.plc.close() - except Exception: - pass - event.accept() - - -if __name__ == "__main__": - app = QApplication(sys.argv) - app.setStyleSheet(""" - QWidget { background:#1a1a1a; color:#ffffff; font-size:13px; } - QScrollBar:vertical { - background:#2a2a2a; width:8px; border-radius:4px; - } - QScrollBar::handle:vertical { - background:#444444; border-radius:4px; min-height:20px; - } - QScrollBar::add-line:vertical, - QScrollBar::sub-line:vertical { height:0; } - """) - window = PLCTestGUI() - window.show() - sys.exit(app.exec_()) diff --git a/test.py b/test.py deleted file mode 100644 index 8dc5367..0000000 --- a/test.py +++ /dev/null @@ -1,14 +0,0 @@ -import re -from pymelsec import Type3E -from pymelsec.constants import DT - -plc = Type3E(host="192.168.3.39", port=5010, plc_type="Q") -plc.connect("192.168.3.39", 5010) - -# D500에 값 전송 -plc.batch_write(ref_device="B5F", values=[1], data_type=DT.BIT) - -re = plc.batch_read(ref_device="B52", read_size=1, data_type=DT.BIT) -print(re) - -plc.close() \ No newline at end of file diff --git a/utils/touch_keyboard.py b/utils/touch_keyboard.py new file mode 100644 index 0000000..5355983 --- /dev/null +++ b/utils/touch_keyboard.py @@ -0,0 +1,123 @@ +"""Windows 터치 키보드(TabTip) 표시/숨김 — ITipInvocation COM API 사용.""" +import ctypes +import os +import sys +from ctypes import wintypes + +_TABTIP_PATHS = ( + r"C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe", + r"C:\Program Files (x86)\Common Files\microsoft shared\ink\TabTip.exe", +) + +CLSID_UIHostNoLaunch = ( + 0x4CE576FA, + 0x83DC, + 0x4F88, + (0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76), +) +IID_ITipInvocation = ( + 0x37C994E7, + 0x432B, + 0x4834, + (0xA2, 0xF7, 0xDC, 0xE1, 0xF1, 0x3B, 0x83, 0x4B), +) + +REGDB_E_CLASSNOTREG = -2147221164 # 0x80040154 +SW_SHOW = 5 +CLSCTX_LOCAL_SERVER = 4 + + +class GUID(ctypes.Structure): + _fields_ = [ + ("Data1", wintypes.DWORD), + ("Data2", wintypes.WORD), + ("Data3", wintypes.WORD), + ("Data4", wintypes.BYTE * 8), + ] + + def __init__(self, guid_tuple): + super().__init__() + self.Data1 = guid_tuple[0] + self.Data2 = guid_tuple[1] + self.Data3 = guid_tuple[2] + self.Data4[:] = guid_tuple[3] + + +def _tabtip_path() -> str | None: + for path in _TABTIP_PATHS: + if os.path.exists(path): + return path + return None + + +def _launch_tabtip() -> bool: + path = _tabtip_path() + if not path: + return False + ret = ctypes.windll.shell32.ShellExecuteW( + None, "open", path, None, None, SW_SHOW, + ) + return ret > 32 + + +def _create_tip_invocation(ole32): + clsid = GUID(CLSID_UIHostNoLaunch) + iid = GUID(IID_ITipInvocation) + obj = ctypes.c_void_p() + hr = ole32.CoCreateInstance( + ctypes.byref(clsid), + None, + CLSCTX_LOCAL_SERVER, + ctypes.byref(iid), + ctypes.byref(obj), + ) + if hr == REGDB_E_CLASSNOTREG: + if not _launch_tabtip(): + return None + hr = ole32.CoCreateInstance( + ctypes.byref(clsid), + None, + CLSCTX_LOCAL_SERVER, + ctypes.byref(iid), + ctypes.byref(obj), + ) + if hr != 0: + return None + return obj + + +def _toggle_touch_keyboard() -> bool: + ole32 = ctypes.windll.ole32 + user32 = ctypes.windll.user32 + + obj = _create_tip_invocation(ole32) + if not obj: + return False + + vtable_ptr = ctypes.cast(obj, ctypes.POINTER(ctypes.c_void_p)).contents.value + vtable = ctypes.cast(vtable_ptr, ctypes.POINTER(ctypes.c_void_p)) + toggle_fn = ctypes.WINFUNCTYPE( + ctypes.c_long, ctypes.c_void_p, wintypes.HWND, + )(vtable[3]) + + hr = toggle_fn(obj, user32.GetDesktopWindow()) + release_fn = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)(vtable[2]) + release_fn(obj) + return hr == 0 + + +def show_touch_keyboard() -> bool: + """터치 키보드를 화면에 표시한다.""" + if sys.platform != "win32": + return False + ole32 = ctypes.windll.ole32 + ole32.CoInitialize(None) + try: + return _toggle_touch_keyboard() + finally: + ole32.CoUninitialize() + + +def hide_touch_keyboard() -> bool: + """표시 중인 터치 키보드를 숨긴다 (Toggle).""" + return show_touch_keyboard()