@@ -192,6 +192,89 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
192192 "hardware_trigger" : SupportLevel .BEST_EFFORT ,
193193 }
194194
195+ def _debug_trigger_nodes (self , node_map , * , context : str = "" ) -> None :
196+ names = (
197+ "TriggerMode" ,
198+ "TriggerSelector" ,
199+ "TriggerSource" ,
200+ "TriggerActivation" ,
201+ "AcquisitionMode" ,
202+ # Generic line nodes, if available.
203+ "LineSelector" ,
204+ "LineMode" ,
205+ "LineSource" ,
206+ # TIS 37U / DMK 37BUX287 strobe/output nodes.
207+ "GPIn" ,
208+ "GPOut" ,
209+ "StrobeEnable" ,
210+ "StrobePolarity" ,
211+ "StrobeOperation" ,
212+ "StrobeDuration" ,
213+ "StrobeDelay" ,
214+ )
215+
216+ label = f"GenTL trigger debug { context } " .strip ()
217+
218+ for name in names :
219+ node = self ._node (node_map , name )
220+ if node is None :
221+ continue
222+
223+ value = self ._node_value (node_map , name , None )
224+
225+ extras = []
226+
227+ symbolics = self ._node_symbolics (node )
228+ if symbolics :
229+ extras .append (f"symbolics={ symbolics } " )
230+
231+ for attr in ("access_mode" , "is_writable" , "is_readable" ):
232+ try :
233+ extras .append (f"{ attr } ={ getattr (node , attr )} " )
234+ except Exception :
235+ pass
236+
237+ LOG .debug ("%s: %s=%r %s" , label , name , value , " " .join (extras ))
238+
239+ def _debug_frame_rate_nodes (self , node_map , * , context : str = "" ) -> None :
240+ names = (
241+ "AcquisitionFrameRateEnable" ,
242+ "AcquisitionFrameRateControlEnable" ,
243+ "AcquisitionFrameRate" ,
244+ "AcquisitionFrameRateAbs" ,
245+ "AcquisitionResultingFrameRate" ,
246+ "ResultingFrameRate" ,
247+ "AcquisitionFrameRateResulting" ,
248+ "DeviceFrameRate" ,
249+ "ExposureAuto" ,
250+ "ExposureTime" ,
251+ "ExposureTimeAbs" ,
252+ "DeviceLinkThroughputLimit" ,
253+ "DeviceLinkThroughputLimitMode" ,
254+ "PayloadSize" ,
255+ "Width" ,
256+ "Height" ,
257+ "PixelFormat" ,
258+ )
259+
260+ label = f"GenTL FPS debug { context } " .strip ()
261+
262+ for name in names :
263+ node = self ._node (node_map , name )
264+ if node is None :
265+ continue
266+
267+ value = self ._node_value (node_map , name , None )
268+
269+ extras = []
270+ for attr in ("min" , "max" , "inc" ):
271+ try :
272+ extras .append (f"{ attr } ={ getattr (node , attr )} " )
273+ except Exception :
274+ pass
275+
276+ LOG .debug ("%s: %s=%r %s" , label , name , value , " " .join (extras ))
277+
195278 # ------------------------------------------------------------------
196279 # Discovery
197280 # ------------------------------------------------------------------
@@ -464,6 +547,7 @@ def open(self) -> None:
464547 self ._configure_gain (node_map )
465548 self ._configure_frame_rate (node_map )
466549 self ._configure_trigger (node_map ) # keep low in the list
550+ self ._debug_trigger_nodes (node_map , context = "after configuration before acquisition" )
467551 self ._ensure_settings_ns ()["trigger_actual" ] = self ._trigger_to_dict (self ._trigger )
468552 self ._read_telemetry (node_map )
469553 self ._persist_device_metadata (selected_info , selected_serial )
@@ -1186,31 +1270,42 @@ def _configure_trigger_off(self, node_map, *, strict: bool = False) -> None:
11861270 def _configure_trigger_input (self , node_map , cfg , * , strict : bool = False ) -> None :
11871271 role = str (self ._trigger_attr (cfg , "role" , "external" ) or "external" ).strip ().lower ()
11881272 selector = str (self ._trigger_attr (cfg , "selector" , "FrameStart" ) or "FrameStart" )
1189- source = str (self ._trigger_attr (cfg , "source" , "Line0" ) or "Line0" )
11901273 activation = str (self ._trigger_attr (cfg , "activation" , "RisingEdge" ) or "RisingEdge" )
1274+ source = str (self ._trigger_attr (cfg , "source" , "auto" ) or "auto" ).strip ()
11911275
11921276 # Disable trigger while changing trigger-related nodes.
11931277 self ._set_enum_node (node_map , "TriggerMode" , "Off" , strict = False )
11941278
11951279 selector_ok = self ._set_enum_node (node_map , "TriggerSelector" , selector , strict = strict )
1196- source , source_resolved = self ._resolve_trigger_source (node_map , source , strict = strict )
1197- source_ok = source_resolved and self ._set_enum_node (node_map , "TriggerSource" , source , strict = strict )
11981280 activation_ok = self ._set_enum_node (node_map , "TriggerActivation" , activation , strict = strict )
11991281
1200- # TriggerSelector and TriggerSource are required routing nodes.
1201- # If either failed in non-strict mode, do not arm TriggerMode=On.
1202- # Otherwise the camera may wait on a previous/default input line.
1203- if not (selector_ok and source_ok ):
1282+ source_ok = False
1283+ if source and source .lower () not in {"" , "auto" , "none" }:
1284+ source_node = self ._node (node_map , "TriggerSource" )
1285+ source_symbolics = self ._node_symbolics (source_node )
1286+
1287+ if source_node is not None :
1288+ if source in source_symbolics :
1289+ source_ok = self ._set_enum_node (node_map , "TriggerSource" , source , strict = False )
1290+ if not source_ok :
1291+ LOG .warning (
1292+ "GenTL TriggerSource=%s is supported but not writable; "
1293+ "continuing without changing TriggerSource. Available: %s" ,
1294+ source ,
1295+ source_symbolics ,
1296+ )
1297+ else :
1298+ LOG .warning (
1299+ "Requested GenTL TriggerSource=%s not in available sources %s; "
1300+ "continuing without changing TriggerSource." ,
1301+ source ,
1302+ source_symbolics ,
1303+ )
1304+
1305+ if not selector_ok :
12041306 LOG .warning (
1205- "Could not apply GenTL trigger input routing "
1206- "(selector_ok=%s, source_ok=%s); disabling trigger. "
1207- "requested role=%s selector=%s source=%s activation=%s" ,
1208- selector_ok ,
1209- source_ok ,
1210- role ,
1307+ "Could not apply GenTL TriggerSelector=%s; disabling trigger." ,
12111308 selector ,
1212- source ,
1213- activation ,
12141309 )
12151310 self ._configure_trigger_off (node_map , strict = False )
12161311 self ._trigger = CameraTriggerSettings ()
@@ -1231,55 +1326,153 @@ def _configure_trigger_input(self, node_map, cfg, *, strict: bool = False) -> No
12311326 return
12321327
12331328 LOG .info (
1234- "GenTL trigger input configured: role=%s selector=%s source=%s activation=%s activation_ok=%s" ,
1329+ "GenTL trigger input configured: role=%s selector=%s activation=%s "
1330+ "selector_ok=%s activation_ok=%s source_requested=%s source_ok=%s" ,
12351331 role ,
12361332 selector ,
1237- source ,
12381333 activation ,
1334+ selector_ok ,
12391335 activation_ok ,
1336+ source ,
1337+ source_ok ,
12401338 )
12411339
12421340 def _configure_trigger_master (self , node_map , cfg , * , strict : bool = False ) -> None :
1341+ """Configure this camera as a free-running master that emits STROBE_OUT pulses.
1342+
1343+ For DMK 37BUX287 / TIS 37U series, the physical output is controlled by
1344+ StrobeEnable/StrobePolarity/StrobeOperation rather than SFNC LineSelector/
1345+ LineMode/LineSource nodes.
1346+ """
12431347 output_line = str (self ._trigger_attr (cfg , "output_line" , "Line2" ) or "Line2" )
12441348 output_source = str (self ._trigger_attr (cfg , "output_source" , "ExposureActive" ) or "ExposureActive" )
12451349
1246- # Master camera runs freerun and exposes an output signal.
1350+ # Optional extra fields if present in trigger dict/model.
1351+ strobe_polarity = str (self ._trigger_attr (cfg , "strobe_polarity" , "ActiveHigh" ) or "ActiveHigh" )
1352+ strobe_operation = str (self ._trigger_attr (cfg , "strobe_operation" , "Exposure" ) or "Exposure" )
1353+ strobe_duration = self ._trigger_attr (cfg , "strobe_duration" , None )
1354+ strobe_delay = self ._trigger_attr (cfg , "strobe_delay" , None )
1355+
1356+ # Master camera should be free-running.
12471357 self ._configure_trigger_off (node_map , strict = False )
12481358
1249- line_selected = self ._set_enum_node (
1250- node_map ,
1251- "LineSelector" ,
1252- output_line ,
1253- strict = strict ,
1254- )
1359+ # ------------------------------------------------------------------
1360+ # Preferred path for The Imaging Source 37U / DMK 37BUX287:
1361+ # StrobeEnable, StrobePolarity, StrobeOperation, StrobeDuration, StrobeDelay
1362+ # ------------------------------------------------------------------
1363+ strobe_enable_node = self ._node (node_map , "StrobeEnable" )
1364+
1365+ if strobe_enable_node is not None :
1366+ # Disable first while changing parameters.
1367+ self ._set_enum_node (node_map , "StrobeEnable" , "Off" , strict = False )
1368+
1369+ polarity_ok = self ._set_enum_node (
1370+ node_map ,
1371+ "StrobePolarity" ,
1372+ strobe_polarity ,
1373+ strict = False ,
1374+ )
12551375
1256- # In non-strict mode, do not continue configuring output behavior if the
1257- # requested line could not be selected. Otherwise we may accidentally drive
1258- # whichever GPIO line the camera had selected previously/defaulted to.
1259- if not line_selected :
1260- LOG .warning (
1261- "Could not select GenTL output line '%s'; skipping trigger output configuration." ,
1262- output_line ,
1376+ operation_ok = self ._set_enum_node (
1377+ node_map ,
1378+ "StrobeOperation" ,
1379+ strobe_operation ,
1380+ strict = False ,
12631381 )
1264- return
12651382
1266- mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1267- source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1383+ if strobe_duration is not None :
1384+ try :
1385+ node = self ._node (node_map , "StrobeDuration" )
1386+ if node is not None :
1387+ node .value = int (strobe_duration )
1388+ LOG .info ("Configured GenTL StrobeDuration=%s" , int (strobe_duration ))
1389+ except Exception as exc :
1390+ if strict :
1391+ raise RuntimeError (f"Failed to set StrobeDuration={ strobe_duration } : { exc } " ) from exc
1392+ LOG .warning ("Failed to set StrobeDuration=%s: %s" , strobe_duration , exc )
1393+
1394+ if strobe_delay is not None :
1395+ try :
1396+ node = self ._node (node_map , "StrobeDelay" )
1397+ if node is not None :
1398+ node .value = int (strobe_delay )
1399+ LOG .info ("Configured GenTL StrobeDelay=%s" , int (strobe_delay ))
1400+ except Exception as exc :
1401+ if strict :
1402+ raise RuntimeError (f"Failed to set StrobeDelay={ strobe_delay } : { exc } " ) from exc
1403+ LOG .warning ("Failed to set StrobeDelay=%s: %s" , strobe_delay , exc )
1404+
1405+ enable_ok = self ._set_enum_node (
1406+ node_map ,
1407+ "StrobeEnable" ,
1408+ "On" ,
1409+ strict = strict ,
1410+ )
1411+
1412+ if enable_ok :
1413+ LOG .info (
1414+ "GenTL trigger master configured via Strobe*: "
1415+ "StrobeEnable=On StrobePolarity=%s polarity_ok=%s "
1416+ "StrobeOperation=%s operation_ok=%s" ,
1417+ strobe_polarity ,
1418+ polarity_ok ,
1419+ strobe_operation ,
1420+ operation_ok ,
1421+ )
1422+ return
1423+
1424+ if strict :
1425+ raise RuntimeError ("Could not enable GenTL StrobeEnable=On" )
12681426
1269- if not (mode_ok and source_ok ):
12701427 LOG .warning (
1271- "GenTL trigger master output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1272- mode_ok ,
1273- source_ok ,
1428+ "StrobeEnable node exists but could not be enabled; falling back to generic Line* output configuration."
12741429 )
1275- return
12761430
1277- LOG .info (
1278- "GenTL trigger master configured: output_line=%s output_source=%s" ,
1279- output_line ,
1280- output_source ,
1431+ # ------------------------------------------------------------------
1432+ # Generic SFNC fallback for cameras that expose LineSelector/LineMode/LineSource.
1433+ # ------------------------------------------------------------------
1434+ line_selector = self ._node (node_map , "LineSelector" )
1435+ if line_selector is not None :
1436+ line_selected = self ._set_enum_node (
1437+ node_map ,
1438+ "LineSelector" ,
1439+ output_line ,
1440+ strict = strict ,
1441+ )
1442+
1443+ if not line_selected :
1444+ LOG .warning (
1445+ "Could not select GenTL output line '%s'; skipping Line* output configuration." ,
1446+ output_line ,
1447+ )
1448+ else :
1449+ mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1450+ source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1451+
1452+ if mode_ok and source_ok :
1453+ LOG .info (
1454+ "GenTL trigger master configured via Line*: output_line=%s output_source=%s" ,
1455+ output_line ,
1456+ output_source ,
1457+ )
1458+ return
1459+
1460+ LOG .warning (
1461+ "GenTL Line* trigger output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1462+ mode_ok ,
1463+ source_ok ,
1464+ )
1465+
1466+ msg = (
1467+ "Could not configure GenTL trigger master output. "
1468+ "No supported Strobe* or Line* output path was successfully configured."
12811469 )
12821470
1471+ if strict :
1472+ raise RuntimeError (msg )
1473+
1474+ LOG .warning (msg )
1475+
12831476 def _restore_trigger_idle (self , node_map ) -> None :
12841477 """Best-effort restore to a safe non-triggering state after acquisition stops.
12851478
0 commit comments