工作流程:透過 HTTP API 進行重播驗收¶
專案建構以一次乾淨的重播收尾:整個任務播放完畢、每則訊息都依 id 讀過,數值上的交叉核對也都做完。本頁就是透過 HTTP API 驅動的那一次重播,給沒有畫布可看的腳本或代理程式 (agent) 使用:如何開始一次真正全新的執行、如何在它播放時盯著它並得知它已經結束,以及證明它切了該切之處的證據。
登入、回應封套,以及這裡每一條路由都遵循的其他約定,見透過 HTTP 驅動網頁服務(英文,未鏡像)。
flowchart LR
Free["執行個體空閒?"]
Reset["重置"]
Start["開始<br>保留 runId"]
Poll["輪詢狀態、觀察執行<br>同一個 runId"]
Evidence["讀取證據"]
Msgs["盤點訊息"]
Free --> Reset --> Start --> Poll --> Evidence --> Msgs
Msgs -->|修正後再重播| Free
1. 從空閒的執行個體與已重置的工作階段開始¶
服務替連上它的所有用戶端只持有一個專案與一次執行,所以重播一開始要先確認執行個體是空閒的,而且絕不以停止一次無法證明屬於自己的執行作結。
GET /api/Execution/status。如果executionStatus是Running或Paused,就表示有一次執行正在進行——不要開始、重置或停止它。重置也會停止正在進行的執行。POST /api/Execution/reset,每次開始之前都要做。開始並不會重置。一次執行完成之後,POST /api/Execution/start會在那次執行留下的工作階段之上再播放一次任務——已加工的工件、它的步與它的訊息都還在。於是每一筆「首次讀取,否則寫入」記錄都看到已經產生的步,改為寫入而不是讀取,所以第 0 階段的記錄會以加工後的零件覆寫快取的毛胚;之後的每一次執行,不論有沒有重置,都從已經切過的毛胚開始,直到那個檔案被刪除為止。先前那次執行的步也會讓未接觸警告無從觸發,所以第二次執行裡沒有任何東西回報這項損害。
重置會清空步序列與工作階段的各份訊息清單,所以要先讀完前一次執行的證據(§4、§5),再為下一次執行重置。
2. 開始,並依執行 id 輪詢¶
POST /api/Execution/start,接著檢查回應本體,而不只看狀態碼。一個已啟用、卻編譯不過的腳本命令,會以 HTTP 200、success: false與code: "script_compile_error"拒絕開始;在有執行正在進行時開始,會回應alreadyRunning: true,而且什麼也不會開始。保留真正開始時所傳回的runId。- 輪詢
GET /api/Execution/status,直到它回報那個runId,且executionStatus為Finished或Ready。重置之後,被停止、或以例外結束的執行,就是以Ready回報自己:只等Finished的輪詢程式,遇到失敗的執行永遠不會返回。少了 §1 的重置,這樣的執行會承接上一次執行的Finished。Ready背後的例外寫在服務日誌裡,GET /api/Project/logs會傳回當天的日誌。runId變大,表示在那之後另有呼叫端開始了一次執行。 - 在任何東西重置工作階段之前,先讀取下面的證據。
邊播放邊觀察¶
一趟長時間的播放,值得在它執行時就看著,而不只是在結束時評判。程式原點錯了、刀長或補正設錯了,或毛胚擺錯了位置,都不會讓執行停下:它會一連幾個小時不斷碰撞、空切或切得太深,而之後在那塊毛胚上播放的每一支程式都白費了。下面這些請求讀取進行中的執行、不改變任何東西,而且夠輕量,可以在輪詢程式等待時每隔幾分鐘重複一次:
| 看什麼 | 請求 | 正常時 |
|---|---|---|
| 仍在執行、仍是你的 | GET /api/Execution/status |
executionStatus 為 Running,且帶著你開始時傳回的那個 runId |
| 進度 | GET /api/execution/cl-strip/range |
兩次查看之間 count 有增加 |
| 警報 | GET /api/Execution/messages?minSeverity=Message&tail=0&idPrefix=<prefix>,對 Collision-- 與 Play-RapidCut 各查一次,Stroke 則改用 minSeverity=Error(排除開場一次性的設置警告 StrokeLimit--Unconfigured) |
每份清單的 matched 都維持 0 |
| 新的警告 | GET /api/Execution/messages?minSeverity=Warning&tail=1000 |
沒有你還沒交代過的 id(§5) |
| 播放到哪裡 | §4 的時序圖,帶 widthHint=0、比 count 早一段的 dispBegin、dispEnd=<count>,inspectingKey 為 FileNo、LineNo、ToolId、Cl.X、Cl.Y、Cl.Z,取其中最後一個不是 NaN 的值——count 也算進了還在計算中的步,它們回答 NaN |
檔案與行號持續往前走;刀具是程式所呼叫的那一把,位在程式切削的地方 |
| 有沒有在切、切得多重 | 同一張時序圖,涵蓋最後幾千步,用 IsTouched、CuttingDepth_mm、MaxAbsForce_N、YieldingStressRatio 與 MaxSpindlePowerRatio——後三者是物理量:專案沒開物理、沒有物理授權,或顯示物理選項關著時,它們回答 NaN 或空的 items |
在程式切削之處有接觸;切深不超過刀具的刃長——只有沒吃到壁面時才接近每層下刀量,壁面、轉角或側刃加工可能以整段刃長吃刀(§6);應力比與主軸功率比都低於 1 |
帶 tail=0 時,每份清單只回答它的 total 與符合篩選條件的 matched 計數,不傳回訊息本身,所以一次警報檢查,每個前綴只花一個小小的回應。播放期間不選取任何步時,應用程式的畫布會顯示刀具,以及到目前為止切出來的毛胚;幾何差異則要到播放結束才會建立。
只有在設置錯誤會毀掉之後的加工時,才停止播放——警報接連不斷、程式該切的地方沒有接觸、切深或負載遠遠超出該趟加工、刀具出現在程式從不去的地方。這時才停止它(POST /api/Execution/stop 會停止當下的任何一次執行,所以只停你確知是自己的那一次),修正設置,再從 §1 重播。不會毀掉之後加工的缺陷——一則已經弄清楚的警告、退刀時的一次碰觸——就記下來,讓播放跑完;它和其餘的證據一起在 §4 與 §5 評判。
輪詢程式放棄時,就讓它放棄:逾時不是呼叫 POST /api/Execution/stop 的理由。它不帶執行 id,會停止當下的任何一次執行——在共用的執行個體上,那未必是你的——而被停止的執行,不會跑到任務中它停下之處以下的各列:既沒有執行後作業的輸出,也沒有階段末記錄。
3. 先粗後細——並在兩者之間清掉毛胚快取¶
新專案最初的幾次播放檢查的是基準,而不是表面,所以它們以粗的初始解析度(PUT /api/Workpiece/init-resolution)與粗的加工解析度執行。為了驗收執行而改用精細解析度時,有一個陷阱。第一支程式上方的第 0 階段記錄,快取的是依當時的初始解析度網格化的毛胚,而之後的每一次執行都會把那個檔案讀回來——所以在變更初始解析度、毛胚幾何或它的預留量之後,下一次執行仍從舊的毛胚開始,而且沒有任何東西會說明這一點。
下一次開始之前,先刪除第 0 階段的檔案:DELETE /api/Mission/commands/{path}/recordmeshedgeom/file,其中 {path} 是該記錄項目的路徑(GET …/recordmeshedgeom/file-status 會回報檔案是否存在)。你打算從它接續的階段末檔案,同樣是以舊的寬度寫下的;刪掉它,或把它的階段再播放一次。變更加工解析度命令則不需要刪除任何東西——它管的是它下方的切削,而不是快取的毛胚。這些記錄所屬的配置見能接續的任務;在應用程式中,記錄自己的重設會刪除它的檔案。
4. 完成的執行不等於通過的執行¶
Finished 說的是任務走到了終點,並沒有說切到了任何東西。一支從未裝上刀具的程式——執行器解析不了的刀具字、空的刀具名稱表——會播放每一行,完全不產生任何加工步,卻仍以 Finished 結束。播放結束時的警告 Play-Touch--None 只在有步、而其中沒有一步碰到毛胚時才會提出,所以一趟零步的播放永遠走不到它;NC 訊息也往往完全看不出沒裝上刀具這件事——不論主軸上有沒有刀具,G43 H1 都照表補正,而不帶 H 的 G43 則沿用模態的 H(程式寫出任何 H 之前為 H0),一聲不吭,一如控制器的做法。
零步的執行也會躲在接續配置的背後。沒有產生任何步時,程式下方的每一筆「首次讀取,否則寫入」記錄仍然會讀取,所以先前一次成功的執行所留下的階段末檔案會被載入回來——而畫布與幾何差異會把那個先前的結果,顯示得像是這次執行做出來的一樣。
所以要憑證據驗收一次重播:
| 檢查 | 請求 | 通過條件 |
|---|---|---|
| 有步 | GET /api/execution/cl-strip/range |
count > 0 |
| 有東西被切到 | GET /api/execution/strip-chart?aspect=Individual&inspectingKey=IsTouched&xValueCategory=IndexByStep&widthHint=1&dispBegin=0&dispEnd=<count> |
items[0].max 中的最大值為 1 |
| 每支程式都執行過 | GET /api/NcProgram/files |
每個檔案——以及列在其呼叫者的 children 之下的每支副程式——都至少有一趟 (invocation) 的 executedLineCount 不為零 |
| 差異已經建立 | GET /api/Workpiece/diff-settings |
hasDiffOverlay 為 true(而且 detectionRadius 不再是 null) |
| 訊息 | GET /api/Execution/messages |
每個 id 都有交代(§5) |
GET /api/Workpiece/diff-settings 回應 { hasWorkpiece, diffVisualRadius, detectionRadius, hasDiffOverlay };沒有載入專案時則回應 { hasWorkpiece: false }。hasDiffOverlay 說的是此刻有一份覆蓋層掛在即時的網格樹上——既不代表找到了偏差,也不代表有要求過比對——而 detectionRadius 在覆蓋層存在之前為 null,所以 null 的半徑與 false 的旗標是同一個答案。重置會把兩者都清掉。
要明確傳入 dispBegin 與 dispEnd:少了它們,時序圖只涵蓋目前的顯示範圍,而不是整次執行。widthHint=1 最多傳回兩個點:第一個涵蓋去掉最後一步的範圍,第二個只涵蓋最後一步。範圍的峰值取 items[0].max 中的最大值(谷值則取 items[0].min 中的最小值);widthHint=0 則改為每一步傳回一個點。
對某一行的效果有疑問時,GET /api/NcProgram/syntax-piece?fileIndex=<i>&lineIndex=<n> 會傳回執行器剖析出來的那個語句,連同它產生的步——要看出某個位址字究竟附在哪個命令上,這是最直接的方法。
5. 盤點訊息¶
GET /api/Execution/messages 每份清單傳回一個區段——shell、nc、step 與 ncManip——並以同樣的方式篩選它們全部:
minSeverity=Warning&head=50傳回最前面的符合項,一連串連鎖訊息的起因就在那裡;預設則是從尾端取。tail的上限是 1000,而tail=0只傳回計數——這是估量每份清單有多大的快速方法。idPrefix把範圍縮小到同一族的 id;kinds指名要讀哪幾份清單。sinceIndex一次只替一份清單分頁,所以這時kinds必須恰好指名一份。
什麼時候可以停——只剩下已宣告的資訊性訊息,再加上維持交付原樣之寫法的預期訊息——見專案建構 §5。
6. 交叉核對用的數值¶
§4 的時序圖請求,可以傳回任何步屬性在任何步範圍內的峰值——也就是它傳回的各點中,items[0].max 的最大值。鍵從 GET /api/execution/step-properties 取得——切深檢查用 CuttingDepth_mm,力的峰值用 MaxAbsForce_N。一組粗加工的切深峰值應等於它的每層下刀量;精加工程式的切深峰值卻不是它的預留量,因為壁面或轉角可能以整段刃長吃刀——精加工工作的 Z 基準,改用由參照網格建立工件 §1 的距離測試來檢查。以比預留量還粗的加工解析度執行,能告訴你程式有沒有碰到毛胚、碰在哪裡;力與切深則只能取自解析度比預留量更細的執行。要把範圍縮小到一個檔案,就從 GET /api/NcProgram/files 取得該趟的 firstSentenceIndex 與 lastSentenceIndex,再以 GET /api/NcProgram/steps-of-sentence?sentenceIndex=<n> 把兩者各自換成步。
第 i 步結束時的仿真時間,就是它的結束時間碼:xValueCategory=IndexByTime&widthHint=0&dispBegin=<i>&dispEnd=<i+1> 會在 xs 中以秒為單位傳回它。一個檔案的步範圍兩端之差,就是要拿來與後處理器估計值比較的時間,而這項比較的限度見專案建構 §6。
延伸閱讀¶
- 專案建構 — 這次重播所驗收的建構
- 透過 HTTP 驅動網頁服務(英文,未鏡像) — 登入、回應封套,以及一個服務只有一個專案
- 能接續的任務 — 記錄的配置,以及它的快取何時會過時
- 出了問題時 — 應用程式中所顯示的那幾份訊息清單
- 幾何驗證 —
hasDiffOverlay背後的幾何差異