vda-5050-lib
Version:
Universal VDA 5050 library for Node.js and browsers
1,091 lines • 59 kB
JavaScript
"use strict";
/*! Copyright (c) 2021 Siemens AG. Licensed under the MIT License. */
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgvController = void 0;
const __1 = require("..");
class AgvController extends __1.AgvClient {
constructor(agvId, clientOptions, controllerOptions, adapterOptions) {
var _a;
super(agvId, {
...clientOptions,
topicObjectValidation: { inbound: false, outbound: (_a = clientOptions.topicObjectValidation) === null || _a === void 0 ? void 0 : _a.outbound },
});
this.agvId = agvId;
this._controllerOptions = this._controllerOptionsWithDefaults(controllerOptions);
this._currentInstantActions = [];
this._instantActionsEndedPublishCount = new Map();
this._instantActionsErroredPublishCount = new Map();
this.currentOrder = undefined;
this._cancelOrderContext = undefined;
this._currentState = {
actionStates: [],
batteryState: { batteryCharge: 0.8, charging: false },
driving: false,
edgeStates: [],
errors: [],
lastNodeId: "",
lastNodeSequenceId: 0,
nodeStates: [],
operatingMode: __1.OperatingMode.Manual,
orderId: "",
orderUpdateId: 0,
safetyState: { eStop: __1.EStop.None, fieldViolation: false },
};
this._agvAdapter = new this.controllerOptions.agvAdapterType(this, adapterOptions, this.debug.extend(this.controllerOptions.agvAdapterType.name));
this._currentFactsheet = {};
if (this._agvAdapter.apiVersion !== this.adapterApiVersion) {
throw new Error(`${this._agvAdapter.name}@${this._agvAdapter.apiVersion} not compatible with adapter protocol ${this.adapterApiVersion} used by ${this.constructor.name}`);
}
this.debug("Created instance with controllerOptions %o", this.controllerOptions);
}
get controllerOptions() {
return this._controllerOptions;
}
get adapterApiVersion() {
return 2;
}
get currentState() {
return this._cloneState(this._currentState);
}
get hasActiveOrder() {
return this._currentState.nodeStates.length > 0 ||
this._currentState.edgeStates.length > 0 ||
this._currentState.actionStates.some(s => !this.isInstantActionState(s) &&
s.actionStatus !== __1.ActionStatus.Failed &&
s.actionStatus !== __1.ActionStatus.Finished);
}
get hasCancelingOrder() {
return this._cancelOrderContext !== undefined;
}
isInstantActionState(state) {
return this._instantActionsEndedPublishCount.has(state.actionId);
}
updateAgvPositionVelocity(agvPosition, velocity, reportImmediately = false) {
this._updateState(this._cloneState({ agvPosition, velocity }), reportImmediately);
}
updateBatteryState(batteryState, reportImmediately = false) {
this._updateState(this._cloneState({ batteryState }), reportImmediately);
}
updateDrivingState(driving, reportImmediately = false) {
this._updateState(this._cloneState({ driving }), reportImmediately);
}
updatePausedState(paused, reportImmediately = false) {
this._updateState(this._cloneState({ paused }), reportImmediately);
}
updateNewBaseRequest(newBaseRequest, reportImmediately = true) {
this._updateState(this._cloneState({ newBaseRequest }), reportImmediately);
}
updateSafetyStatus(safetyStatus, reportImmediately = true) {
this._updateState(this._cloneState({ safetyState: safetyStatus }), reportImmediately);
}
updateOperatingMode(operatingMode, reportImmediately = true) {
this._updateState(this._cloneState({ operatingMode }), reportImmediately);
}
updateFactsheet(factsheet) {
const f = factsheet === undefined ? {} : JSON.parse(JSON.stringify(factsheet));
this._currentFactsheet = f;
}
updateErrors(error, mode, reportImmediately = false) {
const index = this._findErrorIndex(error);
let newErrors;
if (index !== -1) {
if (mode === "add") {
return;
}
newErrors = [...this._currentState.errors];
newErrors.splice(index, 1);
}
else {
if (mode === "remove") {
return;
}
newErrors = [...this._currentState.errors, error];
}
this._updateState(this._cloneState({ errors: newErrors }), reportImmediately);
}
updatePartialState(newState, reportImmediately = false) {
this._updateState(this._cloneState(newState), reportImmediately);
}
async onStarted() {
await super.onStarted();
this._attachAdapter();
}
async onStopping() {
await this._detachAdapter();
await super.onStopping();
}
onStateChanged(changes) {
}
executeInstantAction(context) {
this.debug("Invoking instant executeAction handler with context %o", context);
this._agvAdapter.executeAction(context);
}
_controllerOptionsWithDefaults(options) {
const optionalDefaults = {
publishStateInterval: 30000,
publishVisualizationInterval: 1000,
finalInstantActionStateChangePublishCount: 5,
};
const opts = Object.assign(optionalDefaults, options);
opts.finalInstantActionStateChangePublishCount = Math.max(1, opts.finalInstantActionStateChangePublishCount);
return opts;
}
_attachAdapter() {
this.debug("Invoking attach handler");
this._agvAdapter.attach({
attached: async (initialState) => {
this.updatePartialState(initialState, false);
await this._subscribeOnStarted();
this._publishCurrentState();
},
});
}
_detachAdapter() {
return new Promise(resolve => {
this.debug("Invoking detach handler");
this._agvAdapter.detach({
detached: detachState => {
this.updatePartialState(detachState, true);
clearTimeout(this._publishStateTimerId);
clearInterval(this._publishVisualizationIntervalId);
resolve();
},
});
});
}
async _subscribeOnStarted() {
await this.subscribe(__1.Topic.Order, order => this._processOrder(order));
await this.subscribe(__1.Topic.InstantActions, actions => this._processInstantActions(actions));
this.registerConnectionStateChange((currentState, prevState) => {
if (currentState === "online" && prevState !== "online") {
this._publishCurrentState();
}
});
this._setupPublishVisualizationInterval();
}
_resetPublishStateTimer() {
clearTimeout(this._publishStateTimerId);
this._publishStateTimerId = setTimeout(() => this._publishCurrentState(), this.controllerOptions.publishStateInterval);
}
_setupPublishVisualizationInterval() {
clearInterval(this._publishVisualizationIntervalId);
if (this.controllerOptions.publishVisualizationInterval <= 0) {
return;
}
this._publishVisualizationIntervalId = setInterval(() => this._publishVisualization(), this.controllerOptions.publishVisualizationInterval);
}
async _publishVisualization() {
try {
const vis = {};
if (this._currentState.agvPosition !== undefined) {
vis.agvPosition = this._currentState.agvPosition;
}
if (this._currentState.velocity !== undefined) {
vis.velocity = this._currentState.velocity;
}
await this.publish(__1.Topic.Visualization, vis, { dropIfOffline: true });
}
catch (error) {
this.debug("Couldn't publish visualization: %s", error);
}
}
async _publishCurrentState() {
this._resetPublishStateTimer();
const publishedState = await this.publish(__1.Topic.State, this._currentState, { dropIfOffline: true });
if (publishedState !== undefined) {
delete this._currentState.timestamp;
this._cleanupInstantActionStates();
}
}
async _publishFactsheet(context) {
await this.publish(__1.Topic.Factsheet, this._currentFactsheet, { dropIfOffline: true, retainMessage: true });
context.updateActionStatus({
actionStatus: __1.ActionStatus.Finished,
resultDescription: "Reported new factsheet",
});
}
_updateState(newPartialState, publishImmediately = false) {
this._mergeState(newPartialState);
if (publishImmediately) {
this._publishCurrentState();
}
this.onStateChanged(newPartialState);
}
_mergeState(newPartialState) {
for (const [key, value] of Object.entries(newPartialState)) {
if (value !== undefined) {
this._currentState[key] = value;
}
else {
delete this._currentState[key];
}
}
if (!newPartialState.timestamp) {
delete this._currentState.timestamp;
}
}
_cloneState(state) {
return state === undefined ? {} : JSON.parse(JSON.stringify(state));
}
_findErrorIndex(error) {
return this._currentState.errors.findIndex(e => e.errorDescription === error.errorDescription &&
e.errorLevel === error.errorLevel &&
e.errorType === error.errorType &&
this._areErrorReferencesEqual(e.errorReferences, error.errorReferences));
}
_areErrorReferencesEqual(refs1, refs2) {
if (refs1.length !== refs2.length) {
return false;
}
for (const { referenceKey, referenceValue } of refs1) {
if (!refs2.find(r => r.referenceKey === referenceKey && r.referenceValue === referenceValue)) {
return false;
}
}
return true;
}
_processOrder(order) {
var _a;
this.debug("Processing order %o", order);
try {
this.validateTopicObject(__1.Topic.Order, order, this.clientOptions.vdaVersion);
this._validateOrderConstraints(order);
}
catch (err) {
const error = this._createOrderError(order, __1.ErrorType.OrderValidation, `invalid order: ${err}`);
this.debug("Invalid order: %j", error);
this._rejectOrder(error);
return;
}
if (this.hasCancelingOrder) {
const error = this._createOrderError(order, __1.ErrorType.Order, "active order is being canceled");
this.debug("Order rejected as an active order is being canceled: %j", error);
this._rejectOrder(error);
return;
}
if (!this._checkRouteTraversable(order)) {
return;
}
if (!this._checkOrderActionsExecutable(order)) {
return;
}
if (this._currentState.batteryState.charging) {
const error = this._createOrderError(order, __1.ErrorType.Order, "order is not executable while charging", { referenceKey: "batteryState.charging", referenceValue: "true" });
this.debug("Order rejected as charging is in progress: %j", error);
this._rejectOrder(error);
return;
}
if (this._currentState.safetyState.eStop !== __1.EStop.None) {
const error = this._createOrderError(order, __1.ErrorType.Order, "order is not executable as emergency stop is active", { referenceKey: "safetyState.eStop", referenceValue: this._currentState.safetyState.eStop });
this.debug("Order rejected as emergency stop is active: %j", error);
this._rejectOrder(error);
return;
}
if (this._currentState.safetyState.fieldViolation) {
const error = this._createOrderError(order, __1.ErrorType.Order, "order is not executable due to protective field violation", { referenceKey: "safetyState.fieldViolation", referenceValue: this._currentState.safetyState.fieldViolation.toString() });
this.debug("Order rejected as protective field is violated: %j", error);
this._rejectOrder(error);
return;
}
if (this._currentState.operatingMode !== __1.OperatingMode.Automatic &&
this._currentState.operatingMode !== __1.OperatingMode.Semiautomatic) {
const error = this._createOrderError(order, __1.ErrorType.Order, "order is not executable due to operating mode", { referenceKey: "operatingMode", referenceValue: this._currentState.operatingMode });
this.debug("Order rejected due to operating mode: %j", error);
this._rejectOrder(error);
return;
}
if (order.orderId === ((_a = this.currentOrder) === null || _a === void 0 ? void 0 : _a.orderId)) {
if (order.orderUpdateId < this.currentOrder.orderUpdateId) {
const error = this._createOrderError(order, __1.ErrorType.OrderUpdate, "invalid orderUpdateId");
this.debug("Order update rejected as orderUpdateId is invalid: %j", error);
this._rejectOrder(error);
}
else if (order.orderUpdateId === this.currentOrder.orderUpdateId) {
this.debug("Order update discarded as orderUpdateId is already assigned");
this._updateState({}, true);
}
else {
if (this.hasActiveOrder) {
if (!this._isOrderBaseStitching(order)) {
const error = this._createOrderError(order, __1.ErrorType.OrderUpdate, "stitching order base not extending active order base");
this.debug("Stitching order rejected as it doesn't extend the active order base: %j", error);
this._rejectOrder(error);
}
else {
this._acceptOrder(order, "stitch");
}
}
else {
if (!this._isOrderUpdateBaseStitching(order)) {
const error = this._createOrderError(order, __1.ErrorType.OrderUpdate, "order update base not extending current order base");
this.debug("Order update rejected as it doesn't extend the current order base: %j", error);
this._rejectOrder(error);
}
else {
this._acceptOrder(order, "update");
}
}
}
}
else {
if (this.hasActiveOrder) {
if (!this._isOrderBaseStitching(order)) {
const error = this._createOrderError(order, __1.ErrorType.OrderUpdate, "stitching order base not extending active order base");
this.debug("Stitching order rejected as it doesn't extend the active order base: %j", error);
this._rejectOrder(error);
}
else {
this._acceptOrder(order, "stitch");
}
}
else {
if (this._checkNodeWithinDeviationRange(order)) {
this._acceptOrder(order, "new");
}
}
}
}
_validateOrderConstraints(order) {
const nodeLen = order.nodes.length;
if (nodeLen === 0 || !order.nodes[0].released) {
throw new Error("Order must contain at least one base node");
}
let isBase = true;
let firstHorizonIndex = -1;
for (let i = 0; i < nodeLen; i++) {
const node = order.nodes[i];
if ((i === 0 && node.sequenceId % 2 !== 0) ||
(i > 0 && node.sequenceId !== order.nodes[i - 1].sequenceId + 2)) {
throw new Error("Order contains node with invalid sequenceId");
}
if (isBase) {
isBase = node.released;
if (!isBase) {
firstHorizonIndex = i;
}
}
else {
if (node.released) {
throw new Error("Incorrect sequence of base-horizon nodes");
}
}
}
const edgeLen = order.edges.length;
isBase = true;
if (edgeLen + 1 !== nodeLen) {
throw new Error("Incompatible sequence of nodes and edges");
}
for (let i = 0; i < edgeLen; i++) {
const edge = order.edges[i];
if (edge.sequenceId !== order.nodes[i].sequenceId + 1) {
throw new Error("Order contains edge with invalid sequenceId");
}
if (isBase) {
isBase = edge.released;
if (!isBase && firstHorizonIndex !== i + 1) {
throw new Error("Incorrect sequence of base-horizon edges");
}
else if (isBase && !order.nodes[i + 1].released) {
throw new Error("EndNode of last base edge is not released");
}
}
else {
if (edge.released) {
throw new Error("Incorrect sequence of base-horizon edges");
}
}
if (edge.startNodeId !== order.nodes[i].nodeId || edge.endNodeId !== order.nodes[i + 1].nodeId) {
throw new Error("An edge doesn't have proper start and/or end nodes");
}
}
}
_isOrderBaseStitching(order) {
if (!this.currentOrder) {
return false;
}
const currentHorizonStartIndex = this.currentOrder.nodes.findIndex(n => !n.released);
const currentBaseEnd = this.currentOrder.nodes[currentHorizonStartIndex === -1 ?
this.currentOrder.nodes.length - 1 : currentHorizonStartIndex - 1];
const newBaseStart = order.nodes[0];
return currentBaseEnd.nodeId === newBaseStart.nodeId &&
currentBaseEnd.sequenceId === newBaseStart.sequenceId;
}
_isOrderUpdateBaseStitching(order) {
const newBaseStart = order.nodes[0];
return newBaseStart.nodeId === this._currentState.lastNodeId &&
newBaseStart.sequenceId === this._currentState.lastNodeSequenceId;
}
_acceptOrder(order, mode) {
this.debug("Order accepted with mode '%s'", mode);
switch (mode) {
case "new": {
this.currentOrder = order;
this._updateState({
orderId: order.orderId,
orderUpdateId: order.orderUpdateId,
errors: this._getNonOrderRejectionErrors(false),
nodeStates: this._getNodeStates(order),
edgeStates: this._getEdgeStates(order),
actionStates: this._getInstantActionStates().concat(this._getActionStates(order)),
}, true);
this._processNode(this.currentOrder.nodes[0]);
break;
}
case "update": {
this.currentOrder = order;
this._updateState({
orderUpdateId: order.orderUpdateId,
errors: this._getNonOrderRejectionErrors(false),
nodeStates: this._getNodeStates(order),
edgeStates: this._getEdgeStates(order),
actionStates: this._getInstantActionStates().concat(this._getActionStates(order)),
}, true);
this._processNode(this.currentOrder.nodes[0]);
break;
}
case "stitch": {
this.currentOrder.orderId = order.orderId;
this.currentOrder.orderUpdateId = order.orderUpdateId;
this.currentOrder.zoneSetId = order.zoneSetId;
let currentHorizonStartIndex = this.currentOrder.nodes.findIndex(n => !n.released);
const currentBaseEnd = this.currentOrder.nodes[currentHorizonStartIndex === -1 ?
this.currentOrder.nodes.length - 1 : currentHorizonStartIndex - 1];
this.currentOrder.nodes = this.currentOrder.nodes
.slice(0, currentHorizonStartIndex === -1 ? undefined : currentHorizonStartIndex)
.concat(order.nodes.slice(1));
currentBaseEnd.actions = currentBaseEnd.actions.concat(order.nodes[0].actions);
currentHorizonStartIndex = this.currentOrder.edges.findIndex(n => !n.released);
this.currentOrder.edges = this.currentOrder.edges
.slice(0, currentHorizonStartIndex === -1 ? undefined : currentHorizonStartIndex)
.concat(order.edges);
const isLastBaseNodeProcessed = !this._currentState.nodeStates.some(s => s.nodeId === currentBaseEnd.nodeId && s.sequenceId === currentBaseEnd.sequenceId);
const allLastBaseNodeActionsEnded = currentBaseEnd.actions.every(a => this._isActionEnded(a));
this._updateState({
orderId: order.orderId,
orderUpdateId: order.orderUpdateId,
errors: this._getNonOrderRejectionErrors(true),
nodeStates: this._currentState.nodeStates.filter(s => s.released).concat(this._getNodeStates(order, true)),
edgeStates: this._currentState.edgeStates.filter(s => s.released).concat(this._getEdgeStates(order)),
actionStates: this._currentState.actionStates.concat(this._getActionStates(order, false)),
}, true);
if (isLastBaseNodeProcessed && allLastBaseNodeActionsEnded) {
this._processEdge(currentBaseEnd);
}
break;
}
}
}
_rejectOrder(error) {
this._updateState({ errors: [...this._currentState.errors, error] }, true);
}
_cancelOrder(context) {
this._cancelOrderContext = context;
this._currentState.actionStates.forEach(s => {
if (!this.isInstantActionState(s) && s.actionStatus === __1.ActionStatus.Waiting) {
s.actionStatus = __1.ActionStatus.Failed;
}
});
this._updateActionStatus(context, {
actionStatus: __1.ActionStatus.Running,
});
let hasActionsToBeCanceled = false;
this._currentState.actionStates.forEach(s => {
if (!this.isInstantActionState(s) &&
s.actionStatus !== __1.ActionStatus.Finished && s.actionStatus !== __1.ActionStatus.Failed) {
let actionContext;
for (const node of this.currentOrder.nodes) {
if (!node.released) {
break;
}
const action = node.actions.find(a => a.actionId === s.actionId);
if (action) {
actionContext = {
action,
scope: "node",
updateActionStatus: change => this._updateActionStatus(actionContext, change),
node,
activeOrderId: this.currentOrder.orderId,
};
break;
}
}
if (!actionContext) {
for (const edge of this.currentOrder.edges) {
if (!edge.released) {
break;
}
const action = edge.actions.find(a => a.actionId === s.actionId);
if (action) {
actionContext = {
action,
scope: "edge",
updateActionStatus: change => this._updateActionStatus(actionContext, change),
edge,
edgeStartNode: this._getEdgeStartNode(this.currentOrder, edge),
edgeEndNode: this._getEdgeEndNode(this.currentOrder, edge),
activeOrderId: this.currentOrder.orderId,
};
break;
}
}
}
hasActionsToBeCanceled = true;
this.debug("Invoking cancelAction handler with context %o", actionContext);
this._agvAdapter.cancelAction(actionContext);
}
});
if (!hasActionsToBeCanceled) {
this._onOrderActionsCanceled();
}
}
_areAllOrderActionsCanceled() {
for (const node of this.currentOrder.nodes) {
if (!node.released) {
break;
}
if (!node.actions.every(a => this._isActionEnded(a))) {
return false;
}
}
for (const edge of this.currentOrder.edges) {
if (!edge.released) {
break;
}
if (!edge.actions.every(a => this._isActionEnded(a))) {
return false;
}
}
return true;
}
_onOrderActionsCanceled() {
this.debug("Invoking stopTraverse handler");
this._agvAdapter.stopTraverse({
drivingToNextNode: (nextNode) => {
this.debug("Invoked drivingToNextNode callback with next node %o", nextNode);
this._updateState({
nodeStates: this._currentState.nodeStates.filter(s => s.nodeId === nextNode.nodeId && s.sequenceId === nextNode.sequenceId),
edgeStates: [],
}, true);
},
stopped: () => {
this.debug("Invoked stopped callback");
const cancelOrderContext = this._cancelOrderContext;
this._cancelOrderContext = undefined;
this._updateState({
nodeStates: [],
edgeStates: [],
}, false);
this._updateActionStatus(cancelOrderContext, {
actionStatus: __1.ActionStatus.Finished,
});
},
});
}
_checkRouteTraversable(order) {
const context = { nodes: order.nodes, edges: order.edges };
this.debug("Invoking isRouteTraversable handler on context %o", context);
const errorRefs = this._agvAdapter.isRouteTraversable(context) || [];
if (errorRefs.length !== 0) {
const error = this._createOrderError(order, __1.ErrorType.OrderNoRoute, "order route is not traversable", ...errorRefs);
this.debug("Order rejected as route is not traversable: %j", error);
this._rejectOrder(error);
return false;
}
return true;
}
_checkOrderActionsExecutable(order) {
const reportError = (context, errorRefs) => {
const error = this._createOrderError(order, __1.ErrorType.Order, "order action is not executable", { referenceKey: "actionId", referenceValue: context.action.actionId }, { referenceKey: "actionType", referenceValue: context.action.actionType }, ...errorRefs);
this.debug("Order rejected as an action is not executable: %j", error);
this._rejectOrder(error);
};
for (const node of order.nodes) {
for (const action of node.actions) {
const context = {
action,
scope: "node",
updateActionStatus: undefined,
node,
activeOrderId: order.orderId,
};
this.debug("Invoking isActionExecutable handler on context %o", context);
const errorRefs = this._agvAdapter.isActionExecutable(context);
if ((errorRefs === null || errorRefs === void 0 ? void 0 : errorRefs.length) > 0) {
reportError(context, errorRefs);
return false;
}
}
}
for (const edge of order.edges) {
for (const action of edge.actions) {
const context = {
action,
scope: "edge",
updateActionStatus: undefined,
edge,
edgeStartNode: this._getEdgeStartNode(order, edge),
edgeEndNode: this._getEdgeEndNode(order, edge),
activeOrderId: order.orderId,
};
this.debug("Invoking isActionExecutable handler on context %o", context);
const errorRefs = this._agvAdapter.isActionExecutable(context);
if ((errorRefs === null || errorRefs === void 0 ? void 0 : errorRefs.length) > 0) {
reportError(context, errorRefs);
return false;
}
}
}
return true;
}
_checkNodeWithinDeviationRange(order) {
const firstNode = order.nodes[0];
this.debug("Invoking isNodeWithinDeviationRange handler with node %o", firstNode);
const errorRefs = this._agvAdapter.isNodeWithinDeviationRange(firstNode) || [];
if (errorRefs.length !== 0) {
const error = this._createOrderError(order, __1.ErrorType.OrderNoRoute, "first node of new order not within deviation range", { referenceKey: "nodeId", referenceValue: firstNode.nodeId }, ...errorRefs);
this.debug("Order rejected as first node is not within deviation range: %j", error);
this._rejectOrder(error);
return false;
}
return true;
}
_getNodeStates(order, excludeFirstNode = false) {
return (excludeFirstNode ? order.nodes.slice(1) : order.nodes)
.map(n => {
const state = {
nodeId: n.nodeId,
released: n.released,
sequenceId: n.sequenceId,
};
if (n.nodeDescription !== undefined) {
state.nodeDescription = n.nodeDescription;
}
if (n.nodePosition !== undefined) {
state.nodePosition = n.nodePosition;
}
return state;
});
}
_getEdgeStates(order) {
return order.edges
.map(e => {
let trajectory;
if (!this._agvAdapter.trajectory) {
trajectory = e.trajectory;
}
else {
trajectory = this._agvAdapter.trajectory({
edge: e,
startNode: this._getEdgeStartNode(order, e),
endNode: this._getEdgeEndNode(order, e),
});
this.debug("Invoking trajectory calculation handler on edge %o with result %o", e, trajectory);
}
const state = {
edgeId: e.edgeId,
released: e.released,
sequenceId: e.sequenceId,
};
if (e.edgeDescription !== undefined) {
state.edgeDescription = e.edgeDescription;
}
if (trajectory !== undefined) {
state.trajectory = trajectory;
}
return state;
});
}
_getActionStates(order, excludeFirstNode = false) {
const actionStateFrom = (a) => {
const s = {
actionId: a.actionId,
actionStatus: __1.ActionStatus.Waiting,
actionType: a.actionType,
};
if (a.actionDescription !== undefined) {
s.actionDescription = a.actionDescription;
}
return s;
};
return (excludeFirstNode ? order.nodes.slice(1) : order.nodes)
.filter(n => n.released)
.flatMap(n => n.actions.map(a => actionStateFrom(a)))
.concat(order.edges
.filter(e => e.released)
.flatMap(e => e.actions.map(a => actionStateFrom(a))));
}
_getInstantActionStates() {
return this._currentState.actionStates.filter(s => this.isInstantActionState(s));
}
_getNonOrderRejectionErrors(shouldKeepOrderActionErrors) {
return this._currentState.errors.filter(e => this._instantActionsErroredPublishCount.has(e) ||
(shouldKeepOrderActionErrors && e.errorReferences && e.errorReferences.some(r => r.referenceKey === "actionId")));
}
_cleanupInstantActionStates() {
const errorsToRemove = new Set();
this._instantActionsErroredPublishCount.forEach((count, err, map) => {
if (!this._currentState.errors.includes(err)) {
return;
}
count++;
if (count >= this.controllerOptions.finalInstantActionStateChangePublishCount) {
map.delete(err);
errorsToRemove.add(err);
}
else {
map.set(err, count);
}
});
const actionIdsToRemove = new Set();
this._instantActionsEndedPublishCount.forEach((count, id, map) => {
const state = this._currentState.actionStates.find(s => s.actionId === id);
if (!state || (state.actionStatus !== __1.ActionStatus.Finished && state.actionStatus !== __1.ActionStatus.Failed)) {
return;
}
count++;
if (count >= this.controllerOptions.finalInstantActionStateChangePublishCount) {
map.delete(id);
actionIdsToRemove.add(id);
}
else {
map.set(id, count);
}
});
const newState = {};
if (errorsToRemove.size > 0) {
newState.errors = this._currentState.errors.filter(e => !errorsToRemove.has(e));
}
if (actionIdsToRemove.size > 0) {
newState.actionStates = this._currentState.actionStates.filter(s => !actionIdsToRemove.has(s.actionId));
}
if (newState.errors !== undefined || newState.actionStates !== undefined) {
this._updateState(newState, false);
}
}
_getEdgeStartNode(order, edge) {
return order.nodes.find(n => n.nodeId === edge.startNodeId && n.sequenceId === edge.sequenceId - 1);
}
_getEdgeEndNode(order, edge) {
return order.nodes.find(n => n.nodeId === edge.endNodeId && n.sequenceId === edge.sequenceId + 1);
}
_getTrailingEdge(node) {
return this.currentOrder.edges.find(e => e.startNodeId === node.nodeId && e.sequenceId === node.sequenceId + 1);
}
_processNode(node, traversedEdge) {
this.debug("Processing node %s (sequenceId %d)", node.nodeId, node.sequenceId);
if (!node.released) {
this.debug("Stop node processing because node is not released");
return;
}
if (this._currentState.paused) {
this.debug("Stop node processing because AGV is in a paused state");
this._currentPausedNode = node;
return;
}
let edgeStates;
if (traversedEdge) {
this._finishEdgeActions(traversedEdge);
edgeStates = this._currentState.edgeStates.filter(s => !(s.edgeId === traversedEdge.edgeId && s.sequenceId === traversedEdge.sequenceId));
}
this._updateState({
nodeStates: this._currentState.nodeStates
.filter(s => !(s.nodeId === node.nodeId && s.sequenceId === node.sequenceId)),
...(edgeStates ? { edgeStates } : {}),
lastNodeId: node.nodeId,
lastNodeSequenceId: node.sequenceId,
}, true);
this._processNodeActions(node);
}
_processEdge(node) {
const edge = this._getTrailingEdge(node);
if (edge === undefined || !edge.released) {
this.debug("Stop processing of node %s (sequenceId %d) because no trailing released edge is existing", node.nodeId, node.sequenceId);
return;
}
this.debug("Processing edge %s (sequenceId %d)", edge.edgeId, edge.sequenceId);
this._processEdgeActions(edge);
}
_traverseEdge(edge) {
var _a;
const context = {
edge,
startNode: this._getEdgeStartNode(this.currentOrder, edge),
endNode: this._getEdgeEndNode(this.currentOrder, edge),
trajectory: (_a = this._currentState.edgeStates.find(s => s.edgeId === edge.edgeId && s.sequenceId === edge.sequenceId)) === null || _a === void 0 ? void 0 : _a.trajectory,
edgeTraversed: () => this._updateEdgeTraversed(context),
};
this.debug("Invoking traverse handler on edgeId %s (sequenceId %d) with context %o", edge.edgeId, edge.sequenceId, context);
this._agvAdapter.traverseEdge(context);
}
_updateEdgeTraversed(context) {
this.debug("Edge %s (sequenceId %d) has been traversed", context.edge.edgeId, context.edge.sequenceId);
if (this.hasCancelingOrder) {
this.debug("Skip processing node %s (sequenceId %d) as active order is canceled", context.endNode, context.edge);
return;
}
this._processNode(context.endNode, context.edge);
}
_isActionEnded(action) {
const as = this._currentState.actionStates.find(s => s.actionId === action.actionId);
return as !== undefined && (as.actionStatus === __1.ActionStatus.Finished || as.actionStatus === __1.ActionStatus.Failed);
}
_getHardBlockingActionAfterParallelActions(actions, action) {
const actionIndex = actions.findIndex(a => a.actionId === action.actionId);
return actions.find((a, i) => i > actionIndex && a.blockingType === __1.BlockingType.Hard);
}
_areParallelActionsEnded(actions, endedAction, softOnly) {
const actionIndex = actions.findIndex(a => a.actionId === endedAction.actionId);
for (let i = actionIndex - 1; i >= 0; i--) {
const action = actions[i];
if (action.blockingType === __1.BlockingType.Hard) {
break;
}
else {
const ended = this._isActionEnded(action);
if (softOnly) {
if (action.blockingType === __1.BlockingType.Soft && !ended) {
return false;
}
}
else {
if (!ended) {
return false;
}
}
}
}
const len = actions.length;
for (let i = actionIndex + 1; i < len; i++) {
const action = actions[i];
if (action.blockingType === __1.BlockingType.Hard) {
return true;
}
else {
const ended = this._isActionEnded(action);
if (softOnly) {
if (action.blockingType === __1.BlockingType.Soft && !ended) {
return false;
}
}
else {
if (!ended) {
return false;
}
}
}
}
return true;
}
_updateActionStatus(context, change) {
const { action, scope } = context;
const { actionStatus, resultDescription, errorDescription, linkedState } = change;
this.debug("Updated action %o change: %o", action, change);
const newActionState = {
actionId: action.actionId,
actionStatus: actionStatus,
actionType: action.actionType,
};
if (action.actionDescription !== undefined) {
newActionState.actionDescription = action.actionDescription;
}
if (resultDescription !== undefined) {
newActionState.resultDescription = resultDescription;
}
const newActionStates = [...this._currentState.actionStates];
const i = this._currentState.actionStates.findIndex(s => s.actionId === action.actionId);
if (i === -1) {
newActionStates.push(newActionState);
}
else {
newActionStates[i] = newActionState;
}
const errors = {};
if (actionStatus === __1.ActionStatus.Failed && errorDescription) {
const error = this._createActionError(context, scope === "instant" ? __1.ErrorType.InstantAction : __1.ErrorType.OrderAction, errorDescription, []);
errors.errors = [...this._currentState.errors, error];
}
this._updateState(Object.assign({ actionStates: newActionStates }, errors, this._cloneState(linkedState)), true);
if (actionStatus === __1.ActionStatus.Failed || actionStatus === __1.ActionStatus.Finished) {
switch (scope) {
case "node": {
if (this.hasCancelingOrder) {
if (this._areAllOrderActionsCanceled()) {
this._onOrderActionsCanceled();
}
}
else {
if (action.blockingType === __1.BlockingType.Hard) {
this._processNodeActions(context.node, action);
}
else {
const nextHardAction = this._getHardBlockingActionAfterParallelActions(context.node.actions, action);
if (nextHardAction) {
if (this._areParallelActionsEnded(context.node.actions, action, false)) {
this._processNodeAction(context.node, nextHardAction, true);
}
}
else {
if (this._areParallelActionsEnded(context.node.actions, action, true)) {
this._processEdge(context.node);
}
}
}
}
break;
}
case "edge": {
if (this.hasCancelingOrder) {
if (this._areAllOrderActionsCanceled()) {
this._onOrderActionsCanceled();
}
}
else {
if (action.blockingType === __1.BlockingType.Hard) {
this._processEdgeActions(context.edge, action);
}
else {
const nextHardAction = this._getHardBlockingActionAfterParallelActions(context.edge.actions, action);
if (nextHardAction) {
if (this._areParallelActionsEnded(context.edge.actions, action, false)) {
this._processEdgeAction(context.edge, nextHardAction, true);
}
}
else {
if (this._areParallelActionsEnded(context.edge.actions, action, true)) {
this._traverseEdge(context.edge);
}
}
}
}
break;
}
case "instant": {
const actionIndex = this._currentInstantActions.findIndex(a => a.actionId === action.actionId);
if (action.blockingType === __1.BlockingType.Hard) {
this._currentInstantActions.splice(actionIndex, 1);
this._processInstantActionChunk(undefined);
}
else {
const nextHardAction = this._getHardBlockingActionAfterParallelActions(this._currentInstantActions, action);
if (nextHardAction && this._areParallelActionsEnded(this._currentInstantActions, action, false)) {
this._currentInstantActions.splice(actionIndex, 1);
this._processInstantAction(nextHardAction, true);
}
else {
this._currentInstantActions.splice(actionIndex, 1);
}
}
break;
}
}
}
}
_processNodeActions(node, afterAction) {
const afterIndex = afterAction === undefined ? -1 : node.actions.findIndex(a => a.actionId === afterAction.actionId);
const hardIndex = node.actions.findIndex((a, i) => i > afterIndex && a.blockingType === __1.BlockingType.Hard);
const softIndex = node.actions.findIndex((a, i) => i > afterIndex && a.blockingType === __1.BlockingType.Soft);
const stopIndex = hardIndex === -1 ? node.actions.length : hardIndex;
const stopDriving = softIndex !== -1 && softIndex < stopIndex;
if (stopIndex === afterIndex + 1) {
if (hardIndex === -1) {
this._processEdge(node);
}
else {
this._processNodeAction(node, node.actions[stopIndex], true);
}
}
else {
for (let i = afterIndex + 1; i < stopIndex; i++) {
this._processNodeAction(node, node.actions[i], stopDriving);
}
}
}
_processNodeAction(node, action, stopDriving) {
const context = {
action,
scope: "node",
stopDriving,
updateActionStatus: change => this._updateActionStatus(context, change),
node,
activeOrderId: this.currentOrder.orderId,
};
this.debug("Invoking node executeAction handler with context %o", context);
this._agvAdapter.executeAction(context);
}
_processEdgeActions(edge, afterAction) {
const afterIndex = afterAction === undefined ? -1 : edge.actions.findIndex(a => a.actionId === afterAction.actionId);
const hardIndex = edge.actions.findIndex((a, i) => i > afterIndex && a.blockingType === __1.BlockingType.Hard);
const softIndex = edge.actions.findIndex((a, i) => i > afterIndex && a.blockingType === __1.BlockingType.Soft);
const stopIndex = hardIndex === -1 ? edge.actions.length : hardIndex;
const stopDriving = softIndex !== -1 && softIndex < stopIndex;
if (stopIndex === afterIndex + 1) {
if (hardIndex === -1) {
this._traverseEdge(edge);
}
else {
this._processEdgeAction(edge, edge.actions[stopIndex], true);
}
}
else {
for (let i = afterIndex + 1; i < stopIndex; i++) {
this._processEdgeAction(edge, edge.actions[i], stopDriving);
}
}
}
_processEdgeAction(edge, action, stopDriving) {
const context = {
action,
scope: "edge",
stopDriving,
updateActionStatus: change => this._updateActionStatus(context, change),
edge,
edgeStartNode: this._getEdgeStartNode(this.currentOrder, edge),
edgeEndNode: this._getEdgeEndNode(this.currentOrder, edge),
activeOrderId: this.currentOrder.orderId,
};
this.debug("Invoking edge executeAction handler with context %o", context);
this._agvAdapter.executeAction(context);
}
_finishEdgeActions(edge) {
for (const action of edge.actions) {
if (!this._isActionEnded(action)) {
const context = {
action,
scope: "edge",
updateActionStatus: change => this._updateActionStatus(context, change),
edge,
edgeStartNode: this._getEdgeStartNode(this.currentOrder, edge),
edgeEndNode: this._getEdgeEndNode(this.currentOrder, edge),
activeOrderId: this.currentOrder.orderId,
};
this.debug("Invoking finishEdgeAction handler with context %o", context);
this._agvAdapter.finishEdgeAction(context);
}
}
}
_processInstantActions(actions) {
try {
this.validateTopicObject(__1.Topic.InstantActions, actions, this.clientOptions.vdaVersion);
}
catch (err) {
const error = this._createInstantActionsValidationError(actions, `invalid instant actions: ${err}`);
this.debug("Invalid instant actions: %j", error);
this._instantActionsErroredPublishCount.set(error, 0);
this._updateState({
errors: [...this._currentState.errors, error],
}, true);
return;
}
const afterAction = this._currentInstantActions[this._currentInstantActions.length - 1];
const hasPendingHardAction = this._currentInstantActions.some(a => a.blockingType === __1.BlockingType.Hard);
if (this.clientOptions.vdaVersion === "1.1.0") {
this._currentInstantActions.push(...actions.instantActions.filter(a => this._checkInstantActionExecutable(a)));
}
else {
this._currentInstantActions.push(...actions.actions.filter(a => this._checkInstantActionExecutable(a)));
}
if (!hasPendingHardAction) {
this._processInstantActionChunk(afterAction, afterAction !== undefined);
}
}
_processInstantActio