No description
  • HTML 56.5%
  • CSS 28.6%
  • C# 14.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
joe 208221e782 Autoprober.Paducah: slot 11 by default, rotated viewer, Live button
- F430 JobSlot 11: the app loads the combined job before a job runs if another
  is running (the camera also boots slot 11).
- Main viewer turned clockwise (Rotate 90, 960 x 1280); a job's image stays up
  until the Messages page's Live button (HoldJobImageMs -1).
- combining-jobs.md: the boot slot, JobSlot and Rotate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:18:57 -05:00
Components Autoprober.Paducah: slot 11 by default, rotated viewer, Live button 2026-10-28 22:18:57 -05:00
docs Autoprober.Paducah: slot 11 by default, rotated viewer, Live button 2026-10-28 22:18:57 -05:00
F430 Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
GC Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
Properties Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
wwwroot Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
.editorconfig Autoprober.Paducah: .editorconfig and Directory.Build props/targets 2026-10-28 21:52:18 -05:00
.gitignore Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
appsettings.Development.json Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
appsettings.json Autoprober.Paducah: slot 11 by default, rotated viewer, Live button 2026-10-28 22:18:57 -05:00
Autoprober.Paducah.csproj Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
Directory.Build.props Autoprober.Paducah: .editorconfig and Directory.Build props/targets 2026-10-28 21:52:18 -05:00
Directory.Build.targets Autoprober.Paducah: .editorconfig and Directory.Build props/targets 2026-10-28 21:52:18 -05:00
job01.avp Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
job02.avp Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
job03.avp Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
live.avp Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00
Program.cs Autoprober.Paducah: the Paducah autoprober on PhAT 2026-10-28 21:49:35 -05:00

Autoprober.Paducah

The Paducah autoprober on PhAT. A Meca500 rides an IAI RCP6S linear stage, and the two are run as one robot with 7 axes. The machine also has an ATI force/torque sensor, a camera, a GC-1000 safety PLC, a Beijer (Crevis) GN-9289 I/O block and two chillers whose FD-H flow sensors are read through a Keyence NQ-EP4L. There are no XYZ stages.

Running it

Profile Settings URL
http appsettings.json http://localhost:5189
dotnet run --launch-profile http

Hardware

Part Model Connection In the app
Robot arm Mecademic Meca500 TCP (port 10000) Robots[0], Meca500 driver
Linear stage IAI RCP6-SA6R-WA-42P-3-600-P5-S-ML (600 mm stroke) Axis 1 of an RCON-PC-2 (axis 2 unused), through an RCON-GW-EP-FU1 EtherNet/IP gateway Robots[0]:Rail, simulated (no driver yet)
Gripper MPM500-2FP pneumatic module on the Meca500 The Meca500 (its two valves) Mpm500 true: Valve 1 and Valve 2
Force/torque sensor ATI Mini40 (9105-TW-MINI40-E-1.8) with a 9105-NETBA-ZE3 Net F/T box Ethernet (UDP RDT, port 49152); its latch output goes to a GC-1000 input Robots[0]:ForceSensor, NetFT driver (simulated until its IP is set)
Camera Omron MicroHAWK F430-F000M50C-SWV (firmware 9.2.2.4010) at 192.168.100.103 Ethernet: REST on 80 (images, overlays, records), the TCP command port 49205, or EtherNet/IP (tags, trigger) SmartCameras[0] "F430", MicroHawk over Rest, free-running combined job
Safety PLC Keyence GC-1000 with a GC-S1R (172.16.11.114) EtherNet/IP SafetyPlc (simulated until the GC is reachable): the latch gate, which chiller to monitor, and status
Light curtain Keyence GL-R87F The GC-1000 SafetyPlc status "Light curtain clear" (once the GC program maps it)
PAO chiller BOYD RC011J13DD3M127 — Chillers, NQ port 2
Water chiller HRSF018-AN-20, with a PF3W720-N03-BT-MRA flow switch — Chillers, NQ port 1
Chiller flow / temperature Keyence FD-H20 and FI-TI15 Keyence NQ-EP4L (IO-Link master, 172.16.11.113) Chillers
I/O Beijer GN-9289 with GT-12DF, GT-226F, GT-3804 Modbus TCP IoBlocks[0]

GC-1000 safety PLC

The GC-1000 (with a GC-S1R) does the safety wiring; the app only asks it things over its communication bits. These are not safety functions: the GC program decides what each bit does.

Its I/O (from the user, 2026-10-26):

GC terminal Net What
Safety in 00/01 ESS011 E-stop
Safety in 02 Reset button
Safety in 03 617/6171 Meca 1 protective stop B
Safety in 06 5041 Load cell latch 1 (the ATI Net F/T's threshold latch)
Safety in 09 PAO chiller flow/temperature out-of-range alarm
Safety in 10 Water chiller flow/temperature out-of-range alarm
Safety out 00 5111 515 Meca 1 SWSTOPA (light curtain)
Safety out 03 5122 430 IAI S- (to troubleshoot; meant to stop the rail on the light curtain)
Safety out 04 5131 601 Meca PSTOP
Safety out 05 5132 521–514 IAI power cutoff
Aux out 00 5141 501 Meca ResetA
GC-S1R Opens if the monitored chiller is out of range

The app talks to it through SafetyPlc (one EtherNet/IP I/O connection; the GC allows only one owner, so nothing else may connect to it that way). The bit map in appsettings.json is a proposal: program the GC to match it, or change the settings to match the program.

Communication input (app → GC) Bit The GC program should
Load cell latch enable 0.00 pass safety in 06 (the latch) to safety out 04 (Meca PSTOP) only while this is on. The app turns it on for each Force/Torque move only (ForceSensor:LatchGate Type SafetyPlc).
Monitor PAO chiller 0.01 watch safety in 09 for the S1R.
Monitor water chiller 0.02 watch safety in 10 for the S1R.

A sequence run with chiller=PAO Chiller (or Water Chiller, by name) turns that chiller's bit on and the other off before it starts; the start is refused if the GC is offline. The selection stays until another run names the other chiller (a run without chiller= leaves it as it is).

Status (GC → app, communication output) Bit OK when Guards runs
E-stop OK 0.00 on yes: a start is refused and a running sequence fails while it's off
Light curtain clear 0.01 on
Robot protective stop OK 0.02 on
Load cell latch tripped 0.03 off
PAO chiller alarm 0.04 off
Water chiller alarm 0.05 off
Monitored chiller OK (S1R closed) 0.06 on

The sidebar (Stages:ShowSafetyPlc) shows the GC (Connected / Offline / errors), the chiller it's monitoring and a light per status bit. SafetyPlc:Type is Simulated for now (status all OK).

Machine

Part Settings Notes
Robot Robots[0]: Name "Rail Robot", Type Meca500, Host, Rail, DefaultSpeedPercent, MaxMmPerS, MaxDegPerS One robot with 7 axes, the Controller's "Rail Robot" tab: Home/Stop and the pose on top; then a Joints / Cartesian switch and a Speed (%) for Map and Move; then the Map (stored positions), Move (jog the rail and X/Y/Z, α/β/γ or J1–J6; go to a pose or to joints), Force and Torque subtabs. Speeds are a percentage of the top speed: straight-line moves at MaxMmPerS / MaxDegPerS (100 %), joint moves at the Meca500's joint speed percentage, the rail at its SpeedMmPerS. A move with the rail goes to the rail position first, then moves the arm. Home homes the rail, then the arm.
Map robot-positions.json (RobotPositionsFile) Each position has a label (unique, no spaces; new ones get P1, P2, ...) that sequences move to: await MoveRobotAsync("load") (or MoveRobotAsync("Rail Robot", "load")), a joint move with the rail first. It is stored as the rail position (a UnitsNet Length) and the joint angles J1–J6 (Angles), each saved with its unit ({"Value": 7.48, "Unit": "Inch"}). A switch shows them as Joints or Cartesian: the pose is computed from the joints (Meca500 forward kinematics, default TRF/WRF). A pose edited in the Cartesian view is saved as the joints that reach it, the configuration nearest the old joints; one out of reach is refused. Go always does a joint move, so the robot arrives in the stored configuration. Files in the old format (Kind/Values) are converted when read.
Force / Torque Robots[0].ForceSensor: Type NetFT (or Simulated), Host, LatchStopsRobot, LatchGate (Type SafetyPlc with Input (and optional Echo); or, without SafetyPlc, EtherNetIP, Modbus or Simulated with Host, Group/Bit or Register/Bit or Coil, optional EchoGroup/EchoBit); guarded-moves.json (GuardedMovesFile) The ATI Net F/T at the end effector (axes along the tool frame), read over UDP (RDT, port 49152; counts per unit from its netftapi2.xml). Its threshold latch goes to an input of the GC-1000 safety controller, and a GC output goes to the Meca500's protective stop input; the GC program passes the latch through only while a communication bit (the latch gate) is on. The app turns the gate on for each Force/Torque move only, and off at startup, after the move, on errors and when cancelled, so the latch can't stop other moves. A Force/Torque move the robot stops (with the reading at 90 % of the move's threshold or more) is the threshold reached; the app then turns the gate off, resets the latch and resumes the robot, so it can back off. (Without LatchGate, the latch is taken as wired straight to the robot: the app tares at the contact load before resetting the latch.) The gate over EtherNet/IP owns the GC's I/O connection (command assembly 110), so nothing else may connect to it that way; set EchoGroup/EchoBit to a GC communication output the program sets from the gate, and the app waits for it to confirm each change. Set the Net F/T's latch threshold on its web page at (or just below) the moves' thresholds; a stop well short of a move's threshold is reported as an error. The Force tab shows Fx, Fy, Fz (Torque: Tx, Ty, Tz) with Tare, and defines a guarded move: a direction (Force: a straight line; Torque: an axis through the tool point) in the Tool or Base frame, a maximum distance or angle, a threshold force or torque, and a speed (%). Run moves until the force (torque) along the direction reaches the threshold, then stops; or stops at the maximum. Moves are saved with a label (F1, T1, ...): await ForceMoveAsync("touch") / TorqueMoveAsync("T1") in a sequence, which returns whether the threshold was reached, how far it went and the force or torque. The status panel shows the six values (Stages:DefaultForceUnit / DefaultTorqueUnit, N and N·m). The simulated sensor has a flat surface at SurfaceZMm (stiffness StiffnessNPerMm) and twists back when J6 turns while pressed on it.
Smart camera (F430) SmartCameras[0]: Type MicroHawk (or Simulated), Host, HttpPort, Control Tcp (TcpPort: the job's tcpcmd.port, 49205 here), Rest (the same commands through PUT /api/v1/command) or EtherNetIP, ModeTag, Modes, CaptureMode, ResultTags, TimeoutMs, LiveIntervalMs, and for a combined job FreeRun, RanTag, MainViewer, HoldJobImageMs, LogRuns Now (2026-10-28): a combined job (combining-jobs.md): FreeRun true, ModeTag long17, RanTag long18, modes Live 0 and Job 1–3. Live view takes each new record (no trigger) and, with MainViewer, shows it in the main viewer (Camera:Provider None; Live starts with the app). A job button (Messages page) or InspectAsync("Job 2") writes 2 to long17 and waits for the record with long18 = 2; that picture shows with the job's graphics for HoldJobImageMs, and LogRuns keeps its image, SVG and result tags (from the record) on the Vision logs page under the job's name. A request that gets no record in TimeoutMs is withdrawn (long17 back to 0). Before (triggered): one job on the camera with modes instead of a job per recipe: the job reads ModeTag (int1) and its tools act on it (0 = Capture, just takes a picture; 1–3 = Recipe 1–3). The app writes the tag only when the mode changes, triggers (TRIGGER on the command port, or the TRIGGER control bit over EtherNet/IP), waits for the new record (REST /api/v1/record), then reads the ResultTags and fetches the image and the job's SVG overlay. The Smart camera page (Other) picks a mode, runs Inspect, shows PASS/FAIL, the tags and the image with the overlay, and has Live (captures in CaptureMode over and over). In a sequence: var r = await InspectAsync("Recipe 1") (r.Pass, r.Tags["float1"]). Set the job's trigger to serial/EtherNet/IP (not continuous) so Live and the recipes share it. Tested on the real F430 (2026-10-26): trigger to record in about 140 ms; Live runs about 2.6 captures/s (1280×960 PNGs of 1.2 MB). The F430's REST API answers 415 unless Accept names what it returns (application/json, image/*, image/svg+xml, text/plain for commands). Its record's cycletime is the time since the previous trigger, not the inspection time. The loaded job is slot 1 (FET.avp; slots 1–6 hold FET, Job2, 10-32, 0-80, 2-56, Identify); it doesn't read int1 yet. Jobs: the page lists the jobs stored on the camera (JOBINFO -v, over REST so it works whichever job runs) and Load job loads one (JOBLOAD -slot=n -r, about 3 s for FET; refused during Live; the mode tag is written again on the next inspection). A loaded job must use the same TCP command port (TcpPort) for Control Tcp. Jobs are made in AutoVISION or with the Visionscape SDK (see visionscape.md).
Robot settings robot-speeds.json (RobotSpeedsFile) Engineers set each robot's top speeds here (what 100 % means): straight-line moves' tool point and turn, the rail, and the default speed of moves that don't give one. Saved with their units over appsettings.json's MaxMmPerS, MaxDegPerS, Rail:SpeedMmPerS and DefaultSpeedPercent; the next move uses them. "Reset to appsettings.json" drops them.
Units Stages: DefaultLengthUnit Inch, DefaultAngleUnit Degree The Controller page starts in these (its pickers change them for that page); the sidebar always uses them.
Rail (IAI RCP6-SA6R, 600 mm) Robots[0]:Rail: MinMm 0, MaxMm 600, SpeedMmPerS Axis 1 of an RCON-PC-2, reached through an RCON-GW-EP-FU1 (EtherNet/IP) gateway. Simulated for now: PhAT has no RCON gateway driver yet (Colombo's Vendors.IAI Gateway/Axis is the one to port). SpeedMmPerS (200) is a placeholder: check it against the actuator's lead (the "WA" in the model) and the RCON's parameters.
I/O (GN-9289, Modbus TCP) IoBlocks[0] Points In1–In16, Out1–Out16 and TC1–TC4 (0.1 °C, Int16). The addresses assume the slots run GT-12DF (16 DI), GT-226F (16 DO), GT-3804 (4 TC). Inputs are discrete inputs 0–15, outputs are coils 0x0800–0x080F, and the thermocouples are input registers 1–4. Check the slot order and the GT-3804's unit setting (°C/°F) on the unit. The live values are on the Camera diagnostics page.
Chillers Chillers: NQ Host, Ports (name → NQ port) Water Chiller (port 1) and PAO Chiller (port 2). Engineers set the flow and temperature limits on the Chillers page; they're saved in chiller-limits.json. A sequence run with chiller=<name> (or the port number) is refused while that loop is out of limits, and fails if it leaves them for TripMs.
Sidebar Stages: BuiltInStages false, ShowRobots, ShowIo, ShowChillers, ShowSafetyPlc true, ShowHandler false The robot's state, then Rail, X, Y, Z, α, β, γ and the force/torque; the GN-9289 (a light per input and output, the thermocouples); a flow and a temperature dial per chiller; the GC-1000's status.

The app opens on Messages (there is no separate Autoprober page): the F430's Job 1–3 buttons, the message log, and the main viewer with the F430's live view. The Controller is under Other, for Engineers and up (set in NavPanel.razor; an admin can change it on the Users page). The machine network is 172.16.11.x: NQ-EP4L .113, GC-1000 .114, GN-9289 .102 (the GC and GN-9289 addresses are what the app expects; set them on the devices). The Meca500 is still at its factory default (192.168.0.100), the F430 is at 192.168.100.103, and the Net F/T address is not known yet.