coatyio / vda-5050-lib.js Goto Github PK
View Code? Open in Web Editor NEWUniversal VDA 5050 library for Node.js and browsers.
License: MIT License
Universal VDA 5050 library for Node.js and browsers.
License: MIT License
Hi,
Describe the bug
After sending the task (example below), the AgvController will call the traverseEdge function defined in AgvAdapter. If I then call context.edgeTraversed() , it doesn't remove the edge from the EdgeState (just nothing happens). In VDA5050 v2.0.0, an edge can only be released when both the start and end nodes of the edge are released. I don't know if this is also the case in version 1.1.0.
{ "orderId": "order0001", "orderUpdateId": 0, "nodes": [ { "nodeId": "N1", "sequenceId": 0, "released": true, "nodePosition": { "x": 0, "y": 0, "theta": 3.14, "mapId": "map", "allowedDeviationXY": 5 }, "actions": [] }, { "nodeId": "N2", "sequenceId": 2, "released": false, "nodePosition": { "x": 0, "y": 5, "theta": 3.14, "mapId": "map", "allowedDeviationXy": 2 }, "actions": [] } ], "edges": [ { "edgeId": "1", "sequenceId": 1, "released": true, "startNodeId": "N1", "endNodeId": "N2", "actions": [] } ], "timestamp": "2023-03-07T15:15:05.395Z", "headerId": 0, "manufacturer": "Test", "serialNumber": "001", "version": "1.1.0" }
Expected behavior
Order validation must fail
Your Environment (please complete the following information):
If we assign one order after another everything worked fine. If we assign a new order (new order id) before the last order was finished, the orders will accepted and the AGV is driving as expected but not all order callbacks being raised.
The picture below will show the difference between these two scenarios. First we assign one order after another then we switch to the mode that we assign a new order before the last was completed.
async function processOrder(agv, order) {
return new Promise(async(resolve, reject) => {
try {
const headeredOrder = await masterController.assignOrder(agv, order, {
onOrderProcessed: (withError, byCancelation, active, context) => {
if (withError) {
console.error("mca: " + new Date().toISOString() + " rejected orderId: %s orderUpdateId: %d with error %o", context.order.orderId, context.order.orderUpdateId, withError);
reject(withError);
} else if (byCancelation) {
console.log("mca: " + new Date().toISOString() + " canceled orderId: %s orderUpdateId: %d", context.order.orderId, context.order.orderUpdateId);
resolve();
} else if (active) {
console.log("mca: " + new Date().toISOString() + " processed (still active) orderId: %s orderUpdateId: %d", context.order.orderId, context.order.orderUpdateId);
resolve();
} else {
console.log("mca: " + new Date().toISOString() + " processed (complete) orderId: %s orderUpdateId: %d", context.order.orderId, context.order.orderUpdateId);
resolve();
}
},
onNodeTraversed: (node, nextEdge, nextNode, context) => {
console.log("mca: " + new Date().toISOString() + " node traversed %o", node);
},
onEdgeTraversing: (edge, startNode, endNode, stateChanges, invocationCount, context) => {
console.log("mca: " + new Date().toISOString() + " edge traversing %o with state changes %o on invocation %d", edge, stateChanges, invocationCount);
},
onEdgeTraversed: (edge, startNode, endNode, context) => {
console.log("mca: " + new Date().toISOString() + " edge traversed %o", edge);
},
onActionStateChanged: (actionState, withError) => {
if (withError) {
console.error("mca: " + new Date().toISOString() + " action state changed %o with error %o", actionState, withError);
} else {
console.log("mca: " + new Date().toISOString() + " action state changed %o", actionState);
}
},
});
if (headeredOrder === undefined) {
console.log("mca: " + new Date().toISOString() + " discarded orderId: %s orderUpdateId: %d as active order has same orderId and orderUpdateId", order.orderId, order.orderUpdateId);
} else {
console.log("mca: " + new Date().toISOString() + " assigned order %o", headeredOrder);
}
}
catch (error) {
console.error("mca: " + new Date().toISOString() + " invalid order: %s", error.message);
reject(error);
}
});
}
Order No.1
{
"orderId": "49263",
"orderUpdateId": 0,
"nodes": [
{
"nodeId": "208",
"sequenceId": 22209,
"released": true,
"actions": []
},
{
"nodeId": "209",
"sequenceId": 22211,
"released": true,
"actions": []
}
],
"edges": [
{
"edgeId": "e_208_209",
"sequenceId": 22210,
"startNodeId": "208",
"endNodeId": "209",
"released": true,
"actions": []
}
],
"timestamp": "2022-01-21T11:00:40.120Z",
"headerId": 345,
"manufacturer": "siemens",
"serialNumber": "simove002",
"version": "1.1.0"
}
Order No.2
{
"orderId": "49264",
"orderUpdateId": 0,
"nodes": [
{
"nodeId": "209",
"sequenceId": 22211,
"released": true,
"actions": []
},
{
"nodeId": "210",
"sequenceId": 22213,
"released": true,
"actions": []
}
],
"edges": [
{
"edgeId": "e_209_210",
"sequenceId": 22212,
"startNodeId": "209",
"endNodeId": "210",
"released": true,
"actions": []
}
],
"timestamp": "2022-01-21T11:00:46.129Z",
"headerId": 346,
"manufacturer": "siemens",
"serialNumber": "simove002",
"version": "1.1.0"
}
Order No.3
{
"orderId": "49265",
"orderUpdateId": 0,
"nodes": [
{
"nodeId": "210",
"sequenceId": 22213,
"released": true,
"actions": []
},
{
"nodeId": "211",
"sequenceId": 22215,
"released": true,
"actions": []
}
],
"edges": [
{
"edgeId": "e_210_211",
"sequenceId": 22214,
"startNodeId": "210",
"endNodeId": "211",
"released": true,
"actions": []
}
],
"timestamp": "2022-01-21T11:00:54.144Z",
"headerId": 347,
"manufacturer": "siemens",
"serialNumber": "simove002",
"version": "1.1.0"
}
Describe the bug
The connection topic is not updated in case of a reconnect.
Is this a regression?
No.
To Reproduce
Minimal steps to reproduce the behavior:
Expected behavior
The connection topic should be updated with { connectionState: ConnectionState.Online }
once the mqtt connection is reconnected.
Describe the bug
The AgvController._subscribeOnStarted method is asynchonrously overwriting user registered connection state listeners.
Is this a regression?
No
To Reproduce
Minimal steps to reproduce the behavior:
class MyAgvController extends AgvController {
...
protected async onStarted() {
await super.onStarted()
this.registerConnectionStateChange((st, ps) => { console.log(`connection state change ${ps} -> ${st}`) })
}
...
}
Expected behavior
The disconnect state change (online -> offline) should be called through our registered callback.
Environment:
Additional context
The problem lies in the asynchronous callback in the AgvController.attach
method:
private _attachAdapter() {
this.debug("Invoking attach handler");
this._agvAdapter.attach({
attached: async initialState => { // this is an async callback
// Ensure subscriptions on orders and instant actions are
// registered with the broker before publishing the initial
// state. A Master Control may observe this state to immediately
// trigger initial instant actions or orders.
this.updatePartialState(initialState, false);
await this._subscribeOnStarted();
this._publishCurrentState();
},
});
}
...
private async _subscribeOnStarted() {
// First, subscribe to orders and instant actions.
await this.subscribe(Topic.Order, order => this._processOrder(order));
await this.subscribe(Topic.InstantActions, actions => this._processInstantActions(actions));
// Ensure State is reported immediately once after client is online again.
console.log(`_subscribeOnStarted register connection listener@ ${Date.now()}`);
this.registerConnectionStateChange((currentState, prevState) => { // this is called after all subscriptions are done, which may happen AFTER onStarted has returned, and after we registered our listener
if (currentState === "online" && prevState !== "online") {
this._publishCurrentState();
}
});
this._setupPublishVisualizationInterval();
}
I suggest you use a second private class property to store the callback used in _subscribeOnStarted instead of the public callback.
Describe the bug
When an AgvController is stopped and then started again, the application throws an error. This also happens if we wait a while between stop and start.
Is this a regression?
Afaik not
To Reproduce
With an mqtt broker running on localhost:1883, run the following script:
import { VirtualAgvAdapterOptions } from './../dist/adapter/virtual-agv-adapter.d';
import { AgvId } from './../dist/common/client-types.d';
import { ClientOptions } from './../dist/common/client.d';
import { VirtualAgvAdapter } from './adapter/virtual-agv-adapter';
import { AgvController } from './controller/agv-controller';
(async () => {
// Use minimal client options: communication namespace and broker endpoint address.
const agvClientOptions: ClientOptions = { interfaceName: "logctx42", transport: { brokerUrl: "mqtt://localhost:1883" }, vdaVersion: "2.0.0" };
// The target AGV.
const agvId001: AgvId = { manufacturer: "RobotCompany", serialNumber: "001" };
// Specify associated adapter type; use defaults for all other AGV controller options.
const agvControllerOptions = {
agvAdapterType: VirtualAgvAdapter,
};
// Use defaults for all adapter options of Virtual AGV adapter.
const agvAdapterOptions: VirtualAgvAdapterOptions = {};
// Create instance of AGV Controller with client, controller, and adapter options.
const agvController = new AgvController(agvId001, agvClientOptions, agvControllerOptions, agvAdapterOptions);
const sleep = async t => {await new Promise(r => setTimeout(r, 1000 * t))}
await agvController.start();
await sleep(5)
await agvController.stop();
await sleep(5)
await agvController.start();
})()
Expected behavior
The AgvController restarts without throwing an error.
Screenshots
C:\Repositories\vda-5050-lib.js\src\common\client.ts:712
throw new Error("Client is not started");
^
Error: Client is not started
at AgvController.publishTopic (C:\Repositories\vda-5050-lib.js\src\common\client.ts:712:19)
at AgvController.publish (C:\Repositories\vda-5050-lib.js\src\client\agv-client.ts:63:21)
at AgvController._publishCurrentState (C:\Repositories\vda-5050-lib.js\src\controller\agv-controller.ts:1216:43)
at AgvController._connectionStateChangeCallback (C:\Repositories\vda-5050-lib.js\src\controller\agv-controller.ts:1178:22)
at AgvController._emitConnectionStateChange (C:\Repositories\vda-5050-lib.js\src\common\client.ts:1186:18)
at MqttClient.<anonymous> (C:\Repositories\vda-5050-lib.js\src\common\client.ts:1130:26)
at MqttClient.emit (node:events:526:35)
at MqttClient.emit (node:domain:488:12)
at Readable.<anonymous> (C:\Repositories\vda-5050-lib.js\node_modules\mqtt\lib\client.js:1857:14)
at Readable.emit (node:events:514:28)
Your Environment (please complete the following information):
Additional context
When the mqtt client connects on the second start, a publish state is triggered:
this._emitConnectionStateChange("online");
in \src\common\client.ts
l.1131.
However this._isStarted
is only set to true
after connecting the mqtt broker in \src\common\client.ts l.472
, leading to the error in AgvController.publishTopic
.
A possible solution would be to set this._isStarted
to true
when the mqtt client connects, but before it emits the connection state change:
mqtt
...
.on("connect", () => {
this._isStarted = true
this._emitConnectionStateChange("online");
})
in \src\common\client.ts
ll.1129-1132.
Describe the bug
masterController.assignOrder
requires only that the message is published to an MQTT server to resolve the promise with a header. This does not mean the message has been delivered to an AGV.
If the AGV then goes off line, never to come back, perhaps it never received the message or does not persist it, how to then manage this Order?
The order cannot be sent with assignOrder
again, it will just be rejected with the promise being resolved with undefined
.
Sending a cancelOrder
action does not alter the MasterController
's state of the order locally.
A late joining AGV will also not receive the Order, as its not retained.
To Reproduce
Run the example code in the readme.md
, with the AgvController
joining after the Order
is published.
Expected behavior
Probably just some guidelines around managing the lifecycle of Orders that don't go exactly to plan.
A mechanism to republish? Or cancel centrally.
Compatible with VDA5050 version 2.0.0, is there a plan?
https://github.com/VDA5050/VDA5050/tree/2.0.0
Hello,
I've noticed an issue while using version 1.4.0 with vdaVersion: "2.0.0". The name in the Interface InstantActions has not been updated from v.1.1.0.
Should be: instantActions [action] -> actions [action]
For reference, check the vda5050 documentation, chapter 6.9.
Describe the bug
We wrote a simulator for VDA-AGVs to test our fleetmanager.
While simulating an AGV, we discovered that we might never reach a location because we "miss" it by a fraction of a mm
.
For that reason, the simulated AGV continued driving well beyond the last base node.
The cause was a IEEE754 rounding error:
In our specific case, we expected tx
, dx
in _advanceTraverse
in virtual-agv-adapter.ts
to be 0.0
, however, due to a rounding error tx
was somewhere in the range of 10-16 and dx
was 10-17. For that reason, ,the condition if (Math.abs(tx) <= Math.abs(dx) && Math.abs(ty) <= Math.abs(dy))
never fired, as tx
was slightly larger than dx
. This caused the simulated AGV to "never reach" the desired node and the simulated AGV position kept increasing well beyond the desired nodes position.
To Reproduce
Make an AGV go from NodeA
to NodeB
via an Edge that goes straight from NodeA to NodeB
:
NodeA: {x: -2mm, y: +4474mm}
NodeB: {x: -2mm, y: +248mm}
Obviously, the problem did not always happen when traversing the Edge from NodeA
to NodeB
, as it is highly dependend on how large of a time realInterval
is. In our case, we used tickRate = 5
, which translates to ~200
. When changing the tickRate
it may have worked better or worse, however we traced to underlying problem to a floating point rounding error, not a timing error.
For further information (including the exact project it was tested with), contact tobias.egger (at) siemens.com
Expected behavior
The simulated AGV should not miss the VDA node due to a floating point rounding error.
Your Environment (please complete the following information):
Describe the bug
Rename "AllowedDeviationXy" to "AllowedDeviationXY"
Is this a regression?
In schema it was alway "AllowedDeviationXy" and in the written spec it was "AllowedDeviationXY"
Decision taken to leave it as "AllowedDeviationXY" also in schema
Is your feature request related to a problem? Please describe.
This would allow for types described for LIF (Layout Interchange Format) graphs as part of VDMA's LIF definition 1.0.0 as of March 2024.
Describe the solution you'd like
Additional typedef definitions for LIF graphs.
Describe alternatives you've considered
Implmenting this locally for internal use.
Additional context
Mostly based on existing types with extentions.
Hi,
When you try remove an error using updateError function of class AgvController all errors are removed except the one that should be removed.
I think problem is in the function updateError. You are doing newErrors = [...this._currentState.errors].splice(index, 1)
. Array.prototype.splice() returning an array containing the deleted elements. Then you update state using newErrors variable: this._updateState(this._cloneState({ errors: newErrors }), reportImmediately)
Given an active but processed order, onOrderProcessed
handler is called and isOrderProcessedHandlerInvoked
is assigned true
But later the order may transition isActive
from false
to true
, eg, by way of a cancel instant action
onOrderProcessed
is not called again with the updated isActive
state, but I'd expect it to be.
calling onOrderProcessed
is prevented by checking isOrderProcessedHandlerInvoked
here
Hello,
Paramterer inbount in topicObjectValidation in ClientOptions is not applied.
The problem is in the AgvController constructor. In line 812 in file agv-controller.ts, where the parent constructor is called, the inbound value is hard-coded to false.
Describe the bug
The library is (at some parts) not consistent with the V1.1.0 specification.
Examples I have found so far:
State
field information
should be called informations
and be mandatoryErrorLevel
should be fatal
and warning
instead of FATAL
and WARNING
NodePosition
field allowedDeviationXy
should be called allowedDeviationXY
This is the V1.1.0 specification I'm referring to.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.