Coder Social home page Coder Social logo

coatyio / vda-5050-lib.js Goto Github PK

View Code? Open in Web Editor NEW
30.0 5.0 6.0 9.76 MB

Universal VDA 5050 library for Node.js and browsers.

License: MIT License

JavaScript 35.12% TypeScript 64.88%
vda-5050 typescript javascript nodejs browser vdma communication-interface agv master-control vda

vda-5050-lib.js's People

Contributors

hewu94 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vda-5050-lib.js's Issues

Library not consistent with V1.1.0 specification

Describe the bug
The library is (at some parts) not consistent with the V1.1.0 specification.

Examples I have found so far:

  • Optional State field information should be called informations and be mandatory
  • Enum values of ErrorLevel 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.

Naming issue in V2.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.

order validation fails to catch error when edge has end node not released

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):

  • Package Version: 1.3.1
  • Node.js Version: 18.17.1
  • OS: Ubuntu 20.04

Cancelling inactive mission doesnt trigger onOrderProcessed again

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

if (result !== false && !cache.isOrderProcessedHandlerInvoked) {

Multiple parallel AssignOrder calls to the same AGV result in not all callback functions being raised

Issue

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.

callback bug

AssignOrder implementation:

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);
        }
    });
}

Orders:

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"
}

Environment:

  • Package Version: 1.1.1
  • Node.js Version: v16.13.2
  • OS: Windows 10/ Ubuntu

updateError function of class AgvController removes all errors except the one that should be removed.

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)

Floating Point Imprecision causes incorrect behaviour in _advanceTraverse

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.

image

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):

  • Package Version: 1.3.0
  • Node.js Version: v17.3.1
  • OS: Windows 10

AgvController cannot be started after being stopped

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):

  • Package Version: 1.4.0
  • Node.js Version: 20.10.0
  • OS: Windows 10

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.

Rename "AllowedDeviationXy" to "AllowedDeviationXY"

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

How to deal with Orders not completed by (or even delivered to) an AGV?

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.