@@ -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 # ------------------------------------------------------------------
@@ -461,6 +544,7 @@ def open(self) -> None:
461544 self ._configure_gain (node_map )
462545 self ._configure_frame_rate (node_map )
463546 self ._configure_trigger (node_map ) # keep low in the list
547+ self ._debug_trigger_nodes (node_map , context = "after configuration before acquisition" )
464548 self ._ensure_settings_ns ()["trigger_actual" ] = self ._trigger_to_dict (self ._trigger )
465549 self ._read_telemetry (node_map )
466550 self ._persist_device_metadata (selected_info , selected_serial )
@@ -1183,31 +1267,42 @@ def _configure_trigger_off(self, node_map, *, strict: bool = False) -> None:
11831267 def _configure_trigger_input (self , node_map , cfg , * , strict : bool = False ) -> None :
11841268 role = str (self ._trigger_attr (cfg , "role" , "external" ) or "external" ).strip ().lower ()
11851269 selector = str (self ._trigger_attr (cfg , "selector" , "FrameStart" ) or "FrameStart" )
1186- source = str (self ._trigger_attr (cfg , "source" , "Line0" ) or "Line0" )
11871270 activation = str (self ._trigger_attr (cfg , "activation" , "RisingEdge" ) or "RisingEdge" )
1271+ source = str (self ._trigger_attr (cfg , "source" , "auto" ) or "auto" ).strip ()
11881272
11891273 # Disable trigger while changing trigger-related nodes.
11901274 self ._set_enum_node (node_map , "TriggerMode" , "Off" , strict = False )
11911275
11921276 selector_ok = self ._set_enum_node (node_map , "TriggerSelector" , selector , strict = strict )
1193- source , source_resolved = self ._resolve_trigger_source (node_map , source , strict = strict )
1194- source_ok = source_resolved and self ._set_enum_node (node_map , "TriggerSource" , source , strict = strict )
11951277 activation_ok = self ._set_enum_node (node_map , "TriggerActivation" , activation , strict = strict )
11961278
1197- # TriggerSelector and TriggerSource are required routing nodes.
1198- # If either failed in non-strict mode, do not arm TriggerMode=On.
1199- # Otherwise the camera may wait on a previous/default input line.
1200- if not (selector_ok and source_ok ):
1279+ source_ok = False
1280+ if source and source .lower () not in {"" , "auto" , "none" }:
1281+ source_node = self ._node (node_map , "TriggerSource" )
1282+ source_symbolics = self ._node_symbolics (source_node )
1283+
1284+ if source_node is not None :
1285+ if source in source_symbolics :
1286+ source_ok = self ._set_enum_node (node_map , "TriggerSource" , source , strict = False )
1287+ if not source_ok :
1288+ LOG .warning (
1289+ "GenTL TriggerSource=%s is supported but not writable; "
1290+ "continuing without changing TriggerSource. Available: %s" ,
1291+ source ,
1292+ source_symbolics ,
1293+ )
1294+ else :
1295+ LOG .warning (
1296+ "Requested GenTL TriggerSource=%s not in available sources %s; "
1297+ "continuing without changing TriggerSource." ,
1298+ source ,
1299+ source_symbolics ,
1300+ )
1301+
1302+ if not selector_ok :
12011303 LOG .warning (
1202- "Could not apply GenTL trigger input routing "
1203- "(selector_ok=%s, source_ok=%s); disabling trigger. "
1204- "requested role=%s selector=%s source=%s activation=%s" ,
1205- selector_ok ,
1206- source_ok ,
1207- role ,
1304+ "Could not apply GenTL TriggerSelector=%s; disabling trigger." ,
12081305 selector ,
1209- source ,
1210- activation ,
12111306 )
12121307 self ._configure_trigger_off (node_map , strict = False )
12131308 self ._trigger = CameraTriggerSettings ()
@@ -1228,55 +1323,153 @@ def _configure_trigger_input(self, node_map, cfg, *, strict: bool = False) -> No
12281323 return
12291324
12301325 LOG .info (
1231- "GenTL trigger input configured: role=%s selector=%s source=%s activation=%s activation_ok=%s" ,
1326+ "GenTL trigger input configured: role=%s selector=%s activation=%s "
1327+ "selector_ok=%s activation_ok=%s source_requested=%s source_ok=%s" ,
12321328 role ,
12331329 selector ,
1234- source ,
12351330 activation ,
1331+ selector_ok ,
12361332 activation_ok ,
1333+ source ,
1334+ source_ok ,
12371335 )
12381336
12391337 def _configure_trigger_master (self , node_map , cfg , * , strict : bool = False ) -> None :
1338+ """Configure this camera as a free-running master that emits STROBE_OUT pulses.
1339+
1340+ For DMK 37BUX287 / TIS 37U series, the physical output is controlled by
1341+ StrobeEnable/StrobePolarity/StrobeOperation rather than SFNC LineSelector/
1342+ LineMode/LineSource nodes.
1343+ """
12401344 output_line = str (self ._trigger_attr (cfg , "output_line" , "Line2" ) or "Line2" )
12411345 output_source = str (self ._trigger_attr (cfg , "output_source" , "ExposureActive" ) or "ExposureActive" )
12421346
1243- # Master camera runs freerun and exposes an output signal.
1347+ # Optional extra fields if present in trigger dict/model.
1348+ strobe_polarity = str (self ._trigger_attr (cfg , "strobe_polarity" , "ActiveHigh" ) or "ActiveHigh" )
1349+ strobe_operation = str (self ._trigger_attr (cfg , "strobe_operation" , "Exposure" ) or "Exposure" )
1350+ strobe_duration = self ._trigger_attr (cfg , "strobe_duration" , None )
1351+ strobe_delay = self ._trigger_attr (cfg , "strobe_delay" , None )
1352+
1353+ # Master camera should be free-running.
12441354 self ._configure_trigger_off (node_map , strict = False )
12451355
1246- line_selected = self ._set_enum_node (
1247- node_map ,
1248- "LineSelector" ,
1249- output_line ,
1250- strict = strict ,
1251- )
1356+ # ------------------------------------------------------------------
1357+ # Preferred path for The Imaging Source 37U / DMK 37BUX287:
1358+ # StrobeEnable, StrobePolarity, StrobeOperation, StrobeDuration, StrobeDelay
1359+ # ------------------------------------------------------------------
1360+ strobe_enable_node = self ._node (node_map , "StrobeEnable" )
1361+
1362+ if strobe_enable_node is not None :
1363+ # Disable first while changing parameters.
1364+ self ._set_enum_node (node_map , "StrobeEnable" , "Off" , strict = False )
1365+
1366+ polarity_ok = self ._set_enum_node (
1367+ node_map ,
1368+ "StrobePolarity" ,
1369+ strobe_polarity ,
1370+ strict = False ,
1371+ )
12521372
1253- # In non-strict mode, do not continue configuring output behavior if the
1254- # requested line could not be selected. Otherwise we may accidentally drive
1255- # whichever GPIO line the camera had selected previously/defaulted to.
1256- if not line_selected :
1257- LOG .warning (
1258- "Could not select GenTL output line '%s'; skipping trigger output configuration." ,
1259- output_line ,
1373+ operation_ok = self ._set_enum_node (
1374+ node_map ,
1375+ "StrobeOperation" ,
1376+ strobe_operation ,
1377+ strict = False ,
12601378 )
1261- return
12621379
1263- mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1264- source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1380+ if strobe_duration is not None :
1381+ try :
1382+ node = self ._node (node_map , "StrobeDuration" )
1383+ if node is not None :
1384+ node .value = int (strobe_duration )
1385+ LOG .info ("Configured GenTL StrobeDuration=%s" , int (strobe_duration ))
1386+ except Exception as exc :
1387+ if strict :
1388+ raise RuntimeError (f"Failed to set StrobeDuration={ strobe_duration } : { exc } " ) from exc
1389+ LOG .warning ("Failed to set StrobeDuration=%s: %s" , strobe_duration , exc )
1390+
1391+ if strobe_delay is not None :
1392+ try :
1393+ node = self ._node (node_map , "StrobeDelay" )
1394+ if node is not None :
1395+ node .value = int (strobe_delay )
1396+ LOG .info ("Configured GenTL StrobeDelay=%s" , int (strobe_delay ))
1397+ except Exception as exc :
1398+ if strict :
1399+ raise RuntimeError (f"Failed to set StrobeDelay={ strobe_delay } : { exc } " ) from exc
1400+ LOG .warning ("Failed to set StrobeDelay=%s: %s" , strobe_delay , exc )
1401+
1402+ enable_ok = self ._set_enum_node (
1403+ node_map ,
1404+ "StrobeEnable" ,
1405+ "On" ,
1406+ strict = strict ,
1407+ )
1408+
1409+ if enable_ok :
1410+ LOG .info (
1411+ "GenTL trigger master configured via Strobe*: "
1412+ "StrobeEnable=On StrobePolarity=%s polarity_ok=%s "
1413+ "StrobeOperation=%s operation_ok=%s" ,
1414+ strobe_polarity ,
1415+ polarity_ok ,
1416+ strobe_operation ,
1417+ operation_ok ,
1418+ )
1419+ return
1420+
1421+ if strict :
1422+ raise RuntimeError ("Could not enable GenTL StrobeEnable=On" )
12651423
1266- if not (mode_ok and source_ok ):
12671424 LOG .warning (
1268- "GenTL trigger master output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1269- mode_ok ,
1270- source_ok ,
1425+ "StrobeEnable node exists but could not be enabled; falling back to generic Line* output configuration."
12711426 )
1272- return
12731427
1274- LOG .info (
1275- "GenTL trigger master configured: output_line=%s output_source=%s" ,
1276- output_line ,
1277- output_source ,
1428+ # ------------------------------------------------------------------
1429+ # Generic SFNC fallback for cameras that expose LineSelector/LineMode/LineSource.
1430+ # ------------------------------------------------------------------
1431+ line_selector = self ._node (node_map , "LineSelector" )
1432+ if line_selector is not None :
1433+ line_selected = self ._set_enum_node (
1434+ node_map ,
1435+ "LineSelector" ,
1436+ output_line ,
1437+ strict = strict ,
1438+ )
1439+
1440+ if not line_selected :
1441+ LOG .warning (
1442+ "Could not select GenTL output line '%s'; skipping Line* output configuration." ,
1443+ output_line ,
1444+ )
1445+ else :
1446+ mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1447+ source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1448+
1449+ if mode_ok and source_ok :
1450+ LOG .info (
1451+ "GenTL trigger master configured via Line*: output_line=%s output_source=%s" ,
1452+ output_line ,
1453+ output_source ,
1454+ )
1455+ return
1456+
1457+ LOG .warning (
1458+ "GenTL Line* trigger output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1459+ mode_ok ,
1460+ source_ok ,
1461+ )
1462+
1463+ msg = (
1464+ "Could not configure GenTL trigger master output. "
1465+ "No supported Strobe* or Line* output path was successfully configured."
12781466 )
12791467
1468+ if strict :
1469+ raise RuntimeError (msg )
1470+
1471+ LOG .warning (msg )
1472+
12801473 def _restore_trigger_idle (self , node_map ) -> None :
12811474 """Best-effort restore to a safe non-triggering state after acquisition stops.
12821475
0 commit comments