- C# 99.7%
- PowerShell 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
An op's Run condition (Always / only if <op> failed / fail if <op> failed) lets one op back up another inside a job; a fallback that passes covers the op it stands in for, and skipped ops don't fail the job. Pick best takes the result of one of several ops (best score, first that passed, all must pass, all agree). Skipped ops are recorded in the run log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| Auth | ||
| build | ||
| Camera | ||
| Cameras | ||
| Data | ||
| Devices | ||
| Ftp | ||
| Hosting | ||
| Inventory | ||
| Machine | ||
| Masks | ||
| Models | ||
| native | ||
| Networking | ||
| Quantities | ||
| Robots | ||
| Sequencing | ||
| Stages | ||
| Telemetry | ||
| Toolpaths | ||
| Vision | ||
| .editorconfig | ||
| .gitignore | ||
| Directory.Build.props | ||
| Directory.Build.targets | ||
| PhAT.csproj | ||
| README.md | ||
PhAT
The shared libraries behind the apps (MessageLog, Atlanta, PhAT.LittleBoy, PhAT.FatMan, Autoprober.Nacogdoches, and the other Autoprobers).
| Project | What's in it |
|---|---|
PhAT (class library) |
Services and drivers, no UI. |
PhAT.Razor (Razor class library) |
Blazor components and feature pages built on PhAT. |
PhAT.Wpf (WPF class library) |
WPF counterparts of the vision components (image viewer, job editor, mask editor, camera view, ...); see its README. Used by PhAT.VisionTool, a desktop app for vision jobs. |
PhAT.Tests (console) |
Checks for the Galil, networking (command server, data listener, Modbus, CCS, SCPI, UR) and Velox code against fake devices: dotnet run --project PhAT.Tests. See its README. |
PhAT
| Folder / namespace | Contents |
|---|---|
Auth |
Users and roles (UserStore, RoleResolver), cookie login endpoints |
Camera |
The camera host: live view, captures, live settings, grab statistics, lights |
Cameras |
Camera drivers: Basler, The Imaging Source, Allied Vision (Vimba, FirePackage), OpenCV, simulated; autofocus and auto-exposure |
Data |
SQLite message log and launch log |
Devices |
Protocol clients: EtherNet/IP (CIP), Galil DMC-40x0 (no gclib; see MessageLog/docs/galil.md), Keyence LK-G5000, GC-1000 and NQ-EP4L (IO-Link master), Mecademic, Modbus TCP (client and server, Moxa functions), Opto 22 groov RIO / EPIC (groov Manage REST API), SCPI, Universal Robots (Dashboard, RTDE, URScript) |
Hosting |
Build info (version, build time, commit) from build/PhAT.BuildInfo.targets; AppHost.CreateBuilder (standalone or Windows service, see build/README-service.md); ProcessGate (wait until another program has been up N s) |
Networking |
Command (tester) server, data listener (files framed by content, text commands), network shares, serial request/reply. See Autoprober.Nacogdoches/docs/phat-additions.md |
Ftp |
The in-app FTP server |
Inventory |
Polled inventory with change detection: installed software, network addresses, instruments (USB, VISA, *IDN?), EtherNet/IP scan, NI MAX report, NI licensing |
Machine |
Digital I/O blocks (Modbus TCP, groov RIO: see groov RIO), robots (Meca500, UR), displacement sensors, the STOP service |
Masks |
Mask library and endpoints |
Robots |
Robot kinematics (UR5e, FANUC LR Mate), motion planning over a plane, URScript / FANUC LS export |
Sequencing |
The Engine: C# and .csx sequences, machine lock |
Stages |
Stage model and controllers, stored positions and their history, probers (Velox: see Waiting for VeloxPro) |
Telemetry |
Metrics: this PC (CPU, memory, disks, network), watched apps (process, /metrics/json), MetricsExport for an app's own .NET metrics, IMetricSink for logging |
Toolpaths |
2D G-code reader, regions, infill patterns |
Vision |
Vision jobs, ops, processors, calibration, run logs; job checks (VisionJobCheck) and zip export/import (VisionJobArchive). Jobs reload when their files change on disk ("Vision:WatchJobs", default on). |
PhAT references Microsoft.AspNetCore.App (hosted services, endpoints, auth) and carries the vendor SDK
references (pylon, IC4, OpenCV, Tesseract, ...). It also ships libdmtx.dll, and FirePackage's DLLs when
that SDK is installed. Its None items copy them into every app's output.
Waiting for VeloxPro
An app installed as a Windows service starts at boot, but FormFactor's VeloxPro only starts when someone logs in. To connect to Velox only once VeloxPro has been running for a while:
"Prober": { "Driver": "Velox", "WaitForProcess": "VeloxPro", "ProcessUpSeconds": 30 }
- Connecting:
VeloxClient.ConnectAsyncwaits for the program. That applies to the main connection, the stop connection and the prober's status connection. - Commands meanwhile: a command sent before then fails at once with
VeloxNotReadyException. - Status text:
VeloxClient.NotReady(andVeloxProber.NotReady) say why, e.g. "Waiting for VeloxPro.exe (not running)" or "Waiting for VeloxPro.exe to load (12 of 30 s)". Changes raiseConnectionChanged. - Tester replies: a tester reply can send "RECOVERABLE," followed by that text unchanged; the
message of
VeloxNotReadyExceptionis the same text. - Prober stages: they show that text as their error until the program is up.
- Exit: if VeloxPro exits, the next connection waits again.
- Start time: it comes from the process. If a service account can't read another user's process, it counts from when the app first saw it.
- Default: empty
WaitForProcessconnects at once.
Velox notifications and live positions
For the Velox drivers, AddStages also registers one shared VeloxNotifications listener. It
registers as <AppName>Events with notifications on, starts with the app, and waits for the
VeloxPro gate like the client. Apps subscribe to its Received and decode with:
AsAlert(): AlertNotification, 0xCA;AsButtonPress(): 0x152;AsLicense(): LicenseInfo, 0x103;AsAppChange(): RegisterProberAppChange, 0x109, e.g. to know when Spectrum has registered.
Each returns null for other types.
- Alerts:
VeloxAlertgives the alert id,Value(i)andLetter(i). Letters may arrive as their ASCII code. - Which stage:
Targetis 'C', 'S' or 'P' with the positioner number. - Position alerts:
AffectsPositionmarks XY, Z, theta and index moves, load, new homes, contact / focus heights, and a prober reset. - Frame: per the manual's alert table, the positions in alerts are in the zero frame.
Live positions: VeloxProber raises PositionsChanged on those alerts, and ProberService
then reads the positions at once in the home frame, at most every 50 ms. So moves made from Velox's
own UI or the joystick show up on the stages straight away. The 1 s idle poll and PollMs while
moving stay as a fallback.
The VeloxSimulator driver sends these notifications for moves, homes, contact heights and app
(un)registration, as Cmd=<id>:<name>:<data> lines.
Not yet seen on a real prober: the line format and the alert values come from the manual.
groov RIO
Opto 22 groov RIO channels as named I/O, through the groov Manage REST API. It also works for a groov
EPIC: set Module to the slot.
- How it reads: one request per module reads all its digital channels, and one reads all its
analog channels, every
PollMs. - Quality errors: a channel the RIO flags as bad reads as unknown.
- Writes: outputs are written at once.
In appsettings.json:
"GroovRio": [
{
"Name": "RIO1",
"Host": "192.168.1.50",
"ApiKey": "<API key of a groov user with Manage permission>",
"PollMs": 100,
"Points": [
{ "Name": "DoorClosed", "Kind": "DigitalInput", "Channel": 0 },
{ "Name": "Beacon", "Kind": "DigitalOutput", "Channel": 7 },
{ "Name": "ChillerTemp", "Kind": "AnalogInput", "Channel": 5, "Unit": "°C" },
{ "Name": "Valve", "Kind": "AnalogOutput", "Channel": 8 }
]
}
]
- Program.cs: call
builder.Services.AddGroovRio(builder.Configuration)beforeAddStages. The RIO becomes the app'sIDigitalIo/IAnalogIoif no other I/O was registered first. Only one I/O source is used per app for now, as withAddModbusIoandAddGalil. - Channels and values:
Channelis the channel number shown in groov Manage.Kindmust match how the channel is configured on the RIO.- Analog values arrive already scaled by the RIO, in its configured units.
Invertedflips a digital point.
- Certificate: the RIO serves HTTPS with a self-signed certificate. Without
CertificateThumbprintany certificate is accepted (still encrypted), and the log shows the thumbprint seen. Copy it intoCertificateThumbprintto accept only that RIO. - API key: create it in groov Manage → Accounts, for a user with Manage permission. A wrong key is reported as 401.
- Status:
GroovRioIo.Unitsshows the connection state, the module (e.g.GRV-R7-MM1001-10) and the thumbprint.Pointsshows each value with the channel's name on the RIO.
Tested only against a fake RIO in PhAT.Tests (groovrio), not a real device.
Stored position history
StoredPositionStore (stages) and RobotPositionStore (robots) record every add, edit and removal.
- Where: a JSON Lines file beside the positions file, one change per line, only ever appended:
stage-positions.history.jsonlandrobot-positions.history.jsonl. - What each change holds: the time, who made it, and the position before and after. "Before" is empty for an add and "after" for a removal. An edit that changes nothing isn't recorded.
- Who:
Add,UpdateandRemovetake an optionaluser. A stage's Map and the robot Map pass the signed-in user's name. - Who may change them: on the Maps, only engineers and admins add, edit, remove or roll back. Operators can view the positions and their history, and move to a position.
- Rolling back:
Rollback(changeId, user)puts the one position that change touched back as it was before it, and leaves every other position alone.- Rolling back an add removes the position; rolling back a removal re-adds it with the same Id.
- Any later edits to that position are replaced too. The rollback is recorded as a change of its
own (
RollbackOf), so it can be rolled back in turn. - It is refused if the position is already that way, or (robots) if its old label now belongs to another position.
- In the UI: the History button beside "Add current position" lists the changes, newest first, each with a Roll back button and a confirmation.
Launch units
The launches table stores X, Y, Z and ScopeZ in µm and θ in degrees, as it always has. The Launches
page, its analysis and its distributions show them in mils, or in "Launches:LengthUnit" (default
"Stages:DefaultLengthUnit"). Sequences record a launch with quantities, in any unit:
await RecordLaunchAsync(carrier, lid, pocket, port.Position, scope.Position.Z, pass, power, vision, msn: msn);
The older overload, which takes plain numbers in µm and degrees, still works.
Network shares from the page
Admins add, edit and remove shares on the Network shares page: name, UNC path, optional drive letter, user and password.
- Where they're kept:
NetworkShares:SharesFile(default network-shares.json), with no passwords in it. - Passwords: encrypted with DPAPI for this PC in
NetworkShares:SecretsFile, as before. - Shares from appsettings.json: still connected. The page marks them "appsettings" and only changes their passwords.
- Code:
NetworkShareService.SaveAsync/RemoveAsync/Problem(validation).
Vision job pass rule
A job's Pass when setting (VisionJob.PassWhen, in the job editor's header) decides
VisionJobResult.Success, and with it the run log's pass/fail and the Vision logs page:
All(the default): every op must succeed.AnyRead: the job passes when at least one text extractor op reads. Use it for jobs whose ops are alternative reads (barcode-1…7, then text); the other ops' failures don't fail it.
Runs logged before the rule was set keep their pass/fail (it is in the log file name).
Vision job "Run only if another failed"
A job's Run setting (VisionJob.RunIfFailed, beside "Pass when" in the job editor's header)
makes it a fallback: "Only if "Read A" failed" runs the job only when Read A's most recent run
failed. Otherwise the job is skipped. "Always" (the default, nothing in the JSON) runs it as before.
- Which run of the other job counts: its most recent run in the same batch.
-
A sequence run is one batch, sub-sequences included (
SequenceContext.VisionHistory). Run the job first, then its fallback:using var a = await RunVisionAsync("Read A"); using var b = await RunVisionAsync("Read A fallback"); // skipped unless Read A failed just now -
Each
VisionManagerService.RunAllAsync(names)call is one batch. A fallback waits for the job it names when that job is listed before it. -
VisionManagerService.RunAsync(name)with no batch uses the most recent run of the other job by name in the app.
-
- When it's skipped: the other job passed, was skipped itself, or hasn't run in the batch. So in a chain (B if A failed, C if B failed), C runs only when A and B both failed.
- If the other job doesn't exist: the run fails with an error. Renaming a job updates the jobs that name it.
- How a skip shows:
- The result has
SkippedandSkipReason, no images or ops, andSuccessfalse. It isn't a failure:OutcomeisSkipped. - The sequence log says "Vision "X": Skipped: "Read A" passed (runs only if it failed)."
- The run log line has
"skipped": trueandskipReason. - The vision log records only JSON, named
..._skip.json. - The Vision logs page shows the run as Skipped (grey), with its own filter. Skipped runs aren't counted in the pass %.
- The result has
- The editor's Run button runs the job whatever this setting says (trial runs).
Stage ↔ pixel calibration
The Calibration page's "Stage ↔ pixel" tab works out how image pixels map to stage positions.
- How it runs: moves a stage over a grid (start, step and end per axis), or over a list of offsets, around an origin. The origin is the current position or a Map position. At each point it runs a vision job.
- The fit: where the chosen op found the target, fitted against where the stage was. It's a full 2D affine fit (rotation and separate X/Y scales), checked against a minimum R² per axis.
- Saving: engineers save the fit by name in
stage-calibrations.json("Vision:StageCalibrationsFile"). - Using it in a sequence:
var cal = Get<StageCalibrationStore>().Find("Launch", "Target", "Scope");
var (dx, dy) = cal!.Delta(found.Center, reference.Center); // stage move between two pixels
var (x, y) = cal.ToStage(pixel); // stage position for a pixel
PhAT.Razor
Everything lives under Components/ (namespace PhAT.Razor.Components.*):
- Shared components:
ImageViewer,MessageLogGrid,CameraImageViewer,VisionResultOverlay, and theAuth,Devices(NqMasterPanel),Launches,Masks,StagesandVisionfolders. - Layout:
ReconnectModal. - Feature pages (
Pages/): Calibration, Commands (command server), Controller, Diagnostics, Engine, FTP, Launches, Login, Masks, Inventory, Paths, Shares (network shares), Telemetry (metrics dashboard), Templates, Users, Vision, Vision logs. - Footer:
BuildInfoLabel(version, commit, build time). - Telemetry:
Gauge,SparklineandMetricFormat(Telemetry/). - Scope viewer:
ScopeViewer(shown/hidden, picked area) andScopeAreaOverlay. - Themes (
Theme/):PhatTheme(applies the theme),ThemePicker(the header button) andThemeState. See Themes.
Collocated .razor.js modules are served at _content/PhAT.Razor/Components/.... Scoped CSS reaches
the app through its <App>.styles.css bundle.
Themes
Light, dark or "Windows" (follows the PC's setting), plus an accent color.
-
Each instance has a default in appsettings. A different accent per station makes them easy to tell apart:
"Theme": { "Mode": "Dark", "Accent": "#CA5010", "AllowUserChoice": true }Mode:Light(the default, the look from before themes),DarkorSystem.Accent:#rrggbb. Empty means Fluent's default blue.AllowUserChoice: when false, the button is hidden and everyone sees the instance's theme.- Changes apply without a restart.
-
Each user can override it with the header's theme button (sun / moon). The choice is saved in that browser (localStorage
phat.theme). "Station default" goes back to the instance's theme. -
Colors in components: use Fluent's tokens (
--neutral-layer-1,--neutral-foreground-rest,--accent-fill-rest,--foreground-on-accent-rest, ...), which switch on their own. For status colors use PhAT's tokens fromwwwroot/phat-theme.css, with the light value as fallback, e.g.color: var(--phat-error-fg, #a4262c):--phat-error-fg/-bg,--phat-success-fg/-bg,--phat-warning-fg/-bg,--phat-info-fg/-bg;--phat-canvas(behind images).
Image overlays and strong fills (STOP, pass / fail badges) keep their fixed colors.
-
Known limit: a saved dark choice applies once the page connects, so a dark user sees the light page for a moment on load.
Using it in an app
- Reference the library:
<ProjectReference Include="..\PhAT.Razor\PhAT.Razor.csproj" />. This brings in PhAT too. - Routes.razor:
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="[PhAT.Razor.PhATRazor.Assembly]" ...>. - Program.cs:
- Add
.AddAdditionalAssemblies(PhAT.Razor.PhATRazor.Assembly)afterMapRazorComponents<App>(). - Register the services the pages you link to need (
AddVisionJobs,AddStages,AddEngine, ...). A page whose services aren't registered fails when it's opened.
- Add
- _Imports.razor: add
@using PhAT.Razor.Components.Layoutfor<ReconnectModal />, plus whicheverPhAT.*namespaces the app uses. - Themes:
- in Program.cs:
builder.Services.AddThemes(builder.Configuration);; - in App.razor, before app.css:
<link rel="stylesheet" href="@Assets["_content/PhAT.Razor/phat-theme.css"]" />; - in MainLayout:
<PhatTheme />, plus<ThemePicker />in the header (@using PhAT.Razor.Components.Theme). - The layout also needs
<FluentMenuProvider />: the data grids' column-header menus use it.
- in Program.cs:
The apps keep their own Program.cs, App.razor, Routes.razor, MainLayout / NavPanel, home
page, appsettings.json / Instances/ and data (TestFrames, VisionJobs, VisionModels, databases).