@seedtactics/insight-client 17.0.1-beta.12 → 17.0.1-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/assets/ProgramHighlight-CnZe9PDO.js +3 -0
- package/dist/assets/index-DWZg8nkq.js +367 -0
- package/dist/cell-status/current-status.d.ts +1 -0
- package/dist/cell-status/current-status.js +2 -0
- package/dist/cell-status/loading.d.ts +13 -0
- package/dist/cell-status/loading.js +36 -3
- package/dist/cell-status/material-details.d.ts +2 -2
- package/dist/cell-status/material-details.js +26 -13
- package/dist/components/App.d.ts +0 -5
- package/dist/components/App.js +1 -1
- package/dist/components/analysis/BufferChart.js +3 -2
- package/dist/components/operations/AllMaterial.js +9 -12
- package/dist/components/operations/RecentCycleChart.js +16 -21
- package/dist/components/station-monitor/BasketLoadStationWork.d.ts +1 -7
- package/dist/components/station-monitor/BasketLoadStationWork.js +9 -6
- package/dist/components/station-monitor/CancelLoadButton.js +2 -0
- package/dist/components/station-monitor/InvalidateCycle.js +24 -19
- package/dist/components/station-monitor/LoadStation.d.ts +0 -5
- package/dist/components/station-monitor/LoadStation.js +33 -72
- package/dist/components/station-monitor/Material.js +4 -2
- package/dist/components/station-monitor/PrintedLabel.js +15 -19
- package/dist/components/station-monitor/QuarantineButton.js +17 -26
- package/dist/components/station-monitor/SystemOverview.js +3 -2
- package/dist/components/station-monitor/Whiteboard.js +14 -15
- package/dist/data/material-operation-policy.d.ts +6 -0
- package/dist/data/material-operation-policy.js +26 -2
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +1 -0
- package/dist/index.html +2 -39
- package/dist/index.js +1 -6
- package/dist/network/api.d.ts +14 -34
- package/dist/network/api.js +62 -64
- package/dist/network/backend-mock.js +3 -0
- package/dist/network/backend.d.ts +1 -0
- package/dist/network/websocket.d.ts +1 -0
- package/dist/network/websocket.js +122 -36
- package/docs/material-quarantine.md +5 -4
- package/docs/mazak.md +9 -12
- package/docs/operator-procedures.md +14 -0
- package/package.json +22 -17
- package/dist/assets/ProgramHighlight-C6zSEXCX.js +0 -3
- package/dist/assets/index-kk-GRa5i.js +0 -388
- package/dist/components/station-monitor/BasketMovementArrival.d.ts +0 -40
- package/dist/components/station-monitor/BasketMovementArrival.js +0 -165
package/dist/network/api.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export declare class JobsClient {
|
|
|
31
31
|
protected processRecent(response: Response): Promise<RecentHistoricData>;
|
|
32
32
|
latestSchedule(signal?: AbortSignal): Promise<MostRecentSchedule>;
|
|
33
33
|
protected processLatestSchedule(response: Response): Promise<MostRecentSchedule>;
|
|
34
|
+
completeBasketLoadStation(completion: BasketLoadStationCompletion, signal?: AbortSignal): Promise<void>;
|
|
35
|
+
protected processCompleteBasketLoadStation(response: Response): Promise<void>;
|
|
34
36
|
currentStatus(signal?: AbortSignal): Promise<CurrentStatus>;
|
|
35
37
|
protected processCurrentStatus(response: Response): Promise<CurrentStatus>;
|
|
36
38
|
unscheduledRebookings(signal?: AbortSignal): Promise<Rebooking[]>;
|
|
@@ -710,7 +712,6 @@ export declare class CurrentStatus implements ICurrentStatus {
|
|
|
710
712
|
baskets?: {
|
|
711
713
|
[key: string]: BasketStatus;
|
|
712
714
|
} | undefined;
|
|
713
|
-
basketMoveInstructions?: BasketMoveInstruction[] | undefined;
|
|
714
715
|
customState?: any | undefined;
|
|
715
716
|
constructor(data?: ICurrentStatus);
|
|
716
717
|
init(_data?: any): void;
|
|
@@ -735,7 +736,6 @@ export interface ICurrentStatus {
|
|
|
735
736
|
baskets?: {
|
|
736
737
|
[key: string]: BasketStatus;
|
|
737
738
|
} | undefined;
|
|
738
|
-
basketMoveInstructions?: BasketMoveInstruction[] | undefined;
|
|
739
739
|
customState?: any | undefined;
|
|
740
740
|
}
|
|
741
741
|
export declare class HistoricJob extends Job implements IHistoricJob {
|
|
@@ -893,6 +893,7 @@ export declare enum LocType {
|
|
|
893
893
|
}
|
|
894
894
|
export declare class InProcessMaterialAction implements IInProcessMaterialAction {
|
|
895
895
|
type: ActionType;
|
|
896
|
+
automatedTransfer?: boolean;
|
|
896
897
|
workId?: string | undefined;
|
|
897
898
|
loadCancellationId?: string | undefined;
|
|
898
899
|
loadOntoPalletNum?: number | undefined;
|
|
@@ -916,6 +917,7 @@ export declare class InProcessMaterialAction implements IInProcessMaterialAction
|
|
|
916
917
|
}
|
|
917
918
|
export interface IInProcessMaterialAction {
|
|
918
919
|
type: ActionType;
|
|
920
|
+
automatedTransfer?: boolean;
|
|
919
921
|
workId?: string | undefined;
|
|
920
922
|
loadCancellationId?: string | undefined;
|
|
921
923
|
loadOntoPalletNum?: number | undefined;
|
|
@@ -1116,38 +1118,6 @@ export declare enum BasketLocationEnum {
|
|
|
1116
1118
|
Storage = "Storage",
|
|
1117
1119
|
InTransit = "InTransit"
|
|
1118
1120
|
}
|
|
1119
|
-
export declare class BasketMoveInstruction implements IBasketMoveInstruction {
|
|
1120
|
-
instructionId: string;
|
|
1121
|
-
basketId: number;
|
|
1122
|
-
source?: BasketPosition | undefined;
|
|
1123
|
-
destination: BasketPosition;
|
|
1124
|
-
reason: BasketMoveReason;
|
|
1125
|
-
displayText: string;
|
|
1126
|
-
prerequisiteInstructionId?: string | undefined;
|
|
1127
|
-
constructor(data?: IBasketMoveInstruction);
|
|
1128
|
-
init(_data?: any): void;
|
|
1129
|
-
static fromJS(data: any): BasketMoveInstruction;
|
|
1130
|
-
toJSON(data?: any): any;
|
|
1131
|
-
}
|
|
1132
|
-
export interface IBasketMoveInstruction {
|
|
1133
|
-
instructionId: string;
|
|
1134
|
-
basketId: number;
|
|
1135
|
-
source?: BasketPosition | undefined;
|
|
1136
|
-
destination: BasketPosition;
|
|
1137
|
-
reason: BasketMoveReason;
|
|
1138
|
-
displayText: string;
|
|
1139
|
-
prerequisiteInstructionId?: string | undefined;
|
|
1140
|
-
}
|
|
1141
|
-
export declare enum BasketMoveReason {
|
|
1142
|
-
LoadMaterial = "LoadMaterial",
|
|
1143
|
-
UnloadMaterial = "UnloadMaterial",
|
|
1144
|
-
ProcessTransfer = "ProcessTransfer",
|
|
1145
|
-
SupplyMaterialToCell = "SupplyMaterialToCell",
|
|
1146
|
-
SupplyEmptyBasket = "SupplyEmptyBasket",
|
|
1147
|
-
ReturnToStorage = "ReturnToStorage",
|
|
1148
|
-
RemoveForCorrection = "RemoveForCorrection",
|
|
1149
|
-
Other = "Other"
|
|
1150
|
-
}
|
|
1151
1121
|
export declare class ProblemDetails implements IProblemDetails {
|
|
1152
1122
|
type?: string | undefined;
|
|
1153
1123
|
title?: string | undefined;
|
|
@@ -1330,6 +1300,16 @@ export interface IRebooking {
|
|
|
1330
1300
|
notes?: string | undefined;
|
|
1331
1301
|
workorder?: string | undefined;
|
|
1332
1302
|
}
|
|
1303
|
+
export declare class BasketLoadStationCompletion implements IBasketLoadStationCompletion {
|
|
1304
|
+
workId: string;
|
|
1305
|
+
constructor(data?: IBasketLoadStationCompletion);
|
|
1306
|
+
init(_data?: any): void;
|
|
1307
|
+
static fromJS(data: any): BasketLoadStationCompletion;
|
|
1308
|
+
toJSON(data?: any): any;
|
|
1309
|
+
}
|
|
1310
|
+
export interface IBasketLoadStationCompletion {
|
|
1311
|
+
workId: string;
|
|
1312
|
+
}
|
|
1333
1313
|
export declare class SimulationResults implements ISimulationResults {
|
|
1334
1314
|
scheduleId: string;
|
|
1335
1315
|
jobs: Job[];
|
package/dist/network/api.js
CHANGED
|
@@ -393,6 +393,40 @@ export class JobsClient {
|
|
|
393
393
|
}
|
|
394
394
|
return Promise.resolve(null);
|
|
395
395
|
}
|
|
396
|
+
completeBasketLoadStation(completion, signal) {
|
|
397
|
+
let url_ = this.baseUrl + "/api/v1/jobs/basket-load-station/complete";
|
|
398
|
+
url_ = url_.replace(/[?&]$/, "");
|
|
399
|
+
const content_ = JSON.stringify(completion);
|
|
400
|
+
let options_ = {
|
|
401
|
+
body: content_,
|
|
402
|
+
method: "POST",
|
|
403
|
+
signal,
|
|
404
|
+
headers: {
|
|
405
|
+
"Content-Type": "application/json",
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
return this.http.fetch(url_, options_).then((_response) => {
|
|
409
|
+
return this.processCompleteBasketLoadStation(_response);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
processCompleteBasketLoadStation(response) {
|
|
413
|
+
const status = response.status;
|
|
414
|
+
let _headers = {};
|
|
415
|
+
if (response.headers && response.headers.forEach) {
|
|
416
|
+
response.headers.forEach((v, k) => (_headers[k] = v));
|
|
417
|
+
}
|
|
418
|
+
if (status === 200) {
|
|
419
|
+
return response.text().then((_responseText) => {
|
|
420
|
+
return;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else if (status !== 200 && status !== 204) {
|
|
424
|
+
return response.text().then((_responseText) => {
|
|
425
|
+
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
return Promise.resolve(null);
|
|
429
|
+
}
|
|
396
430
|
currentStatus(signal) {
|
|
397
431
|
let url_ = this.baseUrl + "/api/v1/jobs/status";
|
|
398
432
|
url_ = url_.replace(/[?&]$/, "");
|
|
@@ -3625,11 +3659,6 @@ export class CurrentStatus {
|
|
|
3625
3659
|
: new BasketStatus();
|
|
3626
3660
|
}
|
|
3627
3661
|
}
|
|
3628
|
-
if (Array.isArray(_data["BasketMoveInstructions"])) {
|
|
3629
|
-
this.basketMoveInstructions = [];
|
|
3630
|
-
for (let item of _data["BasketMoveInstructions"])
|
|
3631
|
-
this.basketMoveInstructions.push(BasketMoveInstruction.fromJS(item));
|
|
3632
|
-
}
|
|
3633
3662
|
this.customState = _data["CustomState"];
|
|
3634
3663
|
}
|
|
3635
3664
|
}
|
|
@@ -3700,11 +3729,6 @@ export class CurrentStatus {
|
|
|
3700
3729
|
: undefined;
|
|
3701
3730
|
}
|
|
3702
3731
|
}
|
|
3703
|
-
if (Array.isArray(this.basketMoveInstructions)) {
|
|
3704
|
-
data["BasketMoveInstructions"] = [];
|
|
3705
|
-
for (let item of this.basketMoveInstructions)
|
|
3706
|
-
data["BasketMoveInstructions"].push(item ? item.toJSON() : undefined);
|
|
3707
|
-
}
|
|
3708
3732
|
data["CustomState"] = this.customState;
|
|
3709
3733
|
return data;
|
|
3710
3734
|
}
|
|
@@ -4050,6 +4074,7 @@ export class InProcessMaterialAction {
|
|
|
4050
4074
|
init(_data) {
|
|
4051
4075
|
if (_data) {
|
|
4052
4076
|
this.type = _data["Type"];
|
|
4077
|
+
this.automatedTransfer = _data["AutomatedTransfer"];
|
|
4053
4078
|
this.workId = _data["WorkId"];
|
|
4054
4079
|
this.loadCancellationId = _data["LoadCancellationId"];
|
|
4055
4080
|
this.loadOntoPalletNum = _data["LoadOntoPalletNum"];
|
|
@@ -4077,6 +4102,7 @@ export class InProcessMaterialAction {
|
|
|
4077
4102
|
toJSON(data) {
|
|
4078
4103
|
data = typeof data === "object" ? data : {};
|
|
4079
4104
|
data["Type"] = this.type;
|
|
4105
|
+
data["AutomatedTransfer"] = this.automatedTransfer;
|
|
4080
4106
|
data["WorkId"] = this.workId;
|
|
4081
4107
|
data["LoadCancellationId"] = this.loadCancellationId;
|
|
4082
4108
|
data["LoadOntoPalletNum"] = this.loadOntoPalletNum;
|
|
@@ -4497,60 +4523,6 @@ export var BasketLocationEnum;
|
|
|
4497
4523
|
BasketLocationEnum["Storage"] = "Storage";
|
|
4498
4524
|
BasketLocationEnum["InTransit"] = "InTransit";
|
|
4499
4525
|
})(BasketLocationEnum || (BasketLocationEnum = {}));
|
|
4500
|
-
export class BasketMoveInstruction {
|
|
4501
|
-
constructor(data) {
|
|
4502
|
-
if (data) {
|
|
4503
|
-
for (var property in data) {
|
|
4504
|
-
if (data.hasOwnProperty(property))
|
|
4505
|
-
this[property] = data[property];
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
|
-
if (!data) {
|
|
4509
|
-
this.destination = new BasketPosition();
|
|
4510
|
-
}
|
|
4511
|
-
}
|
|
4512
|
-
init(_data) {
|
|
4513
|
-
if (_data) {
|
|
4514
|
-
this.instructionId = _data["InstructionId"];
|
|
4515
|
-
this.basketId = _data["BasketId"];
|
|
4516
|
-
this.source = _data["Source"] ? BasketPosition.fromJS(_data["Source"]) : undefined;
|
|
4517
|
-
this.destination = _data["Destination"]
|
|
4518
|
-
? BasketPosition.fromJS(_data["Destination"])
|
|
4519
|
-
: new BasketPosition();
|
|
4520
|
-
this.reason = _data["Reason"];
|
|
4521
|
-
this.displayText = _data["DisplayText"];
|
|
4522
|
-
this.prerequisiteInstructionId = _data["PrerequisiteInstructionId"];
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
static fromJS(data) {
|
|
4526
|
-
data = typeof data === "object" ? data : {};
|
|
4527
|
-
let result = new BasketMoveInstruction();
|
|
4528
|
-
result.init(data);
|
|
4529
|
-
return result;
|
|
4530
|
-
}
|
|
4531
|
-
toJSON(data) {
|
|
4532
|
-
data = typeof data === "object" ? data : {};
|
|
4533
|
-
data["InstructionId"] = this.instructionId;
|
|
4534
|
-
data["BasketId"] = this.basketId;
|
|
4535
|
-
data["Source"] = this.source ? this.source.toJSON() : undefined;
|
|
4536
|
-
data["Destination"] = this.destination ? this.destination.toJSON() : undefined;
|
|
4537
|
-
data["Reason"] = this.reason;
|
|
4538
|
-
data["DisplayText"] = this.displayText;
|
|
4539
|
-
data["PrerequisiteInstructionId"] = this.prerequisiteInstructionId;
|
|
4540
|
-
return data;
|
|
4541
|
-
}
|
|
4542
|
-
}
|
|
4543
|
-
export var BasketMoveReason;
|
|
4544
|
-
(function (BasketMoveReason) {
|
|
4545
|
-
BasketMoveReason["LoadMaterial"] = "LoadMaterial";
|
|
4546
|
-
BasketMoveReason["UnloadMaterial"] = "UnloadMaterial";
|
|
4547
|
-
BasketMoveReason["ProcessTransfer"] = "ProcessTransfer";
|
|
4548
|
-
BasketMoveReason["SupplyMaterialToCell"] = "SupplyMaterialToCell";
|
|
4549
|
-
BasketMoveReason["SupplyEmptyBasket"] = "SupplyEmptyBasket";
|
|
4550
|
-
BasketMoveReason["ReturnToStorage"] = "ReturnToStorage";
|
|
4551
|
-
BasketMoveReason["RemoveForCorrection"] = "RemoveForCorrection";
|
|
4552
|
-
BasketMoveReason["Other"] = "Other";
|
|
4553
|
-
})(BasketMoveReason || (BasketMoveReason = {}));
|
|
4554
4526
|
export class ProblemDetails {
|
|
4555
4527
|
constructor(data) {
|
|
4556
4528
|
if (data) {
|
|
@@ -5005,6 +4977,32 @@ export class Rebooking {
|
|
|
5005
4977
|
return data;
|
|
5006
4978
|
}
|
|
5007
4979
|
}
|
|
4980
|
+
export class BasketLoadStationCompletion {
|
|
4981
|
+
constructor(data) {
|
|
4982
|
+
if (data) {
|
|
4983
|
+
for (var property in data) {
|
|
4984
|
+
if (data.hasOwnProperty(property))
|
|
4985
|
+
this[property] = data[property];
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
init(_data) {
|
|
4990
|
+
if (_data) {
|
|
4991
|
+
this.workId = _data["WorkId"];
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
static fromJS(data) {
|
|
4995
|
+
data = typeof data === "object" ? data : {};
|
|
4996
|
+
let result = new BasketLoadStationCompletion();
|
|
4997
|
+
result.init(data);
|
|
4998
|
+
return result;
|
|
4999
|
+
}
|
|
5000
|
+
toJSON(data) {
|
|
5001
|
+
data = typeof data === "object" ? data : {};
|
|
5002
|
+
data["WorkId"] = this.workId;
|
|
5003
|
+
return data;
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5008
5006
|
export class SimulationResults {
|
|
5009
5007
|
constructor(data) {
|
|
5010
5008
|
if (data) {
|
|
@@ -229,6 +229,9 @@ export function registerMockBackend(offsetSeconds, mockD, mockEvts) {
|
|
|
229
229
|
quarantineQueuedMaterial() {
|
|
230
230
|
return Promise.resolve();
|
|
231
231
|
},
|
|
232
|
+
completeBasketLoadStation() {
|
|
233
|
+
return Promise.resolve();
|
|
234
|
+
},
|
|
232
235
|
cancelLoad() {
|
|
233
236
|
return Promise.resolve();
|
|
234
237
|
},
|
|
@@ -12,6 +12,7 @@ export interface JobAPI {
|
|
|
12
12
|
addUnallocatedCastingToQueue(castingName: string, queue: string, qty: number, operatrorName: string | null, workorder: string | null, serials: string[]): Promise<ReadonlyArray<Readonly<api.IInProcessMaterial>>>;
|
|
13
13
|
signalMaterialForQuarantine(materialId: number, operName: string | null, reason: string | undefined): Promise<void>;
|
|
14
14
|
quarantineQueuedMaterial(materialId: number, operName: string | null, reason: string | undefined): Promise<void>;
|
|
15
|
+
completeBasketLoadStation(request: Readonly<api.IBasketLoadStationCompletion>): Promise<void>;
|
|
15
16
|
cancelLoad(materialId: number, operName: string | null, request: Readonly<api.ICancelLoadRequest>): Promise<void>;
|
|
16
17
|
invalidatePalletCycle(materialId: number, operName: string | null | undefined, changeCastingTo: string | null | undefined, changeJobUniqueTo: string | null | undefined, process: number): Promise<Readonly<api.IMaterialDetails> | null | undefined>;
|
|
17
18
|
unscheduledRebookings(signal?: AbortSignal): Promise<ReadonlyArray<Readonly<api.IRebooking>> | null>;
|
|
@@ -33,36 +33,90 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
33
33
|
import { ServerEvent } from "./api.js";
|
|
34
34
|
import { JobsBackend, LogBackend } from "./backend.js";
|
|
35
35
|
import { fmsInformation } from "./server-settings.js";
|
|
36
|
-
import {
|
|
37
|
-
import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log,
|
|
36
|
+
import { useEffect, useRef } from "react";
|
|
37
|
+
import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log, onLiveServerEvent, onHistoricalServerEvent, } from "../cell-status/loading.js";
|
|
38
38
|
import { addDays } from "date-fns";
|
|
39
39
|
import { last30SchIds } from "../cell-status/scheduled-jobs.js";
|
|
40
|
+
import { HashSet, OrderedMap } from "@seedtactics/immutable-collections";
|
|
40
41
|
import { atom, useAtomValue, useSetAtom } from "jotai";
|
|
41
42
|
const websocketReconnectingAtom = atom(false);
|
|
42
43
|
export const websocketReconnecting = websocketReconnectingAtom;
|
|
44
|
+
// True while the websocket is open and this connection has loaded or received a current status, so
|
|
45
|
+
// the retained current status reflects the server. Loading the historical jobs and log does not
|
|
46
|
+
// affect it.
|
|
47
|
+
const currentStatusIsLiveRW = atom(false);
|
|
48
|
+
export const currentStatusIsLive = currentStatusIsLiveRW;
|
|
49
|
+
const websocketSessionAtom = atom({ id: 0, statusFromSocket: false });
|
|
50
|
+
// Only historical effects wait for HTTP catch-up. A fresh session discards its predecessor's
|
|
51
|
+
// unapplied buffer and reloads from lastEventCounter, which has not advanced past that history.
|
|
52
|
+
const bufferedLogAtom = atom(null);
|
|
53
|
+
// HTTP can include entries whose socket messages arrive after the response. Suppress those exact
|
|
54
|
+
// identities, never all counters below a maximum: live publication is not guaranteed to be ordered.
|
|
55
|
+
const loadedLogCountersAtom = atom(HashSet.empty());
|
|
43
56
|
const errorLoadingLast30RW = atom(null);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.
|
|
53
|
-
|
|
57
|
+
const errorLoadingLogRW = atom(null);
|
|
58
|
+
export const errorLoadingLast30 = atom((get) => get(errorLoadingLast30RW) ?? get(errorLoadingLogRW));
|
|
59
|
+
function isCurrentSession(get, session) {
|
|
60
|
+
return get(websocketSessionAtom).id === session;
|
|
61
|
+
}
|
|
62
|
+
function loadCurrentStatus(get, set, session) {
|
|
63
|
+
return JobsBackend.currentStatus().then((st) => {
|
|
64
|
+
const current = get(websocketSessionAtom);
|
|
65
|
+
if (current.id !== session || current.statusFromSocket)
|
|
66
|
+
return;
|
|
67
|
+
set(onLoadCurrentSt, st);
|
|
68
|
+
set(currentStatusIsLiveRW, true);
|
|
69
|
+
});
|
|
54
70
|
}
|
|
55
|
-
function
|
|
71
|
+
function loadHistory(get, set, session, from, retryDelay = 1000) {
|
|
56
72
|
const now = new Date();
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
73
|
+
const request = from === null ? LogBackend.get(addDays(now, -30), now) : LogBackend.recent(from, undefined);
|
|
74
|
+
return request.then((log) => {
|
|
75
|
+
if (!isCurrentSession(get, session))
|
|
76
|
+
return;
|
|
77
|
+
const buffered = get(bufferedLogAtom) ?? OrderedMap.empty();
|
|
78
|
+
const merged = log.reduce((entries, entry) => entries.set(entry.counter, entry), buffered);
|
|
79
|
+
set(onLoadLast30Log, merged.valuesToAscLazySeq().toRArray());
|
|
80
|
+
set(loadedLogCountersAtom, HashSet.from(merged.keys()));
|
|
81
|
+
set(bufferedLogAtom, null);
|
|
82
|
+
set(errorLoadingLogRW, null);
|
|
83
|
+
}, (e) => {
|
|
84
|
+
if (!isCurrentSession(get, session))
|
|
85
|
+
return;
|
|
86
|
+
set(errorLoadingLogRW, e.message ?? "Error loading log history");
|
|
87
|
+
// Keep buffering and retry from the same applied position without interrupting live status.
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
if (isCurrentSession(get, session)) {
|
|
90
|
+
void loadHistory(get, set, session, from, Math.min(retryDelay * 2, 30000));
|
|
91
|
+
}
|
|
92
|
+
}, retryDelay);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function loadConnection(get, set, session) {
|
|
96
|
+
const from = get(lastEventCounter);
|
|
97
|
+
const schIds = get(last30SchIds);
|
|
98
|
+
const curStProm = loadCurrentStatus(get, set, session);
|
|
99
|
+
const jobsProm = JobsBackend.recent(addDays(new Date(), -30), from !== null && schIds ? Array.from(schIds) : []).then((j) => {
|
|
100
|
+
if (isCurrentSession(get, session))
|
|
101
|
+
set(onLoadLast30Jobs, j);
|
|
102
|
+
});
|
|
103
|
+
const logProm = loadHistory(get, set, session, from);
|
|
104
|
+
finishLoading([curStProm, jobsProm, logProm], get, set, session);
|
|
105
|
+
}
|
|
106
|
+
function finishLoading(loads, get, set, session) {
|
|
107
|
+
Promise.all(loads)
|
|
108
|
+
.catch((e) => {
|
|
109
|
+
if (isCurrentSession(get, session))
|
|
110
|
+
set(errorLoadingLast30RW, e.message ?? "Error");
|
|
111
|
+
})
|
|
112
|
+
.finally(() => {
|
|
113
|
+
if (isCurrentSession(get, session))
|
|
114
|
+
set(websocketReconnectingAtom, false);
|
|
115
|
+
});
|
|
63
116
|
}
|
|
64
117
|
class ReconnectingWebsocket {
|
|
65
118
|
constructor(url) {
|
|
119
|
+
this.session = null;
|
|
66
120
|
this.userCalledClose = false;
|
|
67
121
|
this.reconnectAttempts = 0;
|
|
68
122
|
this.url = url;
|
|
@@ -71,6 +125,12 @@ class ReconnectingWebsocket {
|
|
|
71
125
|
close() {
|
|
72
126
|
this.userCalledClose = true;
|
|
73
127
|
this.ws?.close();
|
|
128
|
+
this.endSession(false);
|
|
129
|
+
}
|
|
130
|
+
endSession(reconnecting) {
|
|
131
|
+
const session = this.session;
|
|
132
|
+
this.session = null;
|
|
133
|
+
this.handleClose?.(session, reconnecting);
|
|
74
134
|
}
|
|
75
135
|
connect() {
|
|
76
136
|
if (this.userCalledClose)
|
|
@@ -83,7 +143,7 @@ class ReconnectingWebsocket {
|
|
|
83
143
|
localWs.addEventListener("open", () => {
|
|
84
144
|
clearTimeout(connectTimeout);
|
|
85
145
|
this.reconnectAttempts = 0;
|
|
86
|
-
this.handleOpen?.();
|
|
146
|
+
this.session = this.handleOpen?.() ?? null;
|
|
87
147
|
});
|
|
88
148
|
localWs.addEventListener("close", () => {
|
|
89
149
|
clearTimeout(connectTimeout);
|
|
@@ -91,7 +151,7 @@ class ReconnectingWebsocket {
|
|
|
91
151
|
if (this.userCalledClose) {
|
|
92
152
|
return;
|
|
93
153
|
}
|
|
94
|
-
this.
|
|
154
|
+
this.endSession(true);
|
|
95
155
|
const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 30000);
|
|
96
156
|
setTimeout(() => {
|
|
97
157
|
this.reconnectAttempts++;
|
|
@@ -99,7 +159,7 @@ class ReconnectingWebsocket {
|
|
|
99
159
|
}, delay);
|
|
100
160
|
});
|
|
101
161
|
localWs.addEventListener("message", (evt) => {
|
|
102
|
-
this.handleMessage?.(evt);
|
|
162
|
+
this.handleMessage?.(evt, this.session);
|
|
103
163
|
});
|
|
104
164
|
// Browsers expose websocket failures as opaque events with no useful detail.
|
|
105
165
|
// Reconnect handling is driven by close/open, so avoid polluting stderr by
|
|
@@ -107,25 +167,51 @@ class ReconnectingWebsocket {
|
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
169
|
const onOpenAtom = atom(null, (get, set) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
170
|
+
const session = get(websocketSessionAtom).id + 1;
|
|
171
|
+
set(websocketSessionAtom, { id: session, statusFromSocket: false });
|
|
112
172
|
set(websocketReconnectingAtom, true);
|
|
173
|
+
set(currentStatusIsLiveRW, false);
|
|
113
174
|
set(errorLoadingLast30RW, null);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
loadInitial(set);
|
|
119
|
-
}
|
|
175
|
+
set(errorLoadingLogRW, null);
|
|
176
|
+
set(bufferedLogAtom, OrderedMap.empty());
|
|
177
|
+
loadConnection(get, set, session);
|
|
178
|
+
return session;
|
|
120
179
|
});
|
|
121
|
-
|
|
180
|
+
// Every close, including disposal, ends the socket's own session; only an unexpected close
|
|
181
|
+
// reconnects. A connection cannot end a newer connection's session.
|
|
182
|
+
const onCloseAtom = atom(null, (get, set, session, reconnecting) => {
|
|
183
|
+
if (reconnecting)
|
|
184
|
+
set(websocketReconnectingAtom, true);
|
|
185
|
+
if (session === null || !isCurrentSession(get, session))
|
|
186
|
+
return;
|
|
187
|
+
set(websocketSessionAtom, { id: session + 1, statusFromSocket: false });
|
|
188
|
+
set(currentStatusIsLiveRW, false);
|
|
189
|
+
set(bufferedLogAtom, null);
|
|
190
|
+
});
|
|
191
|
+
const onMessageAtom = atom(null, (get, set, evt, session) => {
|
|
192
|
+
if (session === null || !isCurrentSession(get, session))
|
|
193
|
+
return;
|
|
122
194
|
const serverEvt = ServerEvent.fromJS(JSON.parse(evt.data));
|
|
123
|
-
|
|
195
|
+
const eventAndTime = { evt: serverEvt, now: new Date(), expire: true };
|
|
196
|
+
set(onLiveServerEvent, eventAndTime);
|
|
197
|
+
const log = serverEvt.logEntry;
|
|
198
|
+
const buffered = get(bufferedLogAtom);
|
|
199
|
+
if (!log || !get(loadedLogCountersAtom).has(log.counter)) {
|
|
200
|
+
if (log && buffered !== null) {
|
|
201
|
+
set(bufferedLogAtom, buffered.set(log.counter, log));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
set(onHistoricalServerEvent, eventAndTime);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (serverEvt.newCurrentStatus) {
|
|
208
|
+
set(websocketSessionAtom, { id: session, statusFromSocket: true });
|
|
209
|
+
set(currentStatusIsLiveRW, true);
|
|
210
|
+
}
|
|
124
211
|
});
|
|
125
212
|
export function WebsocketConnection() {
|
|
126
213
|
const onOpen = useSetAtom(onOpenAtom);
|
|
127
|
-
const
|
|
128
|
-
const onReconnecting = useCallback(() => setReconnecting(true), [setReconnecting]);
|
|
214
|
+
const onClose = useSetAtom(onCloseAtom);
|
|
129
215
|
const onMessage = useSetAtom(onMessageAtom);
|
|
130
216
|
const fmsInfoLoadable = useAtomValue(fmsInformation);
|
|
131
217
|
const websocketRef = useRef(null);
|
|
@@ -151,7 +237,7 @@ export function WebsocketConnection() {
|
|
|
151
237
|
}
|
|
152
238
|
const websocket = new ReconnectingWebsocket(uri);
|
|
153
239
|
websocket.handleOpen = onOpen;
|
|
154
|
-
websocket.
|
|
240
|
+
websocket.handleClose = onClose;
|
|
155
241
|
websocket.handleMessage = onMessage;
|
|
156
242
|
websocketRef.current = websocket;
|
|
157
243
|
return () => {
|
|
@@ -160,6 +246,6 @@ export function WebsocketConnection() {
|
|
|
160
246
|
websocketRef.current = null;
|
|
161
247
|
}
|
|
162
248
|
};
|
|
163
|
-
}, [fmsInfoLoadable, onMessage, onOpen,
|
|
249
|
+
}, [fmsInfoLoadable, onMessage, onOpen, onClose]);
|
|
164
250
|
return null;
|
|
165
251
|
}
|
|
@@ -36,10 +36,11 @@ The [operations](client-operations#material) and [quality](client-quality#quaran
|
|
|
36
36
|
webpages allow supervisors, quality engineers and others to manually move material between the
|
|
37
37
|
various quarantined queues, view all the material, add notes, re-introduce material to "active"
|
|
38
38
|
queues, or scrap material. In addition, the [server configuration](server-config) contains a setting
|
|
39
|
-
_QuarantineQueue_ for the initial quarantine queue.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
_QuarantineQueue_ for the initial quarantine queue. Direct quarantine moves material that is waiting
|
|
40
|
+
in a queue into this configured queue. A quarantine signal instead records a request for the backend
|
|
41
|
+
to handle material when it leaves automation control; it does not immediately move material or
|
|
42
|
+
cancel a load. The [operator procedures](operator-procedures#quarantine-material) explain this
|
|
43
|
+
distinction. The available actions depend on the installation and the material's current state.
|
|
43
44
|
|
|
44
45
|
FMS Insight allows arbitrary quarantined material queues, but we suggest you follow a scheme similar
|
|
45
46
|
to the "TODO, In-Progress, Done" Kanban task management/project management technique.
|
package/docs/mazak.md
CHANGED
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
title: Mazak Palletech Integration
|
|
3
3
|
nav: FMS Insight Server > Mazak
|
|
4
4
|
description: >-
|
|
5
|
-
FMS Insight
|
|
6
|
-
|
|
7
|
-
all the data in the cell controller.
|
|
5
|
+
FMS Insight supports Mazak Palletech Web Version and Smooth/Neo PMC. FMS Insight can read all
|
|
6
|
+
events from the cell controller and can also edit almost all the data in the cell controller.
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
# FMS Insight Mazak Palletech Integration
|
|
11
10
|
|
|
12
|
-
FMS Insight
|
|
13
|
-
|
|
14
|
-
all the data in the cell controller.
|
|
11
|
+
FMS Insight supports Mazak Palletech Web Version and Smooth/Neo PMC. FMS Insight can read all events
|
|
12
|
+
from the cell controller and can also edit almost all the data in the cell controller.
|
|
15
13
|
|
|
16
14
|
## Load station numbering
|
|
17
15
|
|
|
@@ -31,8 +29,7 @@ updating configuration.
|
|
|
31
29
|
To facilitate the communication between FMS Insight and the Mazak cell controller, you must acquire
|
|
32
30
|
a program called "Mazak Open Database Kit". This is a software program developed by Mazak which
|
|
33
31
|
allows safe access to the data inside the cell controller. Please contact your Mazak representative
|
|
34
|
-
and ask to obtain "Open Database Kit" that matches the specific cell controller (
|
|
35
|
-
Smooth PMC).
|
|
32
|
+
and ask to obtain "Open Database Kit" that matches the specific cell controller (Web or Smooth PMC).
|
|
36
33
|
|
|
37
34
|
## Enable Log CSV
|
|
38
35
|
|
|
@@ -49,10 +46,10 @@ specify the same folder in the FMS Insight server configuration file.
|
|
|
49
46
|
|
|
50
47
|
## Load Instructions
|
|
51
48
|
|
|
52
|
-
On
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
On MazakWeb, one parameter must be changed. If you are using Mazak Smooth PMC, this step can be
|
|
50
|
+
skipped! Open the Mazak Palletech software, go to the parameter edit screen, select `X`, and scroll
|
|
51
|
+
to the setting `X-31`. Set the `X-31` setting from 0 to 1. This setting will cause CSV files
|
|
52
|
+
describing the current load and unload operation at the load station to be output to the
|
|
56
53
|
`c:\Mazak\FMS\LDS` directory. FMS Insight monitors this directory and uses the CSV files to display
|
|
57
54
|
the parts being loaded and unloaded from each pallet at the load station.
|
|
58
55
|
|
|
@@ -69,6 +69,12 @@ detailed inspection, or some other rework. Material can be quarantined on the qu
|
|
|
69
69
|
the material dialog and clicking a button. This moves the material to the quarantine queue where it
|
|
70
70
|
is visible on the supervisor's material page.
|
|
71
71
|
|
|
72
|
+
A quarantine signal records a request for the backend to handle material when it leaves automation
|
|
73
|
+
control. The signal does not immediately move material or cancel a load. Direct quarantine moves
|
|
74
|
+
material that is waiting in a queue into the configured quarantine queue. The available actions
|
|
75
|
+
depend on the installation and the material's current state. Follow the displayed instructions and
|
|
76
|
+
the installation's procedure before moving physical material.
|
|
77
|
+
|
|
72
78
|
Once the material is ready to be re-introduced into the cell, the operator should bring it back to
|
|
73
79
|
the shop floor. Once the material has arrived, the operator can click a button on the queues webpage
|
|
74
80
|
(or scan the barcode) to add the material into the queue. Once it is back in the queue, the cell
|
|
@@ -94,6 +100,14 @@ quarantine queue. That material can then be re-introduced back into the queue so
|
|
|
94
100
|
FMS Insight matches the material on the shop floor. In addition, FMS Insight allows the ability to
|
|
95
101
|
invalidate the inadvertent load events that were created.
|
|
96
102
|
|
|
103
|
+
Only the latest still-valid process can be invalidated. For material with recorded processes 1, 2,
|
|
104
|
+
and 3, first invalidate process 3, then review the refreshed history before invalidating process 2
|
|
105
|
+
in a separate action. Continue backwards one process at a time as needed. To change a casting or job
|
|
106
|
+
assignment, first invalidate any later processes in separate actions.
|
|
107
|
+
|
|
108
|
+
Invalidation affects whole recorded cycle events and all material listed in the confirmation. The
|
|
109
|
+
original events remain in the event log but no longer contribute to active material reconstruction.
|
|
110
|
+
|
|
97
111
|
Insight does not reassign recorded pallet manufacturing history from one material identity to
|
|
98
112
|
another. If machining was attributed to the wrong identity, record an explanatory operator note and
|
|
99
113
|
use the installation's traceability exception procedure. Quarantine, invalidation, and readmission
|